@rubytech/create-maxy-code 0.1.281 → 0.1.283

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.
Files changed (76) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +0 -1
  3. package/payload/platform/plugins/admin/PLUGIN.md +2 -1
  4. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +154 -0
  5. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +24 -0
  6. package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +174 -0
  7. package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +22 -2
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -5
  9. package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +378 -56
  10. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -5
  12. package/payload/platform/plugins/cloudflare/references/api.md +107 -34
  13. package/payload/platform/plugins/cloudflare/references/d1-data-capture.md +29 -21
  14. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -2
  15. package/payload/platform/plugins/cloudflare/references/hosting-sites.md +7 -5
  16. package/payload/platform/plugins/cloudflare/references/manual-setup.md +3 -2
  17. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +6 -6
  18. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  19. package/payload/platform/plugins/docs/references/telegram-guide.md +3 -3
  20. package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js +25 -1
  21. package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js.map +1 -1
  22. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts +7 -0
  23. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts.map +1 -1
  24. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js +9 -0
  25. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js.map +1 -1
  26. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.d.ts.map +1 -1
  27. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js +6 -2
  28. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js.map +1 -1
  29. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +1 -0
  30. package/payload/platform/plugins/telegram/PLUGIN.md +7 -2
  31. package/payload/platform/plugins/telegram/mcp/dist/index.js +66 -11
  32. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  33. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +6 -0
  34. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -0
  35. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +20 -0
  36. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -0
  37. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +21 -0
  38. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -0
  39. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +52 -0
  40. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -0
  41. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
  42. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +5 -2
  43. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
  44. package/payload/platform/plugins/telegram/references/setup-guide.md +10 -7
  45. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +79 -0
  46. package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +1 -1
  47. package/payload/platform/plugins/whatsapp/PLUGIN.md +7 -11
  48. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +10 -56
  49. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  50. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +26 -2
  51. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +9 -14
  52. package/payload/platform/scripts/setup-account.sh +7 -0
  53. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  54. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -2
  55. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  56. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/http-server.js +80 -3
  58. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  59. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +6 -0
  60. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  61. package/payload/platform/services/whatsapp-channel/dist/notification.js +14 -0
  62. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  63. package/payload/platform/services/whatsapp-channel/dist/server.js +5 -3
  64. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  65. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  66. package/payload/server/public/assets/AdminShell-8Y7-maNi.js +1 -0
  67. package/payload/server/public/assets/{admin-0_VxffD5.js → admin-DyuZ4NT5.js} +1 -1
  68. package/payload/server/public/assets/{browser-Cxjkhtsk.js → browser-XrVAUQdG.js} +1 -1
  69. package/payload/server/public/assets/{data-BOgwOWnw.js → data-tcu3MXmK.js} +1 -1
  70. package/payload/server/public/assets/{graph-BqD2GjQd.js → graph-I4GyC2MW.js} +1 -1
  71. package/payload/server/public/browser.html +2 -2
  72. package/payload/server/public/data.html +2 -2
  73. package/payload/server/public/graph.html +2 -2
  74. package/payload/server/public/index.html +2 -2
  75. package/payload/server/server.js +882 -767
  76. package/payload/server/public/assets/AdminShell-FO766lOW.js +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare",
3
- "description": "Cloudflare operations — tunnel setup/reset, DNS, Pages hosting, D1 data capture, and dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, calling the Cloudflare API with a minted narrow token, or quoting a dashboard click-path the operator performs themselves.",
3
+ "description": "Cloudflare operations — tunnel setup/reset, DNS, Pages hosting, D1 data capture, and dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, calling the Cloudflare API with a reused per-scope narrow token, or quoting a dashboard click-path the operator performs themselves.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: cloudflare
3
- description: Cloudflare operations — tunnel setup/reset, DNS, Pages hosting, D1 data capture, and dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, calling the Cloudflare API with a minted narrow token, or quoting a dashboard click-path the operator performs themselves.
3
+ description: Cloudflare operations — tunnel setup/reset, DNS, Pages hosting, D1 data capture, and dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, calling the Cloudflare API with a reused per-scope narrow token, or quoting a dashboard click-path the operator performs themselves.
4
4
  tools: []
5
5
  mcp-manifest: skip
6
6
  ---
7
7
 
8
8
  # Cloudflare Tunnel
9
9
 
10
- Each installation has its own Cloudflare account. Two auth paths serve two operation classes. **Tunnel** auth is OAuth: the operator signs in via `cloudflared tunnel login` (issued by the agent through Bash); `cloudflared` writes `cert.pem` to the brand-scoped config directory. **API** auth (DNS, zones, Pages, D1, Access, token mint) is the master token: the operator provisions one fully-scoped master token in the dashboard (an advanced, operator-guided step the agent never automates), stored at the account-scoped secrets file; the agent reads it only to mint a narrowly-scoped, short-lived token per operation and exports that ephemeral token for the one call. Account state is brand-isolated and the master token never leaves the secrets file — see `references/api.md`.
10
+ Each installation has its own Cloudflare account. Two auth paths serve two operation classes. **Tunnel** auth is OAuth: the operator signs in via `cloudflared tunnel login` (issued by the agent through Bash); `cloudflared` writes `cert.pem` to the brand-scoped config directory. **API** auth (DNS, zones, Pages, D1, Access, token management) is the master token: the operator provisions one fully-scoped master token in the dashboard (an advanced, operator-guided step the agent never automates), stored at the account-scoped secrets file; the agent reads it only to provision **one stable narrow token per scope** minting it once if absent, persisting it to the secrets file, reusing it thereafter — and periodically reconciles strays. Account state is brand-isolated and the master token never leaves the secrets file — see `references/api.md`.
11
11
 
12
12
  ## When to activate
13
13
 
@@ -25,7 +25,7 @@ Each installation has its own Cloudflare account. Two auth paths serve two opera
25
25
 
26
26
  ## Operator-facing surface
27
27
 
28
- The plugin registers no agent-facing MCP tools. Every Cloudflare operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, or calling the Cloudflare API with a minted narrow token, with stdout/stderr streamed verbatim into the PTY chat (secrets redacted). The OAuth URL printed by `cloudflared tunnel login` is linkified by the native PTY; the operator clicks it in their own browser. There is no shell-script wrapper, no orchestrator state machine, no MCP-tool surface.
28
+ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, or calling the Cloudflare API with a reused per-scope narrow token, with stdout/stderr streamed verbatim into the PTY chat (secrets redacted). The OAuth URL printed by `cloudflared tunnel login` is linkified by the native PTY; the operator clicks it in their own browser. There is no shell-script wrapper, no orchestrator state machine, no MCP-tool surface.
29
29
 
