@topy-ai/maggie 0.7.48 → 0.7.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-zh-TW.md +2 -2
- package/README.md +31 -4
- package/bin/maggie.js +17 -4
- package/bundled-contracts/google-integrations/capability-report-v2.schema.json +2 -1
- package/bundled-contracts/maggie-content/email-localization-v1.schema.json +89 -0
- package/bundled-contracts/maggie-content/lifecycle-email-v1.schema.json +76 -0
- package/bundled-contracts/maggie-deployment/readiness-v1.schema.json +6 -1
- package/bundled-contracts/maggie-ops/marketing-run-v1.schema.json +89 -0
- package/bundled-contracts/maggie-ops/notification-catalogue-example-v1.json +941 -0
- package/bundled-contracts/maggie-ops/notification-catalogue-v1.schema.json +273 -0
- package/bundled-contracts/maggie-service-booking/fact-provenance-v1.schema.json +128 -0
- package/bundled-contracts/maggie-service-booking/import-stages-v1.schema.json +191 -0
- package/bundled-references/google-integrations-runbook.md +54 -0
- package/bundled-skills/maggie-content-localization/SKILL.md +17 -1
- package/bundled-skills/maggie-content-localization/references/email-and-lifecycle.md +83 -0
- package/bundled-skills/maggie-deployment/SKILL.md +89 -7
- package/bundled-skills/maggie-deployment/references/vps.md +38 -6
- package/bundled-skills/maggie-design/SKILL.md +55 -0
- package/bundled-skills/maggie-feedback/SKILL.md +29 -0
- package/bundled-skills/maggie-ops/SKILL.md +37 -0
- package/bundled-skills/maggie-ops/references/notifications-operations.md +154 -0
- package/bundled-skills/maggie-service-booking/SKILL.md +59 -0
- package/bundled-skills/maggie-social-share/SKILL.md +19 -2
- package/bundled-skills/maggie-social-share/references/marketing-run.md +57 -0
- package/bundled-skills/maggie-social-share/references/queue-and-connections.md +51 -0
- package/bundled-tools/clis/maggie_deployment.py +58 -17
- package/bundled-tools/clis/maggie_deployment_auth_canary.py +216 -0
- package/bundled-tools/clis/maggie_deployment_collect.py +225 -0
- package/bundled-tools/clis/maggie_deployment_readiness.py +53 -3
- package/bundled-tools/clis/maggie_design.py +8 -0
- package/bundled-tools/clis/maggie_design_verify.py +359 -0
- package/bundled-tools/clis/maggie_docs.py +82 -4
- package/bundled-tools/clis/maggie_email_localization.py +382 -0
- package/bundled-tools/clis/maggie_feedback.py +9 -2
- package/bundled-tools/clis/maggie_icon_glyph_check.py +382 -0
- package/bundled-tools/clis/maggie_icon_release_gate.py +28 -1
- package/bundled-tools/clis/maggie_localization.py +5 -0
- package/bundled-tools/clis/maggie_marketing_run.py +272 -0
- package/bundled-tools/clis/maggie_notifications.py +528 -0
- package/bundled-tools/clis/maggie_ops.py +87 -0
- package/bundled-tools/clis/maggie_schedule.py +12 -2
- package/bundled-tools/clis/maggie_service_import.py +520 -0
- package/bundled-tools/clis/maggie_social_queue.py +104 -0
- package/bundled-tools/clis/maggie_social_share.py +6 -2
- package/bundled-tools/runtime/google_ads.py +306 -0
- package/bundled-tools/runtime/google_capabilities.py +18 -2
- package/bundled-tools/runtime/google_remediation.py +152 -0
- package/package.json +1 -1
- package/references/google-integrations-runbook.md +54 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Map redacted Google provider failures to one prerequisite and one safe action.
|
|
2
|
+
|
|
3
|
+
The classifier is deterministic and offline. It reads an error code, HTTP
|
|
4
|
+
status, and short message only to choose a rule; it never returns the input
|
|
5
|
+
message, so tokens or raw provider payloads cannot leak into a report.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Ordered: the first matching rule wins, so specific product errors precede
|
|
15
|
+
# generic HTTP-status fallbacks.
|
|
16
|
+
RULES: tuple[dict[str, Any], ...] = (
|
|
17
|
+
{
|
|
18
|
+
"code": "adc_invalid_scope",
|
|
19
|
+
"patterns": (r"invalid_scope",),
|
|
20
|
+
"prerequisite": "oauth-scope-request",
|
|
21
|
+
"nextAction": "Re-run the ADC login or impersonation command requesting only the exact allowlisted scope for this provider (see the scope allowlist); do not reuse a token minted without that scope.",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"code": "token_scope_insufficient",
|
|
25
|
+
"patterns": (r"ACCESS_TOKEN_SCOPE_INSUFFICIENT", r"insufficient(?:_| )(?:authentication )?scopes?", r"Request had insufficient authentication scopes"),
|
|
26
|
+
"prerequisite": "oauth-granted-scope",
|
|
27
|
+
"nextAction": "Mint a new short-lived token that includes the provider's required scope, then repeat the same read-only request.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"code": "redirect_uri_mismatch",
|
|
31
|
+
"patterns": (r"redirect_uri_mismatch",),
|
|
32
|
+
"prerequisite": "oauth-client-redirect-uri",
|
|
33
|
+
"nextAction": "Add the exact callback URL used by this connect path to the OAuth client's authorized redirect URIs, or switch to the client that already lists it.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"code": "oauth_consent_not_configured",
|
|
37
|
+
"patterns": (r"consent screen", r"OAuth consent", r"access_denied.*(?:not completed|verification)", r"app (?:is )?(?:not verified|blocked)", r"org_internal"),
|
|
38
|
+
"prerequisite": "oauth-consent-configuration",
|
|
39
|
+
"nextAction": "Configure the OAuth consent screen (user type, test users or verification status) in the Cloud project that owns the client, then reconnect.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"code": "developer_token_prohibited",
|
|
43
|
+
"patterns": (r"DEVELOPER_TOKEN_PROHIBITED", r"developer token is not allowed with the project", r"DEVELOPER_TOKEN_(?:MISMATCH|INVALID_FOR_PROJECT)"),
|
|
44
|
+
"prerequisite": "ads-developer-token-project-pairing",
|
|
45
|
+
"nextAction": "Use the OAuth client from the Cloud project already paired with this developer token, or request access for the new project; one developer token is bound to one project.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"code": "developer_token_not_approved",
|
|
49
|
+
"patterns": (r"DEVELOPER_TOKEN_NOT_APPROVED", r"only approved for use with test accounts", r"DEVELOPER_TOKEN_NOT_ON_ALLOWLIST"),
|
|
50
|
+
"prerequisite": "ads-developer-token-access-level",
|
|
51
|
+
"nextAction": "Apply for Basic or Standard access for the developer token, or run against a Google Ads test account until it is approved.",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"code": "developer_token_missing",
|
|
55
|
+
"patterns": (r"DEVELOPER_TOKEN_PARAMETER_MISSING", r"developer[- ]token (?:header )?(?:is )?(?:missing|required)", r"DEVELOPER_TOKEN_(?:INVALID|MISSING)"),
|
|
56
|
+
"prerequisite": "ads-developer-token",
|
|
57
|
+
"nextAction": "Install the developer token in the host secret store and send it as the developer-token header; record only its presence.",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"code": "login_customer_mismatch",
|
|
61
|
+
"patterns": (r"login[-_ ]customer[-_ ]id", r"LOGIN_CUSTOMER_ID", r"USER_PERMISSION_DENIED.*manager"),
|
|
62
|
+
"prerequisite": "ads-login-customer-id",
|
|
63
|
+
"nextAction": "Set login-customer-id to the manager account that links to the target customer (digits only), or omit it when the user has direct access.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"code": "ads_user_permission_denied",
|
|
67
|
+
"patterns": (r"USER_PERMISSION_DENIED", r"CUSTOMER_NOT_FOUND", r"NOT_ADS_USER"),
|
|
68
|
+
"prerequisite": "ads-account-access",
|
|
69
|
+
"nextAction": "Grant the authenticated Google user access to the target Ads customer (or its manager), then repeat listAccessibleCustomers before any report.",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"code": "ga4_property_membership",
|
|
73
|
+
"patterns": (r"does not have sufficient permissions for this property", r"User does not have (?:sufficient )?permission.*propert", r"properties/\d+.*PERMISSION_DENIED"),
|
|
74
|
+
"prerequisite": "ga4-property-membership",
|
|
75
|
+
"nextAction": "Add the principal (user or service account email) to the GA4 property with Viewer or higher in Property Access Management.",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"code": "gsc_property_membership",
|
|
79
|
+
"patterns": (r"User does not have sufficient permission for site", r"sites/.*PERMISSION_DENIED"),
|
|
80
|
+
"prerequisite": "gsc-property-membership",
|
|
81
|
+
"nextAction": "Add the principal as a user of the exact Search Console property (URL-prefix or domain) being queried.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"code": "api_disabled",
|
|
85
|
+
"patterns": (r"SERVICE_DISABLED", r"has not been used in project .* or it is disabled", r"API .*is disabled", r"accessNotConfigured"),
|
|
86
|
+
"prerequisite": "cloud-api-enablement",
|
|
87
|
+
"nextAction": "Enable this provider's API in the Cloud project that owns the OAuth client or service account, wait a few minutes, then retry.",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"code": "quota_exhausted",
|
|
91
|
+
"patterns": (r"RESOURCE_EXHAUSTED", r"RATE_LIMIT_EXCEEDED", r"quota (?:exceeded|exhausted)", r"rateLimitExceeded", r"RESOURCE_TEMPORARILY_EXHAUSTED"),
|
|
92
|
+
"prerequisite": "provider-quota",
|
|
93
|
+
"nextAction": "Stop retrying, wait for the quota window or request more quota; record the request as blocked rather than repeating it.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"code": "iam_token_creator_missing",
|
|
97
|
+
"patterns": (r"iam\.serviceAccounts\.getAccessToken", r"serviceAccountTokenCreator"),
|
|
98
|
+
"prerequisite": "service-account-impersonation",
|
|
99
|
+
"nextAction": "Grant the caller roles/iam.serviceAccountTokenCreator on the target service account, then re-run the impersonation command.",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"code": "product_role_denied",
|
|
103
|
+
"patterns": (r"PERMISSION_DENIED", r"The caller does not have permission", r"forbidden"),
|
|
104
|
+
"prerequisite": "product-role",
|
|
105
|
+
"nextAction": "Grant the principal the needed product-level role on the selected resource; Cloud IAM alone does not grant product access.",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"code": "invalid_grant",
|
|
109
|
+
"patterns": (r"invalid_grant", r"Token has been expired or revoked", r"UNAUTHENTICATED", r"invalid_token"),
|
|
110
|
+
"prerequisite": "oauth-credential-validity",
|
|
111
|
+
"nextAction": "Reconnect the account to obtain a fresh credential; the stored token is expired or revoked.",
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
STATUS_FALLBACK = {
|
|
116
|
+
401: RULES[-1],
|
|
117
|
+
403: next(rule for rule in RULES if rule["code"] == "product_role_denied"),
|
|
118
|
+
429: next(rule for rule in RULES if rule["code"] == "quota_exhausted"),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
UNKNOWN = {
|
|
122
|
+
"code": "unclassified",
|
|
123
|
+
"prerequisite": "unknown",
|
|
124
|
+
"nextAction": "Record the provider error code and HTTP status (no payload) and check the prerequisites in order: scope, consent, API enablement, product access, quota.",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def classify(error: Any = None, *, status: int | None = None, provider: str | None = None) -> dict[str, Any]:
|
|
129
|
+
"""Return {code, prerequisite, nextAction, provider, httpStatus}; never echoes input text."""
|
|
130
|
+
if isinstance(error, dict):
|
|
131
|
+
status = status if status is not None else error.get("httpStatus", error.get("status"))
|
|
132
|
+
parts = [error.get(key) for key in ("code", "reason", "status", "message")]
|
|
133
|
+
text = " ".join(str(item) for item in parts if isinstance(item, (str, int)))
|
|
134
|
+
provider = provider or error.get("provider")
|
|
135
|
+
else:
|
|
136
|
+
text = str(error or "")
|
|
137
|
+
text = text[:2000]
|
|
138
|
+
rule = None
|
|
139
|
+
for candidate in RULES:
|
|
140
|
+
if any(re.search(pattern, text, re.IGNORECASE) for pattern in candidate["patterns"]):
|
|
141
|
+
rule = candidate
|
|
142
|
+
break
|
|
143
|
+
if rule is None and isinstance(status, int):
|
|
144
|
+
rule = STATUS_FALLBACK.get(status)
|
|
145
|
+
rule = rule or UNKNOWN
|
|
146
|
+
return {
|
|
147
|
+
"code": rule["code"],
|
|
148
|
+
"prerequisite": rule["prerequisite"],
|
|
149
|
+
"nextAction": rule["nextAction"],
|
|
150
|
+
"provider": provider if isinstance(provider, str) else None,
|
|
151
|
+
"httpStatus": status if isinstance(status, int) else None,
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -119,6 +119,60 @@ selected `resource`. A report that only proves account discovery, or comes from
|
|
|
119
119
|
the wrong browser account/property, is rejected before a write workflow can use
|
|
120
120
|
it.
|
|
121
121
|
|
|
122
|
+
## Failure remediation
|
|
123
|
+
|
|
124
|
+
A failed check must name the one prerequisite that failed, not a generic
|
|
125
|
+
"access denied". Put only the provider error code/reason and HTTP status in the
|
|
126
|
+
row's optional `failure` field; the validator returns `remediation` with a
|
|
127
|
+
stable `code`, the failed `prerequisite`, and one safe `nextAction`. It never
|
|
128
|
+
echoes the input text. `maggie ops google-diagnose` applies the same mapping to
|
|
129
|
+
one error.
|
|
130
|
+
|
|
131
|
+
| Signal | Failed prerequisite | Safe next action |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `invalid_scope` (ADC) | OAuth scope request | Re-run the ADC/impersonation command with the exact allowlisted scope |
|
|
134
|
+
| `ACCESS_TOKEN_SCOPE_INSUFFICIENT` | Granted token scope | Mint a new token that includes the provider scope |
|
|
135
|
+
| `redirect_uri_mismatch` | OAuth client redirect URI | Add the exact callback URL to the client, or use the client that lists it |
|
|
136
|
+
| Consent screen not configured / app not verified | OAuth consent configuration | Configure user type, test users, or verification in the client's project |
|
|
137
|
+
| `SERVICE_DISABLED` / `accessNotConfigured` | Cloud API enablement | Enable the API in the project that owns the client or service account |
|
|
138
|
+
| `RESOURCE_EXHAUSTED` / HTTP 429 | Provider quota | Stop retrying; wait for the window or request quota; record `blocked` |
|
|
139
|
+
| `DEVELOPER_TOKEN_PARAMETER_MISSING` | Ads developer token | Install the token in the host secret store; send the `developer-token` header |
|
|
140
|
+
| `DEVELOPER_TOKEN_NOT_APPROVED` | Ads token access level | Apply for Basic/Standard access, or use a test account |
|
|
141
|
+
| `DEVELOPER_TOKEN_PROHIBITED` | Developer token/project pairing | Use the OAuth client from the paired Cloud project, or request access for the new project |
|
|
142
|
+
| `USER_PERMISSION_DENIED` with a manager | `login-customer-id` | Set it to the manager that links the target, or omit it for direct access |
|
|
143
|
+
| `USER_PERMISSION_DENIED` / `CUSTOMER_NOT_FOUND` | Ads account access | Grant the user access to the customer or its manager |
|
|
144
|
+
| GA4 "insufficient permissions for this property" | GA4 property membership | Add the principal to the property with Viewer or higher |
|
|
145
|
+
| `iam.serviceAccounts.getAccessToken` denied | Service-account impersonation | Grant `roles/iam.serviceAccountTokenCreator` on the target service account |
|
|
146
|
+
| Other `PERMISSION_DENIED` / HTTP 403 | Product role | Grant the product-level role; Cloud IAM alone is insufficient |
|
|
147
|
+
| `invalid_grant` / HTTP 401 | Credential validity | Reconnect; the stored token is expired or revoked |
|
|
148
|
+
|
|
149
|
+
## Google Ads read-only preflight and Keyword Planner
|
|
150
|
+
|
|
151
|
+
`maggie ops google-ads-preflight` checks, in order and independently: customer
|
|
152
|
+
and `login-customer-id` format (10 digits; hyphens stripped), developer-token
|
|
153
|
+
presence (environment variable name only), the `adwords` scope via token
|
|
154
|
+
introspection (token sent in a POST body, never a URL),
|
|
155
|
+
`customers:listAccessibleCustomers`, the manager hierarchy through
|
|
156
|
+
`customer_client` when a manager is used, a `customer` read (currency, manager,
|
|
157
|
+
test-account flags), and Keyword Planner readiness. The Keyword Planner check
|
|
158
|
+
reports `ready` without spending a request unless `--probe-keyword-planner` is
|
|
159
|
+
passed, which sends one one-result `generateKeywordIdeas` call. Every failure
|
|
160
|
+
carries the same `remediation` mapping as above.
|
|
161
|
+
|
|
162
|
+
`maggie ops google-ads-keyword-ideas` sends read-only `generateKeywordIdeas`
|
|
163
|
+
requests (keyword, URL, or keyword+URL seed; language and geo-target constants;
|
|
164
|
+
bounded pagination) and normalizes each idea to `text`, `avgMonthlySearches`,
|
|
165
|
+
`competition`, `competitionIndex`, and `lowTopOfPageBid`/`highTopOfPageBid`
|
|
166
|
+
converted from micros into the customer currency.
|
|
167
|
+
|
|
168
|
+
The adapter refuses any request outside `listAccessibleCustomers`,
|
|
169
|
+
`googleAds:search`, and `generateKeywordIdeas`, so it cannot mutate campaigns,
|
|
170
|
+
budgets, or keywords. The REST API version defaults to `v21` and can be set with
|
|
171
|
+
`--api-version` or `MAGGIE_GOOGLE_ADS_API_VERSION`; check Google's sunset
|
|
172
|
+
schedule and use a currently supported version. Keyword Planner volumes are
|
|
173
|
+
estimates for planning and require an approved developer token on a real
|
|
174
|
+
account; test accounts return no metrics.
|
|
175
|
+
|
|
122
176
|
## Capability matrix
|
|
123
177
|
|
|
124
178
|
Every provider/resource row must expose this shape:
|