@rubytech/create-maxy-code 0.1.282 → 0.1.284

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 (45) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +0 -1
  3. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-search.test.js +3 -3
  4. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-search.test.js.map +1 -1
  5. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +4 -4
  6. package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +28 -4
  7. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  8. package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -5
  9. package/payload/platform/plugins/cloudflare/references/api.md +78 -38
  10. package/payload/platform/plugins/cloudflare/references/d1-data-capture.md +27 -21
  11. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -2
  12. package/payload/platform/plugins/cloudflare/references/hosting-sites.md +7 -5
  13. package/payload/platform/plugins/cloudflare/references/manual-setup.md +3 -2
  14. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +6 -6
  15. package/payload/platform/plugins/docs/references/telegram-guide.md +3 -3
  16. package/payload/platform/plugins/telegram/PLUGIN.md +7 -2
  17. package/payload/platform/plugins/telegram/mcp/dist/index.js +66 -11
  18. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +6 -0
  20. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -0
  21. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +20 -0
  22. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -0
  23. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +21 -0
  24. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -0
  25. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +52 -0
  26. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -0
  27. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
  28. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +5 -2
  29. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
  30. package/payload/platform/plugins/telegram/references/setup-guide.md +10 -7
  31. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +79 -0
  32. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  33. package/payload/platform/services/claude-session-manager/dist/http-server.js +17 -0
  34. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  35. package/payload/server/public/assets/AdminShell-8Y7-maNi.js +1 -0
  36. package/payload/server/public/assets/{admin-0_VxffD5.js → admin-DyuZ4NT5.js} +1 -1
  37. package/payload/server/public/assets/{browser-Cxjkhtsk.js → browser-XrVAUQdG.js} +1 -1
  38. package/payload/server/public/assets/{data-BOgwOWnw.js → data-tcu3MXmK.js} +1 -1
  39. package/payload/server/public/assets/{graph-BqD2GjQd.js → graph-I4GyC2MW.js} +1 -1
  40. package/payload/server/public/browser.html +2 -2
  41. package/payload/server/public/data.html +2 -2
  42. package/payload/server/public/graph.html +2 -2
  43. package/payload/server/public/index.html +2 -2
  44. package/payload/server/server.js +827 -531
  45. package/payload/server/public/assets/AdminShell-FO766lOW.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.282",
3
+ "version": "0.1.284",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -55,7 +55,6 @@
55
55
  },
56
56
 