30
30
  ### Skills
31
31
 
@@ -38,7 +38,7 @@ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is th
38
38
  | Reference | Topics |
39
39
  |---|---|
40
40
  | [manual-setup.md](references/manual-setup.md) | Tunnel runbook — Steps 0–7 with isolated `cloudflared` command blocks. The agent reads the relevant step before issuing each command. |
41
- | [api.md](references/api.md) | Cloudflare API library — canonical docs URL + curated endpoint map (DNS, zones, tunnels, token create/verify), the advanced master-token creation walkthrough, the account-scoped secrets-file storage convention, and the agent's mint-narrow-token discipline. |
41
+ | [api.md](references/api.md) | Cloudflare API library — canonical docs URL + curated endpoint map (DNS, zones, tunnels, token list/create/verify/revoke), the advanced master-token creation walkthrough, the account-scoped secrets-file storage convention, the agent's reuse-a-stable-per-scope-token discipline, and the standing reconcile pass that revokes strays. |
42
42
  | [d1-data-capture.md](references/d1-data-capture.md) | Form → Pages Function → D1 store → read/sweep. The Pages-Edit **and** D1-Edit token-scope requirement, `wrangler d1 create`/`execute --remote`, the `swept` column. |
43
43
  | [hosting-sites.md](references/hosting-sites.md) | Deploy a static or Next.js site to Cloudflare Pages via `wrangler pages deploy`. |
44
44
  | [web-analytics.md](references/web-analytics.md) | Why a registered site reports 0 visitors — the RUM API is blocked (no permission group), registered ≠ collecting, edge auto-injection is impossible for a Pages-only custom domain, and the beacon must be injected into the site HTML, rebuilt, and redeployed. Outcome: beacon present in live HTML. |
@@ -59,4 +59,4 @@ The setup-done claim only fires when `curl -I https://<hostname>` issued from ou
59
59
 
60
60
  ## Discipline
61
61
 
62
- The agent's permitted surfaces are: direct `cloudflared` and `wrangler` invocations via Bash following the references; Cloudflare API calls authenticated by a freshly-minted narrow token (the master token stays in the secrets file, never on a command line that echoes, never in chat); the reference files; and live verification (`curl -I` for tunnels, the deployed URL for hosting, a `SELECT` for D1). Out of bounds: Playwright or Chrome DevTools driving the dashboard, browser-automating master-token creation, WebSearch-for-CF-recipes, ad-hoc `cloudflared` flag invention not in the runbook, and writing or echoing any token. When a step fails, the agent reports the exact output (secrets redacted), cites the recovery step from `references/reset-guide.md`, and stops.
62
+ The agent's permitted surfaces are: direct `cloudflared` and `wrangler` invocations via Bash following the references; Cloudflare API calls authenticated by a reused per-scope narrow token, minted once if absent and persisted to the secrets file (the master token stays in that same file, never on a command line that echoes, never in chat); the reference files; and live verification (`curl -I` for tunnels, the deployed URL for hosting, a `SELECT` for D1). Out of bounds: Playwright or Chrome DevTools driving the dashboard, browser-automating master-token creation, WebSearch-for-CF-recipes, ad-hoc `cloudflared` flag invention not in the runbook, and writing or echoing any token. When a step fails, the agent reports the exact output (secrets redacted), cites the recovery step from `references/reset-guide.md`, and stops.
@@ -1,19 +1,19 @@
1
- # Cloudflare API — master token, minted narrow tokens, endpoint map
1
+ # Cloudflare API — master token, reused per-scope narrow tokens, endpoint map
2
2
 
3
- The Cloudflare REST API is a permitted surface on this install. It is how the agent does DNS edits, Pages deploys, D1 queries, Access policies, and apex-CNAME creation without driving the dashboard. This reference is the library: where the canonical docs live, how auth works (one operator-provisioned master token; the agent mints short-lived narrow tokens from it), where the master is stored, and the curated endpoints worth knowing.
3
+ The Cloudflare REST API is a permitted surface on this install. It is how the agent does DNS edits, Pages deploys, D1 queries, Access policies, and apex-CNAME creation without driving the dashboard. This reference is the library: where the canonical docs live, how auth works (one operator-provisioned master token; the agent mints **one narrow token per scope**, persists it to the secrets file, and reuses it), where the master is stored, and the curated endpoints worth knowing — including how to list and revoke tokens, and the standing reconcile pass that keeps the account from accumulating strays.
4
4
 
5
5
  Canonical reference (always the source of truth for request shapes, never mirror it wholesale here): **https://developers.cloudflare.com/api/**
6
6
 
7
7
  ---
8
8
 
9
- ## Auth model — one master, many minted narrow tokens
9
+ ## Auth model — one master, reused per-scope narrow tokens
10
10
 
11
11
  There are two distinct tokens in play. Keep them separate.
12
12
 
13
- - **Master token** — fully-scoped, long-lived, provisioned **once** by the operator in the dashboard (see § Provisioning the master token, below). Broad enough to manage Pages, D1, DNS, **and** create API tokens. The agent reads it only to mint narrow tokens. It is never passed to `wrangler`, never put on a command line that gets echoed, never printed.
14
- - **Minted narrow token** — scoped to exactly one operation class (a single-zone DNS edit, a one-project Pages deploy, a D1 query), short-lived, created by the agent via the API call below. Exported into the environment for the one `wrangler` / API call, then discarded. Never written to disk, never committed, never echoed into chat.
13
+ - **Master token** — fully-scoped, long-lived, provisioned **once** by the operator in the dashboard (see § Provisioning the master token, below). Broad enough to manage Pages, D1, DNS, **and** create API tokens. The agent reads it only to mint per-scope tokens. It is never passed to `wrangler`, never put on a command line that gets echoed, never printed.
14
+ - **Per-scope narrow token** — scoped to exactly one operation class (a single-zone DNS edit, a one-project Pages deploy, a D1 query), with a **deterministic name** (`<brand>-pages-d1`, `<brand>-dns`, `<brand>-access`). There is **one** such token per scope. The agent loads it from the secrets file; **if absent, it mints it once** from the master correctly scoped, **no expiry — permanent and reused** — and persists it back to the secrets file. Thereafter it is loaded and reused, never re-minted. Exported into the environment for the one `wrangler` / API call; never echoed into chat, never written into a project tree.
15
15
 
