feat(providers): add optional AccountCredentialField for self-describing plugins#131
Merged
Merged
Conversation
…ing plugins
Provider plugins (built-in `google_ads` / `meta_ads`, and third-party
plugins discovered via the `mureo.providers` entry-point group) can
now declare an optional `account_credential_fields:
tuple[AccountCredentialField, ...]` class attribute so introspection
tooling — the `mureo providers …` CLI, configuration wizards, plugin
authoring guides — can render setup prompts, validate config, and
document plugins without hardcoding per-provider knowledge.
The benefit for OSS-only operators is concrete: after installing a
new plugin, an introspection tool can show "this plugin requires
these per-account fields" without the operator (or the tool itself)
needing to know what the plugin needs ahead of time.
Public surface:
* `mureo.core.providers.AccountCredentialField` — frozen dataclass
with `key`, `display_name`, `placeholder=""`, `required=False`,
`description=""`. Primitive `str` / `bool` only so
`dataclasses.asdict` is JSON-serialisable for tooling that surfaces
the descriptor over an HTTP boundary.
* `mureo.core.providers.get_account_credential_fields(provider)` —
defensive accessor. Returns `()` when the optional attribute is
absent (pre-feature plugins keep loading unchanged) and raises
`TypeError` when present-but-malformed (non-tuple, or any element
that is not an `AccountCredentialField`) so the failure surfaces
near the plugin, not deep inside the consuming UI.
`BaseProvider` Protocol body is unchanged. The new attribute is
documented as optional in the `BaseProvider` docstring; the Protocol
itself stays stable so `@runtime_checkable` `isinstance()` results
do not flip for any existing plugin.
Built-in adapters updated:
* `mureo.adapters.google_ads.GoogleAdsAdapter` declares
`customer_id` (placeholder `"123-456-7890"`, required). The MCC
`login_customer_id` is intentionally excluded — it identifies the
operator's manager account and is shared across every Google Ads
account the operator manages (operator-shared OAuth-level
credential, not per-account).
* `mureo.adapters.meta_ads.MetaAdsAdapter` declares `ad_account_id`
(placeholder `"act_1234567890"`, required). Token / app id / app
secret are operator-shared in the common Business Manager setup
and live outside this declaration.
Docs:
* `docs/plugin-authoring.md` §3 (`BaseProvider`) gains a *Declaring
per-account credential fields (optional)* subsection covering the
dataclass shape, default values, and the accessor's defensive-read
/ strict-validation semantics — plus the operator-shared /
per-account credential split shown above.
Tests:
* `tests/core/providers/test_account_credential_field.py` (4 tests)
— construction, frozen-ness, defaults, `asdict` is JSON-friendly.
* `tests/core/providers/test_registry.py` (5 new tests) — empty
return when attr absent, declared tuple read-through, `TypeError`
on non-tuple, `TypeError` on wrong element type, public re-export
from `mureo.core.providers`.
* `tests/adapters/google_ads/test_adapter.py` +1 — `customer_id`
declaration shape.
* `tests/adapters/meta_ads/test_adapter.py` +1 — `ad_account_id`
declaration shape.
* `tests/adapters/{google_ads,meta_ads}/test_imports.py` — adapter
import allowlist gains `mureo.core.providers.credentials`.
Backward compatibility: 394 tests pass across `tests/core/providers/`
and `tests/adapters/`, including the `base.py` foundation-rule AST
scan and the adapter import allowlist. Existing plugins that do not
declare `account_credential_fields` continue to register and discover
unchanged. ruff and black clean on `mureo/`.
hyoshi
added a commit
that referenced
this pull request
May 22, 2026
…tial_fields (#131) (#132) See CHANGELOG.md for details. Provider plugins can now declare an optional `account_credential_fields: tuple[AccountCredentialField, ...]` class attribute so introspection tooling — the `mureo providers …` CLI, configuration wizards, plugin authoring guides — can render setup prompts, validate config, and document plugins without hardcoding per-provider knowledge. `BaseProvider` Protocol body is unchanged (`getattr`-based defensive read), and built-in adapters now self-describe: `GoogleAdsAdapter` declares `customer_id`, `MetaAdsAdapter` declares `ad_account_id`. Backward compatibility: plugins that do not declare `account_credential_fields` continue to register and discover unchanged; `get_account_credential_fields()` returns `()` for them. Version bumped to 0.9.7 across .claude-plugin/plugin.json, mureo/__init__.py, and pyproject.toml.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Provider plugins (built-in
google_ads/meta_ads, and third-party plugins discovered via themureo.providersentry-point group) can now declare an optionalaccount_credential_fields: tuple[AccountCredentialField, ...]class attribute so introspection tooling — themureo providers …CLI, configuration wizards, plugin authoring guides — can render setup prompts, validate config, and document plugins without hardcoding per-provider knowledge.Public surface added to
mureo.core.providersAccountCredentialFieldkey,display_name,placeholder="",required=False,description="". Primitivestr/boolonly sodataclasses.asdictis JSON-serialisable.get_account_credential_fields(provider)()for providers that do not declare the attribute, raisesTypeErrorwhen the declaration is malformed (non-tuple or wrong element type).BaseProviderProtocol body is unchanged. The new attribute is documented as optional in theBaseProviderdocstring and is read viagetattrso@runtime_checkableisinstance()results do not flip for any existing plugin.Built-in adapter updates
mureo.adapters.google_ads.GoogleAdsAdapterdeclarescustomer_id(placeholder"123-456-7890", required). MCClogin_customer_idis intentionally excluded — it identifies the operator's manager account and is shared across every Google Ads account the operator manages (operator-shared OAuth-level credential, not per-account).mureo.adapters.meta_ads.MetaAdsAdapterdeclaresad_account_id(placeholder"act_1234567890", required). Token / app id / app secret are operator-shared in the common Business Manager setup and live outside this declaration.Backward compatibility
account_credential_fieldscontinue to register and discover unchanged;get_account_credential_fields()returns(), which downstream tooling treats as "no per-account configuration needed."BaseProviderProtocol body is unchanged so@runtime_checkableisinstance(obj, BaseProvider)keeps returning the same value for every existing plugin.get_account_credential_fieldsvalidation raisesTypeErroronly when a plugin actively declares a malformed attribute — pure additive footprint for everyone else.Plugin author docs
docs/plugin-authoring.md§3 (BaseProvider) gains a Declaring per-account credential fields (optional) subsection covering the dataclass shape, default values, and the accessor's defensive-read / strict-validation semantics — plus the operator-shared / per-account credential split shown above.Test plan
ruff check mureo/,black --check mureo/) — clean locallytests/core/providers/+tests/adapters/@dataclass(frozen=True)+ a defensivegetattr-based accessor. Noeval/ subprocess / shell / network.from mureo.core.providers import get_account_credential_fields, AccountCredentialField, runget_account_credential_fields(GoogleAdsAdapter)and confirm it returns the declared tuple.account_credential_fields); confirm discovery still loads it andget_account_credential_fields(...)returns().