@topy-ai/maggie 0.7.53 → 0.7.55
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 +28 -50
- package/README.md +80 -64
- package/bin/maggie.js +61 -9
- package/bundled-contracts/maggie-assistant-tools/booking-receptionist-example-v1.json +18 -2
- package/bundled-contracts/maggie-assistant-tools/report-v1.schema.json +43 -0
- package/bundled-contracts/maggie-assistant-tools/tool-registry-example-v1.json +249 -11
- package/bundled-contracts/maggie-assistant-tools/tool-registry-v1.schema.json +175 -0
- package/bundled-contracts/maggie-ops/agent-routines-v1.schema.json +48 -0
- package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +1 -1
- package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +2 -2
- package/bundled-skills/catalog.json +1 -1
- package/bundled-skills/maggie-assistant-tools/SKILL.md +34 -1
- package/bundled-skills/maggie-assistant-tools/references/report-inbox.md +45 -0
- package/bundled-skills/maggie-assistant-tools/references/unattended-agents.md +70 -0
- package/bundled-skills/maggie-blog/SKILL.md +15 -0
- package/bundled-skills/maggie-booking/SKILL.md +20 -12
- package/bundled-skills/maggie-content-localization/SKILL.md +26 -0
- package/bundled-skills/maggie-dash/SKILL.md +37 -2
- package/bundled-skills/maggie-deployment/SKILL.md +104 -1
- package/bundled-skills/maggie-deployment/references/vps.md +5 -0
- package/bundled-skills/maggie-design/SKILL.md +81 -0
- package/bundled-skills/maggie-feedback/SKILL.md +10 -2
- package/bundled-skills/maggie-marketplace/SKILL.md +22 -2
- package/bundled-skills/maggie-marketplace/references/directory-search.md +1 -1
- package/bundled-skills/maggie-marketplace/references/directory.mjs +2 -2
- package/bundled-skills/maggie-mobile-app/SKILL.md +29 -0
- package/bundled-skills/maggie-ops/SKILL.md +28 -0
- package/bundled-skills/maggie-ops/references/agents-repository.md +108 -0
- package/bundled-skills/maggie-ops/references/ops-autopilot.md +65 -0
- package/bundled-skills/maggie-ops/references/routine-mode.md +92 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +9 -1
- package/bundled-skills/maggie-social-share/SKILL.md +50 -0
- package/bundled-tools/clis/maggie.py +63 -3
- package/bundled-tools/clis/maggie_assistant_tools.py +225 -1
- package/bundled-tools/clis/maggie_booking.py +9 -8
- package/bundled-tools/clis/maggie_dash.py +173 -3
- package/bundled-tools/clis/maggie_deployment_ops.py +64 -1
- package/bundled-tools/clis/maggie_design.py +7 -1
- package/bundled-tools/clis/maggie_design_bulk.py +290 -0
- package/bundled-tools/clis/maggie_design_lint.py +131 -1
- package/bundled-tools/clis/maggie_feedback.py +41 -13
- package/bundled-tools/clis/maggie_host_runtime.py +458 -0
- package/bundled-tools/clis/maggie_live_urls.py +147 -0
- package/bundled-tools/clis/maggie_localization.py +3 -0
- package/bundled-tools/clis/maggie_localization_keys.py +151 -0
- package/bundled-tools/clis/maggie_media_generate.py +174 -0
- package/bundled-tools/clis/maggie_mobile.py +84 -1
- package/bundled-tools/clis/maggie_ops.py +2 -0
- package/bundled-tools/clis/maggie_ops_routines.py +352 -0
- package/bundled-tools/clis/maggie_product_release.py +49 -1
- package/bundled-tools/clis/maggie_query_ideas.py +154 -0
- package/bundled-tools/clis/maggie_skill_catalog.py +16 -2
- package/bundled-tools/clis/maggie_social_share.py +223 -2
- package/bundled-tools/clis/maggie_version_sources.py +93 -0
- package/bundled-tools/integrations/maggie-api-pull.md +49 -0
- package/package.json +1 -1
- package/bundled-contracts/maggiedash/execution-board.json +0 -5463
- package/bundled-references/maggiedash-booking/CURRENT-STATE.md +0 -210
- package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +0 -5463
- package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +0 -270
- package/bundled-references/maggiedash-booking/PROGRESS.md +0 -279
- package/bundled-references/maggiedash-booking/README.md +0 -288
- package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +0 -551
- package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +0 -107
- package/bundled-references/maggiedash-booking/TASKS.md +0 -178
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Unattended agents: keys, sign-in grants and clipped lists
|
|
2
|
+
|
|
3
|
+
A scheduled routine (Claude Routines, Codex Automations, a cloud task) calls
|
|
4
|
+
the product's MCP with nobody watching. Three things go wrong when the tool
|
|
5
|
+
surface was designed only for a person in a chat. Each is checked by
|
|
6
|
+
`maggie assistant registry-validate` once the registry declares it.
|
|
7
|
+
|
|
8
|
+
## 1. A prompt is not a limit
|
|
9
|
+
|
|
10
|
+
A routine's prompt can say "add at most ten ideas a day", but nothing stops
|
|
11
|
+
the next run, a changed prompt, or a confused model from doing more. Every
|
|
12
|
+
write an unattended caller can reach needs a cap the **server** counts.
|
|
13
|
+
|
|
14
|
+
Declare it in `registry.unattended` (`enforcedBy: "server"`):
|
|
15
|
+
|
|
16
|
+
- **Agent keys** (`agentKeys`): an API key made for one routine holds a
|
|
17
|
+
standing permission naming the writes it may run without a preview and a
|
|
18
|
+
daily number for each (`standingTools.<tool>.perDay`). Before such a write
|
|
19
|
+
the server claims one use; when the claim is refused (today's number is
|
|
20
|
+
used, the permission was revoked) the tool returns the reason, not a
|
|
21
|
+
preview (`refusalReturnsReason: true`) — nobody is there to confirm one, and
|
|
22
|
+
an agent key can never confirm previews (`canConfirmPreviews: false`).
|
|
23
|
+
Standing permissions cover reversible or spending writes only; public or
|
|
24
|
+
irreversible writes always go through a person.
|
|
25
|
+
- **Sign-in (OAuth) grants** (`oauth`): a connector the person signed in with
|
|
26
|
+
acts as that person, with their full role. Give the grant its own server
|
|
27
|
+
caps — a total daily write number (`dailyWriteLimit`) and/or one per tool
|
|
28
|
+
(`perToolPerDay`) — that the owner sets on the connections page, and count
|
|
29
|
+
them per grant. The validator fails any write a grant can reach with no cap.
|
|
30
|
+
|
|
31
|
+
Record each claimed use with the key or grant id in the audit log, the same
|
|
32
|
+
way a standing workflow approval is recorded (see
|
|
33
|
+
[approvals and audit](approvals-and-audit.md)).
|
|
34
|
+
|
|
35
|
+
## 2. The sign-in default is the role's full tool set
|
|
36
|
+
|
|
37
|
+
A grant's default tool set should be what the person's role allows on MCP,
|
|
38
|
+
filtered at call time by the role (`oauth.defaultTools: "role"`). A default
|
|
39
|
+
copied from the first feature that used sign-in (for example only the
|
|
40
|
+
translation tools) leaves a routine connected by sign-in unable to reach the
|
|
41
|
+
rest of the product, with no error that says why. A narrower default is
|
|
42
|
+
allowed only with `narrowedReason`; the validator names the tools it leaves
|
|
43
|
+
out.
|
|
44
|
+
|
|
45
|
+
Re-check membership and role on every call, so removing or demoting a person
|
|
46
|
+
cuts their connected apps off at once.
|
|
47
|
+
|
|
48
|
+
## 3. Clipped list fields need a full-text get tool
|
|
49
|
+
|
|
50
|
+
List tools keep responses small by shortening long fields (a post's text to
|
|
51
|
+
about 300 characters). An agent asked to check what will be published then
|
|
52
|
+
reports text "cut off mid-sentence" that is in fact whole. For every list
|
|
53
|
+
that shortens fields:
|
|
54
|
+
|
|
55
|
+
- declare `clips` on the list tool: the shortened `fields`, `maxChars`, the
|
|
56
|
+
`getTool` that returns one item in full, and the `idField` it takes;
|
|
57
|
+
- mark each shortened item in the result: `clipped: true` or
|
|
58
|
+
`clipped: ["text"]`;
|
|
59
|
+
- serve the get tool on every channel the list is on, read-only, same scope;
|
|
60
|
+
- say in the list's model-facing description that text is shortened and which
|
|
61
|
+
tool reads it in full.
|
|
62
|
+
|
|
63
|
+
Check a recorded result of the list:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
maggie assistant list-check --registry tools.registry.json --tool social_list --result recorded-list.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
It fails items whose field reaches `maxChars` (or ends with an ellipsis)
|
|
70
|
+
without a `clipped` marker, and items without the id the get tool needs.
|
|
@@ -56,6 +56,21 @@ Search/sort views are not indexable; drafts never appear in public routes, RSS,
|
|
|
56
56
|
or sitemap output. Provider keys remain server-side. Public publication and
|
|
57
57
|
migrations always require explicit confirmation.
|
|
58
58
|
|
|
59
|
+
API Pull deliveries need their live URL recorded so social posts and internal
|
|
60
|
+
links point at the page that exists now: the site reports it on publish
|
|
61
|
+
(`maggie blog live-urls record --content-id ID --url URL`), and
|
|
62
|
+
`maggie blog live-urls match --sitemap URL --write` fills the gaps
|
|
63
|
+
additively — it never retires an earlier match, a post tracked with an empty
|
|
64
|
+
URL is still matched, and the original page wins over translated copies that
|
|
65
|
+
share the slug. See
|
|
66
|
+
[where a delivered post went live](../../bundled-tools/integrations/maggie-api-pull.md#where-a-delivered-post-went-live).
|
|
67
|
+
|
|
68
|
+
A pull site also pulls rewrites (`GET /updates`, same schedule as `/posts`):
|
|
69
|
+
apply each one to the post it is for (`source_content_id`, else slug or
|
|
70
|
+
address, previous slugs too), change only the body, keep the replaced body for
|
|
71
|
+
undo, hold `publication_status: draft` rewrites and never create a post from
|
|
72
|
+
one. See [rewrites on pull sites](../../bundled-tools/integrations/maggie-api-pull.md#rewrites-on-pull-sites).
|
|
73
|
+
|
|
59
74
|
Automatic publication is an opt-in host policy, not the default. Keep human
|
|
60
75
|
review enabled unless the owner explicitly chooses otherwise. If a host has
|
|
61
76
|
that approval, validate its evidence before its scheduler calls the host's
|
|
@@ -21,9 +21,9 @@ temporary project detail, secret, or unverified guess into active memory.
|
|
|
21
21
|
|
|
22
22
|
## Contract and safety boundary
|
|
23
23
|
|
|
24
|
-
Read [
|
|
25
|
-
[
|
|
26
|
-
reference before changing code. MaggieDash owns the manager UI and versioned
|
|
24
|
+
Read the [PRD](../../bundled-references/maggiedash-booking/PRD.md), the
|
|
25
|
+
[host adapter contract](../../bundled-references/maggiedash-booking/HOST-ADAPTER.md) and the
|
|
26
|
+
task's linked reference before changing code. MaggieDash owns the manager UI and versioned
|
|
27
27
|
contracts. The host owns routes, traditional email/password sessions,
|
|
28
28
|
PostgreSQL connection/transaction lifetime, tenant authorization, provider
|
|
29
29
|
credentials, payment webhooks, notifications, and deployment. MaggieDash also
|
|
@@ -32,11 +32,12 @@ booking, customer, configuration, schedule, health, audit, hold, and lifecycle
|
|
|
32
32
|
persistence; it does not replace host connection ownership or payment/public-
|
|
33
33
|
checkout adapters.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
mark a task passed until its
|
|
39
|
-
record are complete.
|
|
35
|
+
Maintainers changing MaggieDash Booking itself work from the delivery board
|
|
36
|
+
and runbook in the Maggie source repository (`docs/maggiedash-booking/`
|
|
37
|
+
`EXECUTION-BOARD.json`, `TASK-RUNBOOK.md`, `TASKS.md`, `PROGRESS.md`); they are
|
|
38
|
+
not installed with the skill. Do not mark a task passed until its
|
|
39
|
+
implementation gate, docs update, and evidence record are complete. A host
|
|
40
|
+
project records its own evidence in its own repository.
|
|
40
41
|
|
|
41
42
|
Never put provider secrets, database credentials, payment card data, or a
|
|
42
43
|
customer export in UI props, browser bundles, fixtures, screenshots, or
|
|
@@ -127,6 +128,12 @@ Booking installer:
|
|
|
127
128
|
maggie booking install --project . --confirm
|
|
128
129
|
```
|
|
129
130
|
|
|
131
|
+
Booking is an optional MaggieDash module: this installs the core (if it is
|
|
132
|
+
not there yet) plus Booking, the same as `maggie dash add booking --confirm`.
|
|
133
|
+
Install it only when the business takes bookings — `maggie dash install`
|
|
134
|
+
alone gives a content site the core without payment or booking files, and
|
|
135
|
+
`maggie dash recommend` says whether the site looks like it sells services.
|
|
136
|
+
|
|
130
137
|
For a host project that does not already contain the Booking runtime packages,
|
|
131
138
|
let the installer add only the missing dependencies through the package manager
|
|
132
139
|
already declared by the project (`npm`, `pnpm`, `yarn`, or `bun`):
|
|
@@ -156,7 +163,7 @@ Use `--schedule systemd` or `--schedule cron` with `--ready` only to generate a
|
|
|
156
163
|
reviewable scheduler artifact; enabling it remains an operator action after
|
|
157
164
|
backup, migration, and release evidence checks.
|
|
158
165
|
|
|
159
|
-
The default installer source is MaggieDash `main` (currently `0.2.
|
|
166
|
+
The default installer source is MaggieDash `main` (currently `0.2.15`; pin
|
|
160
167
|
another release with `--ref`). It contains the released Astro `hostBootstrap`
|
|
161
168
|
manifest and 47-route Booking source, including the tenant-scoped treatment
|
|
162
169
|
package catalogue, and follows the booking rules above (manage-link state
|
|
@@ -364,7 +371,7 @@ settings, reports, and audit. If `permissions_json` is absent on a legacy role,
|
|
|
364
371
|
apply the safe role defaults server-side; never treat a missing object as full
|
|
365
372
|
read access. Explicitly granted staff capabilities must be honored by the
|
|
366
373
|
route handler, while staff mutations remain limited to approved lifecycle
|
|
367
|
-
transitions. Keep the access contract, Astro host, reference host,
|
|
374
|
+
transitions. Keep the access contract, Astro host, reference host, installed host copies,
|
|
368
375
|
and CLI validator synchronized whenever a route capability changes.
|
|
369
376
|
The bootstrap now uses the host's server-side Resend configuration to send
|
|
370
377
|
single-use invitation and password-reset links; raw tokens are never stored.
|
|
@@ -519,8 +526,9 @@ Follow the task board rather than building a disconnected calendar first:
|
|
|
519
526
|
6. Add the provider-neutral payment port and a test-mode adapter. Reconcile
|
|
520
527
|
signed duplicate/out-of-order webhooks before enabling capture.
|
|
521
528
|
7. Run the UI source gate, contract gate, host runtime conformance, browser
|
|
522
|
-
journeys, and release gate. Record each command in
|
|
523
|
-
|
|
529
|
+
journeys, and release gate. Record each command in the host project's own
|
|
530
|
+
progress log (maintainers: `docs/maggiedash-booking/PROGRESS.md` in the
|
|
531
|
+
Maggie source repository).
|
|
524
532
|
|
|
525
533
|
8. Verify the scheduled worker queues and delivers a reminder once for a
|
|
526
534
|
confirmed booking inside the 24-hour window. Check the notification
|
|
@@ -70,6 +70,32 @@ Norwegian, Finnish, Greek, Hebrew, Czech, Romanian, Hungarian, Thai, Persian,
|
|
|
70
70
|
and Swahili. Use BCP 47 regions such as `es-MX`, `pt-BR`, `ar-SA`, or `ja-JP`
|
|
71
71
|
when market-specific copy is required.
|
|
72
72
|
|
|
73
|
+
## Text keys are site-wide; generated pages need position keys
|
|
74
|
+
|
|
75
|
+
A translation stored under its source text (a text hash) applies to **every
|
|
76
|
+
page** that shows that text. That is right for a shared header and wrong for
|
|
77
|
+
generic phrases on generated pages: "your practice" is 诊所 on a dental page
|
|
78
|
+
and 事务所 on an accountants page, and only one translation can exist per
|
|
79
|
+
text. For generated or templated pages (one page per industry, city or
|
|
80
|
+
product), render the source page, then store each node's translation under a
|
|
81
|
+
**per-page position key** (page + node position, with the source text hash so
|
|
82
|
+
a changed source shows as stale). Keep text keys only as a fallback for text
|
|
83
|
+
that is genuinely the same everywhere.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
maggie localization key-check rows.json [--locale zh-hans]
|
|
87
|
+
maggie localization fragments --html dist --brand "Maggie"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`key-check` reads per-page rows (`page`, `position`, `text`, `translation`)
|
|
91
|
+
and lists source texts translated differently on different pages — each one
|
|
92
|
+
needs position keys. `fragments` reads rendered HTML and lists text split
|
|
93
|
+
around an inline brand span into one- or two-word pieces ("Can", "No.",
|
|
94
|
+
"What") that cannot be translated alone; translate the whole sentence with
|
|
95
|
+
the span as a placeholder instead. Agents writing in parallel must use one
|
|
96
|
+
glossary for short shared fragments, or the same piece gets different
|
|
97
|
+
translations from different writers.
|
|
98
|
+
|
|
73
99
|
## Email and lifecycle email
|
|
74
100
|
|
|
75
101
|
Localized email is a content surface with its own checks. Resolve the
|
|
@@ -31,9 +31,35 @@ must already be configured for the project owner.
|
|
|
31
31
|
maggie dash install --project . --confirm
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
This installs **only the core**: the dashboard, the auth boundary
|
|
35
|
+
(email/password sign-in, first-owner registration, password reset), the
|
|
36
|
+
`/_maggie/admin` route, navigation and the core schema. It then reads the host
|
|
37
|
+
(framework, existing sign-in, database, payments, whether the site sells
|
|
38
|
+
services or publishes a blog, locales) and prints `recommendations`, one per
|
|
39
|
+
optional module or companion skill with a reason. Install a module only after
|
|
40
|
+
the user agrees:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
maggie dash recommend --project . # analysis only, writes nothing
|
|
44
|
+
maggie dash add booking --project . --confirm # same as: maggie booking install --confirm
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Booking is the optional code module MaggieDash ships (workspace, backend,
|
|
48
|
+
schema, Stripe adapter, worker, env example). Blog, localization, social
|
|
49
|
+
sharing and ops are Maggie skills that use the core panels; the
|
|
50
|
+
recommendations name them. Never pass `--with booking` or `--all-modules` for
|
|
51
|
+
a site that has not asked for bookings: a content-only site must not receive
|
|
52
|
+
payment and booking files. A release without the module split (MaggieDash
|
|
53
|
+
0.2.13 or older) still installs everything and says so in `notices`.
|
|
54
|
+
|
|
55
|
+
Server settings in the Astro scaffold are read at run time
|
|
56
|
+
(`src/lib/maggie/env.ts`). Never read a secret through `import.meta.env.X` in
|
|
57
|
+
host code: Vite inlines it into the build output.
|
|
58
|
+
|
|
34
59
|
The command is idempotent, preserves existing files unless `--force` is
|
|
35
|
-
explicitly supplied, and records the resolved revision
|
|
36
|
-
`.maggie/dash-install.json
|
|
60
|
+
explicitly supplied, and records the resolved revision and installed
|
|
61
|
+
`modules` in `.maggie/dash-install.json` (a later `install` keeps refreshing
|
|
62
|
+
modules that were added). It never accepts or stores a GitHub token as a
|
|
37
63
|
CLI argument. For local development or a pinned checkout, use
|
|
38
64
|
`--source /path/to/MaggieDash` or `--source URL --ref REF`.
|
|
39
65
|
|
|
@@ -97,6 +123,15 @@ fallback. The host owns authentication, event writes, menu persistence,
|
|
|
97
123
|
translation behavior, and validation. Use the dashboard UI v3 contract for the
|
|
98
124
|
required route and capability declarations.
|
|
99
125
|
|
|
126
|
+
MaggieDash 0.2.15 adds the **AI agents** workspace (`/_maggie/admin/mcp`,
|
|
127
|
+
capability `mcp.read`) for a host that serves its own MCP endpoint to outside
|
|
128
|
+
agents: tokens with a tool set and a daily write limit, sign-in (OAuth)
|
|
129
|
+
connections with their own tools and daily limit, per-call change review and
|
|
130
|
+
undo, and — when the host syncs an agents repository — its routines. The host
|
|
131
|
+
implements `GET`/`POST /api/maggie/mcp-admin.json` as described in MaggieDash
|
|
132
|
+
`docs/HOST-AI-AGENTS.md` and enforces every limit on the server; see
|
|
133
|
+
`maggie-assistant-tools` for the rules and `maggie-ops` for routines.
|
|
134
|
+
|
|
100
135
|
For a built/deployed host, an agent must use the host's validated content
|
|
101
136
|
bridge rather than editing release files or receiving database credentials:
|
|
102
137
|
|
|
@@ -25,6 +25,33 @@ metadata:
|
|
|
25
25
|
- Product CLI and MCP packages: `maggie product release-check|smoke|mcp-check`
|
|
26
26
|
([product release](../maggie-assistant-tools/references/product-release.md)).
|
|
27
27
|
|
|
28
|
+
## Downstream surfaces after a major product release
|
|
29
|
+
|
|
30
|
+
A product change is not released while an agent-facing surface still
|
|
31
|
+
describes the old product. After any major change to the app, the dashboard or
|
|
32
|
+
the backend API (a new feature or channel, a renamed tab, a changed rule or
|
|
33
|
+
limit), walk every surface below and either update it or record "no change":
|
|
34
|
+
|
|
35
|
+
| Surface | Check | Evidence |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Remote MCP tools | New or changed API params and response fields have tools; descriptions use the product's words | `tools/list` on the live server: count and the new names |
|
|
38
|
+
| Agent system prompts | In-app assistant and any hosted bridge/agent prompt name tabs, channels and rules as the app does | One question about the change answered correctly |
|
|
39
|
+
| Skills catalog and marketplace | Grep skills for the old wording; `mcp_tools` lists only real tool names; any tool-name allowlist is in sync with the tool source | Validator passes; marketplace repo pushed; agent hosts re-synced and recycled |
|
|
40
|
+
| CLI and MCP npm packages | Commands/columns for the new data; both package versions bumped together | `npm view <pkg> version` |
|
|
41
|
+
| Third-party plugins (ChatGPT, IDE) | Usually nothing if they use the remote MCP; re-review when tool names or auth change | Plugin lists the new tools |
|
|
42
|
+
| SDK / API client types | New response fields typed | Workspace typecheck |
|
|
43
|
+
| Mobile app | Web deploy plus OTA when the native fingerprint matches; a new build when it does not | Fingerprint compare; update id |
|
|
44
|
+
| Assistant memory | A new channel or surface gets its own memory channel | Memory read for that channel |
|
|
45
|
+
| Docs | PRD task list, progress log and the deployment runbook | — |
|
|
46
|
+
| Preview host protection | Before (or after) adding basic auth to a preview host, every workflow, script or webhook that sends to it is repointed to production or sends the preview's basic-auth credentials from a secret — a bearer token does not pass basic auth | `maggie deployment preview-auth-check --host preview.example.com --repo . --live` passes; the next run of each listed workflow is green and the public surfaces it feeds show the new version |
|
|
47
|
+
| npm staged publishes | A just-published version can stay invisible as `latest` for minutes; do not report it until it is. `Cannot publish over previously staged version` means the earlier attempt was already accepted, not a failure | `maggie product published --package-name NAME --version X --publish-log publish.log` reports `live` |
|
|
48
|
+
|
|
49
|
+
Run the walk in the release order (schema → backend → dashboard → app →
|
|
50
|
+
MCP/agent → bridge → npm → skills) and record which surfaces changed in the
|
|
51
|
+
release record. Keep publish tokens in an env file and pass them through a
|
|
52
|
+
temporary npmrc (`NPM_CONFIG_USERCONFIG`) that is deleted afterwards; never
|
|
53
|
+
print them.
|
|
54
|
+
|
|
28
55
|
## Edge and rendered canary
|
|
29
56
|
|
|
30
57
|
After staging or production deployment, verify that the edge serves the
|
|
@@ -592,6 +619,81 @@ judges latency only after warm-up requests. Failover between hosts must
|
|
|
592
619
|
never replay a write: see "Failover must not replay writes" in
|
|
593
620
|
[vps.md](references/vps.md).
|
|
594
621
|
|
|
622
|
+
Host preflight also runs `maggie deployment runtime-inventory` (read-only):
|
|
623
|
+
it warns when the host OS no longer gets security fixes and when a pinned
|
|
624
|
+
container tag is behind the newest patch of its line (see "PHP runtime
|
|
625
|
+
advisories" below).
|
|
626
|
+
|
|
627
|
+
## Shared hosts: one session at a time
|
|
628
|
+
|
|
629
|
+
Before an agent edits systemd units, containers or nginx on a production host,
|
|
630
|
+
it takes the host lock and keeps its session id for every later command:
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
maggie deployment host-lock acquire --host deploy@HOST --session "$SESSION" \
|
|
634
|
+
--purpose "php 8.1 → container migration"
|
|
635
|
+
# ... changes ...
|
|
636
|
+
maggie deployment host-lock release --host deploy@HOST --session "$SESSION"
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
`acquire` refuses while another unexpired session holds the host (it names
|
|
640
|
+
the holder and its purpose) and, like `status`, lists files changed in the last
|
|
641
|
+
30 minutes under `/etc/systemd/system`, `/etc/nginx` and the container unit
|
|
642
|
+
folders. A change you did not make means someone else is working there: stop
|
|
643
|
+
and ask, even when the lock was free. Two sessions once recreated the same
|
|
644
|
+
container in turn, each with its own command, and undid each other's fix.
|
|
645
|
+
`--force` releases another session's lock only after the user confirms that
|
|
646
|
+
session has stopped.
|
|
647
|
+
|
|
648
|
+
## PHP runtime advisories (CVE response)
|
|
649
|
+
|
|
650
|
+
For a PHP advisory, inventory every host first, then migrate one site at a
|
|
651
|
+
time to a patched container without touching the site files:
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
maggie deployment runtime-inventory --ssh deploy@HOST \
|
|
655
|
+
--advisory advisory.json --latest-tags latest-tags.json \
|
|
656
|
+
[--image 'wordpress:php{line}-fpm'] [--writable /var/www/site/uploads] \
|
|
657
|
+
--output .maggie/deployment/HOST-runtime.json
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
`--ssh` runs only the read-only collector (`--print-collector` shows it; save
|
|
661
|
+
its output and pass `--evidence FILE` when the host is reached another way).
|
|
662
|
+
The report lists the OS, every active nginx server block with its
|
|
663
|
+
`fastcgi_pass` target resolved to a host php-fpm unit or a container, and each
|
|
664
|
+
runtime's status against the advisory (`advisory.json`:
|
|
665
|
+
`{"id": "CVE-…", "fixed": {"8.1": "8.1.29", "8.2": "8.2.20"}, "unsupportedLines": ["7.4"]}`).
|
|
666
|
+
It also flags:
|
|
667
|
+
|
|
668
|
+
- an operating system past its end of standard security support (interim
|
|
669
|
+
Ubuntu releases end after nine months) or within 120 days of it;
|
|
670
|
+
- a pinned image tag older than the newest patch of its line
|
|
671
|
+
(`latest-tags.json`: `{"php": {"8.2": "8.2.26"}}`);
|
|
672
|
+
- php-fpm published on a public address instead of `127.0.0.1`;
|
|
673
|
+
- a PHP image (container or systemd unit) running with the image's own
|
|
674
|
+
entrypoint.
|
|
675
|
+
|
|
676
|
+
Nginx configuration is read with `nginx -T`, which includes files symlinked
|
|
677
|
+
into `sites-enabled`. Never discover sites with `grep -r /etc/nginx`: it does
|
|
678
|
+
not follow those symlinks, and a host then looks as if it had no PHP sites.
|
|
679
|
+
Use `nginx -T`, or `grep -R` when nginx cannot be run.
|
|
680
|
+
|
|
681
|
+
Each affected site gets a migration plan for review (`mutation: not
|
|
682
|
+
executed`): take the host lock, record the site's file list, start the
|
|
683
|
+
container on `127.0.0.1:PORT` with the site root mounted read-only (only
|
|
684
|
+
`--writable` folders read-write) at the same path, back up and change that
|
|
685
|
+
site's `fastcgi_pass`, `nginx -t`, reload; then verify and keep the rollback
|
|
686
|
+
commands. Every PHP image — `php:*-fpm` and `wordpress:*-fpm` alike — runs with
|
|
687
|
+
`--entrypoint docker-php-entrypoint` and an explicit `php-fpm` command. The
|
|
688
|
+
WordPress image's own entrypoint copies a fresh WordPress into a mounted
|
|
689
|
+
folder that has no `wp-config.php`; a site that was not WordPress once served a
|
|
690
|
+
public `wp-admin/setup-config.php` for half an hour. After start, the plan's
|
|
691
|
+
checks must pass: no file appeared in the site folder (diff with the list
|
|
692
|
+
taken before) and `/wp-admin/setup-config.php` does not answer 200. When a
|
|
693
|
+
systemd unit manages the container, change the unit's `ExecStart` the same
|
|
694
|
+
way and recreate through the unit; otherwise the next restart brings the old
|
|
695
|
+
command back.
|
|
696
|
+
|
|
595
697
|
## Provider boundary
|
|
596
698
|
|
|
597
699
|
Deployment providers implement the same conceptual operations:
|
|
@@ -610,7 +712,8 @@ content, approval, URL, or SEO contracts. Read
|
|
|
610
712
|
Stop before deployment when the target is ambiguous, the domain is not
|
|
611
713
|
confirmed, a production secret/binding is missing, migrations are not
|
|
612
714
|
idempotent/backed up, the build target and runtime disagree, or verification
|
|
613
|
-
cannot prove public/private route and indexability behavior.
|
|
715
|
+
cannot prove public/private route and indexability behavior. On a shared production host, stop when
|
|
716
|
+
another session holds the host lock or recent changes there are not yours.
|
|
614
717
|
## Shared interactive decision loop
|
|
615
718
|
|
|
616
719
|
Follow the shared [Maggie Decision Loop](../../references/decision-loop.md) for every invocation that has a meaningful choice. Before changing files, calling an external API, consuming quota, or changing production state:
|
|
@@ -32,6 +32,11 @@ maggie tool maggie_deployment.py . \
|
|
|
32
32
|
--output .maggie/deployment-preflight.json
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
Inventory the host read-only first (`maggie deployment runtime-inventory --ssh
|
|
36
|
+
USER@HOST`): an end-of-life OS or a stale pinned image is a finding to fix or
|
|
37
|
+
accept explicitly before deploying onto it. Take `maggie deployment host-lock
|
|
38
|
+
acquire` before any change to units, containers or nginx.
|
|
39
|
+
|
|
35
40
|
Confirm the VPS host/SSH user, canonical domain and DNS records, Node version,
|
|
36
41
|
systemd service, environment file, Nginx/TLS configuration, release root,
|
|
37
42
|
database/media migration and backup strategy, health checks, and rollback
|
|
@@ -262,6 +262,59 @@ maggie design step author-<id> --project . \
|
|
|
262
262
|
An author plan is not complete until every `requiredSteps` entry has matching
|
|
263
263
|
evidence and the explicit approval step is recorded.
|
|
264
264
|
|
|
265
|
+
### Bulk author mode (many similar pages)
|
|
266
|
+
|
|
267
|
+
For a family of similar pages (one use-case page per industry, one page per
|
|
268
|
+
city), use one bulk job instead of hand-built scripts:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
maggie design bulk plan --project . --targets targets.json --brief BRIEF.md --facts FACTS.md \
|
|
272
|
+
--validator '["python3", "scripts/validate-page.py"]' --check links --check prices --confirm
|
|
273
|
+
maggie design bulk next <job> --stage write # next batch (default 8) with each page's currentHash
|
|
274
|
+
maggie design bulk record <job> --target ID --stage written --expect-hash HASH
|
|
275
|
+
maggie design bulk validate <job> # project validator + built-in checks
|
|
276
|
+
maggie design bulk next <job> --stage picture # only pages validated and unchanged since
|
|
277
|
+
maggie design bulk record <job> --target ID --stage pictured
|
|
278
|
+
maggie design bulk status <job>
|
|
279
|
+
maggie design bulk seed <job> --confirm # only when every page passed
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The job enforces what goes wrong in a parallel run: a picture is drawn only
|
|
283
|
+
for a page that validated and has not changed since (a picture worker must
|
|
284
|
+
never rewrite a page a writer is still editing); a writer records with the
|
|
285
|
+
hash it read and is refused if the page changed meanwhile (re-read, re-apply,
|
|
286
|
+
record again); changing the brief, facts or validator mid-run sends every
|
|
287
|
+
earlier pass back to `revalidate`; nothing is seeded until all pages are
|
|
288
|
+
validated and pictured. Built-in checks: every internal link points at a page
|
|
289
|
+
of the job or a `--known-routes` entry, and every price appears in the facts
|
|
290
|
+
file. The project validator checks structure, counts, facts and translation
|
|
291
|
+
coverage. Share one glossary between writers for short repeated fragments
|
|
292
|
+
(see maggie-content-localization, position keys).
|
|
293
|
+
|
|
294
|
+
### Page pictures: choose the engine before a large run
|
|
295
|
+
|
|
296
|
+
`maggie media generate` draws one picture with either engine; `maggie media
|
|
297
|
+
engine-plan` states time and cost first. Show the plan and wait for a yes
|
|
298
|
+
before a run of more than a handful of pictures.
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
maggie media engine-plan --count 510 --size 2400x1000 --gemini-unit-cost <price>
|
|
302
|
+
maggie media generate --engine gemini --size 2400x1000 --prompt "..." --output hero.png --confirm
|
|
303
|
+
maggie media generate --engine codex --image screen.png --prompt "Redraw as a clean step picture" --output step.png --confirm
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
| Engine | Billing | Speed (observed) | Reference pictures |
|
|
307
|
+
|---|---|---|---|
|
|
308
|
+
| `codex` | the ChatGPT plan's quota via the local `codex` CLI, no per-picture charge | about 75 s | yes (`--image`, before `--`) |
|
|
309
|
+
| `gemini` | the project's own `GEMINI_API_KEY` (its Google account) | about 19 s at 2K | no |
|
|
310
|
+
|
|
311
|
+
For 510 pictures that is hours versus minutes. Gemini takes the nearest
|
|
312
|
+
aspect ratio and a size from the target (2400x1000 → 21:9 at 2K; 1K up to
|
|
313
|
+
1300 px, 2K up to 2600 px, else 4K); crop to the exact size and convert to
|
|
314
|
+
WebP afterwards. `engine-plan` leaves the Gemini cost empty until you pass
|
|
315
|
+
`--gemini-unit-cost` from the current price list. Only transient Gemini
|
|
316
|
+
errors (429, 5xx, network) are retried.
|
|
317
|
+
|
|
265
318
|
## Content UI initialization
|
|
266
319
|
|
|
267
320
|
Use content UI initialization when `maggie-blog` or
|
|
@@ -398,6 +451,34 @@ repeated sheet and modal changes. Add `maggie design textinput-check`: a
|
|
|
398
451
|
after collapsing; see the `chat-composer` pattern). `// maggie-allow-textinput`
|
|
399
452
|
marks a reviewed exception.
|
|
400
453
|
|
|
454
|
+
Closing a modal sheet is the third iOS-only trap. Closing a sheet and, in the
|
|
455
|
+
same tick, showing a toast, refreshing or writing query data, navigating,
|
|
456
|
+
opening another sheet, or changing what the closing sheet shows can leave the
|
|
457
|
+
sheet's transparent window on top of the app: it looks fine but takes no
|
|
458
|
+
touches until the app is killed. Close first, then do the rest once it has
|
|
459
|
+
slid away:
|
|
460
|
+
|
|
461
|
+
export const SHEET_CLOSE_MS = 450; // the slide-out duration
|
|
462
|
+
export function afterSheetCloses(fn: () => void) {
|
|
463
|
+
if (Platform.OS === "web") fn(); else setTimeout(fn, SHEET_CLOSE_MS);
|
|
464
|
+
}
|
|
465
|
+
// setSheet(null); afterSheetCloses(() => { toast(...); refresh(); });
|
|
466
|
+
|
|
467
|
+
maggie design sheet-check --project apps/app
|
|
468
|
+
|
|
469
|
+
`sheet-check` finds closes (`setSheet(null)`, `setXOpen(false)`, `onClose()`,
|
|
470
|
+
and the setter of any state used in a sheet's `open`/`visible` prop) followed
|
|
471
|
+
in the same block by a toast, a refetch/invalidate/`setQueryData`,
|
|
472
|
+
`router.*`/`*.open(…)` or another sheet opening; work inside
|
|
473
|
+
`afterSheetCloses`, `setTimeout`, `requestAnimationFrame`, a local helper that
|
|
474
|
+
wraps them, or after `await new Promise(r => setTimeout(r, …))` is deferred and
|
|
475
|
+
passes. Errors fail the check. Warnings: a parent callback (`onDone()`) right
|
|
476
|
+
after a close, and a sheet whose content reads the same state as its `open`
|
|
477
|
+
prop (`open={Boolean(item)} title={item?.label}`), which empties the sheet
|
|
478
|
+
while it slides away — keep a separate open flag. `// maggie-allow-sheet-close`
|
|
479
|
+
marks a reviewed exception. Keep it with pressable-check, safe-area-check and
|
|
480
|
+
textinput-check in the app's lint loop.
|
|
481
|
+
|
|
401
482
|
### Onboarding wizards
|
|
402
483
|
|
|
403
484
|
Use one pattern for multi-step setup (brand, logo, voice, posting hours, and
|
|
@@ -223,10 +223,18 @@ leaving contradictory claims current.
|
|
|
223
223
|
The CLI never submits automatically. After reviewing the draft:
|
|
224
224
|
|
|
225
225
|
```bash
|
|
226
|
-
maggie feedback submit .maggie/feedback/<feedback-id>.json
|
|
227
|
-
--endpoint https://feedback.noblox.app/api/feedback --confirm
|
|
226
|
+
maggie feedback submit .maggie/feedback/<feedback-id>.json --confirm
|
|
228
227
|
```
|
|
229
228
|
|
|
229
|
+
Without `--endpoint` the draft goes to `https://feedback.noblox.app/api/feedback`
|
|
230
|
+
and, only when that host fails before storing it (no connection, timeout, or a
|
|
231
|
+
5xx such as Cloudflare's 526), to `https://feedback.cmo.so/api/feedback`. Both
|
|
232
|
+
accept the same contract and de-duplicate on `feedbackId`, so a retry never
|
|
233
|
+
creates a second record. A 4xx is the host rejecting the draft and is not
|
|
234
|
+
retried elsewhere. The saved `submission` records the `endpoint` that accepted
|
|
235
|
+
the draft and any `failedEndpoints`. Repeat `--endpoint URL` to give your own
|
|
236
|
+
ordered list. Do not hand-edit drafts to point at a different host.
|
|
237
|
+
|
|
230
238
|
Only HTTPS endpoints are accepted. Project paths, file contents, secrets, and
|
|
231
239
|
logs are excluded by default. `--allow-project-context` is an explicit opt-in;
|
|
232
240
|
even then, send only a short public-safe context note. After submission, the
|
|
@@ -205,7 +205,7 @@ packaging:
|
|
|
205
205
|
maggie skills scaffold --root skills --category social --name cmo-thread \
|
|
206
206
|
--description "Turn an article into a social thread; use after an article is approved."
|
|
207
207
|
maggie skills validate --root skills [--catalog catalog.json] [--require-pinned]
|
|
208
|
-
maggie skills package --root skills --out dist/marketplace --prefix cmo --owner "Example Ltd" --version
|
|
208
|
+
maggie skills package --root skills --out dist/marketplace --prefix cmo --owner "Example Ltd" --version-from package.json:version
|
|
209
209
|
maggie skills smoke --out dist/marketplace --claude
|
|
210
210
|
```
|
|
211
211
|
|
|
@@ -220,6 +220,26 @@ maggie skills smoke --out dist/marketplace --claude
|
|
|
220
220
|
- `smoke` re-checks names, versions, sources and every packaged skill, and
|
|
221
221
|
with `--claude` runs `claude plugin validate`.
|
|
222
222
|
|
|
223
|
+
- **One version source.** `package` has no version of its own: pass
|
|
224
|
+
`--version-from FILE[:json.key|~CONSTANT]` (repeat it to require several
|
|
225
|
+
sources to agree) or `--version`; when they disagree it refuses instead of
|
|
226
|
+
writing one over the other. A packager that keeps its own `VERSION`
|
|
227
|
+
constant will otherwise overwrite a hand-bumped manifest.
|
|
228
|
+
- **Generated data before deploy.** A site or catalogue built from the skills
|
|
229
|
+
(a generated skills JSON, a catalogue page) must be regenerated in the same
|
|
230
|
+
release. Check both before pushing:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
maggie tool maggie_version_sources.py check --source package.json:version \
|
|
234
|
+
--source scripts/pack.mjs~VERSION \
|
|
235
|
+
--generated site/data/skills.json --generator "node scripts/gen-skills.mjs {out}"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
It fails on any version mismatch and on generated data that differs from a
|
|
239
|
+
fresh run of its generator. Maggie's own release does the same: packaging
|
|
240
|
+
refuses when `VERSION` and the package version differ, and publishing runs
|
|
241
|
+
the release-metadata check first.
|
|
242
|
+
|
|
223
243
|
Nothing is published; push the output folder to the marketplace repository
|
|
224
244
|
yourself after review.
|
|
225
245
|
|
|
@@ -231,7 +251,7 @@ imported by the server and every node script (town pages, sitemaps, maps) —
|
|
|
231
251
|
never a copy. It parses UK post towns and US "City, ST", falls back to the
|
|
232
252
|
town field plus the state from the ZIP prefix when a US profile saved only
|
|
233
253
|
the street, keeps non-US places in a US market ("Kingston, Jamaica"), and
|
|
234
|
-
gives a normalised search key so "
|
|
254
|
+
gives a normalised search key so "lakeview" finds "Lake View Spa", ranked
|
|
235
255
|
exact → prefix → word prefix → contains. See [directory towns and
|
|
236
256
|
search](references/directory-search.md) for the generated-column SQL.
|
|
237
257
|
|
|
@@ -27,7 +27,7 @@ counts per town.
|
|
|
27
27
|
|
|
28
28
|
## Name search
|
|
29
29
|
|
|
30
|
-
People type "
|
|
30
|
+
People type "lakeview" for "Lake View Spa". Compare a normalised key —
|
|
31
31
|
lower case, accents removed, letters and digits only (`searchKey`) — as well
|
|
32
32
|
as the plain `ILIKE` match, and rank exact and prefix matches first
|
|
33
33
|
(`nameMatchRank`). In Postgres keep the key in a generated column with an
|
|
@@ -85,8 +85,8 @@ export function parseTown(address, market, locality, postcode) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Name search key: lower case, accents removed, letters and digits only, so "
|
|
89
|
-
* "
|
|
88
|
+
* Name search key: lower case, accents removed, letters and digits only, so "lakeview", "Lake-View" and
|
|
89
|
+
* "Lake View Spa" meet. Store it in a generated column (see directory-search.md) and compare both forms.
|
|
90
90
|
* @param {string | null | undefined} value
|
|
91
91
|
*/
|
|
92
92
|
export const searchKey = (value) => (value || "").normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
@@ -129,6 +129,35 @@ always restores the working copies — checked by hash — even when a step
|
|
|
129
129
|
fails. A mismatch with the build's own files means the change needs a new
|
|
130
130
|
native build. `MAGGIE_MOBILE_EAS` overrides the `npx eas-cli` invocation.
|
|
131
131
|
|
|
132
|
+
### Monorepos: publish only to a runtime an installed build has
|
|
133
|
+
|
|
134
|
+
In a pnpm monorepo the fingerprint also hashes pnpm's virtual-store paths
|
|
135
|
+
(`node_modules/.pnpm/<pkg>@<version>_<peer hash>`), so a dependency change in
|
|
136
|
+
**any** workspace package (React added to the website) can move the app's
|
|
137
|
+
runtime. `eas update` then publishes to a runtime no build has — silently.
|
|
138
|
+
`ignorePaths` and `fileHookTransform` cannot fix it: `@expo/fingerprint` adds
|
|
139
|
+
each native module folder by its real path. Keep the installed builds'
|
|
140
|
+
runtimes in a file and publish through the guard:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{"ios": [{"build": 7, "eas_build_id": "…", "runtime": "19d7a260e8e2…"}], "android": []}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
maggie mobile ota-guard --project . --app-dir apps/app --platform ios --runtimes scripts/ota-runtimes.json
|
|
148
|
+
maggie mobile ota-guard … --publish --message "Fix copy" --confirm
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
It computes the fingerprint (`expo-updates fingerprint:generate`), refuses
|
|
152
|
+
when no installed build has it — naming the two ways out (a native build, or
|
|
153
|
+
publishing from a worktree with the build's lockfile and workspace file after a
|
|
154
|
+
frozen install) — and with `--publish --confirm` runs `eas update` and checks
|
|
155
|
+
that the published runtime is the one it checked. Add a build's runtime to the
|
|
156
|
+
file when it ships; drop it when nobody runs it. At the next native build set
|
|
157
|
+
an explicit `runtimeVersion` with a native-change check, so unrelated
|
|
158
|
+
workspace changes cannot move it. `preflight` warns about a fingerprint
|
|
159
|
+
runtime in a pnpm workspace.
|
|
160
|
+
|
|
132
161
|
Show the plan's `costNotice` and wait for confirmation before step 1.
|
|
133
162
|
|
|
134
163
|
## 5. Store-review rules
|