16
- Why mint instead of hand-rolling: the operator provisions the master once; every per-operation token is minted by the agent on demand, so the operator never hand-crafts scoped tokens and no broad token is ever exported to a tool that could log it.
16
+ Why one-per-scope instead of one-per-operation: the operator provisions the master once; the agent provisions each narrow token once and reuses it, so the account never fills with throwaway tokens, no broad token is ever exported to a tool that could log it, and decay is handled structurally by the reconcile pass (§ Reconcile) rather than by per-token TTL. The narrow token persists alongside the master in the same `600`-mode secrets file but is **strictly narrower** than the master — so the binding "never export the master to `wrangler`" is preserved while the narrow token gains reuse.
17
17
 
18
18
  ---
19
19
 
@@ -38,7 +38,7 @@ Rationale (binding):
38
38
 
39
39
  1. **Account-isolated** — under `data/accounts/<accountId>/`, consistent with brand isolation; one account's master never sits where another account's flow can read it.
40
40
  2. **Outside every deployable/git project tree** — so the god-token can never be committed or shipped in a Pages upload. This is the failure mode a project-root `.env` invites: a `.env` next to a site's source gets swept into the `wrangler pages deploy` upload or a `git add .`.
41
- 3. **Sourced on demand for the master only** — `set -a; . <file>; set +a` to load `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` into the environment for the mint call; per-operation narrow tokens go into the ephemeral environment of their single command, never into this file.
41
+ 3. **Sourced on demand** — `set -a; . <file>; set +a` to load `CLOUDFLARE_API_TOKEN` (master) + `CLOUDFLARE_ACCOUNT_ID` into the environment. The reused per-scope narrow tokens (`<brand>-pages-d1`, etc.) persist **in this same file** as additional keys (e.g. `CF_PAGES_D1_TOKEN=`), minted once if absent and loaded thereafter; each is strictly narrower than the master that already lives here, so persisting it adds no exposure the master did not already carry. A per-scope token is exported only into the single command that uses it, never echoed.
42
42
 
43
43
  Create it once (mode 600, parent dirs 700):
44
44
 
@@ -100,50 +100,92 @@ curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
100
100
 
101
101
  ---
102
102
 
103
- ## Minting a narrow token
103
+ ## Token type route endpoints by prefix (`cfat_` vs `cfut_`)
104
104
 
105
- The agent mints a per-operation token from the master via `POST /accounts/{account_id}/tokens`. Scope it to the single operation, give it a short TTL, export it for the one call, discard it.
105
+ The master an operator provisions is usually **account-scoped** (`cfat_…`, created under **Account Resources** as above): it verifies and mints at the `/accounts/{id}/…` endpoints. But some accounts supply a **user-scoped** token (`cfut_…`) instead, and for a `cfut_…` token the **account-level endpoints return error code `9109`** — the working endpoints are the user-level ones. Detect the type by the token's prefix and route accordingly; never assume account scope.
106
+
107
+ | Operation | `cfat_…` (account-scoped) | `cfut_…` (user-scoped) |
108
+ |---|---|---|
109
+ | Verify | `GET /accounts/${ACC}/tokens/verify` | `GET /user/tokens/verify` |
110
+ | Resolve permission-group ids | `GET /accounts/${ACC}/tokens/permission_groups` | `GET /user/tokens/permission_groups` |
111
+ | Mint a narrow token | `POST /accounts/${ACC}/tokens` | `POST /user/tokens` |
112
+
113
+ Cross-type calls fail with a stable, recognizable signal: a `cfat_…` token at `/user/tokens/verify` returns `Invalid API Token`; a `cfut_…` token at any `/accounts/{id}/…` endpoint returns `9109`. The diagnostic is the verify call's JSON `errors[].code`.
114
+
115
+ **Creating** a token stays an account-level, operator-owned concern — each account documents its own master token and its type (e.g. a per-account `secrets/cloudflare.README.md`). Where an account *can* mint an account-scoped token that is the cleaner default; but a skill that merely *consumes* a supplied token must detect the prefix and adapt, never assume it can mint at `/accounts/…`.
116
+
117
+ ---
118
+
119
+ ## Provisioning and reusing a stable per-scope token
120
+
121
+ Per operation class there is **one** deterministically-named narrow token, reused across runs. On each run the agent **loads it from the secrets file; if it is absent, it mints it once** (correctly scoped, **no expiry**) and persists it back to the secrets file. There is no per-operation throwaway and no `unset` — the token is permanent and reused. Mint via `POST /accounts/{account_id}/tokens` (`cfat_…` master) or `POST /user/tokens` (`cfut_…` master — see § Token type — route endpoints by prefix).
106
122
 
107
123
  ```bash
108
- set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads the master + account id
109
-
110
- # Example: a one-project Pages-deploy token. Adjust the policy for the operation.
111
- MINTED=$(curl -sS -X POST \
112
- -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
113
- -H "Content-Type: application/json" \
114
- "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
115
- --data @- <<'JSON' | jq -r '.result.value'
124
+ set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads the master + account id (+ any persisted per-scope tokens)
125
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
126
+
127
+ # Example: the stable Pages-deploy token. One deterministic name + secrets key per scope.
128
+ TOKEN_KEY="CF_PAGES_D1_TOKEN" # the persisted key in cloudflare.env
129
+ SCOPE_NAME="<brand>-pages-d1" # the deterministic, reused token name on the account
130
+ PAGES_D1=$(grep -m1 "^${TOKEN_KEY}=" "${SECRETS_DIR}/cloudflare.env" 2>/dev/null | cut -d= -f2-)
131
+
132
+ if [ -z "${PAGES_D1}" ]; then # absent → mint once, scoped, no expiry, then persist
133
+ PAGES_D1=$(curl -sS -X POST \
134
+ -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
135
+ -H "Content-Type: application/json" \
136
+ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
137
+ --data @- <<JSON | jq -r '.result.value'
116
138
  {
117
- "name": "ephemeral-pages-deploy",
139
+ "name": "${SCOPE_NAME}",
118
140
  "policies": [{
119
141
  "effect": "allow",
120
- "resources": { "com.cloudflare.api.account.<accountId>": "*" },
142
+ "resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": "*" },
121
143
  "permission_groups": [{ "id": "<pages-edit-permission-group-id>", "name": "Pages Write" }]
122
- }],
123
- "expires_on": "<iso8601-a-few-minutes-out>"
144
+ }]
124
145
  }
125
146
  JSON
126
- )
127
-
128
- # Use it for exactly one command, in this command's environment only:
129
- CLOUDFLARE_API_TOKEN="${MINTED}" wrangler pages deploy ./dist --project-name <project> --branch=main
147
+ )
148
+ ( umask 077; printf '%s=%s\n' "${TOKEN_KEY}" "${PAGES_D1}" >> "${SECRETS_DIR}/cloudflare.env" )
149
+ fi
130
150
 
131
- unset MINTED # discard; never written to disk, never echoed
151
+ # Reused thereafter exported only into the one command's environment, never echoed:
152
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler pages deploy ./dist --project-name <project> --branch=main
132
153
  ```
