@topy-ai/maggie 0.7.16 → 0.7.18
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.md +52 -9
- package/README.zh-TW.md +10 -2
- package/bin/maggie.js +5 -4
- package/bundled-references/universal-booking-adapter.md +36 -1
- package/bundled-skills/maggie-blog/SKILL.md +11 -1
- package/bundled-skills/maggie-dash/SKILL.md +5 -0
- package/bundled-skills/maggie-deployment/SKILL.md +9 -1
- package/bundled-skills/maggie-feedback/SKILL.md +5 -0
- package/bundled-skills/maggie-ops/SKILL.md +8 -0
- package/bundled-skills/maggie-qa-workflow/SKILL.md +11 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +29 -1
- package/bundled-skills/maggie-service-booking/SKILL.md +19 -1
- package/bundled-tools/clis/maggie.py +1 -1
- package/bundled-tools/clis/maggie_blog.py +12 -2
- package/bundled-tools/clis/maggie_feedback.py +17 -5
- package/bundled-tools/clis/maggie_head_tags.py +35 -0
- package/bundled-tools/clis/maggie_indexnow.py +6 -3
- package/bundled-tools/clis/maggie_ops.py +12 -0
- package/bundled-tools/clis/maggie_qa_workflow.py +2 -0
- package/bundled-tools/clis/maggie_release.py +88 -2
- package/bundled-tools/clis/maggie_service_booking.py +42 -3
- package/bundled-tools/clis/maggie_social_cards.py +45 -0
- package/bundled-tools/clis/site_audit.py +2 -2
- package/bundled-tools/runtime/booking_capabilities.py +137 -0
- package/bundled-tools/runtime/maggie_blog.py +85 -0
- package/bundled-tools/runtime/maggie_favicon.py +86 -0
- package/bundled-tools/runtime/maggie_head_tags.py +101 -0
- package/bundled-tools/runtime/maggie_indexnow.py +50 -0
- package/bundled-tools/runtime/maggie_social_cards.py +165 -0
- package/bundled-tools/runtime/service_variants.py +48 -7
- package/package.json +1 -1
- package/references/universal-booking-adapter.md +36 -1
package/README.md
CHANGED
|
@@ -80,9 +80,30 @@ The current package also includes reusable safeguards from the latest feedback
|
|
|
80
80
|
review: `maggie dash api-contract` checks declared request and 2xx response
|
|
81
81
|
shapes; `maggie blog check-gate`/`approve` enforces review before publish;
|
|
82
82
|
sitemap validation flags suspiciously uniform `lastmod` dates; `maggie seo
|
|
83
|
-
indexnow` plans only changed same-origin URLs
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
indexnow` plans only changed same-origin URLs, verifies the deployed key route
|
|
84
|
+
with an optional negative control, and holds back URLs accepted in the last 24
|
|
85
|
+
hours. `maggie seo social-cards` checks per-page OG image dimensions and format;
|
|
86
|
+
`maggie seo head-tags` reports metadata drift across rendered shells;
|
|
87
|
+
`maggie ops favicon-check` verifies the served `/favicon.ico` and declared icon.
|
|
88
|
+
Dash inventory separates renderer kind from public page kind while reporting
|
|
89
|
+
source coverage for code-rendered routes.
|
|
90
|
+
|
|
91
|
+
Service booking providers can be checked with a machine-readable,
|
|
92
|
+
fixture-backed capability matrix:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
maggie service capability-audit --project . \
|
|
96
|
+
--catalogue .maggie/booking/services.json \
|
|
97
|
+
--capabilities-file .maggie/booking/provider-capabilities.json \
|
|
98
|
+
--fixture .maggie/booking/fixtures/provider.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The declaration records variant mode (`native`, `derived-offers`,
|
|
102
|
+
`single-fallback`, or `blocked`), price semantics, and stable-ID confidence.
|
|
103
|
+
The audit also records observed counts and fixture evidence. Localized service
|
|
104
|
+
variants use market/locale-aware canonical routes and reciprocal hreflang.
|
|
105
|
+
Release preflight consumes the latest matching scenario QA run when one is
|
|
106
|
+
configured and blocks incomplete evidence.
|
|
86
107
|
|
|
87
108
|
For Google integrations, validate a redacted provider matrix before reporting
|
|
88
109
|
access. The command fails closed on unknown scopes, missing Ads prerequisites,
|
|
@@ -141,11 +162,14 @@ maggie memory ... # confirmed preferences and lessons
|
|
|
141
162
|
maggie feedback ... # redact, preview, submit, list
|
|
142
163
|
maggie qa ... # scenario browser QA, fix/retest, release gate
|
|
143
164
|
maggie localization ... # plan, validate, review, publish, stale
|
|
144
|
-
maggie service ... # import, sync,
|
|
165
|
+
maggie service ... # import, sync, capability audit, validate
|
|
145
166
|
maggie seo performance ... # sampled PageSpeed/CWV report and baseline
|
|
146
167
|
maggie seo images ... # inventory, variants, confirmation, validate
|
|
147
168
|
maggie seo sitemap ... # typed/semantic plan, agent-files, apply, rollback
|
|
148
|
-
maggie seo indexnow ... # changed URLs, key check,
|
|
169
|
+
maggie seo indexnow ... # changed URLs, deployed key check, 24h guard
|
|
170
|
+
maggie seo social-cards ... # per-page og:image format/dimension audit
|
|
171
|
+
maggie seo head-tags ... # rendered-shell head metadata drift audit
|
|
172
|
+
maggie ops favicon-check ... # served favicon behaviour check
|
|
149
173
|
maggie deployment | migration | release | analytics | schedule
|
|
150
174
|
maggie migration identity --identity-file FILE [--expected-file FILE]
|
|
151
175
|
maggie deployment canary --asset URL=SHA256 --render-report report.json
|
|
@@ -199,6 +223,19 @@ Read the [performance PRD](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/mai
|
|
|
199
223
|
and [image/sitemap PRD](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/docs/image-sitemap-structure-prd.md)
|
|
200
224
|
for adapter, privacy, apply, rollback, and release gates.
|
|
201
225
|
|
|
226
|
+
For a database-backed blog, `maggie blog check-gate` accepts a sanitized host
|
|
227
|
+
settings adapter so review policy can be checked without copying database
|
|
228
|
+
credentials into a local store:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
maggie blog check-gate --project . \
|
|
232
|
+
--adapter-command '["node", "scripts/read-blog-settings.mjs"]'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The adapter is an argv array, receives a versioned request on stdin, and must
|
|
236
|
+
print only the three review-policy fields. Provider stderr is suppressed and a
|
|
237
|
+
missing local store without an adapter fails closed.
|
|
238
|
+
|
|
202
239
|
### Design and deployment release gates
|
|
203
240
|
|
|
204
241
|
Check source-to-runtime icon coverage before shipping a design:
|
|
@@ -247,8 +284,8 @@ artifact schemas.
|
|
|
247
284
|
Recommended upgrade sequence for the current release:
|
|
248
285
|
|
|
249
286
|
```bash
|
|
250
|
-
npx @topy-ai/maggie@0.7.
|
|
251
|
-
npx @topy-ai/maggie@0.7.
|
|
287
|
+
npx @topy-ai/maggie@0.7.18 update --project . --force
|
|
288
|
+
npx @topy-ai/maggie@0.7.18 cleanup --project .
|
|
252
289
|
```
|
|
253
290
|
|
|
254
291
|
Maintainers should pass npm credentials through the repository helper, never
|
|
@@ -258,8 +295,14 @@ as a command-line argument:
|
|
|
258
295
|
node scripts/publish-npm.mjs --maggie-env-file ../.env
|
|
259
296
|
```
|
|
260
297
|
|
|
261
|
-
The 0.7.
|
|
262
|
-
|
|
298
|
+
The 0.7.18 workflow adds staged/untracked changed-surface detection, scenario
|
|
299
|
+
QA release integration, market/locale-aware service variant routes and
|
|
300
|
+
reciprocal hreflang, provider capability matrices with fixture evidence, and
|
|
301
|
+
feedback fixed-proof/path-privacy gates. The 0.7.17 workflow adds sanitized database-backed blog gate adapters, deployed
|
|
302
|
+
IndexNow key verification, social-card and cross-shell head-tag audits, required
|
|
303
|
+
Open Graph image coverage, and behavioural favicon verification. The 0.7.16
|
|
304
|
+
workflow tightens the review-gate exit code and adds retry-path regression
|
|
305
|
+
coverage. The 0.7.15 workflow added API contracts, blog review gates, sitemap freshness,
|
|
263
306
|
change-driven IndexNow, and page-kind/source-coverage inventory. It also keeps
|
|
264
307
|
the general `maggie-qa-workflow` skill and `maggie qa`
|
|
265
308
|
CLI for scenario manifests, secret-free browser evidence metadata, test/fix/
|
package/README.zh-TW.md
CHANGED
|
@@ -8,7 +8,7 @@ Codex、Claude Code 與相容的 coding agents。
|
|
|
8
8
|
## 安裝
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx @topy-ai/maggie@0.7.
|
|
11
|
+
npx @topy-ai/maggie@0.7.18 init --agent all
|
|
12
12
|
npx @topy-ai/maggie doctor --project .
|
|
13
13
|
```
|
|
14
14
|
|
|
@@ -25,7 +25,9 @@ maggie doctor --project . --require-bootstrap --strict
|
|
|
25
25
|
deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
|
|
26
26
|
publish 與 production deployment 需要明確確認。
|
|
27
27
|
|
|
28
|
-
0.7.
|
|
28
|
+
0.7.18 加入 staged/untracked changed-surface release gate、QA run 與 release
|
|
29
|
+
整合、market/locale-aware service variant route、provider capability matrix
|
|
30
|
+
fixture evidence,以及 feedback fixed-proof/path privacy gate。0.7.16 補強 review gate 的 CI exit code 與 IndexNow retry regression;
|
|
29
31
|
0.7.15 也加入 API schema contract、blog review gate、sitemap freshness
|
|
30
32
|
warning、change-driven IndexNow 與 page-kind/source-coverage inventory。
|
|
31
33
|
|
|
@@ -59,6 +61,12 @@ maggie migration identity --identity-file .maggie/db-identity.json \
|
|
|
59
61
|
maggie qa start --project . --scenario-file .maggie/scenario-manifest.json \
|
|
60
62
|
--environment local --base-url http://localhost:4321
|
|
61
63
|
maggie qa summary --project . --run <run-id>
|
|
64
|
+
|
|
65
|
+
# Provider variant capability matrix
|
|
66
|
+
maggie service capability-audit --project . \
|
|
67
|
+
--catalogue .maggie/booking/services.json \
|
|
68
|
+
--capabilities-file .maggie/booking/provider-capabilities.json \
|
|
69
|
+
--fixture .maggie/booking/fixtures/provider.json
|
|
62
70
|
```
|
|
63
71
|
|
|
64
72
|
完整中文說明、19 個 skills 清單和 roadmap:
|
package/bin/maggie.js
CHANGED
|
@@ -98,6 +98,7 @@ Usage:
|
|
|
98
98
|
maggie service import <provider-url> --project PATH
|
|
99
99
|
maggie service sync <provider-url> --project PATH
|
|
100
100
|
maggie service generate --project PATH
|
|
101
|
+
maggie service capability-audit --project PATH --catalogue FILE --capabilities-file FILE --fixture FILE
|
|
101
102
|
maggie service validate --project PATH
|
|
102
103
|
maggie service inspect --project PATH
|
|
103
104
|
maggie service status --project PATH
|
|
@@ -113,7 +114,7 @@ Usage:
|
|
|
113
114
|
maggie api lifecycle --project PATH [--execute --allow-quota]
|
|
114
115
|
maggie memory <init|list|search|context|add|record-error|transition|export> --project PATH
|
|
115
116
|
maggie localization <extract|plan|generate|preview|validate|review|publish|stale|glossary> [options]
|
|
116
|
-
maggie seo performance|images|sitemap|indexnow [options] (sitemap supports strict validate and agent-files)
|
|
117
|
+
maggie seo performance|images|sitemap|indexnow|social-cards|head-tags [options] (sitemap supports strict validate and agent-files)
|
|
117
118
|
maggie feedback <collect|preview|submit|list> [options]
|
|
118
119
|
maggie qa <start|record|summary|export> [options]
|
|
119
120
|
maggie site-audit URL [--crawl] [--access-log FILE] [--require-sitemap-request] [--languages en-GB,es-MX,ja-JP] [--check-hreflang]
|
|
@@ -121,7 +122,7 @@ Usage:
|
|
|
121
122
|
maggie site-audit URL --crawl --baseline FILE
|
|
122
123
|
maggie browser-audit URL --browse PATH --output DIR --required SELECTOR [--sticky SELECTOR]
|
|
123
124
|
maggie verification coverage --contract FILE --evidence FILE
|
|
124
|
-
maggie ops audit|preflight|verify|lockfiles|seed-manifest|google-capabilities --project PATH
|
|
125
|
+
maggie ops audit|preflight|verify|lockfiles|seed-manifest|google-capabilities|favicon-check --project PATH
|
|
125
126
|
maggie ops google-capabilities --project PATH --report FILE [--output FILE]
|
|
126
127
|
maggie ops preflight --project PATH --write
|
|
127
128
|
|
|
@@ -302,8 +303,8 @@ function service(args) {
|
|
|
302
303
|
|
|
303
304
|
function seo(args) {
|
|
304
305
|
const command = args[0];
|
|
305
|
-
const scripts = { performance: "maggie_performance.py", images: "maggie_images.py", sitemap: "maggie_sitemap.py", indexnow: "maggie_indexnow.py" };
|
|
306
|
-
if (!scripts[command]) throw new Error("seo command must be performance, images, sitemap, or
|
|
306
|
+
const scripts = { performance: "maggie_performance.py", images: "maggie_images.py", sitemap: "maggie_sitemap.py", indexnow: "maggie_indexnow.py", "social-cards": "maggie_social_cards.py", "head-tags": "maggie_head_tags.py" };
|
|
307
|
+
if (!scripts[command]) throw new Error("seo command must be performance, images, sitemap, indexnow, social-cards, or head-tags");
|
|
307
308
|
workflowCli(scripts[command], args.slice(1));
|
|
308
309
|
}
|
|
309
310
|
|
|
@@ -25,8 +25,10 @@ when a title changes.
|
|
|
25
25
|
"title": "Signature Scalp Ritual",
|
|
26
26
|
"description": "Provider-supplied factual description.",
|
|
27
27
|
"category": {"level1": "Head Spa", "level2": "Scalp Treatments"},
|
|
28
|
-
|
|
28
|
+
"variants": [{
|
|
29
29
|
"id": "service-123:60",
|
|
30
|
+
"idSource": "provider",
|
|
31
|
+
"variantSource": "native",
|
|
30
32
|
"title": "60 minutes",
|
|
31
33
|
"durationMinutes": 60,
|
|
32
34
|
"price": {"amountMinor": 9500, "currency": "GBP", "display": "£95.00"}
|
|
@@ -147,3 +149,36 @@ Provider adapters may add `raw` evidence in a private local snapshot, but
|
|
|
147
149
|
public page generation consumes only the canonical fields above. A sync must
|
|
148
150
|
preserve removed records as `archived` with `removedAt` and must output a
|
|
149
151
|
change report.
|
|
152
|
+
|
|
153
|
+
## Provider variant capability matrix
|
|
154
|
+
|
|
155
|
+
Before service pages are published, each provider declares its variant
|
|
156
|
+
behavior in a project-owned JSON file. The declaration is validated against a
|
|
157
|
+
sanitized fixture and emits `maggie-provider-variant-capabilities.v1`:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"schemaVersion": "maggie-provider-capabilities.v1",
|
|
162
|
+
"providers": [{
|
|
163
|
+
"provider": "fresha",
|
|
164
|
+
"variantMode": "native",
|
|
165
|
+
"priceSemantics": "minor-unit-and-display",
|
|
166
|
+
"stableIdConfidence": "high"
|
|
167
|
+
}]
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`variantMode` is one of `native`, `derived-offers`, `single-fallback`, or
|
|
172
|
+
`blocked`. `priceSemantics` records whether structured minor units and display
|
|
173
|
+
prices are available. `stableIdConfidence` must be justified by provider or
|
|
174
|
+
derived ID evidence. The audit records service/variant counts, observed source
|
|
175
|
+
types, fixture name and hash, and validation errors without copying fixture
|
|
176
|
+
rows or provider credentials. Run it with:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
maggie service capability-audit \
|
|
180
|
+
--project . \
|
|
181
|
+
--catalogue .maggie/booking/services.json \
|
|
182
|
+
--capabilities-file .maggie/booking/provider-capabilities.json \
|
|
183
|
+
--fixture .maggie/booking/fixtures/provider.json
|
|
184
|
+
```
|
|
@@ -38,6 +38,9 @@ maggie blog init --project . --base-path /our-blogs --confirm
|
|
|
38
38
|
maggie blog ingest --project . --source local --input content/posts.json --confirm
|
|
39
39
|
maggie blog validate --project .
|
|
40
40
|
maggie blog check-gate --project .
|
|
41
|
+
# Database-backed hosts can supply a sanitized policy through an adapter.
|
|
42
|
+
maggie blog check-gate --project . \
|
|
43
|
+
--adapter-command '["node", "scripts/read-blog-settings.mjs"]'
|
|
41
44
|
maggie blog approve --project . --slug example-post --actor reviewer --reason "reviewed" --confirm
|
|
42
45
|
maggie blog publish --project . --slug example-post --actor owner --reason "approved" --confirm
|
|
43
46
|
maggie blog sitemap --project .
|
|
@@ -51,7 +54,14 @@ explicit `approve` transition before `publish`. Stable `contentId` is the
|
|
|
51
54
|
ingest identity and a published slug must not change during a rewrite.
|
|
52
55
|
Search/sort views are not indexable; drafts never appear in public routes, RSS,
|
|
53
56
|
or sitemap output. Provider keys remain server-side. Public publication and
|
|
54
|
-
migrations always require explicit confirmation.
|
|
57
|
+
migrations always require explicit confirmation.
|
|
58
|
+
|
|
59
|
+
The adapter runs as an argv array without a shell, receives a versioned request
|
|
60
|
+
on stdin, and must print only an object containing
|
|
61
|
+
`defaultPostStatus`/`default_post_status`, `requireReview`/`require_review`,
|
|
62
|
+
and `autoPublishEnabled`/`auto_publish`. Provider stderr is suppressed. Use
|
|
63
|
+
`--settings-file` for a reviewed sanitized snapshot. A missing local store with
|
|
64
|
+
neither input fails closed.
|
|
55
65
|
|
|
56
66
|
To initialize native front-end pages from the approved local UI guideline,
|
|
57
67
|
run `maggie-design`:
|
|
@@ -219,6 +219,11 @@ ordinary, and blog routes, plus `sourceCoverage`. A host adapter must include
|
|
|
219
219
|
code-rendered routes in the input or declare coverage incomplete; a zero
|
|
220
220
|
database-row count is not evidence that no public routes exist.
|
|
221
221
|
|
|
222
|
+
When a host renders multiple shells, export a normalized head manifest and run
|
|
223
|
+
`maggie seo head-tags audit`. This is the reusable boundary for detecting icon
|
|
224
|
+
MIME drift, OG-image fallback drift, and verification-tag coverage; MaggieDash
|
|
225
|
+
does not infer these values from a route filename or from one sample page.
|
|
226
|
+
|
|
222
227
|
The catalogue declares purpose, usage, placement, repeatability and layout
|
|
223
228
|
limits, renderer-owned examples, and the shape of repeated entries. A repeat
|
|
224
229
|
may contain an object (`title`, `body`, `href`, and so on), not just a count;
|
|
@@ -32,7 +32,8 @@ response bodies in it.
|
|
|
32
32
|
|
|
33
33
|
When a release changes a visitor-facing HTML, CSS, component, or asset surface,
|
|
34
34
|
the release preflight automatically requires both a passing icon inventory and
|
|
35
|
-
rendered canary evidence.
|
|
35
|
+
rendered canary evidence. It considers unstaged, staged, and untracked
|
|
36
|
+
visitor-facing files. The canary must include screenshots, zero console or
|
|
36
37
|
network errors, and zero placeholder matches. Query-driven routes belong in
|
|
37
38
|
behavior/API checks, not static byte baselines.
|
|
38
39
|
|
|
@@ -161,6 +162,13 @@ python3 tools/clis/maggie_release.py /path/to/project \
|
|
|
161
162
|
--base-url https://staging.example.com
|
|
162
163
|
```
|
|
163
164
|
|
|
165
|
+
If `.maggie/scenario-manifest.json` or `.maggie/qa-runs/` exists, the same
|
|
166
|
+
preflight consumes the latest matching `maggie qa` run for the requested
|
|
167
|
+
environment (and `--base-url`, when supplied). It blocks when a scenario is
|
|
168
|
+
not passed, has no browser-adapter evidence, or the matching run is missing.
|
|
169
|
+
Projects without scenario QA report `not-configured`; Maggie does not run the
|
|
170
|
+
browser itself.
|
|
171
|
+
|
|
164
172
|
This aggregates deployment, migration, provider-health, schedule, analytics,
|
|
165
173
|
service-fact, editorial approval, durable SEO/route/category evidence,
|
|
166
174
|
MaggieDash schema compatibility, and live runtime security-header checks. It writes
|
|
@@ -34,6 +34,11 @@ The draft is written to `.maggie/feedback/`. It contains the Maggie version,
|
|
|
34
34
|
skill, run ID, phase, error fingerprint, expected/actual result, reproduction
|
|
35
35
|
steps, resolution, validation, and metadata-only screenshot references.
|
|
36
36
|
|
|
37
|
+
If a draft is marked fixed with `--fixed` (or the supplied run report says
|
|
38
|
+
`fixed`), both `--resolution` and `--validation` are required. Feedback text
|
|
39
|
+
also scrubs common POSIX/home/temp and Windows absolute paths by default while
|
|
40
|
+
preserving public URLs and route paths.
|
|
41
|
+
|
|
37
42
|
## Submit explicitly
|
|
38
43
|
|
|
39
44
|
The CLI never submits automatically. After reviewing the draft:
|
|
@@ -42,6 +42,8 @@ python3 tools/clis/maggie_ops.py --project . audit
|
|
|
42
42
|
python3 tools/clis/maggie_ops.py --project . preflight --write
|
|
43
43
|
python3 tools/clis/maggie_ops.py --project . status
|
|
44
44
|
python3 tools/clis/maggie_ops.py --project . verify
|
|
45
|
+
python3 tools/clis/maggie_ops.py --project . favicon-check \
|
|
46
|
+
--origin https://example.com
|
|
45
47
|
python3 tools/clis/maggie_ops.py --project . record sitemap-match \
|
|
46
48
|
--dry-run --quota-impact quota --idempotency-key match-2026-08-29-001
|
|
47
49
|
```
|
|
@@ -52,6 +54,12 @@ python3 tools/clis/maggie_ops.py --project . record sitemap-match \
|
|
|
52
54
|
approved operation but does not call a remote provider; provider mutations
|
|
53
55
|
remain behind the server-side API adapter and its execute gate.
|
|
54
56
|
|
|
57
|
+
`favicon-check` is a deployed behavioural check. It fetches `/favicon.ico`
|
|
58
|
+
and the declared `<link rel="icon">` (or an explicit `--declared-url`), then
|
|
59
|
+
requires HTTP 200, a readable square image, and an engine-supported ICO, PNG,
|
|
60
|
+
or GIF response. A logo-shaped source filename is only an unverified source
|
|
61
|
+
candidate; it is never evidence that the public favicon route works.
|
|
62
|
+
|
|
55
63
|
If the user does not name a mode, inspect the project and propose the smallest
|
|
56
64
|
mode that satisfies the request. Do not rebuild the public blog or change its
|
|
57
65
|
framework just to add Ops.
|
|
@@ -69,6 +69,11 @@ maggie qa record --project . --run local-qa-001 \
|
|
|
69
69
|
the test failed, a fix. After a fix, retest the original scenario and at least
|
|
70
70
|
one adjacent scenario affected by the same surface.
|
|
71
71
|
|
|
72
|
+
A passing `test` or `retest` requires at least one `--evidence` reference from
|
|
73
|
+
the host browser adapter. The adapter owns console, network, authentication,
|
|
74
|
+
URL, viewport, and screenshot capture; this skill stores only a relative path,
|
|
75
|
+
URL reference, and hash when a local file exists.
|
|
76
|
+
|
|
72
77
|
## Gate and release evidence
|
|
73
78
|
|
|
74
79
|
The run gate is `fail` when any scenario fails, `blocked` when there is no
|
|
@@ -88,6 +93,12 @@ Before calling a release Pass, also run the relevant build, accessibility,
|
|
|
88
93
|
SEO, media, API, and deployment-canary checks. A green build, HTTP 200, source
|
|
89
94
|
class, or one guest smoke is not browser QA evidence.
|
|
90
95
|
|
|
96
|
+
`maggie release` consumes the latest matching run from `.maggie/qa-runs/` when
|
|
97
|
+
the project has a scenario manifest or QA run directory. A matching run must
|
|
98
|
+
have a passed summary, passing scenarios, a passing final test/retest event,
|
|
99
|
+
and browser evidence for every scenario. No manifest or run reports an
|
|
100
|
+
explicit `not-configured` state.
|
|
101
|
+
|
|
91
102
|
## Privacy and feedback
|
|
92
103
|
|
|
93
104
|
Do not put passwords, tokens, cookies, full private URLs, personal data,
|
|
@@ -181,13 +181,23 @@ maggie seo sitemap rollback --backup-manifest .maggie-sitemap-backups/<plan>/bac
|
|
|
181
181
|
--public-dir public --confirm
|
|
182
182
|
|
|
183
183
|
# Change-driven IndexNow: only pass URLs whose rendered content changed.
|
|
184
|
-
maggie seo indexnow key-check --public-dir public --key-file <key>.txt --key <key>
|
|
184
|
+
maggie seo indexnow key-check --public-dir public --key-file <key>.txt --key <key> \
|
|
185
|
+
--key-url https://example.com/<key>.txt \
|
|
186
|
+
--wrong-key-url https://example.com/not-a-key.txt
|
|
185
187
|
maggie seo indexnow plan --origin https://example.com \
|
|
186
188
|
--changed-urls-file .maggie/changed-urls.json \
|
|
187
189
|
--state-file .maggie/indexnow-state.json --key <key> \
|
|
188
190
|
--output .maggie/indexnow-plan.json
|
|
189
191
|
maggie seo indexnow submit --plan .maggie/indexnow-plan.json \
|
|
190
192
|
--state-file .maggie/indexnow-state.json --confirm
|
|
193
|
+
|
|
194
|
+
# Resolve each page's og:image and inspect its image contract.
|
|
195
|
+
maggie seo social-cards audit --urls-file .maggie/public-urls.json \
|
|
196
|
+
--output .maggie/social-cards.json
|
|
197
|
+
|
|
198
|
+
# Compare normalized head metadata emitted by every rendered shell.
|
|
199
|
+
maggie seo head-tags audit --pages-file .maggie/rendered-head.json \
|
|
200
|
+
--output .maggie/head-tags.json
|
|
191
201
|
```
|
|
192
202
|
|
|
193
203
|
Only confirmed image variants may enter `srcset`; `apply` requires an explicit
|
|
@@ -203,6 +213,24 @@ records accepted state only after HTTP 200/202. A 403/429/5xx or network error
|
|
|
203
213
|
is retryable and must never make the editor save fail; do not retry unchanged
|
|
204
214
|
URLs in a loop.
|
|
205
215
|
|
|
216
|
+
The complete IndexNow key gate checks both the local public file and the
|
|
217
|
+
deployed URL body. Add a wrong-key URL as a negative control when the host
|
|
218
|
+
route is expected to return 404; response bodies are never printed.
|
|
219
|
+
|
|
220
|
+
Social-card auditing uses a `300x157` minimum for a large summary card and
|
|
221
|
+
accepts JPEG, PNG, GIF, and ICO responses. It reports missing, unreachable,
|
|
222
|
+
undersized, unreadable, or unsupported images and warns when one source is
|
|
223
|
+
used for at least 75% of a sample of three or more pages. Page-specific
|
|
224
|
+
first-band images remain an editorial choice and should be declared by the
|
|
225
|
+
host manifest.
|
|
226
|
+
|
|
227
|
+
Head-tag auditing consumes a host-produced JSON manifest with `url`, `shell`,
|
|
228
|
+
and normalized `tags`. It compares structural declarations for icons,
|
|
229
|
+
`og:image`, image MIME type, verification presence, robots, and Twitter cards;
|
|
230
|
+
route-specific title and canonical values are intentionally excluded from
|
|
231
|
+
shell drift. It reports disagreements instead of guessing which shell is
|
|
232
|
+
correct.
|
|
233
|
+
|
|
206
234
|
For a deterministic technical smoke check, run:
|
|
207
235
|
|
|
208
236
|
```bash
|
|
@@ -21,7 +21,8 @@ supporting relations for all suggested candidates. Inspect each service's
|
|
|
21
21
|
`pages` after selection and validate the rendered links. The current selection
|
|
22
22
|
path writes `canonical` for selected pages; multiple selections can violate
|
|
23
23
|
the one-canonical invariant. Resolve roles and run validation before publishing.
|
|
24
|
-
Variant role assignment and layout/URL conventions
|
|
24
|
+
Variant role assignment and layout/URL conventions are enforced by the shared
|
|
25
|
+
market/locale route and reciprocal hreflang contract.
|
|
25
26
|
|
|
26
27
|
## Automatic memory hook
|
|
27
28
|
|
|
@@ -107,6 +108,14 @@ python3 tools/clis/maggie_service_booking.py category-audit \
|
|
|
107
108
|
--project . --rendered-dir /tmp/category-rendered
|
|
108
109
|
|
|
109
110
|
python3 tools/clis/maggie_service_booking.py category-context --project .
|
|
111
|
+
|
|
112
|
+
# Declare provider behavior, then validate it against the imported catalogue
|
|
113
|
+
# and a sanitized fixture before publishing service pages.
|
|
114
|
+
python3 tools/clis/maggie_service_booking.py capability-audit \
|
|
115
|
+
--project . \
|
|
116
|
+
--catalogue .maggie/booking/services.json \
|
|
117
|
+
--capabilities-file .maggie/booking/provider-capabilities.json \
|
|
118
|
+
--fixture .maggie/booking/fixtures/provider.json
|
|
110
119
|
```
|
|
111
120
|
|
|
112
121
|
`import` creates the first catalogue. `sync` compares the newly imported
|
|
@@ -265,6 +274,15 @@ amounts silently. Include `Service` and `Offer` JSON-LD only from validated
|
|
|
265
274
|
catalogue fields. Do not emit `Review`, `AggregateRating`, or medical claims
|
|
266
275
|
unless verified source data is present.
|
|
267
276
|
|
|
277
|
+
Localized and market-specific variants use
|
|
278
|
+
`/services/<market>/<locale>/<variant-slug>/` as their canonical route. A
|
|
279
|
+
translation never reuses the source route. Published variants expose
|
|
280
|
+
reciprocal hreflang links only to published counterparts. Before generation,
|
|
281
|
+
each provider must declare `variantMode` (`native`, `derived-offers`,
|
|
282
|
+
`single-fallback`, or `blocked`), `priceSemantics`, and `stableIdConfidence`
|
|
283
|
+
in a machine-readable capability file. The capability audit requires a
|
|
284
|
+
sanitized fixture and fails closed on missing or contradictory evidence.
|
|
285
|
+
|
|
268
286
|
## AI category/page copy contract
|
|
269
287
|
|
|
270
288
|
Read [`references/ai-service-copy-contract.md`](../../references/ai-service-copy-contract.md)
|
|
@@ -206,7 +206,7 @@ def detect(root: Path) -> dict:
|
|
|
206
206
|
asset_contract = {
|
|
207
207
|
"redirects": found("present", "redirects file") if any(name in names for name in {"_redirects", "redirects.json", "redirects.csv"}) else missing("no redirects manifest detected"),
|
|
208
208
|
"security_headers": found("present", name) if (name := next((name for name in {"_headers", "headers.json", "vercel.json", "netlify.toml"} if name in names), None)) else missing("no deployment/header policy detected"),
|
|
209
|
-
"favicon_or_brand":
|
|
209
|
+
"favicon_or_brand": {"status": "Unverified", "value": "source asset candidate found; verify the served icon with `maggie ops favicon-check`", "evidence": [path]} if (path := next((path for path in implementation_paths if any(token in path.lower() for token in {"favicon", "logo", "brand"}) and Path(path).suffix in {".svg", ".png", ".ico", ".webp"}), None)) else missing("no favicon or brand asset detected"),
|
|
210
210
|
"social_image": found("present", path) if (path := next((path for path in implementation_paths if any(token in path.lower() for token in {"og", "social", "twitter"}) and Path(path).suffix in {".jpg", ".jpeg", ".png", ".webp"}), None)) else missing("no social image asset detected"),
|
|
211
211
|
}
|
|
212
212
|
content_model = {
|
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
12
|
-
from maggie_blog import BlogStore # noqa: E402
|
|
12
|
+
from maggie_blog import BlogStore, review_settings_from_adapter # noqa: E402
|
|
13
13
|
from localization_runner import process_adapter
|
|
14
14
|
from integration_state import integration_state # noqa: E402
|
|
15
15
|
|
|
@@ -27,7 +27,7 @@ def main() -> int:
|
|
|
27
27
|
inspect = sub.add_parser("inspect"); inspect.add_argument("--project", type=Path, default=Path.cwd())
|
|
28
28
|
ingest = sub.add_parser("ingest"); ingest.add_argument("--project", type=Path, default=Path.cwd()); ingest.add_argument("--input", type=Path, required=True); ingest.add_argument("--source", default="local"); ingest.add_argument("--confirm", action="store_true")
|
|
29
29
|
validate = sub.add_parser("validate"); validate.add_argument("--project", type=Path, default=Path.cwd())
|
|
30
|
-
gate = sub.add_parser("check-gate", help="report whether generated content requires review before publication"); gate.add_argument("--project", type=Path, default=Path.cwd())
|
|
30
|
+
gate = sub.add_parser("check-gate", help="report whether generated content requires review before publication"); gate.add_argument("--project", type=Path, default=Path.cwd()); gate.add_argument("--settings-file", type=Path, help="sanitized host settings JSON"); gate.add_argument("--adapter-command", help="trusted provider JSON argv array that prints sanitized settings JSON"); gate.add_argument("--timeout", type=int, default=120)
|
|
31
31
|
approve = sub.add_parser("approve"); approve.add_argument("--project", type=Path, default=Path.cwd()); approve.add_argument("--slug", required=True); approve.add_argument("--actor", required=True); approve.add_argument("--reason", required=True); approve.add_argument("--confirm", action="store_true")
|
|
32
32
|
publish = sub.add_parser("publish"); publish.add_argument("--project", type=Path, default=Path.cwd()); publish.add_argument("--slug", required=True); publish.add_argument("--actor", required=True); publish.add_argument("--reason", required=True); publish.add_argument("--confirm", action="store_true")
|
|
33
33
|
sitemap = sub.add_parser("sitemap"); sitemap.add_argument("--project", type=Path, default=Path.cwd())
|
|
@@ -39,6 +39,16 @@ def main() -> int:
|
|
|
39
39
|
result = integration_state(configured=args.configured, consent_required=args.consent_required, consent=args.consent, authorized=args.authorized, error=args.error)
|
|
40
40
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
41
41
|
return 0 if result["status"] != "error" else 1
|
|
42
|
+
if args.command == "check-gate" and (args.settings_file or args.adapter_command):
|
|
43
|
+
try:
|
|
44
|
+
adapter_command = json.loads(args.adapter_command) if args.adapter_command else None
|
|
45
|
+
settings, source = review_settings_from_adapter(args.project.resolve(), args.settings_file.resolve() if args.settings_file else None, adapter_command, args.timeout)
|
|
46
|
+
result = BlogStore.review_gate(settings)
|
|
47
|
+
result["source"] = source
|
|
48
|
+
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
49
|
+
print(f"maggie-blog: {error}", file=sys.stderr); return 1
|
|
50
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
51
|
+
return 0 if result.get("passed") else 1
|
|
42
52
|
store = BlogStore(args.project.resolve())
|
|
43
53
|
if args.command in {"init", "ingest", "publish", "approve", "rollback", "translate-pending"} and not args.confirm:
|
|
44
54
|
print("CONFIRMATION_REQUIRED: rerun with --confirm", file=sys.stderr); return 2
|
|
@@ -20,6 +20,12 @@ from urllib.request import Request, urlopen
|
|
|
20
20
|
|
|
21
21
|
DEFAULT_ENDPOINT = "https://feedback.noblox.app/api/feedback"
|
|
22
22
|
SECRET_RE = re.compile(r"(?i)(bearer\s+|(?:api[_-]?key|token|secret|password|npm_config[^=]*|authorization)\s*[=:]\s*)[^\s,;]+")
|
|
23
|
+
LOCAL_PATH_RE = re.compile(
|
|
24
|
+
r"(?<![A-Za-z0-9])(?:~/(?:[^\s,;:)\]}]+/)*[^\s,;:)\]}]+|"
|
|
25
|
+
r"/(?:home|Users|private|tmp|var|mnt|opt|root|srv|workspace|workspaces)(?:/[^\s,;:)\]}]+)+|"
|
|
26
|
+
r"[A-Za-z]:[\\/]+(?:[^\\/\s,;:)\]}]+[\\/]+)*[^\\/\s,;:)\]}]+|"
|
|
27
|
+
r"\\\\[^\\/\s,;:)\]}]+(?:[\\/]+[^\\/\s,;:)\]}]+)+)"
|
|
28
|
+
)
|
|
23
29
|
VERSION_RE = re.compile(r"\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?")
|
|
24
30
|
ACKNOWLEDGEMENT_KEYS = ("status", "feedbackId", "requestId")
|
|
25
31
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
@@ -30,7 +36,8 @@ def now() -> str:
|
|
|
30
36
|
|
|
31
37
|
|
|
32
38
|
def safe_text(value: object) -> str:
|
|
33
|
-
|
|
39
|
+
value = SECRET_RE.sub(r"\1[REDACTED]", str(value or ""))
|
|
40
|
+
return LOCAL_PATH_RE.sub("[LOCAL_PATH_REDACTED]", value).strip()
|
|
34
41
|
|
|
35
42
|
|
|
36
43
|
def project_fingerprint(project: Path) -> str:
|
|
@@ -90,6 +97,11 @@ def collect(args: argparse.Namespace) -> int:
|
|
|
90
97
|
skill = args.skill or report.get("skill") or report.get("workflow") or "unknown"
|
|
91
98
|
run_id = args.run_id or report.get("runId") or report.get("jobId") or ""
|
|
92
99
|
actual = args.actual or report.get("actual") or report.get("error") or report.get("message") or ""
|
|
100
|
+
fixed = bool(args.fixed or report.get("fixed") is True or report.get("status") == "fixed")
|
|
101
|
+
resolution = safe_text(args.resolution or report.get("resolution"))
|
|
102
|
+
validation = safe_text(args.validation or report.get("validation"))
|
|
103
|
+
if fixed and (not resolution or not validation):
|
|
104
|
+
raise ValueError("fixed feedback requires non-empty --resolution and --validation")
|
|
93
105
|
context = {"projectFingerprint": project_fingerprint(project)}
|
|
94
106
|
if args.allow_project_context and args.context_note:
|
|
95
107
|
context["note"] = safe_text(args.context_note)
|
|
@@ -103,15 +115,15 @@ def collect(args: argparse.Namespace) -> int:
|
|
|
103
115
|
"skill": skill,
|
|
104
116
|
"runId": run_id,
|
|
105
117
|
"phase": args.phase or report.get("phase") or "",
|
|
106
|
-
"status": report.get("status") or ("fixed" if
|
|
118
|
+
"status": report.get("status") or ("fixed" if fixed else "reported"),
|
|
107
119
|
"summary": safe_text(args.summary),
|
|
108
120
|
"expected": safe_text(args.expected),
|
|
109
121
|
"actual": safe_text(actual),
|
|
110
122
|
"errorFingerprint": safe_text(args.error_fingerprint or report.get("errorFingerprint") or ""),
|
|
111
123
|
"stepsToReproduce": [safe_text(step) for step in args.reproduce],
|
|
112
|
-
"fixed":
|
|
113
|
-
"resolution":
|
|
114
|
-
"validation":
|
|
124
|
+
"fixed": fixed,
|
|
125
|
+
"resolution": resolution,
|
|
126
|
+
"validation": validation,
|
|
115
127
|
"attachments": [attachment(value) for value in args.screenshot],
|
|
116
128
|
"environment": {"os": platform.system().lower(), "python": platform.python_version()},
|
|
117
129
|
"privacy": {"secretsRedacted": True, "projectContextAllowed": bool(args.allow_project_context)},
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Compare normalized rendered head tags across site shells."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
12
|
+
from maggie_head_tags import audit_head_tags, read_pages # noqa: E402
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="maggie seo head-tags")
|
|
17
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
18
|
+
audit = sub.add_parser("audit", help="report head-tag disagreements across rendered shells")
|
|
19
|
+
audit.add_argument("--pages-file", type=Path, required=True, help="JSON page manifest with url, shell, and tags")
|
|
20
|
+
audit.add_argument("--output")
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
try:
|
|
23
|
+
result = audit_head_tags(read_pages(args.pages_file))
|
|
24
|
+
if args.output:
|
|
25
|
+
output = Path(args.output).resolve(); output.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
27
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
28
|
+
return 0 if result["passed"] else 1
|
|
29
|
+
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
30
|
+
print(f"maggie-head-tags: {error}", file=sys.stderr)
|
|
31
|
+
return 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
raise SystemExit(main())
|
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
import sys
|
|
10
10
|
|
|
11
11
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
12
|
-
from maggie_indexnow import check_key_file, plan_indexnow, submit_plan # noqa: E402
|
|
12
|
+
from maggie_indexnow import check_key_file, check_key_http, plan_indexnow, submit_plan # noqa: E402
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def read_json(path: Path, default: object) -> object:
|
|
@@ -26,8 +26,8 @@ def write_json(path: Path, value: object) -> None:
|
|
|
26
26
|
def main() -> int:
|
|
27
27
|
parser = argparse.ArgumentParser(prog="maggie seo indexnow")
|
|
28
28
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
29
|
-
key_check = sub.add_parser("key-check", help="verify
|
|
30
|
-
key_check.add_argument("--public-dir", required=True); key_check.add_argument("--key-file", required=True); key_check.add_argument("--key", required=True)
|
|
29
|
+
key_check = sub.add_parser("key-check", help="verify local and deployed public ownership key evidence")
|
|
30
|
+
key_check.add_argument("--public-dir", required=True); key_check.add_argument("--key-file", required=True); key_check.add_argument("--key", required=True); key_check.add_argument("--key-url", help="deployed key URL to fetch"); key_check.add_argument("--wrong-key-url", help="negative-control URL expected to return HTTP 404"); key_check.add_argument("--timeout", type=int, default=15)
|
|
31
31
|
plan = sub.add_parser("plan", help="hold back recently accepted URLs and create a submission plan")
|
|
32
32
|
plan.add_argument("--origin", required=True); plan.add_argument("--changed-urls-file", required=True); plan.add_argument("--state-file", required=True); plan.add_argument("--key", required=True); plan.add_argument("--key-location"); plan.add_argument("--guard-hours", type=int, default=24); plan.add_argument("--now"); plan.add_argument("--output", required=True)
|
|
33
33
|
submit = sub.add_parser("submit", help="submit an approved plan; provider failure never updates accepted state")
|
|
@@ -36,6 +36,9 @@ def main() -> int:
|
|
|
36
36
|
try:
|
|
37
37
|
if args.command == "key-check":
|
|
38
38
|
result = check_key_file(Path(args.public_dir).resolve(), args.key_file, args.key)
|
|
39
|
+
if args.key_url:
|
|
40
|
+
result["http"] = check_key_http(args.key_url, args.key, args.wrong_key_url, args.timeout)
|
|
41
|
+
if result["http"]["status"] != "pass": result["status"] = "fail"
|
|
39
42
|
print(json.dumps(result, indent=2)); return 0 if result["status"] == "pass" else 1
|
|
40
43
|
if args.command == "plan":
|
|
41
44
|
changed = read_json(Path(args.changed_urls_file), [])
|
|
@@ -15,6 +15,7 @@ from seed_evidence import validate_manifest
|
|
|
15
15
|
from agent_runtime import preflight as agent_preflight
|
|
16
16
|
from restart_gate import validate as validate_restart_gate
|
|
17
17
|
from google_capabilities import validate_report as validate_google_capability_report
|
|
18
|
+
from maggie_favicon import check_favicon
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
REQUIRED_ROUTES = (
|
|
@@ -181,6 +182,12 @@ def command_google_capabilities(args: argparse.Namespace) -> int:
|
|
|
181
182
|
return 0 if result["passed"] else 1
|
|
182
183
|
|
|
183
184
|
|
|
185
|
+
def command_favicon_check(args: argparse.Namespace) -> int:
|
|
186
|
+
result = check_favicon(args.origin, args.declared_url, args.timeout)
|
|
187
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
188
|
+
return 0 if result["passed"] else 1
|
|
189
|
+
|
|
190
|
+
|
|
184
191
|
def main() -> int:
|
|
185
192
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
186
193
|
parser.add_argument("--project", default=".")
|
|
@@ -203,6 +210,11 @@ def main() -> int:
|
|
|
203
210
|
google.add_argument("--report", required=True)
|
|
204
211
|
google.add_argument("--output")
|
|
205
212
|
google.set_defaults(func=command_google_capabilities)
|
|
213
|
+
favicon = sub.add_parser("favicon-check", help="fetch /favicon.ico and the declared icon to verify behaviour")
|
|
214
|
+
favicon.add_argument("--origin", required=True)
|
|
215
|
+
favicon.add_argument("--declared-url")
|
|
216
|
+
favicon.add_argument("--timeout", type=int, default=15)
|
|
217
|
+
favicon.set_defaults(func=command_favicon_check)
|
|
206
218
|
record = sub.add_parser("record", help="record an explicitly authorised operation")
|
|
207
219
|
record.add_argument("operation", choices=("sync", "sitemap-match", "rewrite-queue", "rewrite-approve", "publish", "migration"))
|
|
208
220
|
record.add_argument("--dry-run", action="store_true")
|