@topy-ai/maggie 0.7.3 → 0.7.5
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 +29 -9
- package/README.zh-TW.md +5 -1
- package/bin/maggie.js +18 -2
- package/bundled-contracts/maggie-deployment/release-runner-v1.json +14 -0
- package/bundled-contracts/maggiedash/README.md +22 -0
- package/bundled-contracts/maggiedash/agent-content-v1.json +15 -0
- package/bundled-contracts/maggiedash/redirect-policy-v1.json +15 -0
- package/bundled-contracts/maggiedash/section-identity-v1.json +14 -0
- package/bundled-contracts/maggiedash/telemetry-policy-v1.json +18 -0
- package/bundled-contracts/maggiedash/translation-cache-policy-v1.json +15 -0
- package/bundled-references/maggiedash-telemetry-contract.md +12 -0
- package/bundled-skills/maggie-dash/SKILL.md +76 -3
- package/bundled-skills/maggie-deployment/SKILL.md +20 -2
- package/bundled-skills/maggie-ops/SKILL.md +23 -2
- package/bundled-skills/maggie-seo-geo/SKILL.md +16 -0
- package/bundled-templates/maggiedash/dashboard-ui-contract.json +8 -7
- package/bundled-templates/maggiedash/section-registry.json +12 -0
- package/bundled-tools/clis/maggie_agent_content.py +75 -0
- package/bundled-tools/clis/maggie_dash.py +112 -1
- package/bundled-tools/clis/maggie_deployment.py +51 -0
- package/bundled-tools/clis/maggie_ops.py +23 -0
- package/bundled-tools/clis/maggie_verification.py +33 -0
- package/bundled-tools/runtime/agent_runtime.py +38 -0
- package/bundled-tools/runtime/analytics_traffic.py +6 -1
- package/bundled-tools/runtime/maggie_dash_store.py +121 -8
- package/bundled-tools/runtime/maggie_dash_ui.py +35 -1
- package/bundled-tools/runtime/maggie_sections.py +143 -0
- package/bundled-tools/runtime/restart_gate.py +22 -0
- package/bundled-tools/runtime/surface_coverage.py +48 -0
- package/package.json +1 -1
- package/references/maggiedash-telemetry-contract.md +12 -0
package/README.md
CHANGED
|
@@ -92,6 +92,9 @@ maggie bootstrap interview | phase ...
|
|
|
92
92
|
maggie dash install | init | status | migrate | cms ...
|
|
93
93
|
maggie dash transition ... # explicit content approval transition
|
|
94
94
|
maggie dash variant ... # service variant create/review/preview/publish
|
|
95
|
+
maggie dash sections ... # validate/catalogue/identity keys
|
|
96
|
+
maggie agent-content write ... # host-authorized content bridge
|
|
97
|
+
maggie verification coverage ... # changed surface/locale evidence gate
|
|
95
98
|
maggie clone ... # authorized homepage capture
|
|
96
99
|
maggie design ... # authorized interior-page design
|
|
97
100
|
maggie clone-to-template ... # URL → validated marketplace template
|
|
@@ -191,8 +194,8 @@ artifact schemas.
|
|
|
191
194
|
Recommended upgrade sequence for the current release:
|
|
192
195
|
|
|
193
196
|
```bash
|
|
194
|
-
npx @topy-ai/maggie@0.7.
|
|
195
|
-
npx @topy-ai/maggie@0.7.
|
|
197
|
+
npx @topy-ai/maggie@0.7.5 update --project . --force
|
|
198
|
+
npx @topy-ai/maggie@0.7.5 cleanup --project .
|
|
196
199
|
```
|
|
197
200
|
|
|
198
201
|
Maintainers should pass npm credentials through the repository helper, never
|
|
@@ -202,14 +205,20 @@ as a command-line argument:
|
|
|
202
205
|
node scripts/publish-npm.mjs --maggie-env-file ../.env
|
|
203
206
|
```
|
|
204
207
|
|
|
205
|
-
The 0.7.
|
|
208
|
+
The 0.7.5 workflow adds the installable MaggieDash admin distribution and
|
|
206
209
|
audited CMS operations (`cms revisions`,
|
|
207
|
-
`trash`, `restore`, `schedule`, `duplicate`, `redirect`, and
|
|
208
|
-
`preview`)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
`trash`, `restore`, `schedule`, `publish-due`, `duplicate`, `redirect`, and
|
|
211
|
+
signed `preview`). It also adds a read-only MaggieDash `--diff`/`--dry-run`
|
|
212
|
+
migration inventory, body-only dashboard prop validation, dialog accessibility
|
|
213
|
+
contract checks, 404-only redirect policy, trash/scheduler/preview invariants,
|
|
214
|
+
and a shared first-party traffic filter for every measurement channel. It also
|
|
215
|
+
adds stable section identities and reusable section presets, a machine-readable
|
|
216
|
+
MaggieDash section registry, a short-lived agent content bridge, restart gates
|
|
217
|
+
for out-of-band translation writes, deployment release-runner generation,
|
|
218
|
+
disk-versus-manifest skill inventory checks, and changed-surface/locale
|
|
219
|
+
verification. The release retains the existing service matching,
|
|
220
|
+
localization, seed-manifest, lockfile/analytics, sitemap, deployment and
|
|
221
|
+
rollback workflows.
|
|
213
222
|
|
|
214
223
|
## MaggieDash lifecycle
|
|
215
224
|
|
|
@@ -231,6 +240,17 @@ for local development or a configured private Git URL. The host project keeps
|
|
|
231
240
|
ownership of routes, email/password auth, database, provider credentials, and
|
|
232
241
|
the `/api/maggie/*` adapter.
|
|
233
242
|
|
|
243
|
+
Compare an existing dashboard before replacing it:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
maggie dash install --project . --diff \
|
|
247
|
+
--existing-dir src/plugins/maggie-studio/src
|
|
248
|
+
maggie dash install --project . --dry-run
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
These commands do not write files or installation state. Review the report and
|
|
252
|
+
map host-owned files before using `--force`.
|
|
253
|
+
|
|
234
254
|
Content remains draft-first and external writes remain explicit. Use the
|
|
235
255
|
project-local memory workflow for confirmed preferences and reusable lessons;
|
|
236
256
|
feedback drafts are never promoted to active memory automatically.
|
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.
|
|
11
|
+
npx @topy-ai/maggie@0.7.5 init --agent all
|
|
12
12
|
npx @topy-ai/maggie doctor --project .
|
|
13
13
|
```
|
|
14
14
|
|
|
@@ -25,6 +25,10 @@ maggie doctor --project . --require-bootstrap --strict
|
|
|
25
25
|
deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
|
|
26
26
|
publish 與 production deployment 需要明確確認。
|
|
27
27
|
|
|
28
|
+
MaggieDash 也提供穩定 section identity、可重用 section arrangement、機器可讀
|
|
29
|
+
section registry、短期 agent content bridge,以及 translation out-of-band write
|
|
30
|
+
後的 restart gate。`maggie doctor` 會比較 install manifest 與磁碟上的實際 skills。
|
|
31
|
+
|
|
28
32
|
新版 release gates:
|
|
29
33
|
|
|
30
34
|
```bash
|
package/bin/maggie.js
CHANGED
|
@@ -66,9 +66,11 @@ Usage:
|
|
|
66
66
|
maggie doctor [--project PATH]
|
|
67
67
|
maggie bootstrap interview [project]
|
|
68
68
|
maggie dash init|install|status|migrate|transition|variant|cms --project PATH [options]
|
|
69
|
+
maggie dash sections <validate|prompt|keys> [options]
|
|
69
70
|
maggie dash status --project PATH
|
|
70
71
|
maggie dash migrate --project PATH --confirm
|
|
71
72
|
maggie content FILE --source PROVIDER --project PATH --confirm
|
|
73
|
+
maggie agent-content write --url URL --token-env ENV --payload FILE
|
|
72
74
|
maggie bootstrap phase <start|pass|fail|status> <phase> [project]
|
|
73
75
|
maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
|
|
74
76
|
maggie cleanup --project PATH [--confirm]
|
|
@@ -117,6 +119,7 @@ Usage:
|
|
|
117
119
|
maggie site-audit URL --crawl --save-baseline FILE --reviewer NAME
|
|
118
120
|
maggie site-audit URL --crawl --baseline FILE
|
|
119
121
|
maggie browser-audit URL --browse PATH --output DIR --required SELECTOR [--sticky SELECTOR]
|
|
122
|
+
maggie verification coverage --contract FILE --evidence FILE
|
|
120
123
|
maggie ops audit|preflight|verify|lockfiles|seed-manifest --project PATH
|
|
121
124
|
maggie ops preflight --project PATH --write
|
|
122
125
|
|
|
@@ -320,11 +323,22 @@ function doctor(args) {
|
|
|
320
323
|
["claude-skills", existsSync(join(root, ".claude", "skills"))],
|
|
321
324
|
];
|
|
322
325
|
for (const [name, passed] of checks) console.log(`${passed ? "PASS" : "INFO"} ${name}`);
|
|
326
|
+
const manifestPath = join(root, STATE_DIR, "install.json");
|
|
327
|
+
let manifest = null;
|
|
328
|
+
try { if (existsSync(manifestPath)) manifest = JSON.parse(readFileSync(manifestPath, "utf8")); } catch { manifest = null; }
|
|
329
|
+
const expected = Array.isArray(manifest?.skills) ? [...new Set(manifest.skills)] : null;
|
|
323
330
|
const installed = [];
|
|
331
|
+
const installedNames = new Set();
|
|
324
332
|
for (const agentRoot of [join(root, ".agents"), join(root, ".claude")]) {
|
|
325
|
-
for (const skill of SKILL_NAMES) if (existsSync(join(agentRoot, "skills", skill, "SKILL.md")))
|
|
333
|
+
for (const skill of SKILL_NAMES) if (existsSync(join(agentRoot, "skills", skill, "SKILL.md"))) {
|
|
334
|
+
installed.push(`${agentRoot}/${skill}`);
|
|
335
|
+
installedNames.add(skill);
|
|
336
|
+
}
|
|
326
337
|
}
|
|
327
|
-
|
|
338
|
+
const missing = expected ? expected.filter((skill) => !installedNames.has(skill)) : [];
|
|
339
|
+
const inventoryState = expected && missing.length ? "INCOMPLETE" : "PASS";
|
|
340
|
+
console.log(`${inventoryState} skill-inventory manifest=${expected?.length ?? "unknown"} on-disk=${installedNames.size}${missing.length ? ` missing=${missing.join(",")}` : ""}`);
|
|
341
|
+
console.log(`INFO installed-skills=${installedNames.size}`);
|
|
328
342
|
console.log("INFO doctor is diagnostic; bootstrap completion remains a project decision gate.");
|
|
329
343
|
}
|
|
330
344
|
|
|
@@ -394,7 +408,9 @@ try {
|
|
|
394
408
|
else if (command === "feedback") workflowCli("maggie_feedback.py", args);
|
|
395
409
|
else if (command === "site-audit") workflowCli("site_audit.py", args);
|
|
396
410
|
else if (command === "browser-audit") workflowCli("maggie_browser_audit.py", args);
|
|
411
|
+
else if (command === "verification") workflowCli("maggie_verification.py", args);
|
|
397
412
|
else if (command === "dash") workflowCli("maggie_dash.py", args);
|
|
413
|
+
else if (command === "agent-content") workflowCli("maggie_agent_content.py", args);
|
|
398
414
|
else if (command === "content") workflowCli("maggie_content.py", args);
|
|
399
415
|
else if (command === "init" || command === "install") install(args);
|
|
400
416
|
else if (command === "update") update(args);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://maggiedash.noblox.app/contracts/release-runner-v1.schema.json",
|
|
4
|
+
"title": "Maggie deployment release runner v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "orderedSteps", "retention"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schemaVersion": {"const": "maggie-release-runner.v1"},
|
|
9
|
+
"orderedSteps": {"const": ["install", "typecheck", "migrate", "build", "carry-agent-state", "switch", "restart", "verify", "prune"]},
|
|
10
|
+
"retention": {"const": "exactly-current-plus-one-rollback"},
|
|
11
|
+
"mutation": {"enum": ["reviewable-script", "executed-after-confirmation"]}
|
|
12
|
+
},
|
|
13
|
+
"additionalProperties": false
|
|
14
|
+
}
|
|
@@ -14,6 +14,16 @@ frontend framework.
|
|
|
14
14
|
dry-run, backup, checksum, cutover, and rollback evidence;
|
|
15
15
|
- [`storage-model.md`](storage-model.md): SQLite/PostgreSQL table ownership and
|
|
16
16
|
adapter rules.
|
|
17
|
+
- [`redirect-policy-v1.json`](redirect-policy-v1.json): 404-only lookup and
|
|
18
|
+
self/chain safety for canonical route redirects;
|
|
19
|
+
- [`telemetry-policy-v1.json`](telemetry-policy-v1.json): first-party/toolchain
|
|
20
|
+
traffic exclusion for every measurement channel.
|
|
21
|
+
- [`agent-content-v1.json`](agent-content-v1.json): short-lived host bridge for
|
|
22
|
+
validated agent content writes without database credentials;
|
|
23
|
+
- [`section-identity-v1.json`](section-identity-v1.json): stable section IDs
|
|
24
|
+
for translation keys and ordered migration;
|
|
25
|
+
- [`translation-cache-policy-v1.json`](translation-cache-policy-v1.json):
|
|
26
|
+
restart-after-out-of-band-write evidence.
|
|
17
27
|
|
|
18
28
|
MaggieDash owns these contracts. Provider adapters may add namespaced metadata,
|
|
19
29
|
but they may not change the required identity, status, provenance, or approval
|
|
@@ -32,3 +42,15 @@ published → archived
|
|
|
32
42
|
|
|
33
43
|
No import, AI generation, rewrite, or adapter sync may publish content without
|
|
34
44
|
an approval record and an auditable actor/correlation ID.
|
|
45
|
+
|
|
46
|
+
## CMS invariants
|
|
47
|
+
|
|
48
|
+
- `trashed` is a status. Public readers exclude it; trash is reversible.
|
|
49
|
+
- A no-op save creates no revision, and the first update of an imported row
|
|
50
|
+
snapshots the existing state before replacing it.
|
|
51
|
+
- Scheduled content with a past timestamp is published on the next scheduler
|
|
52
|
+
tick. A trashed row is never selected by the due query.
|
|
53
|
+
- Preview responses are real preview routes protected by HMAC tokens and
|
|
54
|
+
advertise `noindex`, `X-Robots-Tag: noindex`, and `Cache-Control: no-store`.
|
|
55
|
+
- Redirect lookup happens after the route has returned 404. Self-redirects and
|
|
56
|
+
redirect chains are rejected at write time.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://maggiedash.noblox.app/contracts/agent-content-v1.schema.json",
|
|
4
|
+
"title": "MaggieDash agent content bridge v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["method", "authorization", "tokenLifetime", "databaseCredentials"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"method": {"const": "POST"},
|
|
9
|
+
"authorization": {"const": "Bearer short-lived session token"},
|
|
10
|
+
"tokenLifetime": {"const": "terminal session"},
|
|
11
|
+
"databaseCredentials": {"const": "never exposed to agent"},
|
|
12
|
+
"writes": {"type": "array", "items": {"enum": ["validated content operation", "revision", "audit event"]}}
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://maggiedash.noblox.app/contracts/redirect-policy-v1.schema.json",
|
|
4
|
+
"title": "MaggieDash redirect policy v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "lookupAfterStatus", "rejectSelf", "rejectChains"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schemaVersion": {"const": "maggiedash-redirect-policy.v1"},
|
|
9
|
+
"lookupAfterStatus": {"const": 404},
|
|
10
|
+
"rejectSelf": {"const": true},
|
|
11
|
+
"rejectChains": {"const": true},
|
|
12
|
+
"allowedStatusCodes": {"type": "array", "items": {"enum": [301, 302, 307, 308]}}
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://maggiedash.noblox.app/contracts/section-identity-v1.schema.json",
|
|
4
|
+
"title": "MaggieDash stable section identity v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "keyPattern", "fallback", "migrationOrder"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schemaVersion": {"const": "maggiedash-section-identity.v1"},
|
|
9
|
+
"keyPattern": {"const": "page.<id>.sections.<section-id>.<field>"},
|
|
10
|
+
"fallback": {"const": "array-index-until-migrated"},
|
|
11
|
+
"migrationOrder": {"const": "longest-old-prefix-first"}
|
|
12
|
+
},
|
|
13
|
+
"additionalProperties": false
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://maggiedash.noblox.app/contracts/telemetry-policy-v1.schema.json",
|
|
4
|
+
"title": "MaggieDash telemetry policy v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "filterBeforeMeasurement", "channels"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schemaVersion": {"const": "maggiedash-telemetry-policy.v1"},
|
|
9
|
+
"filterBeforeMeasurement": {"const": true},
|
|
10
|
+
"channels": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {"enum": ["page_view", "not_found", "redirect_hit", "booking_click", "analytics", "audit"]},
|
|
13
|
+
"minItems": 1
|
|
14
|
+
},
|
|
15
|
+
"identityInference": {"const": "forbidden"}
|
|
16
|
+
},
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://maggiedash.noblox.app/contracts/translation-cache-policy-v1.schema.json",
|
|
4
|
+
"title": "MaggieDash translation cache policy v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "outOfBandWrite", "restartRequired", "restartCommand", "verificationAfterRestart"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schemaVersion": {"const": "maggie-restart-gate.v1"},
|
|
9
|
+
"outOfBandWrite": {"const": true},
|
|
10
|
+
"restartRequired": {"const": true},
|
|
11
|
+
"restartCommand": {"type": "string", "minLength": 1},
|
|
12
|
+
"verificationAfterRestart": {"const": true}
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# MaggieDash telemetry contract
|
|
2
|
+
|
|
3
|
+
Every measurement path must call the shared first-party traffic filter before
|
|
4
|
+
incrementing a counter or writing a measurement event. This includes page
|
|
5
|
+
views, 404/not-found rows, redirect hits, booking clicks, analytics events and
|
|
6
|
+
operational audit counters. Filtering only page views leaves synthetic sweeps
|
|
7
|
+
in the other totals.
|
|
8
|
+
|
|
9
|
+
Use `should_record_measurement(event)` from
|
|
10
|
+
`tools/runtime/analytics_traffic.py`. The filter accepts explicit Maggie/test
|
|
11
|
+
markers and known toolchain user agents; it never guesses identity from IP
|
|
12
|
+
addresses or private user fields.
|
|
@@ -25,11 +25,42 @@ explicitly supplied, and records the resolved revision in
|
|
|
25
25
|
CLI argument. For local development or a pinned checkout, use
|
|
26
26
|
`--source /path/to/MaggieDash` or `--source URL --ref REF`.
|
|
27
27
|
|
|
28
|
+
Before replacing an existing hand-written dashboard, create a read-only
|
|
29
|
+
migration report. It compares the first-party manifest with the existing
|
|
30
|
+
workspace and labels additions, unchanged files, updates that would be
|
|
31
|
+
preserved, and unmanaged files:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
maggie dash install --project . --diff \
|
|
35
|
+
--existing-dir src/plugins/maggie-studio/src
|
|
36
|
+
maggie dash install --project . --dry-run
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
These modes do not require `--confirm` and do not write dashboard files or
|
|
40
|
+
`.maggie/dash-install.json`. Review the report and explicitly map or back up
|
|
41
|
+
hand-written files before using `--force`; an idempotent installer is not a
|
|
42
|
+
schema migration.
|
|
43
|
+
|
|
28
44
|
The installed dashboard owns the React workspace UI. The host project still
|
|
29
45
|
owns the framework route, email/password session middleware, database, media
|
|
30
46
|
storage, provider credentials, and `/api/maggie/*` adapter endpoints. Read the
|
|
31
47
|
MaggieDash host adapter contract before adding a new framework adapter.
|
|
32
48
|
|
|
49
|
+
For a built/deployed host, an agent must use the host's validated content
|
|
50
|
+
bridge rather than editing release files or receiving database credentials:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
maggie agent-content write \
|
|
54
|
+
--url https://example.test/api/maggie/agent-content.json \
|
|
55
|
+
--token-env MAGGIE_CONTENT_TOKEN --payload .maggie/content-operation.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The host mints a short-lived session token. The bridge uses `POST` with
|
|
59
|
+
`Authorization: Bearer`, delegates to the ordinary save/revision/audit path,
|
|
60
|
+
and returns 401/422 for invalid authority or content. Never put database
|
|
61
|
+
credentials or the short-lived token in the payload or generated reports. See
|
|
62
|
+
the [agent content contract](../../contracts/maggiedash/agent-content-v1.json).
|
|
63
|
+
|
|
33
64
|
## Workflow
|
|
34
65
|
|
|
35
66
|
1. Inspect the project with `maggie dash status --project PATH`.
|
|
@@ -105,8 +136,15 @@ maggie dash cms trash --project . --project-id local-project --document-id <id>
|
|
|
105
136
|
maggie dash cms restore --project . --project-id local-project --document-id <id> --reason "restore" --confirm
|
|
106
137
|
maggie dash cms schedule --project . --project-id local-project --document-id <id> \
|
|
107
138
|
--publish-at 2026-09-20T10:00:00Z --reason "approved release" --confirm
|
|
139
|
+
maggie dash cms publish-due --project . --project-id local-project \
|
|
140
|
+
--reason "scheduled publish tick" --confirm
|
|
108
141
|
maggie dash cms duplicate --project . --project-id local-project --document-id <id> \
|
|
109
142
|
--new-id <new-id> --new-slug <new-slug> --reason "create draft" --confirm
|
|
143
|
+
maggie dash cms preset-save --project . --project-id local-project \
|
|
144
|
+
--preset-id treatment-page --name "Treatment page" \
|
|
145
|
+
--sections-file .maggie/sections.json --confirm
|
|
146
|
+
maggie dash cms preset-insert --project . --project-id local-project \
|
|
147
|
+
--preset-id treatment-page --existing-id hero-1 --reason "add arrangement" --confirm
|
|
110
148
|
maggie dash cms redirect --project . --project-id local-project --from-path /old --to-path /new \
|
|
111
149
|
--reason "canonical slug change" --confirm
|
|
112
150
|
maggie dash cms preview --project . --project-id local-project --document-id <id> \
|
|
@@ -114,6 +152,41 @@ maggie dash cms preview --project . --project-id local-project --document-id <id
|
|
|
114
152
|
```
|
|
115
153
|
|
|
116
154
|
Content writes create immutable revision snapshots. Trash is reversible and
|
|
117
|
-
does not destroy the document
|
|
118
|
-
content and requires a timezone
|
|
119
|
-
|
|
155
|
+
does not destroy the document; public readers must filter `status=trashed`.
|
|
156
|
+
Scheduling is accepted only for approved content and requires a timezone; a
|
|
157
|
+
past timestamp is published on the next scheduler tick, while trashed rows
|
|
158
|
+
are never eligible. A no-op save creates no revision, and the first update of
|
|
159
|
+
an imported row snapshots its prior state. Redirects are consulted only after
|
|
160
|
+
the route returns 404 and self/chain redirects are rejected. Preview tokens
|
|
161
|
+
are short-lived HMAC-signed tokens; preview responses must use a real preview
|
|
162
|
+
URL with `noindex`, `X-Robots-Tag: noindex`, and `Cache-Control: no-store`.
|
|
163
|
+
Never place the secret in source control or generated reports. See the
|
|
164
|
+
provider-neutral [MaggieDash contract set](../../contracts/maggiedash/README.md).
|
|
165
|
+
|
|
166
|
+
Reusable arrangements are stored without page-specific section IDs and expand
|
|
167
|
+
into ordinary editable sections with fresh IDs on every insert. Use the
|
|
168
|
+
machine-readable section catalogue before asking a model to plan copy:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
maggie dash sections validate --registry templates/maggiedash/section-registry.json \
|
|
172
|
+
--sections-file .maggie/sections.json
|
|
173
|
+
maggie dash sections prompt --registry templates/maggiedash/section-registry.json
|
|
174
|
+
maggie dash sections keys --registry templates/maggiedash/section-registry.json \
|
|
175
|
+
--page-id <page-id> --sections-file .maggie/sections.json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The catalogue declares purpose, usage, placement, repeatability and layout
|
|
179
|
+
limits. Over-limit copy is an editor note; unknown types fail validation.
|
|
180
|
+
Translation keys use stable section IDs, with an array-index fallback only
|
|
181
|
+
until the ordered migration is complete.
|
|
182
|
+
|
|
183
|
+
After any script or direct adapter write to translation data, invalidate the
|
|
184
|
+
running process before verification. Record the restart and run the rendering
|
|
185
|
+
check afterward:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
maggie ops restart-gate --manifest .maggie/translation-restart.json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This prevents an in-process cache window from turning correct database state
|
|
192
|
+
into temporarily stale pages and false verification failures.
|
|
@@ -63,11 +63,15 @@ Generate a reviewable VPS plan and configuration artifacts locally:
|
|
|
63
63
|
python3 tools/clis/maggie_deployment.py /path/to/project \
|
|
64
64
|
--vps-plan --domain example.co.uk --service example \
|
|
65
65
|
--release-root /var/www/example \
|
|
66
|
-
--plan-dir .maggie/deployment/vps-staging
|
|
66
|
+
--plan-dir .maggie/deployment/vps-staging \
|
|
67
|
+
--runner-output .maggie/deployment/vps-staging/release-runner.sh
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
The output contains only the immutable release/current layout, systemd unit,
|
|
70
|
-
Nginx reverse-proxy config, health commands
|
|
71
|
+
Nginx reverse-proxy config, health commands, rollback command, and an ordered
|
|
72
|
+
reviewable release runner. The runner keeps migrations before the symlink flip,
|
|
73
|
+
carries `.agents`/`.claude` state, restarts before verification, and prunes to
|
|
74
|
+
the current release plus one rollback candidate. It never
|
|
71
75
|
contains credentials and never SSHs, changes DNS, restarts systemd or deploys.
|
|
72
76
|
|
|
73
77
|
VPS plans include a bounded retention policy: keep two immutable releases,
|
|
@@ -136,6 +140,20 @@ MaggieDash schema compatibility, and live runtime security-header checks. It wri
|
|
|
136
140
|
`.maggie/release-preflight.json`; a failed gate blocks release. It never
|
|
137
141
|
deploys, migrates, publishes, or sends provider requests.
|
|
138
142
|
|
|
143
|
+
Generate a repeatable VPS runner as part of the plan. A deploy step that exists
|
|
144
|
+
only in an operator's shell history is not a release contract:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python3 tools/clis/maggie_deployment.py /path/to/project \
|
|
148
|
+
--vps-plan --domain example.co.uk --service example \
|
|
149
|
+
--release-root /var/www/example \
|
|
150
|
+
--runner-output .maggie/deployment/release-runner.sh
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Review the generated runner before execution. Its order is install → typecheck
|
|
154
|
+
→ migrate → build → carry agent state → switch → restart → verify → prune;
|
|
155
|
+
retention keeps the current release and one rollback candidate.
|
|
156
|
+
|
|
139
157
|
Production additionally requires `docs/deployment-rollback-smoke.json` with
|
|
140
158
|
`passed: true`, `environment: production`, a non-empty `previousRelease`, and
|
|
141
159
|
`testedAt`. A rollback command in a plan is not proof that the rollback was
|
|
@@ -220,5 +220,26 @@ The seed manifest must be opt-in, sanitized, and contain non-empty fixtures;
|
|
|
220
220
|
an empty dev database is not evidence that an operational check passed.
|
|
221
221
|
Known Maggie/toolchain traffic can be measured without polluting first-party
|
|
222
222
|
analytics using `maggie analytics traffic-audit --events events.json`. The
|
|
223
|
-
classifier
|
|
224
|
-
|
|
223
|
+
classifier must run before every measurement channel, including page views,
|
|
224
|
+
404/not-found rows, redirect hits, booking clicks and audit counters. The
|
|
225
|
+
shared contract is in
|
|
226
|
+
[references/maggiedash-telemetry-contract.md](../../references/maggiedash-telemetry-contract.md);
|
|
227
|
+
it excludes only explicit tool/test markers and never infers identity from IP
|
|
228
|
+
or private fields.
|
|
229
|
+
|
|
230
|
+
## Agent runtime preflight
|
|
231
|
+
|
|
232
|
+
Before starting an agent from a dashboard, run the runtime preflight and show
|
|
233
|
+
its warnings to the operator. Terminal output must be stripped of ANSI escape
|
|
234
|
+
sequences before URL/code parsing. Headless hosts should use device auth and
|
|
235
|
+
state the account prerequisite before starting. Release installs and agent
|
|
236
|
+
conversation state must be carried outside the immutable release directory.
|
|
237
|
+
When continuing a conversation, pass the agent's resume subcommand before
|
|
238
|
+
other arguments; otherwise the CLI may interpret it as a new prompt.
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
maggie ops agent-preflight --output .maggie/agent-runtime-preflight.json
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The preflight is advisory about the host environment but its warnings are
|
|
245
|
+
load-bearing operational evidence, not a claim that an agent login succeeded.
|
|
@@ -19,6 +19,22 @@ Static audit success
|
|
|
19
19
|
does not establish Google indexing, scroll behavior, responsive visibility,
|
|
20
20
|
or translation quality. Record those as unverified without separate evidence.
|
|
21
21
|
|
|
22
|
+
Choose verification from the changed surface. An admin-only change still needs
|
|
23
|
+
the admin routes; an API change needs the API responses; a localized change
|
|
24
|
+
needs every affected locale. Declare the matrix and validate explicit evidence
|
|
25
|
+
before release:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
maggie verification coverage \
|
|
29
|
+
--contract contracts/maggie-verification/surface-coverage-v1.json \
|
|
30
|
+
--evidence .maggie/surface-coverage.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The evidence must contain one observation for every declared surface/locale
|
|
34
|
+
pair with a status and `passed: true`. Public-page checks alone cannot close an
|
|
35
|
+
admin or API change, and one source-language rendering cannot close a
|
|
36
|
+
localized change.
|
|
37
|
+
|
|
22
38
|
Crawl reports include `summary.byLocaleTemplate` with total, passed, failed
|
|
23
39
|
and failed URLs per declared locale and template. Regions/scripts remain
|
|
24
40
|
distinct (for example en-GB versus en-US). A template is identified only when
|
|
@@ -13,19 +13,20 @@
|
|
|
13
13
|
"components": [
|
|
14
14
|
{
|
|
15
15
|
"name": "WorkspaceBar",
|
|
16
|
-
"props": ["
|
|
16
|
+
"props": ["tabs", "active", "onSelect", "lead", "actions"],
|
|
17
17
|
"forbiddenProps": []
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"name": "WorkspaceCard",
|
|
21
|
-
"props": ["
|
|
21
|
+
"props": ["children", "className"],
|
|
22
22
|
"forbiddenProps": []
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"name": "ContentTabs",
|
|
26
|
-
"props": ["tabs", "active", "actions", "children"],
|
|
27
|
-
"forbiddenProps": ["title", "description"]
|
|
28
23
|
}
|
|
29
24
|
],
|
|
25
|
+
"dialogs": {
|
|
26
|
+
"primitive": "Modal",
|
|
27
|
+
"overlayPrimitive": "Overlay",
|
|
28
|
+
"forbiddenSourcePatterns": ["window.confirm(", "window.prompt("],
|
|
29
|
+
"requiredLiterals": ["role=\"dialog\"", "aria-modal", "Escape", "opener.current"]
|
|
30
|
+
},
|
|
30
31
|
"accessibility": ["active tab exposes aria-selected", "actions have accessible names", "workspace navigation has a landmark"]
|
|
31
32
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "maggiedash-section-registry.v1",
|
|
3
|
+
"description": "The machine-readable section vocabulary used by page planners and editors.",
|
|
4
|
+
"sections": [
|
|
5
|
+
{"type": "hero", "purpose": "Says what the page is about and offers the one important action.", "usage": "First on the page; once.", "position": "opening", "repeatable": false, "fields": [{"name": "title", "usage": "Plain words a visitor would search for.", "limit": 70, "required": true}, {"name": "intro", "usage": "Who it is for and what it does.", "limit": 220}, {"name": "ctaLabel", "usage": "The action as a verb.", "limit": 30}]},
|
|
6
|
+
{"type": "prose", "purpose": "Explains a topic at length, optionally beside an image.", "usage": "Use in the body for explanation.", "position": "body", "repeatable": true, "fields": [{"name": "heading", "usage": "The argument in a sentence.", "limit": 90}, {"name": "paragraphs", "usage": "Two or three standalone paragraphs.", "limit": 400, "repeats": {"min": 1, "max": 4}}]},
|
|
7
|
+
{"type": "cards", "purpose": "Presents parallel points side by side.", "usage": "Use for steps or genuinely parallel points.", "position": "body", "repeatable": true, "fields": [{"name": "heading", "usage": "What the cards share.", "limit": 90}, {"name": "items", "usage": "The parallel points.", "limit": 220, "repeats": {"min": 3, "max": 3}}]},
|
|
8
|
+
{"type": "links", "purpose": "Sends the reader to related pages.", "usage": "Near the end after the page has done its job.", "position": "closing", "repeatable": true, "fields": [{"name": "heading", "usage": "What the links have in common.", "limit": 90}, {"name": "items", "usage": "Real related pages on this site.", "limit": 160, "repeats": {"min": 2, "max": 6}}]},
|
|
9
|
+
{"type": "faq", "purpose": "Answers questions a visitor would otherwise ask.", "usage": "Once near the end; use real visitor questions.", "position": "closing", "repeatable": false, "fields": [{"name": "heading", "usage": "The question topic.", "limit": 90}, {"name": "items", "usage": "Direct questions and answers.", "limit": 600, "repeats": {"min": 2, "max": 8}}]},
|
|
10
|
+
{"type": "cta", "purpose": "Makes the closing ask.", "usage": "Last on the page; once.", "position": "closing", "repeatable": false, "fields": [{"name": "heading", "usage": "The invitation in a sentence.", "limit": 90, "required": true}, {"name": "body", "usage": "What happens next.", "limit": 220}, {"name": "label", "usage": "The action as a verb.", "limit": 30, "required": true}]}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Use a short-lived MaggieDash host token for validated content writes."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from urllib.error import HTTPError, URLError
|
|
12
|
+
from urllib.request import Request, urlopen
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
SENSITIVE_KEYS = {"databaseUrl", "databaseURL", "password", "secret", "token", "apiKey"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_payload(value: object) -> dict:
|
|
19
|
+
if not isinstance(value, dict):
|
|
20
|
+
raise ValueError("payload must be a JSON object")
|
|
21
|
+
forbidden = sorted(SENSITIVE_KEYS.intersection(value))
|
|
22
|
+
if forbidden:
|
|
23
|
+
raise ValueError("payload must not contain credentials; use the short-lived token environment variable")
|
|
24
|
+
if not value.get("op"):
|
|
25
|
+
raise ValueError("payload.op is required")
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def write(url: str, token_env: str, payload: dict, timeout: int = 30) -> dict:
|
|
30
|
+
token = os.environ.get(token_env, "")
|
|
31
|
+
if not token:
|
|
32
|
+
raise ValueError(f"{token_env} is not set")
|
|
33
|
+
request = Request(url, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), method="POST", headers={
|
|
34
|
+
"Accept": "application/json", "Content-Type": "application/json", "Authorization": f"Bearer {token}",
|
|
35
|
+
})
|
|
36
|
+
try:
|
|
37
|
+
with urlopen(request, timeout=timeout) as response:
|
|
38
|
+
raw = response.read().decode("utf-8", errors="replace")
|
|
39
|
+
status = response.status
|
|
40
|
+
except HTTPError as error:
|
|
41
|
+
raw = error.read().decode("utf-8", errors="replace")
|
|
42
|
+
status = error.code
|
|
43
|
+
except URLError as error:
|
|
44
|
+
raise RuntimeError("content host could not be reached") from error
|
|
45
|
+
try:
|
|
46
|
+
body = json.loads(raw) if raw else {}
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
body = {"error": "host returned non-JSON"}
|
|
49
|
+
if isinstance(body, dict):
|
|
50
|
+
for key in SENSITIVE_KEYS:
|
|
51
|
+
body.pop(key, None)
|
|
52
|
+
return {"status": status, "passed": 200 <= status < 300, "response": body}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main() -> int:
|
|
56
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
57
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
58
|
+
command = sub.add_parser("write", help="POST a validated content operation")
|
|
59
|
+
command.add_argument("--url", required=True)
|
|
60
|
+
command.add_argument("--token-env", default="MAGGIE_CONTENT_TOKEN")
|
|
61
|
+
command.add_argument("--payload", type=Path, required=True)
|
|
62
|
+
command.add_argument("--timeout", type=int, default=30)
|
|
63
|
+
args = parser.parse_args()
|
|
64
|
+
try:
|
|
65
|
+
payload = validate_payload(json.loads(args.payload.read_text(encoding="utf-8")))
|
|
66
|
+
result = write(args.url, args.token_env, payload, args.timeout)
|
|
67
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
68
|
+
return 0 if result["passed"] else 1
|
|
69
|
+
except (OSError, ValueError, RuntimeError) as error:
|
|
70
|
+
print(f"agent-content: {error}", file=sys.stderr)
|
|
71
|
+
return 2
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
raise SystemExit(main())
|