133
154
 
134
- Look up `<pages-edit-permission-group-id>` (and the ids for DNS Edit, D1 Edit, etc.) once via `GET /accounts/{account_id}/tokens/permission_groups`; they are stable per account.
155
+ No `expires_on`: the per-scope token is permanent and reused, so decay is the job of the **reconcile pass** (§ Reconcile), not a per-token TTL — the old short-TTL guidance was never honored in practice and produced only never-expiring strays. Look up `<pages-edit-permission-group-id>` (and the ids for DNS Edit, D1 Edit, etc.) once via `GET /accounts/{account_id}/tokens/permission_groups`; they are stable per account. Before reusing or before reconciling, `GET …/tokens` (§ Curated endpoint map) shows what already exists, so a scope is never minted twice under different names.
135
156
 
136
157
  **Redaction is binding.** Every example above pipes the token into a variable or an `Authorization` header and never to stdout. The agent surfaces the operation as verb + target — "minting a Pages-deploy token", "creating CNAME `chat.example.com`" — never the secret. A failure surfaces the API error body with any `Authorization` value masked.
137
158
 
159
+ ### A freshly-minted token is not instantly usable — verify with backoff
160
+
161
+ This applies only on the **mint-once path** (the absent branch above) — a reused token loaded from secrets is already active and needs no wait. A just-minted token returns `10000 Authentication error` for a few seconds before it becomes active. Do not fire a fresh token's first real call blind; **verify it first**, retrying on a transient `10000` with backoff and giving up after ~30s. Route `${VERIFY_URL}` by the master's prefix (account- vs user-scoped — see § Token type):
162
+
163
+ ```bash
164
+ # Run this inside the mint-once (absent) branch, before persisting/using the token.
165
+ # ${PAGES_D1} = the per-scope token just minted; ${VERIFY_URL} = the prefix-correct verify endpoint.
166
+ for i in $(seq 1 10); do
167
+ s=$(curl -sS -H "Authorization: Bearer ${PAGES_D1}" "${VERIFY_URL}" \
168
+ | jq -r '.result.status // (.errors[0].code | tostring)')
169
+ [ "$s" = "active" ] && break
170
+ [ "$s" = "10000" ] && { sleep 3; continue; } # transient post-mint window — expected
171
+ echo "verify failed: ${s}" >&2; break # any other code is a real failure
172
+ done
173
+ ```
174
+
175
+ Proceed to the operation only once verify returns `active`. The `10000` window is transient and expected after a fresh mint; any other code is a real failure, not something to retry blindly.
176
+
138
177
  ---
139
178
 
140
179
  ## Curated endpoint map
141
180
 
142
- Request/response shapes live at the canonical docs URL; this is the index of what to reach for. `${ACC}` = `CLOUDFLARE_ACCOUNT_ID`, `${ZONE}` = the zone id, `${TOKEN}` = a **minted narrow** token.
181
+ Request/response shapes live at the canonical docs URL; this is the index of what to reach for. `${ACC}` = `CLOUDFLARE_ACCOUNT_ID`, `${ZONE}` = the zone id, `${TOKEN}` = a **reused per-scope narrow** token.
143
182
 
144
- ### Token create / verify
145
- - `GET /accounts/${ACC}/tokens/verify` confirm an **account-scoped** (`cfat_…`) token is active. This is the endpoint for the master and every minted narrow token here, all of which are account-scoped. **`/user/tokens/verify` is user-scoped only** and returns `Invalid API Token` for a `cfat_…` token even when it is valid do not use it to verify an account token.
146
- - `POST /accounts/${ACC}/tokens` — mint a narrow token (see above).
183
+ ### Token list / create / verify / revoke
184
+ The endpoints below are the **account-scoped** (`cfat_…`) family the common case. For a **user-scoped** (`cfut_…`) master, swap to the `/user/tokens/…` family per § Token type route endpoints by prefix; the account endpoints return `9109` for that token type.
185
+ - `GET /accounts/${ACC}/tokens/verify` — confirm an **account-scoped** (`cfat_…`) token is active. This is the endpoint for the master and every per-scope narrow token here, all of which are account-scoped. **`/user/tokens/verify` is user-scoped only** and returns `Invalid API Token` for a `cfat_…` token even when it is valid — do not use it to verify an account token.
186
+ - `GET /accounts/${ACC}/tokens` — list all tokens on the account (id, name, status, `expires_on`). Use it to find a per-scope token to reuse and to enumerate strays before a reconcile. User-scoped twin: `GET /user/tokens`.
187
+ - `POST /accounts/${ACC}/tokens` — mint a per-scope token (see § Provisioning and reusing a stable per-scope token). Mint only when the list above shows the scope absent.
188
+ - `DELETE /accounts/${ACC}/tokens/{id}` — revoke a token by id (the reconcile/cleanup verb). User-scoped twin: `DELETE /user/tokens/{id}`. Route by the master's prefix exactly as verify/mint do — `cfat_…` → `/accounts/${ACC}/tokens…`, `cfut_…` → `/user/tokens…`; the cross-type call returns `9109`.
147
189
  - `GET /accounts/${ACC}/tokens/permission_groups` — resolve permission-group ids for scoping.
148
190
 
149
191
  ### DNS records (zone-scoped; needs **Zone · DNS · Edit**)
@@ -171,10 +213,41 @@ curl -sS -X POST -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: applicati
171
213
 
172
214
  ---
173
215
 