57
57
  "externalPlugins": [
58
- { "name": "telegram", "marketplace": "claude-plugins-official", "channelPlugin": true },
59
58
  { "name": "discord", "marketplace": "claude-plugins-official", "channelPlugin": true },
60
59
  { "name": "imessage", "marketplace": "claude-plugins-official", "channelPlugin": true },
61
60
  { "name": "superpowers", "marketplace": "claude-plugins-official" },
@@ -24,19 +24,19 @@ function seedSkill(plugin, skill, body) {
24
24
  const fm = (name, description) => `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`;
25
25
  describe("searchSkills", () => {
26
26
  it("description-only match returns the owning plugin + canonical slug path top-ranked", () => {
27
- seedSkill("quotation", "quotation", fm("quotation", "Use when an operator's own way of pricing jobs needs to become something the assistant can apply — set up my pricing, learn how I quote, re-learning their method from past quotes."));
27
+ seedSkill("siteoffice-job", "quotation", fm("quotation", "Use when an operator's own way of pricing jobs needs to become something the assistant can apply — set up my pricing, learn how I quote, re-learning their method from past quotes."));
28
28
  seedSkill("admin", "qr-code", fm("qr-code", "Generate QR codes. Trigger phrases: create a QR, make a QR code."));
29
29
  seedSkill("admin", "datetime", fm("datetime", "Timezone queries and relative-date arithmetic."));
30
30
  const hits = searchSkills(root, "pricing algorithm");
31
31
  expect(hits.length).toBeGreaterThan(0);
32
- expect(hits[0].pluginName).toBe("quotation");
32
+ expect(hits[0].pluginName).toBe("siteoffice-job");
33
33
  expect(hits[0].skillName).toBe("quotation");
34
34
  expect(hits[0].file).toBe("skills/quotation/SKILL.md");
35
35
  });
36
36
  it("a common trigger word does not let a noise skill outrank the distinctive match", () => {
37
37
  // "create" is a noise term (appears in many descriptions); "quote" is
38
38
  // distinctive. quotation must still win for "create a quote".
39
- seedSkill("quotation", "quotation", fm("quotation", "Onboarding a new business's pricing, re-learning their method from past quotes, checking the method reproduces past quotes."));
39
+ seedSkill("siteoffice-job", "quotation", fm("quotation", "Onboarding a new business's pricing, re-learning their method from past quotes, checking the method reproduces past quotes."));
40
40
  seedSkill("admin", "qr-code", fm("qr-code", "Create a QR code. Trigger: create a QR, create a code."));
41
41
  seedSkill("admin", "publish-site", fm("publish-site", "Create and publish a static site online."));
42
42
  seedSkill("admin", "deck-pages", fm("deck-pages", "Create a slide deck of pages."));
@@ -1 +1 @@
1
- {"version":3,"file":"skill-search.test.js","sourceRoot":"","sources":["../../src/__tests__/skill-search.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skill-search.test.js","sourceRoot":"","sources":["../../src/__tests__/skill-search.test.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE7E,IAAI,IAAY,CAAC;AAEjB,UAAU,CAAC,GAAG,EAAE;IACd,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,MAAc,EAAE,KAAa,EAAE,IAAY;IAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3D,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,WAAmB,EAAE,EAAE,CAAC,cAAc,IAAI,kBAAkB,WAAW,cAAc,IAAI,IAAI,CAAC;AAExH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QAC3F,SAAS,CACP,gBAAgB,EAChB,WAAW,EACX,EAAE,CAAC,WAAW,EAAE,qLAAqL,CAAC,CACvM,CAAC;QACF,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,kEAAkE,CAAC,CAAC,CAAC;QACjH,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,gDAAgD,CAAC,CAAC,CAAC;QAEjG,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAErD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,sEAAsE;QACtE,8DAA8D;QAC9D,SAAS,CACP,gBAAgB,EAChB,WAAW,EACX,EAAE,CAAC,WAAW,EAAE,6HAA6H,CAAC,CAC/I,CAAC;QACF,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,wDAAwD,CAAC,CAAC,CAAC;QACvG,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC,cAAc,EAAE,0CAA0C,CAAC,CAAC,CAAC;QACnG,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,+BAA+B,CAAC,CAAC,CAAC;QAEpF,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QAElD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC;QAEnE,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QAEpD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,SAAS,CACP,OAAO,EACP,UAAU,EACV,yKAAyK,CAC1K,CAAC;QACF,SAAS,CACP,OAAO,EACP,aAAa,EACb,2LAA2L,CAC5L,CAAC;QAEF,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,8BAA8B,CAAC,GAAG,CAAC,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,qEAAqE;QACrE,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC,aAAa,EAAE,mCAAmC,CAAC,CAAC,CAAC;QAC1F,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,iCAAiC,CAAC,CAAC,CAAC;QAEnF,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAExD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,GAAG,qBAAqB,CAAC,sFAAsF,CAAC,CAAC;QACxH,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,GAAG,qBAAqB,CAAC,2FAA2F,CAAC,CAAC;QAC7H,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,qBAAqB,CAAC,qIAAqI,CAAC,CAAC;QACvK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,GAAG,qBAAqB,CAAC,4BAA4B,CAAC,CAAC;QAC9D,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,GAAG,qBAAqB,CAAC,kDAAkD,CAAC,CAAC;QACpF,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:add28451435be577d674216c86cd03078f5b25d03ce6def9fc61962971c8e9ed
4
+ content-hash: sha256:5dae80dcc0e3d36e755dcf26570c4d7c8893b6198b8cd769b3e5c6c1c79169f5
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -1728,13 +1728,13 @@ The Telegram plugin connects Maxy to a Telegram bot. Once set up, you can:
1728
1728
 
1729
1729
  ### Step 2: Connect the plugin
1730
1730
 
1731
- Tell Maxy: "Set up Telegram" or "Configure the Telegram bot."
1731
+ Tell Maxy: "Set up Telegram" or "Configure the Telegram bot." Setup runs entirely in the admin chat — there is no settings page.
1732
1732
 
1733
- Maxy will ask for your bot token, then save it and activate the plugin. The bot is now connected.
1733
+ Maxy 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.
1734
1734
 
1735
1735
  ### Step 3: Start the bot
1736
1736
 
1737
- In Telegram, open your bot and send `/start`. The bot is now active and listening.
1737
+ In Telegram, open your bot and send `/start`, then a message. Maxy 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.
1738
1738
 
1739
1739
  ## Sending Messages via Maxy
1740
1740
 
@@ -18,7 +18,7 @@ It composes two patterns you must read alongside it:
18
18
  - **`cloudflare/references/d1-data-capture.md`** — the form → Pages Function → D1 → sweep mechanics, and the token-scope breakage (§ 0). Everything about minting the Pages-+-D1 token, `wrangler.toml`, and `wrangler d1 execute --remote` lives there; this skill does not repeat it. Note its rule that the D1 **query** endpoint rejects a D1-Read token — every `wrangler d1 execute` here uses a **D1-Edit** token, reads included.
19
19
  - **`business-assistant/references/invoicing.md`** — PDF generation via `browser-navigate` + `browser-pdf-save` (its step 4). The **deploy-time base render** (§ 5) uses that exact pattern. Dispatch (§ 6) does **not** — it stamps the persisted base with a PDF library, no browser and no network.
20
20
 
21
- Auth for every `wrangler`/API call is a minted narrow token per `cloudflare/references/api.md` — detect the master's type (`cfat_…` account-scoped vs `cfut_…` user-scoped) and route endpoints by prefix per that reference. Deploy mechanics are `cloudflare/references/hosting-sites.md`.
21
+ Auth for every `wrangler`/API call is the **reused per-scope narrow token** of `cloudflare/references/api.md` (one deterministic token per scope minted once if absent, persisted to the secrets file, then loaded and reused; Task 732), routed by the master's type (`cfat_…` account-scoped vs `cfut_…` user-scoped) by prefix per that reference. Deploy mechanics are `cloudflare/references/hosting-sites.md`.
22
22
 
23
23
  ---
24
24
 
@@ -91,7 +91,9 @@ CLOUDFLARE_API_TOKEN="${MINTED_ACCESS}" curl -sS \
91
91
  | jq '.result | length'
92
92
  ```
93
93
 
94
- **Token scope.** Gating needs one minted narrow token (per `api.md` § Minting a narrow token, routed by prefix with mint→verify backoff as § 8.8 notes) carrying **Access application + policy write** *and* **`Access: Identity Providers Write`**. Resolve those permission-group ids the same way `d1-data-capture.md` § 0 resolves the Pages/D1 ids — one `GET …/tokens/permission_groups`. Token *creation* stays an account-level concern; this skill names the scopes and consumes whatever token type the account supplies. `${MINTED_ACCESS}` below is that token.
94
+ **Token scope.** Gating needs the **reused per-scope Access token** (`<brand>-access` — minted once if absent, persisted, then loaded and reused per `api.md` § Provisioning and reusing a stable per-scope token; routed by prefix, with the mint→verify backoff of § 8.8 applying only on the mint-once path) carrying **Access application + policy write** *and* **`Access: Identity Providers Write`**. Resolve those permission-group ids the same way `d1-data-capture.md` § 0 resolves the Pages/D1 ids — one `GET …/tokens/permission_groups` — but **"Access: Apps and Policies Write" can return duplicate ids** from that call, and minting with the wrong variant poisons the token: it then returns `10000` on **every** call, **reads included**. So **verify the token with a read before relying on it** — a `GET …/access/apps` that returns `10000` is the wrong-id signature (distinct from § 8.8's transient post-mint `10000`, which clears within ~30s); re-resolve and mint with the other variant. Token *creation* stays an account-level concern; this skill names the scopes and consumes whatever token type the account supplies. `${MINTED_ACCESS}` below is that token.
95
+
96
+ **Access-app updates are `PUT`, not `PATCH`.** When you update an existing Access app — most often to widen its destinations — send `PUT …/access/apps/{app_id}` with the **full app body** (re-supply `name`, `destinations`, `type`, `session_duration`, …). `PATCH` returns `10405 "Method not allowed for this authentication scheme"` under API-token auth; there is no working partial-update verb (`api.md` § Access).
95
97
 
96
98
  **Create IdP → app → policy, in that order:**
97
99
 
@@ -103,7 +105,7 @@ CLOUDFLARE_API_TOKEN="${MINTED_ACCESS}" curl -sS \
103
105
  --data '{"name":"One-time PIN","type":"onetimepin"}' | jq '{ok:.success, type:.result.type}'
104
106
  ```
105
107
 
106
- 2. **Create the Access application** scoped to the deployed page's hostname **and** path (the `<project>.pages.dev` host and the signing document's path), so the gate covers exactly that document.
108
+ 2. **Create the Access application** scoped to the deployed host's **document path(s) `and` `/api/*`** the signing document's path(s) plus the capture endpoints `POST /api/accept` and `GET /api/status` (§ 4). Leave `/` and the static assets **public**. Gating only the document path is a **bypass**: with `/api/*` open, anyone can `POST /api/accept` and forge an acceptance, and `GET /api/status` is readable ungated. Leaving `/` public is deliberate and load-bearing — it is exactly what lets the neutral root portal (below) load **without a PIN**; the document path(s) and `/api/*` are the only gated destinations.
107
109
  3. **Attach a policy** whose `include` allowlist names the **signer's email** (and the business owner's). Only those addresses can authenticate and view the page.
108
110
 
109
111
  The IdP call shapes are given inline above; the Access **app** and **policy** endpoints live in `api.md`'s endpoint map, and the permission-group ids are resolved by the `api.md` method (`GET …/tokens/permission_groups`).
@@ -112,7 +114,9 @@ The IdP call shapes are given inline above; the Access **app** and **policy** en
112
114
 
113
115
  **Dispatch is unaffected (§ 6).** The sweep stamps the **persisted base** (`base.pdf`, § 1), never by fetching the gated page, so a gate never turns a dispatched "PDF" into an Access login screen — § 8.3 makes that structural. Keep it in mind whenever a gated document is swept.
114
116
 
115
- **Root landing pagea neutral portal, required for every gated site.** The § 1 project ships only the document page(s), so there is **no `/` page**. Cloudflare Access routinely lands a visitor on the **bare root `/`**: after login it does not always deep-link back to the requested path, and its logout endpoint (`/cdn-cgi/access/logout`) returns the user to `/?__cf_access_message=logged_out`. With no `/` page both land on a **404** a client-facing portal showing a raw 404 the moment a signer logs out. So a gated site **must** ship a root `index.html`. It is a **neutral portal**, not a redirect: a single host can carry **many** gated documents (each its own slug/path/`DOC_REF` § 3's D1 table is shared across a brand's documents, and § 1a scopes the Access app to host **and** path), and they all share one `/`. The root **cannot know which document** a visitor came from Access provides **no logout `returnTo`/redirect parameter**, so the root cannot route by origin — so it must name no document at all.
117
+ **Logout links carry `returnTo` point them at the public root (the primary loop-fix).** Cloudflare Access **does** honour a logout redirect: `GET /cdn-cgi/access/logout?returnTo=<https origin root>` 302s straight to that root and **strips** the stale `__cf_access_message` param. The parameter is **camelCase `returnTo` only** `return_to` and `redirect_url` are ignored. So build every signer-facing logout link as `/cdn-cgi/access/logout?returnTo=https://<host>/`, with `returnTo` pointing at the **public root**, never at the gated document path (which would just re-challenge for a PIN). A logout link built this way lands the signer cleanly on the public root with no stale paramthis is what actually breaks the "enter PIN logged-out screen" loop.
118
+
119
+ **Root landing page — a neutral portal, the belt-and-suspenders backstop.** The § 1 project ships only the document page(s), so there is **no `/` page** unless you add one. Even with `returnTo` available, Cloudflare Access still occasionally lands a visitor on the **bare root `/`** — a logout link that omitted `returnTo`, or a post-login that did not deep-link back to the requested path — and with no `/` page that is a **404** on a client-facing portal. So a gated site **must** ship a root `index.html` as the backstop for those bare-root landings. It is a **neutral portal**, not a redirect: a single host can carry **many** gated documents (each its own slug/path/`DOC_REF` — § 3's D1 table is shared across a brand's documents, and § 1a gates each document's path **and** `/api/*`), and they all share one `/`. The root names **no** document — logout links point `returnTo` at the bare root, not at any document path — so a single portal serving every document's bare-root landing must name none.
116
120
 
117
121
  ```html
118
122
  <!doctype html>
@@ -483,6 +487,8 @@ When the durable routine (§ 8.2) runs, or when the operator asks for new accept
483
487
 
484
488
  The acceptance flow is proven end-to-end on the **live** Pages deployment — the D1 row, not the POST status, is the contract. Every `wrangler d1 execute` uses the **D1-Edit** token (reads included; the query endpoint rejects D1 Read):
485
489
 
490
+ **When the document is Access-gated (§ 1a), `/api/*` is gated too** — so the `curl` POSTs to `/api/accept` and the `GET /api/status` below are themselves **Access-challenged (302 to login), not direct 200s**. Run this end-to-end verification either **before** the gate is applied, or from a context that can authenticate to Access — the same authenticated-session requirement § 5's base render already carries. The `wrangler d1 execute` rows reach D1 through the Cloudflare API, not the gated host, so they verify unchanged.
491
+
486
492
  ```bash
487
493
  # 1. POST a first acceptance to the live Function.
488
494
  curl -sS -X POST -H "Content-Type: application/json" \
@@ -587,6 +593,8 @@ A hand-built document ships the old bare "agree to terms" checkbox (no intent/co
587
593
 
588
594
  A `cfut_…` master at an account endpoint returns `9109`; a freshly-minted token returns `10000` for a few seconds. Both are explicit, reproducible API responses — `api.md` § Token type routes by prefix and § verify-with-backoff absorbs the `10000` window. Diagnostic path: the verify call's JSON `errors[].code`.
589
595
 
596
+ **A `10000` that does *not* clear is a different failure — the wrong permission-group id.** The "Access: Apps and Policies Write" group can return **duplicate ids** from `GET …/tokens/permission_groups` (§ 1a), and minting with the wrong variant poisons the Access token: it returns `10000` on **every** call, **reads included**, and never clears. The transient post-mint `10000` clears within ~30s of backoff; a `10000` that **persists on a `GET …/access/apps` read** is the poisoned-token signature — re-resolve the permission-group id and mint with the other variant. This is why § 1a verifies a freshly-minted Access token with a read before relying on it.
597
+
590
598
  ### 8.9 Access gate active with zero enabled IdPs (no-event lockout)
591
599
 
592
600
  Only relevant when the document is Access-gated (§ 1a). An Access app is active on the page while the org has **no** identity provider, so the login screen offers no method and everyone — operator included — is locked out. It is a **no-event** failure: the gate emits no log and does not reproduce until someone tries to log in, by which point they are already locked out. Detection is a **standing audit reconciling active Access apps against enabled login methods**:
@@ -629,3 +637,19 @@ Two distinct regressions of the § 1a neutral portal, both caught by the § 8.10
629
637
  - **Sticky-param interstitial.** The root **branches on `__cf_access_message=logged_out`** — showing a "signed out" interstitial when the param is present. Because Cloudflare preserves that param through the **entire re-login redirect**, a re-authenticated signer is landed back on `/` with the **stale** param and shown a logged-out screen *while logged in*. Signature: any reference to `__cf_access_message` in the root. The fix is the neutral portal — it makes no auth-state claim, so it is correct in every state.
630
638
 
631
639
  The neutral portal must therefore contain **no** document-specific path **and no** auth-state claim; it shows the same content unconditionally.
640
+
641
+ ### 8.12 Per-deployment preview URLs ungated (confidential-document leak, no-event)
642
+
643
+ Only relevant when the document is Access-gated (§ 1a). A host-scoped Access app covers the **canonical host** but **not** Cloudflare's per-deployment hostnames: every Pages deploy also serves at `https://<hash>.<project>.pages.dev/…`, and that hostname is **not** behind the host-scoped gate. Confirmed on a live gated build: `https://<hash>.<project>.pages.dev/<doc-path>` served **200, ungated**, while the canonical host was gated — so a confidential document leaks via any deployment-hash URL. It is a **no-event** failure: the leaked read logs nothing and does not reproduce until someone holds a preview URL. Remedy — one of:
644
+
645
+ - **Disable preview deployments** for the project, so only the production host exists; or
646
+ - **Gate `*.pages.dev`** with an Access app, so the per-deployment hostnames are challenged too.
647
+
648
+ Signal — a deploy-time / standing check tied to gating: for every gated project, assert preview deployments are disabled (or `*.pages.dev` is gated), then confirm by fetching one live `<hash>.<project>.pages.dev/<doc-path>` and asserting it is **challenged (302 to the Access login), not 200**:
649
+
650
+ ```bash
651
+ # A 200 here is the leak. Expect a 302 to the Access login.
652
+ curl -sS -o /dev/null -w '%{http_code}\n' "https://<hash>.<project>.pages.dev/<doc-path>"
653
+ ```
654
+
655
+ A project with preview deployments enabled and an Access app scoped only to the canonical host is the failure signature.
@@ -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
 
@@ -116,49 +116,55 @@ Cross-type calls fail with a stable, recognizable signal: a `cfat_…` token at
116
116
 
117
117
  ---
118
118
 
119
- ## Minting a narrow token
119
+ ## Provisioning and reusing a stable per-scope token
120
120
 
121
- The agent mints a per-operation token from the master via `POST /accounts/{account_id}/tokens` (`cfat_…` master) or `POST /user/tokens` (`cfut_…` master — see § Token type — route endpoints by prefix). Scope it to the single operation, give it a short TTL, export it for the one call, discard it.
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).
122
122
 
123
123
  ```bash
124
- set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads the master + account id
125
-
126
- # Example: a one-project Pages-deploy token. Adjust the policy for the operation.
127
- MINTED=$(curl -sS -X POST \
128
- -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
129
- -H "Content-Type: application/json" \
130
- "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
131
- --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'
132
138
  {
133
- "name": "ephemeral-pages-deploy",
139
+ "name": "${SCOPE_NAME}",
134
140
  "policies": [{
135
141
  "effect": "allow",
136
- "resources": { "com.cloudflare.api.account.<accountId>": "*" },
142
+ "resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": "*" },
137
143
  "permission_groups": [{ "id": "<pages-edit-permission-group-id>", "name": "Pages Write" }]
138
- }],
139
- "expires_on": "<iso8601-a-few-minutes-out>"
144
+ }]
140
145
  }
141
146
  JSON
142
- )
147
+ )
148
+ ( umask 077; printf '%s=%s\n' "${TOKEN_KEY}" "${PAGES_D1}" >> "${SECRETS_DIR}/cloudflare.env" )
149
+ fi
143
150
 
144
- # Use it for exactly one command, in this command's environment only:
145
- CLOUDFLARE_API_TOKEN="${MINTED}" wrangler pages deploy ./dist --project-name <project> --branch=main
146
-
147
- 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
148
153
  ```
149
154
 
150
- 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.
151
156
 
152
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.
153
158
 
154
159
  ### A freshly-minted token is not instantly usable — verify with backoff
155
160
 
156
- 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):
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):
157
162
 
158
163
  ```bash
159
- # ${MINTED} = the token just minted; ${VERIFY_URL} = the prefix-correct verify endpoint.
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.
160
166
  for i in $(seq 1 10); do
161
- s=$(curl -sS -H "Authorization: Bearer ${MINTED}" "${VERIFY_URL}" \
167
+ s=$(curl -sS -H "Authorization: Bearer ${PAGES_D1}" "${VERIFY_URL}" \
162
168
  | jq -r '.result.status // (.errors[0].code | tostring)')
163
169
  [ "$s" = "active" ] && break
164
170
  [ "$s" = "10000" ] && { sleep 3; continue; } # transient post-mint window — expected
@@ -172,13 +178,15 @@ Proceed to the operation only once verify returns `active`. The `10000` window i
172
178
 
173
179
  ## Curated endpoint map
174
180
 
175
- 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.
176
182
 
177
- ### Token create / verify
183
+ ### Token list / create / verify / revoke
178
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.
179
- - `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.
180
- - `POST /accounts/${ACC}/tokens` — mint a narrow token (see above).
181
- - `GET /accounts/${ACC}/tokens/permission_groups` — resolve permission-group ids for scoping.
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`.
189
+ - `GET /accounts/${ACC}/tokens/permission_groups` — resolve permission-group ids for scoping. **Caveat — some groups return duplicate ids.** A single named group (observed for **"Access: Apps and Policies Write"**) can appear **more than once** in this list under different ids. Minting with the wrong variant yields a token that returns `10000` on **every** call, **reads included** — a permanently poisoned token, not the transient post-mint `10000` window of § A freshly-minted token. **Verify a freshly-minted Access token with a read** (e.g. `GET /accounts/${ACC}/access/apps`) before relying on it: a `10000` on that read means the wrong permission-group id was used — re-resolve and mint with the other variant.
182
190
 
183
191
  ### DNS records (zone-scoped; needs **Zone · DNS · Edit**)
184
192
  - `GET /zones/${ZONE}/dns_records` — list / find a record id.
@@ -202,6 +210,38 @@ curl -sS -X POST -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: applicati
202
210
 
203
211
  ### Access (Zero Trust) applications + policies
204
212
  - `POST /accounts/${ACC}/access/apps` and `POST /accounts/${ACC}/access/apps/{app_id}/policies` — author an Access application + policy programmatically. The dashboard click-path in `dashboard-guide.md` § "Author an Access policy" is the alternative for operators who prefer to click.
213
+ - `PUT /accounts/${ACC}/access/apps/{app_id}` — update an existing Access application (e.g. to widen its destinations). The body is the **full app object**, not a partial patch — re-supply `name`, `destinations`/`domain`, `type`, `session_duration`, and the rest, not just the changed fields. **`PATCH` does not work under API-token auth**: it returns `10405 "Method not allowed for this authentication scheme"`. Use `PUT` with the complete body; there is no working partial-update verb for this auth scheme.
214
+
215
+ ---
216
+
217
+ ## Reconcile — the standing cleanup check
218
+
219
+ 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.
220
+
221
+ ```bash
222
+ set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
223
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
224
+ LIST_URL="https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" # /user/tokens for a cfut_ master
225
+
226
+ # Names kept verbatim — adjust to this account's stable set + tunnel token names.
227
+ KEEP="<brand>-pages-d1 <brand>-dns <brand>-access maxy-api-master maxy-pages Maxy"
228
+
229
+ # 1. List; for each token whose name is NOT in KEEP, revoke it by id.
230
+ curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "${LIST_URL}?per_page=100" \
231
+ | jq -r '.result[] | [.id, .name] | @tsv' \
232
+ | while IFS=$'\t' read -r id name; do
233
+ case " ${KEEP} " in *" ${name} "*) echo "keep ${name}";;
234
+ *) curl -sS -X DELETE -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "${LIST_URL}/${id}" >/dev/null
235
+ echo "revoke ${name}";; esac
236
+ done
237
+
238
+ # 2. Post-condition — re-list and confirm the revoked names are gone. Do not trust the DELETE exit code.
239
+ curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "${LIST_URL}?per_page=100" | jq -r '.result[].name'
240
+ ```
241
+
242
+ `?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.
243
+
244
+ 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.
205
245
 
206
246
  ---
207
247
 
@@ -209,6 +249,6 @@ curl -sS -X POST -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: applicati
209
249
 
210
250
  - **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.
211
251
  - **Apex CNAME** → API (above) or dashboard (`dashboard-guide.md`). Either flattens correctly.
212
- - **Pages deploy** → `wrangler` with a minted Pages-Edit token, per `hosting-sites.md`.
213
- - **D1 read/write** → `wrangler d1 execute --remote` with a minted D1-Edit token, per `d1-data-capture.md`.
214
- - **Inspecting account state** (which zones, which tunnels) → API `GET` with a read-scoped minted token, or the dashboard.
252
+ - **Pages deploy** → `wrangler` with the reused `<brand>-pages-d1` token, per `hosting-sites.md`.
253
+ - **D1 read/write** → `wrangler d1 execute --remote` with the reused D1-Edit token, per `d1-data-capture.md`.
254
+ - **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,47 +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
17
 
18
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.
19
19
 
20
20
  ---
21
21
 
22
- ## 0. Mint the Pages-+-D1 token
22
+ ## 0. Load (or mint once) the stable Pages-+-D1 token
23
23
 
24
- 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:
25
25
 
26
26
  ```bash
27
- set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads master + account id
28
- MINTED_PAGES_D1=$(curl -sS -X POST \
29
- -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
30
- -H "Content-Type: application/json" \
31
- "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
32
- --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'
33
38
  {
34
- "name": "ephemeral-pages-d1",
39
+ "name": "${SCOPE_NAME}",
35
40
  "policies": [{
36
41
  "effect": "allow",
37
- "resources": { "com.cloudflare.api.account.<accountId>": "*" },
42
+ "resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": "*" },
38
43
  "permission_groups": [
39
44
  { "id": "<pages-edit-permission-group-id>", "name": "Pages Write" },
40
45
  { "id": "<d1-edit-permission-group-id>", "name": "D1 Write" }
41
46
  ]
42
- }],
43
- "expires_on": "<iso8601-a-few-minutes-out>"
47
+ }]
44
48
  }
45
49
  JSON
46
- )
50
+ )
51
+ ( umask 077; printf '%s=%s\n' "${TOKEN_KEY}" "${PAGES_D1}" >> "${SECRETS_DIR}/cloudflare.env" )
52
+ fi
47
53
  ```
48
54
 
49
- 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.
50
56
 
51
57
  ---
52
58
 
53
59
  ## 1. Create the database
54
60
 
55
61
  ```bash
56
- CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 create <db-name>
62
+ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler d1 create <db-name>
57
63
  # worked example: wrangler d1 create realagent-leads
58
64
  ```
59
65
 
@@ -78,7 +84,7 @@ database_id = "<database_id>" # from step 1
78
84
  Apply the schema to the remote database (`--remote` targets the live D1, not a local replica):
79
85
 
80
86
  ```bash
81
- 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 \
82
88
  "CREATE TABLE IF NOT EXISTS leads (
83
89
  id INTEGER PRIMARY KEY AUTOINCREMENT,
84
90
  name TEXT,
@@ -132,14 +138,14 @@ Deploy via `hosting-sites.md`. The binding in `wrangler.toml` is what wires the
132
138
  Read everything not yet ingested:
133
139
 
134
140
  ```bash
135
- 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 \
136
142
  "SELECT id, name, email, message, created_at FROM leads WHERE swept = 0 ORDER BY id;"
137
143
  ```
138
144
 
139
145
  After ingesting those rows (e.g. writing them into the graph), mark them swept so the next read returns only newer ones:
140
146
 
141
147
  ```bash
142
- 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 \
143
149
  "UPDATE leads SET swept = 1 WHERE swept = 0;"
144
150
  ```
145
151
 
@@ -152,7 +158,7 @@ D1 capture is done when a **test POST to the live form** appears in the unswept
152
158
  ```bash
153
159
  curl -sS -X POST -d "email=test@example.com&name=verify" "https://<project>.pages.dev/api/contact" -i | head -1
154
160
  # then:
155
- 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 \
156
162
  "SELECT email FROM leads WHERE swept = 0;"
157
163
  ```
158
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