216
+ ## Reconcile — the standing cleanup check
217
+
218
+ Token accumulation **emits no event and does not reproduce on demand**, so the decay mechanism is a standing reconcile pass, run **periodically and before a deploy** — not a one-off. It lists every token, keeps the known-good set, and revokes the rest. The known-good set is: the **master**, the stable per-scope operation tokens (`<brand>-pages-d1`, `<brand>-dns`, `<brand>-access`), and the **tunnel** tokens. Everything else — throwaway, duplicate-named, stray — is revoked.
219
+
220
+ ```bash
221
+ set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
222
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
223
+ LIST_URL="https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" # /user/tokens for a cfut_ master
224
+
225
+ # Names kept verbatim — adjust to this account's stable set + tunnel token names.
226
+ KEEP="<brand>-pages-d1 <brand>-dns <brand>-access maxy-api-master maxy-pages Maxy"
227
+
228
+ # 1. List; for each token whose name is NOT in KEEP, revoke it by id.
229
+ curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "${LIST_URL}?per_page=100" \
230
+ | jq -r '.result[] | [.id, .name] | @tsv' \
231
+ | while IFS=$'\t' read -r id name; do
232
+ case " ${KEEP} " in *" ${name} "*) echo "keep ${name}";;
233
+ *) curl -sS -X DELETE -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "${LIST_URL}/${id}" >/dev/null
234
+ echo "revoke ${name}";; esac
235
+ done
236
+
237
+ # 2. Post-condition — re-list and confirm the revoked names are gone. Do not trust the DELETE exit code.
238
+ curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "${LIST_URL}?per_page=100" | jq -r '.result[].name'
239
+ ```
240
+
241
+ `?per_page=100` returns the first page only; an account that has accumulated **more than 100 tokens** — the exact failure this pass exists to undo — needs `&page=2`, `&page=3`, … until a short page returns. Reconcile is the accumulation-survival mechanism, so it must walk every page, not just the first.
242
+
243
+ The tally is **names only** — a token value is never printed (the redaction binding in § Provisioning applies). "Revoked N" is a claim only once the follow-up list (step 2) shows those names absent, never an inference from the DELETE call's exit code.
244
+
245
+ ---
246
+
174
247
  ## When to use which path
175
248
 
176
249
  - **Tunnel create/route/run** → `cloudflared` (OAuth cert), per `manual-setup.md`. The API can manage tunnels too, but the cert path is the established one.
177
250
  - **Apex CNAME** → API (above) or dashboard (`dashboard-guide.md`). Either flattens correctly.
178
- - **Pages deploy** → `wrangler` with a minted Pages-Edit token, per `hosting-sites.md`.
179
- - **D1 read/write** → `wrangler d1 execute --remote` with a minted D1-Edit token, per `d1-data-capture.md`.
180
- - **Inspecting account state** (which zones, which tunnels) → API `GET` with a read-scoped minted token, or the dashboard.
251
+ - **Pages deploy** → `wrangler` with the reused `<brand>-pages-d1` token, per `hosting-sites.md`.
252
+ - **D1 read/write** → `wrangler d1 execute --remote` with the reused D1-Edit token, per `d1-data-capture.md`.
253
+ - **Inspecting account state** (which zones, which tunnels) → API `GET` with a read-scoped token (reused), or the dashboard.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is the established pattern for turning a static site's contact / waitlist form into durable, queryable leads: the form POSTs to a **Pages Function**, the Function inserts a row into a **Cloudflare D1** database, and the agent reads and sweeps new rows with `wrangler d1 execute --remote`. It is live on `realagent.pages.dev` today — the worked example below is that deployment, generalized with placeholders.
4
4
 
5
- Pair this with `hosting-sites.md` (how the site itself gets deployed) and `api.md` (the master token + mint-narrow discipline that authenticates every `wrangler` call).
5
+ Pair this with `hosting-sites.md` (how the site itself gets deployed) and `api.md` (the master token + reused per-scope token discipline that authenticates every `wrangler` call).
6
6
 
7
7
  ---
8
8
 
@@ -13,45 +13,53 @@ A Pages-only token **cannot touch D1**. The deploy succeeds, the form renders, a
13
13
  - **Account · Cloudflare Pages · Edit**
14
14
  - **Account · D1 · Edit**
15
15
 
16
- Mint a narrow token that includes both permission groups (see `api.md` § Minting a narrow token) before any `wrangler d1` or D1-backed deploy command. This was observed live in the session that built `realagent.pages.dev`; it is the first thing to check when captures stop arriving.
16
+ Use the stable `<brand>-pages-d1` token that includes both permission groups (see `api.md` § Provisioning and reusing a stable per-scope token, and § 0 below) before any `wrangler d1` or D1-backed deploy command. This was observed live in the session that built `realagent.pages.dev`; it is the first thing to check when captures stop arriving.
17
+
18
+ **Even a read-only `SELECT` needs D1 *Edit*.** The `wrangler d1 execute --remote` query path goes through the D1 **query** endpoint, which **rejects a D1-Read token** — a token scoped to D1 *Read* fails the `SELECT`, not just the `INSERT`/`UPDATE`. Use the **D1-Edit**-scoped token (or the both-Pages-Edit-and-D1-Edit token above) for every `wrangler d1 execute`, reads included. A separately-minted "D1 Read" token for the sweep `SELECT`s is wrong guidance: it is rejected at the query endpoint.
17
19
 
18
20
  ---
19
21
 
20
- ## 0. Mint the Pages-+-D1 token
22
+ ## 0. Load (or mint once) the stable Pages-+-D1 token
21
23
 
22
- Every command below uses `${MINTED_PAGES_D1}` — a narrow token scoped to **both** Pages Edit and D1 Edit, minted from the master per `api.md` § Minting a narrow token. Mint it once at the start of the session and export it; it is never written to disk or echoed:
24
+ Every command below uses `${PAGES_D1}` — the **stable, reused** per-scope token (`<brand>-pages-d1`) scoped to **both** Pages Edit and D1 Edit. Provision it the way `api.md` § Provisioning and reusing a stable per-scope token prescribes: **load it from the secrets file; mint it once only if absent** (no expiry), persist it, and reuse it thereafter. It is never written into a project tree or echoed:
23
25
 
24
26
  ```bash
25
- set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads master + account id
26
- MINTED_PAGES_D1=$(curl -sS -X POST \
27
- -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
28
- -H "Content-Type: application/json" \
29
- "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
30
- --data @- <<'JSON' | jq -r '.result.value'
27
+ set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads master + account id + persisted per-scope tokens
28
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
29
+
30
+ TOKEN_KEY="CF_PAGES_D1_TOKEN"; SCOPE_NAME="<brand>-pages-d1"
31
+ PAGES_D1=$(grep -m1 "^${TOKEN_KEY}=" "${SECRETS_DIR}/cloudflare.env" 2>/dev/null | cut -d= -f2-)
32
+ if [ -z "${PAGES_D1}" ]; then # absent → mint once, scoped to BOTH Pages Edit + D1 Edit, no expiry
33
+ PAGES_D1=$(curl -sS -X POST \
34
+ -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
35
+ -H "Content-Type: application/json" \
36
+ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
37
+ --data @- <<JSON | jq -r '.result.value'
31
38
  {
32
- "name": "ephemeral-pages-d1",
39
+ "name": "${SCOPE_NAME}",
33
40
  "policies": [{
34
41
  "effect": "allow",
35
- "resources": { "com.cloudflare.api.account.<accountId>": "*" },
42
+ "resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": "*" },
36
43
  "permission_groups": [
37
44
  { "id": "<pages-edit-permission-group-id>", "name": "Pages Write" },
38
45
  { "id": "<d1-edit-permission-group-id>", "name": "D1 Write" }
39
46
  ]
40
- }],
41
- "expires_on": "<iso8601-a-few-minutes-out>"
47
+ }]
42
48
  }
43
49
  JSON
44
- )
50
+ )
51
+ ( umask 077; printf '%s=%s\n' "${TOKEN_KEY}" "${PAGES_D1}" >> "${SECRETS_DIR}/cloudflare.env" )
52
+ fi
45
53
  ```
46
54
 
47
- Resolve the two permission-group ids once via `GET /accounts/{account_id}/tokens/permission_groups` (see `api.md`). When the work is done, `unset MINTED_PAGES_D1`.
55
+ Resolve the two permission-group ids once via `GET /accounts/{account_id}/tokens/permission_groups` (see `api.md`). There is **no `unset`** — `${PAGES_D1}` is the reused per-scope token, not a throwaway; the reconcile pass in `api.md` § Reconcile keeps the account from accumulating strays.
48
56
 
49
57
  ---
50
58
 
51
59
  ## 1. Create the database
52
60
 
53
61
  ```bash
54
- CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 create <db-name>
62
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler d1 create <db-name>
55
63
  # worked example: wrangler d1 create realagent-leads
56
64
  ```
57
65
 
@@ -76,7 +84,7 @@ database_id = "<database_id>" # from step 1
76
84
  Apply the schema to the remote database (`--remote` targets the live D1, not a local replica):
77
85
 
78
86
  ```bash
79
- CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
87
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
80
88
  "CREATE TABLE IF NOT EXISTS leads (
81
89
  id INTEGER PRIMARY KEY AUTOINCREMENT,
82
90
  name TEXT,
@@ -130,14 +138,14 @@ Deploy via `hosting-sites.md`. The binding in `wrangler.toml` is what wires the
130
138
  Read everything not yet ingested:
131
139
 
132
140
  ```bash
133
- CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --json --command \
141
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler d1 execute <db-name> --remote --json --command \
134
142
  "SELECT id, name, email, message, created_at FROM leads WHERE swept = 0 ORDER BY id;"
135
143
  ```
136
144
 
137
145
  After ingesting those rows (e.g. writing them into the graph), mark them swept so the next read returns only newer ones:
138
146
 
139
147
  ```bash
140
- CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
148
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
141
149
  "UPDATE leads SET swept = 1 WHERE swept = 0;"
142
150
  ```
143
151
 
@@ -150,7 +158,7 @@ D1 capture is done when a **test POST to the live form** appears in the unswept
150
158
  ```bash
151
159
  curl -sS -X POST -d "email=test@example.com&name=verify" "https://<project>.pages.dev/api/contact" -i | head -1
152
160
  # then:
153
- CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
161
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
154
162
  "SELECT email FROM leads WHERE swept = 0;"
155
163
  ```
156
164
 
@@ -109,8 +109,9 @@ Use this when you need to audit which hostnames are pointing at which `<UUID>.cf
109
109
 
110
110
  When the agent adds an SSH or SMB ingress hostname (per `references/manual-setup.md`),
111
111
  the Access policy must exist before off-LAN clients can reach the Pi. There are two
112
- paths: the agent can author it via the Cloudflare API with a minted Access-scoped
113
- token (see `references/api.md` § Access), or the operator can author it by hand in the
112
+ paths: the agent can author it via the Cloudflare API with the reused Access-scoped
113
+ token (`<brand>-access`, minted once if absent — see `references/api.md` § Access and
114
+ § Provisioning and reusing a stable per-scope token), or the operator can author it by hand in the
114
115
  dashboard with the click-path below. `cloudflared` CLI has no Access-application create
115
116
  subcommand, so the dashboard path is the manual alternative to the API.
116
117
 
@@ -1,6 +1,6 @@
1
1
  # Deploying a site to Cloudflare Pages
2
2
 
3
- How to put a static or Next.js site on Cloudflare Pages via `wrangler`, the way `realagent.pages.dev` is deployed. This covers the build → deploy → verify loop and the Next.js specifics. Auth is a minted narrow token per `api.md`; if the site also captures form data, read `d1-data-capture.md` for the dual Pages-Edit **and** D1-Edit scope.
3
+ How to put a static or Next.js site on Cloudflare Pages via `wrangler`, the way `realagent.pages.dev` is deployed. This covers the build → deploy → verify loop and the Next.js specifics. Auth is the reused per-scope token per `api.md`; if the site also captures form data, read `d1-data-capture.md` for the dual Pages-Edit **and** D1-Edit scope.
4
4
 
5
5
  This is distinct from `references/serving-published-sites.md`, which serves a platform-published site at a custom domain through the brand's **cloudflared tunnel**. This reference is about **Cloudflare Pages** — Cloudflare hosts the build, not the install device.
6
6
 
@@ -31,19 +31,21 @@ compatibility_flags = ["nodejs_compat"] # required for next-on-pages and most F
31
31
 
32
32
  If the site captures form data, add the `[[d1_databases]]` binding from `d1-data-capture.md` here.
33
33
 
34
- ## 3. Mint a deploy token
34
+ ## 3. Load the reused deploy token
35
35
 
36
- A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). Mint it per `api.md` § Minting a narrow token:
36
+ A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). Load the stable per-scope token — minting it once only if absent — per `api.md` § Provisioning and reusing a stable per-scope token:
37
37
 
38
38
  ```bash
39
39
  set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
40
- # MINTED = a narrow token scoped to Pages Edit (+ D1 Edit if the site uses D1)
40
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
41
+ # PAGES_D1 = the reused per-scope token (Pages Edit, + D1 Edit if the site uses D1),
42
+ # loaded from cloudflare.env and minted once if absent — see api.md § Provisioning and reusing.
41
43
  ```
42
44
 
43
45
  ## 4. Deploy
44
46
 
45
47
  ```bash
46
- CLOUDFLARE_API_TOKEN="${MINTED}" wrangler pages deploy <output-dir> \
48
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler pages deploy <output-dir> \
47
49
  --project-name <project> --branch=main
48
50
  ```
49
51
 
@@ -295,8 +295,9 @@ rather than failing.
295
295
 
296
296
  **Access policy — API or dashboard.** `cloudflared` CLI has no
297
297
  Access-application create subcommand, so author the policy either via
298
- the Cloudflare API with a minted Access-scoped token (see
299
- `references/api.md` § Access) or by hand in the dashboard. After the
298
+ the Cloudflare API with the reused Access-scoped token (`<brand>-access`,
299
+ minted once if absent — see `references/api.md` § Access) or by hand in
300
+ the dashboard. After the
300
301
  ingress is in place, the dashboard path is:
301
302
 
302
303
  ```
@@ -14,7 +14,7 @@ This is the entry point for every Cloudflare task on the install. Pick the opera
14
14
  | Stand up / diagnose / reset a tunnel so a hostname is reachable | `references/manual-setup.md` | external `curl -I https://<hostname>` → `200` |
15
15
  | Switch accounts, edit an apex CNAME by hand, or other dashboard-only clicks | `references/dashboard-guide.md` | the click-path completed in the operator's browser |
16
16
  | Tear down corrupt tunnel state and start clean | `references/reset-guide.md` | a fresh `manual-setup.md` run reaches `200` |
17
- | Call the Cloudflare API (DNS, zones, tunnels, Access, token mint) | `references/api.md` | the API call returns success and the change is observable |
17
+ | Call the Cloudflare API (DNS, zones, tunnels, Access, token list/reuse/revoke) | `references/api.md` | the API call returns success and the change is observable |
18
18
  | Deploy a static / Next.js site to Cloudflare Pages | `references/hosting-sites.md` | the deployed URL serves the new build |
19
19
  | Capture form/waitlist submissions into a database | `references/d1-data-capture.md` | a test POST appears in `SELECT … WHERE swept = 0` |
20
20
  | Diagnose / enable Web Analytics on a site (0 visitors, no data) | `references/web-analytics.md` | beacon present in live HTML (`curl -s https://<host>/ \| grep cloudflareinsights`) |
@@ -22,9 +22,9 @@ This is the entry point for every Cloudflare task on the install. Pick the opera
22
22
  ## Two auth paths, by operation class
23
23
 
24
24
  - **Tunnel auth is OAuth.** `cloudflared tunnel login` writes `cert.pem`; the operator clicks the linkified OAuth URL in their own browser. This is the only path for tunnel create/route/run. See `references/manual-setup.md`.
25
- - **API auth is the master token.** The operator provisions one fully-scoped master token once (an **advanced**, operator-guided dashboard step — the agent does not automate it) and stores it at the account-scoped secrets file. The agent reads the master only to **mint a narrowly-scoped, short-lived token** for each operationa single-zone DNS edit, a one-project Pages deploy, a D1 query and exports that ephemeral token for the one call. See `references/api.md` for the storage convention, the mint flow, and the binding redaction discipline.
25
+ - **API auth is the master token.** The operator provisions one fully-scoped master token once (an **advanced**, operator-guided dashboard step — the agent does not automate it) and stores it at the account-scoped secrets file. The agent reads the master only to **provision one stable narrow token per scope** `<brand>-pages-d1`, `<brand>-dns`, `<brand>-access` loading it from the secrets file, minting it once if absent (no expiry), persisting it, and reusing it for every later operation in that scope. A standing **reconcile** pass revokes strays. See `references/api.md` for the storage convention, the provisioning-and-reuse flow, the reconcile pass, and the binding redaction discipline.
26
26
 
27
- Neither the master token nor any minted token is ever written into a project tree, committed, or echoed into chat.
27
+ Neither the master token nor any per-scope token is ever written into a project tree, committed, or echoed into chat — the per-scope tokens persist only in the account-scoped secrets file, never in a deployable project tree.
28
28
 
29
29
  ## Outcome contracts — what "done" means
30
30
 
@@ -55,7 +55,7 @@ After the service is up, the agent runs `curl -I https://<admin-hostname>` and p
55
55
 
56
56
  ## Apex hostnames
57
57
 
58
- The `cloudflared` CLI cannot route apex records (e.g. `maxy.chat`) — standard DNS forbids a CNAME at the zone apex. There are now two ways to create the apex record: the operator edits the apex CNAME in the dashboard (`references/dashboard-guide.md` § "Edit an apex CNAME"), or the agent creates it via the Cloudflare API with a minted DNS-scoped token (`references/api.md` § DNS records). Either reaches the same flattened-CNAME result; pick the dashboard path when the operator prefers to click, the API path when an end-to-end agent run is wanted.
58
+ The `cloudflared` CLI cannot route apex records (e.g. `maxy.chat`) — standard DNS forbids a CNAME at the zone apex. There are now two ways to create the apex record: the operator edits the apex CNAME in the dashboard (`references/dashboard-guide.md` § "Edit an apex CNAME"), or the agent creates it via the Cloudflare API with the reused DNS-scoped token (`<brand>-dns`, minted once if absent — `references/api.md` § DNS records). Either reaches the same flattened-CNAME result; pick the dashboard path when the operator prefers to click, the API path when an end-to-end agent run is wanted.
59
59
 
60
60
  ## Reset
61
61
 
@@ -66,10 +66,10 @@ When local Cloudflare state is corrupt or the operator wants to start from a kno
66
66
  When the operator's request touches Cloudflare, the agent's permitted actions are:
67
67
 
68
68
  - Invoke `cloudflared` via Bash, following `references/manual-setup.md`.
69
- - Invoke `wrangler` and the Cloudflare API via Bash, following `references/api.md`, `references/hosting-sites.md`, and `references/d1-data-capture.md`, using a freshly-minted narrow token per operation.
69
+ - Invoke `wrangler` and the Cloudflare API via Bash, following `references/api.md`, `references/hosting-sites.md`, and `references/d1-data-capture.md`, using the reused per-scope narrow token (minted once if absent, then reused).
70
70
  - Write `config.yml` and `alias-domains.json` per the runbook.
71
71
  - Install and start the brand's cloudflared user service.
72
72
  - Quote `references/manual-setup.md`, `references/reset-guide.md`, or `references/dashboard-guide.md` verbatim when a dashboard click-path is the right tool.
73
73
  - Verify reachability via `curl -I https://<hostname>` and surface the response.
74
74
 
75
- The agent does not drive the dashboard via Playwright or Chrome DevTools, and does not browser-automate master-token creation. It does not synthesise `cloudflared` flag combinations from web search or training — every command comes from the runbook. It never writes a token to disk in a project tree, commits one, or prints one into chat. When a step fails, the agent reports the exact output, names the recovery step from `references/reset-guide.md`, and stops. Improvisation — "let me try a different flag" or "let me drive the dashboard myself" — is the behaviour this rule exists to prevent.
75
+ The agent does not drive the dashboard via Playwright or Chrome DevTools, and does not browser-automate master-token creation. It does not synthesise `cloudflared` flag combinations from web search or training — every command comes from the runbook. **Cloudflare API calls are made with `curl` + `jq` only — never a Python or node script.** It never writes a token to disk in a project tree, commits one, or prints one into chat. When a step fails, the agent reports the exact output, names the recovery step from `references/reset-guide.md`, and stops. Improvisation — "let me try a different flag", "let me drive the dashboard myself", or "let me write a quick Python script for this API call" — is the behaviour this rule exists to prevent.
@@ -11,7 +11,7 @@ Each installation has its own Cloudflare account. The **tunnel** sign-in is OAut
11
11
  | **Domain scope** (which zones the operator can route) | The operator picks the zone in the dashboard during OAuth or names it in chat; the agent can also enumerate zones via the API with a minted read-scoped token. |
12
12
  | **Local tunnel state** | `~/{configDir}/cloudflared/` — `cert.pem`, `<UUID>.json`, `config.yml`, `alias-domains.json`. |
13
13
 
14
- Tunnel auth on the operator-owned path (Mode A) is the OAuth cert (`cert.pem`); API operations use a narrow token the agent mints from your master token. To switch Cloudflare accounts, the agent runs the reset flow from `plugins/cloudflare/references/reset-guide.md` (deletes the cert and every tunnel on the current account), then the manual-setup flow again — `cloudflared tunnel login` picks a fresh account when you sign in.
14
+ Tunnel auth on the operator-owned path (Mode A) is the OAuth cert (`cert.pem`); API operations use a narrow token the agent mints from your master token. That master may be account-scoped (`cfat_…`) or user-scoped (`cfut_…`); the agent detects the type and routes API endpoints accordingly (the account vs user token endpoints) — see `plugins/cloudflare/references/api.md`. To switch Cloudflare accounts, the agent runs the reset flow from `plugins/cloudflare/references/reset-guide.md` (deletes the cert and every tunnel on the current account), then the manual-setup flow again — `cloudflared tunnel login` picks a fresh account when you sign in.
15
15
 
16
16
  ## Setup flow
17
17
 
@@ -19,13 +19,13 @@ The Telegram plugin connects {{productName}} to a Telegram bot. Once set up, you
19
19
 
20
20
  ### Step 2: Connect the plugin
21
21
 
22
- Tell {{productName}}: "Set up Telegram" or "Configure the Telegram bot."
22
+ Tell {{productName}}: "Set up Telegram" or "Configure the Telegram bot." Setup runs entirely in the admin chat — there is no settings page.
23
23
 
24
- {{productName}} will ask for your bot token, then save it and activate the plugin. The bot is now connected.
24
+ {{productName}} asks for your bot token, then asks whether this is an **admin bot** (only your numeric Telegram IDs may message it) or a **public bot** (customer-facing, with a DM policy). It verifies the token, registers the bot's webhook on your brand's edge, stores the token under your brand's config (never in a shared system directory), and for a public bot — sets the agent that will answer. The bot is now connected.
25
25
 
26
26
  ### Step 3: Start the bot
27
27
 
28
- In Telegram, open your bot and send `/start`. The bot is now active and listening.
28
+ In Telegram, open your bot and send `/start`, then a message. {{productName}} receives it and replies in the same chat. Admin-bot messages from your registered IDs reach the admin agent; public-bot messages reach the public agent you selected.
29
29
 
30
30
  ## Sending Messages via {{productName}}
31
31
 
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
2
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
- import { resolveOutboundAttachments, accountDirFor } from '../lib/attachment-resolve.js';
5
+ import { resolveOutboundAttachments, accountDirFor, resolveArchiveAccountDir, } from '../lib/attachment-resolve.js';
6
6
  describe('Task 714 — attachment-resolve account-dir resolution', () => {
7
7
  let root = ''; // stands in for dirname(PLATFORM_ROOT)
8
8
  let platformRoot = '';
@@ -65,4 +65,28 @@ describe('Task 714 — attachment-resolve account-dir resolution', () => {
65
65
  expect(resolveOutboundAttachments(undefined, undefined)).toEqual([]);
66
66
  });
67
67
  });
68
+ describe('Task 715 — inbound attachment-archive account-dir resolution', () => {
69
+ const accountId = 'acc-1';
70
+ const saved = { ...process.env };
71
+ beforeEach(() => {
72
+ delete process.env.ACCOUNT_DIR;
73
+ delete process.env.PLATFORM_ROOT;
74
+ delete process.env.ACCOUNT_ID;
75
+ });
76
+ afterEach(() => {
77
+ process.env = { ...saved };
78
+ });
79
+ it('uses ACCOUNT_DIR directly when present (bytes get archived)', () => {
80
+ process.env.ACCOUNT_DIR = '/spawn/set/dir';
81
+ expect(resolveArchiveAccountDir(accountId)).toBe('/spawn/set/dir');
82
+ });
83
+ it('derives from PLATFORM_ROOT + the in-scope accountId when ACCOUNT_DIR is unset', () => {
84
+ process.env.PLATFORM_ROOT = '/srv/platform';
85
+ // Note: the in-scope accountId is used, not process.env.ACCOUNT_ID.
86
+ expect(resolveArchiveAccountDir(accountId)).toBe('/srv/data/accounts/acc-1');
87
+ });
88
+ it('returns undefined when neither source resolves (metadata-only WARN branch, no throw)', () => {
89
+ expect(resolveArchiveAccountDir(accountId)).toBeUndefined();
90
+ });
91
+ });
68
92
  //# sourceMappingURL=attachment-resolve.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-resolve.test.js","sourceRoot":"","sources":["../../src/__tests__/attachment-resolve.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attachment-resolve.test.js","sourceRoot":"","sources":["../../src/__tests__/attachment-resolve.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,10 @@
1
+ /** Task 715 — resolve the account dir for the inbound attachment archive
2
+ * (email-ingest's buildAttachmentRecords) the same way the outbound path does:
3
+ * the spawn-set ACCOUNT_DIR wins; absent that, derive from PLATFORM_ROOT + this
4
+ * account's id via accountDirFor (accountId is already in scope). Returns
5
+ * undefined only when neither source resolves, which drops the archive to its
6
+ * metadata-only branch. Reuses Task 714's accountDirFor — no new derivation. */
7
+ export declare function resolveArchiveAccountDir(accountId: string): string | undefined;
1
8
  /** Derive the account directory for a known accountId (the account whose
2
9
  * credentials are loading). Authoritative for the email send/reply path, which
3
10
  * holds params.accountId. Returns undefined when PLATFORM_ROOT is absent — the