@rubytech/create-maxy-code 0.1.277 → 0.1.279

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 (173) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +1 -1
  3. package/payload/platform/plugins/admin/skills/plainly/SKILL.md +33 -16
  4. package/payload/platform/plugins/admin/skills/plainly/references/worked-examples.md +51 -238
  5. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -1
  6. package/payload/platform/plugins/business-assistant/PLUGIN.md +4 -0
  7. package/payload/platform/plugins/business-assistant/references/crm.md +6 -0
  8. package/payload/platform/plugins/business-assistant/references/e-sign.md +302 -0
  9. package/payload/platform/plugins/business-assistant/references/site-lead-intake.md +52 -0
  10. package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -0
  11. package/payload/platform/plugins/cloudflare/references/api.md +17 -3
  12. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +10 -0
  13. package/payload/platform/plugins/cloudflare/references/web-analytics.md +64 -0
  14. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +1 -0
  15. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
  16. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +7 -0
  17. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  18. package/payload/platform/services/whatsapp-channel/dist/notification.js +22 -1
  19. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  20. package/payload/platform/templates/agents/admin/IDENTITY.md +2 -2
  21. package/payload/premium-plugins/.claude-plugin/marketplace.json +5 -0
  22. package/payload/premium-plugins/management-consulting/.claude-plugin/plugin.json +8 -0
  23. package/payload/premium-plugins/management-consulting/PLUGIN.md +92 -0
  24. package/payload/premium-plugins/management-consulting/skills/assumption-audit/SKILL.md +54 -0
  25. package/payload/premium-plugins/management-consulting/skills/business-case-builder/SKILL.md +56 -0
  26. package/payload/premium-plugins/management-consulting/skills/competitive-intel/SKILL.md +51 -0
  27. package/payload/premium-plugins/management-consulting/skills/customer-segmentation/SKILL.md +50 -0
  28. package/payload/premium-plugins/management-consulting/skills/decision-memo/SKILL.md +53 -0
  29. package/payload/premium-plugins/management-consulting/skills/growth-barriers/SKILL.md +54 -0
  30. package/payload/premium-plugins/management-consulting/skills/initiative-prioritizer/SKILL.md +51 -0
  31. package/payload/premium-plugins/management-consulting/skills/kpi-architect/SKILL.md +53 -0
  32. package/payload/premium-plugins/management-consulting/skills/market-mapping/SKILL.md +56 -0
  33. package/payload/premium-plugins/management-consulting/skills/narrative-builder/SKILL.md +53 -0
  34. package/payload/premium-plugins/management-consulting/skills/operating-model-design/SKILL.md +54 -0
  35. package/payload/premium-plugins/management-consulting/skills/portfolio-review/SKILL.md +52 -0
  36. package/payload/premium-plugins/management-consulting/skills/pricing-strategy/SKILL.md +55 -0
  37. package/payload/premium-plugins/management-consulting/skills/profit-pool-analysis/SKILL.md +52 -0
  38. package/payload/premium-plugins/management-consulting/skills/risk-and-mitigation/SKILL.md +49 -0
  39. package/payload/premium-plugins/management-consulting/skills/situation-assessment/SKILL.md +55 -0
  40. package/payload/premium-plugins/management-consulting/skills/stakeholder-alignment/SKILL.md +51 -0
  41. package/payload/premium-plugins/management-consulting/skills/strategic-options/SKILL.md +53 -0
  42. package/payload/premium-plugins/management-consulting/skills/transformation-roadmap/SKILL.md +53 -0
  43. package/payload/premium-plugins/management-consulting/skills/value-realization/SKILL.md +50 -0
  44. package/payload/premium-plugins/management-consulting/skills/war-gaming/SKILL.md +51 -0
  45. package/payload/server/public/assets/AdminShell-DnH3kou-.js +1 -0
  46. package/payload/server/public/assets/{Checkbox-BY2lndUH.js → Checkbox-2reCESkb.js} +1 -1
  47. package/payload/server/public/assets/{admin-C364q_-g.js → admin-xRCXEFNW.js} +1 -1
  48. package/payload/server/public/assets/{arc-BW1WhwmV.js → arc-BgelqC_3.js} +1 -1
  49. package/payload/server/public/assets/architecture-YZFGNWBL-YM-TtgPY.js +1 -0
  50. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CLhiy6Rc.js → architectureDiagram-Q4EWVU46-BabnAZJm.js} +1 -1
  51. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-ue7Vkc-Q.js → blockDiagram-DXYQGD6D-BJzZqNsy.js} +1 -1
  52. package/payload/server/public/assets/{browser-D66JrDpx.js → browser-DT2XfRpL.js} +1 -1
  53. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-dVjuUU_p.js → c4Diagram-AHTNJAMY-By-xuh9F.js} +1 -1
  54. package/payload/server/public/assets/channel-BRPH0atd.js +1 -0
  55. package/payload/server/public/assets/{chunk-2KRD3SAO-CKn-bEkI.js → chunk-2KRD3SAO-Gj2dSpBl.js} +1 -1
  56. package/payload/server/public/assets/{chunk-336JU56O-DLBJaAyL.js → chunk-336JU56O-2PXVPCUA.js} +2 -2
  57. package/payload/server/public/assets/chunk-426QAEUC-QhauxjvK.js +1 -0
  58. package/payload/server/public/assets/{chunk-4BX2VUAB-B2mSmC97.js → chunk-4BX2VUAB-9XIBlnC2.js} +1 -1
  59. package/payload/server/public/assets/{chunk-4TB4RGXK-DPHoq8nE.js → chunk-4TB4RGXK-Cr3mXPnI.js} +1 -1
  60. package/payload/server/public/assets/{chunk-55IACEB6-JLT9m7Hd.js → chunk-55IACEB6-Ccg46J3A.js} +1 -1
  61. package/payload/server/public/assets/{chunk-5FUZZQ4R-DjxdZ8WO.js → chunk-5FUZZQ4R-Cz9MvPKc.js} +1 -1
  62. package/payload/server/public/assets/{chunk-5PVQY5BW-BujJUZqf.js → chunk-5PVQY5BW-BleOdlgO.js} +1 -1
  63. package/payload/server/public/assets/{chunk-67CJDMHE-CCdP3Pdx.js → chunk-67CJDMHE-CyCYCQWy.js} +1 -1
  64. package/payload/server/public/assets/{chunk-7N4EOEYR-bXgVzdM9.js → chunk-7N4EOEYR-KpT3uHzH.js} +1 -1
  65. package/payload/server/public/assets/{chunk-AA7GKIK3-CYeDvY8B.js → chunk-AA7GKIK3-BfOzbuxq.js} +1 -1
  66. package/payload/server/public/assets/{chunk-BSJP7CBP-U0Fi-Ers.js → chunk-BSJP7CBP-Bq0M3wlv.js} +1 -1
  67. package/payload/server/public/assets/{chunk-CIAEETIT-C8QrV6Gg.js → chunk-CIAEETIT-CR-2dNaL.js} +1 -1
  68. package/payload/server/public/assets/{chunk-EDXVE4YY-DF2EBXie.js → chunk-EDXVE4YY-egUGMTOS.js} +1 -1
  69. package/payload/server/public/assets/{chunk-ENJZ2VHE-CoTooXpM.js → chunk-ENJZ2VHE-CQohBEFw.js} +1 -1
  70. package/payload/server/public/assets/{chunk-FMBD7UC4-DxAQS3UB.js → chunk-FMBD7UC4-DIxBUbLP.js} +1 -1
  71. package/payload/server/public/assets/{chunk-FOC6F5B3-CyIK3zeY.js → chunk-FOC6F5B3-C2694Zoq.js} +1 -1
  72. package/payload/server/public/assets/{chunk-ICPOFSXX-D8F-S9Bk.js → chunk-ICPOFSXX-DpXZKHyn.js} +2 -2
  73. package/payload/server/public/assets/{chunk-K5T4RW27-7GcDzjyJ.js → chunk-K5T4RW27-CUicG0Rp.js} +1 -1
  74. package/payload/server/public/assets/{chunk-KGLVRYIC-B7pAr9hf.js → chunk-KGLVRYIC-B5mPJhOr.js} +1 -1
  75. package/payload/server/public/assets/{chunk-LIHQZDEY-DEoU2jrz.js → chunk-LIHQZDEY-q81QhuMs.js} +1 -1
  76. package/payload/server/public/assets/{chunk-ORNJ4GCN-BwMFuN24.js → chunk-ORNJ4GCN-DgurdFZs.js} +1 -1
  77. package/payload/server/public/assets/{chunk-OYMX7WX6-C15Ps-tC.js → chunk-OYMX7WX6-CxqMRu2d.js} +1 -1
  78. package/payload/server/public/assets/chunk-QZHKN3VN-BDAQvtxz.js +1 -0
  79. package/payload/server/public/assets/{chunk-U2HBQHQK-BBPMxdAI.js → chunk-U2HBQHQK-BSLctJC9.js} +1 -1
  80. package/payload/server/public/assets/{chunk-X2U36JSP-xwRszlfe.js → chunk-X2U36JSP--0AUeeFl.js} +1 -1
  81. package/payload/server/public/assets/{chunk-XPW4576I-BA0a8Ygs.js → chunk-XPW4576I-D-hf9Bcn.js} +1 -1
  82. package/payload/server/public/assets/{chunk-YZCP3GAM-C4liAf7D.js → chunk-YZCP3GAM-PvOhSwI9.js} +1 -1
  83. package/payload/server/public/assets/{chunk-ZZ45TVLE-Cy3If_Rl.js → chunk-ZZ45TVLE-1zPUlNWz.js} +1 -1
  84. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BJv89PB6.js +1 -0
  85. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DNUszH5M.js +1 -0
  86. package/payload/server/public/assets/clone-CREg9jLM.js +1 -0
  87. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-Df_JmB8E.js → cose-bilkent-S5V4N54A-Cvo-gkBd.js} +1 -1
  88. package/payload/server/public/assets/{dagre-DKCdgCLD.js → dagre-CMHsNfjP.js} +1 -1
  89. package/payload/server/public/assets/{dagre-KV5264BT-DHZ3rA0O.js → dagre-KV5264BT-CM1DSx4E.js} +1 -1
  90. package/payload/server/public/assets/{data-uiOcPh8T.js → data-Dr7XUvAx.js} +1 -1
  91. package/payload/server/public/assets/{diagram-5BDNPKRD-XAci8Krv.js → diagram-5BDNPKRD-VDHiHwEI.js} +1 -1
  92. package/payload/server/public/assets/{diagram-G4DWMVQ6-DI6g5YJH.js → diagram-G4DWMVQ6-OKdufoeW.js} +1 -1
  93. package/payload/server/public/assets/{diagram-MMDJMWI5-D94oS42o.js → diagram-MMDJMWI5-Yg6QdCA1.js} +1 -1
  94. package/payload/server/public/assets/{diagram-TYMM5635-D_j5_oj_.js → diagram-TYMM5635-CpEMZQDu.js} +1 -1
  95. package/payload/server/public/assets/{dist-BmvZ8OaX.js → dist-811BIK0R.js} +1 -1
  96. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DPx8cHxF.js → erDiagram-SMLLAGMA-Dw5gAkFM.js} +1 -1
  97. package/payload/server/public/assets/{flatten-Bo6YRmWl.js → flatten-BsWEYbBB.js} +1 -1
  98. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-Cdfxz2_5.js → flowDiagram-DWJPFMVM-Cy_R1XHz.js} +1 -1
  99. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-oregwFNl.js → ganttDiagram-T4ZO3ILL-BYARP_eH.js} +1 -1
  100. package/payload/server/public/assets/gitGraph-7Q5UKJZL-CtOPqykB.js +1 -0
  101. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BZI26T0o.js → gitGraphDiagram-UUTBAWPF-BGOe0unY.js} +1 -1
  102. package/payload/server/public/assets/{graph-BSHyF267.js → graph-DOEjjQHD.js} +2 -2
  103. package/payload/server/public/assets/{graph-labels-D5ecmK-Z.js → graph-labels-eOrWYy0R.js} +1 -1
  104. package/payload/server/public/assets/{graphlib-BuiXJGQr.js → graphlib-DaefxCga.js} +1 -1
  105. package/payload/server/public/assets/info-OMHHGYJF-Jba5enA8.js +1 -0
  106. package/payload/server/public/assets/infoDiagram-42DDH7IO-Df9BlkPA.js +2 -0
  107. package/payload/server/public/assets/{isEmpty-D6Kr-M1M.js → isEmpty-BWl67LAZ.js} +1 -1
  108. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DnBy6cze.js → ishikawaDiagram-UXIWVN3A-YrGuzpiI.js} +1 -1
  109. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DeYkJsvd.js → journeyDiagram-VCZTEJTY-EIykOcqg.js} +1 -1
  110. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BHuZmXiA.js → kanban-definition-6JOO6SKY-BR0TC6Je.js} +1 -1
  111. package/payload/server/public/assets/{line-BAjS0lyG.js → line-DPGcT5IM.js} +1 -1
  112. package/payload/server/public/assets/{linear-COm19-p7.js → linear-CPN03uSh.js} +1 -1
  113. package/payload/server/public/assets/{mermaid-parser.core-Cg06vNXY.js → mermaid-parser.core-D7Iu4c9M.js} +2 -2
  114. package/payload/server/public/assets/{mermaid.core-DkUCZA6N.js → mermaid.core-krXRpXn9.js} +3 -3
  115. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-Gbaa7NRj.js → mindmap-definition-QFDTVHPH-HwiCtPzN.js} +1 -1
  116. package/payload/server/public/assets/{ordinal-BDi6f4xk.js → ordinal-krseTxxN.js} +1 -1
  117. package/payload/server/public/assets/packet-4T2RLAQJ-D_eWzeAP.js +1 -0
  118. package/payload/server/public/assets/pie-ZZUOXDRM-BDiy1Ob2.js +1 -0
  119. package/payload/server/public/assets/{pieDiagram-DEJITSTG-v7yUgLoq.js → pieDiagram-DEJITSTG-xodxyWLe.js} +1 -1
  120. package/payload/server/public/assets/{public-CTkj6UYK.js → public-xVaPcg6i.js} +3 -3
  121. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BpVlNbru.js → quadrantDiagram-34T5L4WZ-DuYYY4ZZ.js} +1 -1
  122. package/payload/server/public/assets/radar-PYXPWWZC-C5mlfmtg.js +1 -0
  123. package/payload/server/public/assets/{reduce-CGi9ik8i.js → reduce-tk-xY6Fv.js} +1 -1
  124. package/payload/server/public/assets/{requirementDiagram-MS252O5E-QrSZzaxF.js → requirementDiagram-MS252O5E-D0ta3kru.js} +1 -1
  125. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-IVgJCKl1.js → sankeyDiagram-XADWPNL6-KdHdlzvT.js} +1 -1
  126. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DsN4J_nq.js → sequenceDiagram-FGHM5R23-DoSFUTQZ.js} +1 -1
  127. package/payload/server/public/assets/{src-DLEIsjER.js → src-Cpl1JzME.js} +1 -1
  128. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DN0-8ZrI.js → stateDiagram-FHFEXIEX-BLW0KiSA.js} +1 -1
  129. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-C5zlCV4X.js +1 -0
  130. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BLYu380r.js → timeline-definition-GMOUNBTQ-8QAzhYz1.js} +1 -1
  131. package/payload/server/public/assets/treeView-SZITEDCU-CM-9m-cS.js +1 -0
  132. package/payload/server/public/assets/treemap-W4RFUUIX-DDHPB-Xh.js +1 -0
  133. package/payload/server/public/assets/{useSelectionMode-98jrB9kD.css → useSelectionMode-BnRcJeg2.css} +1 -1
  134. package/payload/server/public/assets/{useSelectionMode-80iYuGPa.js → useSelectionMode-DlJsX-_X.js} +1 -1
  135. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Tfn8qVAB.js → vennDiagram-DHZGUBPP-BKvfScOi.js} +1 -1
  136. package/payload/server/public/assets/wardley-RL74JXVD-CUb3Br4q.js +1 -0
  137. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-CeQNnKpT.js → wardleyDiagram-NUSXRM2D-DaMfDpJ3.js} +1 -1
  138. package/payload/server/public/assets/whatsapp-cDxgZkbM.js +1 -0
  139. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-H-IatOB_.js → xychartDiagram-5P7HB3ND-BIkNQlLa.js} +1 -1
  140. package/payload/server/public/browser.html +5 -5
  141. package/payload/server/public/data.html +6 -6
  142. package/payload/server/public/graph.html +7 -7
  143. package/payload/server/public/index.html +6 -6
  144. package/payload/server/public/public.html +5 -5
  145. package/payload/server/public/whatsapp.html +18 -0
  146. package/payload/server/server.js +1022 -800
  147. package/payload/server/public/assets/AdminShell-BcHJy_Y2.js +0 -1
  148. package/payload/server/public/assets/architecture-YZFGNWBL-xHbVjatf.js +0 -1
  149. package/payload/server/public/assets/channel-1FBBBcz1.js +0 -1
  150. package/payload/server/public/assets/chunk-426QAEUC-CfdaOTNb.js +0 -1
  151. package/payload/server/public/assets/chunk-QZHKN3VN-CHE_iZO7.js +0 -1
  152. package/payload/server/public/assets/classDiagram-6PBFFD2Q--mLJq--t.js +0 -1
  153. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-C8TJtMFc.js +0 -1
  154. package/payload/server/public/assets/clone-UUs4PDQ1.js +0 -1
  155. package/payload/server/public/assets/gitGraph-7Q5UKJZL-CmHdoS27.js +0 -1
  156. package/payload/server/public/assets/info-OMHHGYJF-DTA9msO-.js +0 -1
  157. package/payload/server/public/assets/infoDiagram-42DDH7IO-C9cKcMg3.js +0 -2
  158. package/payload/server/public/assets/packet-4T2RLAQJ-6GjqIKgu.js +0 -1
  159. package/payload/server/public/assets/pie-ZZUOXDRM-T72DJ5JR.js +0 -1
  160. package/payload/server/public/assets/radar-PYXPWWZC-x-6xW1dE.js +0 -1
  161. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DGDGYxGd.js +0 -1
  162. package/payload/server/public/assets/treeView-SZITEDCU-5-6wEryJ.js +0 -1
  163. package/payload/server/public/assets/treemap-W4RFUUIX-DF48Cc86.js +0 -1
  164. package/payload/server/public/assets/wardley-RL74JXVD-duKIa6Hq.js +0 -1
  165. /package/payload/server/public/assets/{_baseFor-Cam2PbSt.js → _baseFor-BHtDrjIo.js} +0 -0
  166. /package/payload/server/public/assets/{array-DYRGGQae.js → array-DetWRiSa.js} +0 -0
  167. /package/payload/server/public/assets/{chunk-CnGqDkHZ.js → chunk-CWOGyPgy.js} +0 -0
  168. /package/payload/server/public/assets/{cytoscape.esm-nWsJMTNI.js → cytoscape.esm-C9yNhe1u.js} +0 -0
  169. /package/payload/server/public/assets/{defaultLocale-Du1XY3Dp.js → defaultLocale-_WRwicXn.js} +0 -0
  170. /package/payload/server/public/assets/{init-B5BXBRcm.js → init-sTEcj9YX.js} +0 -0
  171. /package/payload/server/public/assets/{katex-HOUACuRw.js → katex-s61Rgv6l.js} +0 -0
  172. /package/payload/server/public/assets/{path-CNO468J-.js → path-B0Ik7Tu9.js} +0 -0
  173. /package/payload/server/public/assets/{rough.esm-DRO6hWPh.js → rough.esm-DKRO8IF-.js} +0 -0
@@ -0,0 +1,302 @@
1
+ # E-signatures — signing any hosted document (form → D1 → PDF + email)
2
+
3
+ How to put a legally-recordable signature on **any** document the business hosts — a quote, a contract, a proposal, a set of terms — without DocuSign. The document is deployed to **Cloudflare Pages** with a signing form as its final section; the signer submits; a Pages Function writes the acceptance to **Cloudflare D1**; the agent later sweeps the row, renders the signed content to PDF, and emails both parties a confirmation.
4
+
5
+ This reference owns **only the signature**: the form, the capture, the PDF, and the dispatch. The document's body — what is actually being signed — is whatever flow built it (a quotation plugin, a hand-written contract, anything). This begins at *"a built document needs a signature."*
6
+
7
+ It composes two patterns you must read alongside it:
8
+
9
+ - **`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 reference does not repeat it.
10
+ - **`business-assistant/references/invoicing.md`** — PDF generation via `browser-navigate` + `browser-pdf-save` (its step 4). The signed-content PDF uses that exact pattern.
11
+
12
+ Auth for every `wrangler`/API call is a minted narrow token per `cloudflare/references/api.md`. Deploy mechanics are `cloudflare/references/hosting-sites.md`.
13
+
14
+ ---
15
+
16
+ ## 0. Pre-flight — block on either prerequisite
17
+
18
+ Do **not** deploy until both pass. Each failure has one exact remediation; stop and give it.
19
+
20
+ 1. **Email must be configured.** Call `email-status`. If it does not report a configured inbox, stop:
21
+ > "Email isn't set up yet. Run `email-setup` first, then ask me again."
22
+
23
+ Dispatch (§ 6) sends through `email-send`; without a configured inbox the acceptance is captured but never delivered.
24
+
25
+ 2. **The Cloudflare master token must exist.** With `SECRETS_DIR` per `api.md` (`~/<brand>-code/data/accounts/<accountId>/secrets`):
26
+
27
+ ```bash
28
+ ( test -s "${SECRETS_DIR}/cloudflare.env" && grep -q '^CLOUDFLARE_API_TOKEN=' "${SECRETS_DIR}/cloudflare.env" ) \
29
+ && echo "ok" || echo "missing"
30
+ ```
31
+
32
+ On `missing`, stop:
33
+ > "There's no Cloudflare master token. Provision it once per `cloudflare/references/api.md` § Provisioning the master token, then ask me again."
34
+
35
+ ---
36
+
37
+ ## 1. The signable document
38
+
39
+ A self-contained HTML document — all styles inline, no external resources — deployed to **Cloudflare Pages**, not the local platform server. Pages is required because only a Pages Function can receive the acceptance POST; a page served off the platform device has nowhere to write the row.
40
+
41
+ - **Slug carries a random hex suffix** so the URL is unguessable: `quote-lakeside-7f3a9c.html` (or a project whose name carries the suffix). Mint the suffix once: `openssl rand -hex 3`.
42
+ - **Page carries `<meta name="robots" content="noindex,nofollow">`** in `<head>` — a signable document is private, never indexed.
43
+ - **Assign a `DOC_REF`** — a short stable identifier for this one document, e.g. `QUOTE-LAKESIDE`. It keys the acceptance row and seeds the token. It is per-document and **never reused for changed terms** (see immutability, below).
44
+
45
+ **Immutability — signed content must not change.** Once an acceptance is recorded against a `DOC_REF`, the deployed document **must not be edited**. The PDF that is dispatched is rendered from the live page, so any later edit silently changes what the records claim was signed. Re-issuing changed terms is a **new** document with a **new** `DOC_REF` and a fresh deploy — never an edit to the signed one. (Failure-mode detail: § 8.3.)
46
+
47
+ ---
48
+
49
+ ## 2. The signing form (final section of the document)
50
+
51
+ The form is the document's last section. It carries three fixed controls plus whatever the operator's document needs:
52
+
53
+ - **`name`** — required.
54
+ - **`email`** — required (this is where the signer's confirmation is sent).
55
+ - **An agreement checkbox** — required; the signer cannot submit without ticking it.
56
+ - **Operator-defined fields** — any extra inputs the specific document needs (company, project address, a quoted figure to confirm). These are **discovered from the document**, not fixed here; the client JS below sweeps every named field beyond the fixed three into `captured_json`.
57
+
58
+ On submit, client JS mints an **acceptance token** and POSTs JSON to the Pages Function. On `{"ok":true}` the form is replaced by a confirmation panel showing the token; on any failure the signer gets a pre-filled `mailto:` fallback so the acceptance is never lost.
59
+
60
+ **Acceptance token** — `<DOC_REF>-<5-char base36 timestamp>-<6-char djb2 hash>`. The hash is djb2 over `name + email + DOC_REF`: it binds the signer's identity to this document, so a UNIQUE collision on the token means *the same signer re-submitting the same document* — a genuine duplicate, not a clash between two signers.
61
+
62
+ The confirmation and fallback panels are built with `textContent` and DOM nodes, never `innerHTML` — the signer's own name and email flow back into the page, and `innerHTML` would make that an injection surface.
63
+
64
+ ```html
65
+ <section id="sign">
66
+ <h2>Accept &amp; sign</h2>
67
+ <form id="esign">
68
+ <input name="name" required placeholder="Full name">
69
+ <input name="email" type="email" required placeholder="Email">
70
+ <!-- operator-defined fields go here, e.g.:
71
+ <input name="company" placeholder="Company">
72
+ <input name="project_address" placeholder="Project address"> -->
73
+ <label><input type="checkbox" name="agree" required>
74
+ I have read and agree to the terms above.</label>
75
+ <button type="submit">Sign</button>
76
+ </form>
77
+ <div id="confirm" hidden></div>
78
+ </section>
79
+
80
+ <script>
81
+ const DOC_REF = "QUOTE-LAKESIDE"; // unique per document; never reused for changed terms
82
+ const ACCEPT_URL = "/api/accept";
83
+ const OWNER_MAILTO = "OWNER@EXAMPLE.COM"; // fallback recipient = the business's configured address
84
+
85
+ // 6-char base36 djb2 — deterministic over signer identity + document.
86
+ function djb2(s){let h=5381;for(let i=0;i<s.length;i++)h=((h<<5)+h+s.charCodeAt(i))>>>0;return h.toString(36).slice(-6).padStart(6,"0");}
87
+
88
+ document.getElementById("esign").addEventListener("submit", async (e) => {
89
+ e.preventDefault();
90
+ const f = e.target;
91
+ const name = f.name.value.trim();
92
+ const email = f.email.value.trim();
93
+ const ts = Date.now().toString(36).slice(-5);
94
+ const token = `${DOC_REF}-${ts}-${djb2(name + email + DOC_REF)}`;
95
+
96
+ // Everything beyond the fixed three fields is operator-defined; carry it verbatim.
97
+ const extra = {};
98
+ for (const el of f.elements) {
99
+ if (!el.name || ["name", "email", "agree"].includes(el.name)) continue;
100
+ extra[el.name] = el.type === "checkbox" ? el.checked : el.value;
101
+ }
102
+ const payload = { name, email, token, doc_ref: DOC_REF, ...extra };
103
+
104
+ const c = document.getElementById("confirm");
105
+ c.textContent = "";
106
+ try {
107
+ const r = await fetch(ACCEPT_URL, {
108
+ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),
109
+ });
110
+ const j = await r.json();
111
+ if (!j.ok) throw new Error(j.error || "rejected");
112
+ f.hidden = true; c.hidden = false;
113
+ const p = document.createElement("p");
114
+ const ref = document.createElement("strong");
115
+ ref.textContent = j.token;
116
+ p.append("Signed. Your acceptance reference is ", ref, ". A copy has been sent for processing.");
117
+ c.append(p);
118
+ } catch (err) {
119
+ // Function unreachable or insert failed — never drop the acceptance; offer the pre-filled mailto.
120
+ const subj = encodeURIComponent(`Acceptance: ${DOC_REF} (${token})`);
121
+ const body = encodeURIComponent(`I accept ${DOC_REF}.\nName: ${name}\nEmail: ${email}\nReference: ${token}`);
122
+ c.hidden = false;
123
+ const p = document.createElement("p");
124
+ const a = document.createElement("a");
125
+ a.href = `mailto:${OWNER_MAILTO}?subject=${subj}&body=${body}`;
126
+ a.textContent = "Send it by email instead";
127
+ p.append("We couldn't record your signature automatically. ", a, ".");
128
+ c.append(p);
129
+ }
130
+ });
131
+ </script>
132
+ ```
133
+
134
+ `OWNER_MAILTO` is the **business's own configured address** — read it from configured business identity, never invent one. It is the fallback path only; the primary path is the Function.
135
+
136
+ **PDF tip:** add `@media print { #sign { display: none } }` to the document's styles so the dispatched PDF carries the agreed content, not an empty form.
137
+
138
+ ---
139
+
140
+ ## 3. D1 table — one per brand, shared across that brand's documents
141
+
142
+ Follow `d1-data-capture.md` for creating the database, the `wrangler.toml` binding, and the both-Pages-Edit-**and**-D1-Edit token. The schema here is generic — it imposes no business fields; operator-defined fields live in `captured_json`:
143
+
144
+ ```bash
145
+ CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
146
+ "CREATE TABLE IF NOT EXISTS acceptances (
147
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
148
+ doc_ref TEXT NOT NULL,
149
+ name TEXT NOT NULL,
150
+ email TEXT NOT NULL,
151
+ token TEXT NOT NULL UNIQUE,
152
+ captured_json TEXT,
153
+ accepted_at TEXT NOT NULL DEFAULT (datetime('now')),
154
+ swept INTEGER NOT NULL DEFAULT 0
155
+ );"
156
+ ```
157
+
158
+ One `acceptances` table serves every document the brand signs; rows are separated by `doc_ref`. `token UNIQUE` is what makes a re-submit idempotent. `swept` is the dispatch cursor (§ 6).
159
+
160
+ ---
161
+
162
+ ## 4. The Pages Function — `functions/api/accept.ts`
163
+
164
+ Served at `POST /api/accept` by file path. It validates the four required fields, inserts, and handles the UNIQUE collision gracefully — an already-recorded token still returns `ok:true` so the signer never sees an error for signing twice. No secret lives in this file; the `DB` binding is injected at runtime.
165
+
166
+ ```ts
167
+ interface Env { DB: D1Database }
168
+
169
+ const cors = {
170
+ "Access-Control-Allow-Origin": "*",
171
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
172
+ "Access-Control-Allow-Headers": "Content-Type",
173
+ };
174
+ const json = (body: unknown, status = 200) =>
175
+ new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...cors } });
176
+
177
+ export const onRequestOptions: PagesFunction = async () => new Response(null, { status: 204, headers: cors });
178
+
179
+ export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => {
180
+ let p: any;
181
+ try { p = await request.json(); } catch { return json({ ok: false, error: "bad json" }, 400); }
182
+
183
+ const name = String(p.name ?? "").trim();
184
+ const email = String(p.email ?? "").trim();
185
+ const token = String(p.token ?? "").trim();
186
+ const docRef = String(p.doc_ref ?? "").trim();
187
+ if (!name || !email || !token || !docRef)
188
+ return json({ ok: false, error: "name, email, token, doc_ref required" }, 400);
189
+
190
+ // Everything beyond the four required fields is operator-defined; stash verbatim.
191
+ const { name: _n, email: _e, token: _t, doc_ref: _d, ...extra } = p;
192
+ const capturedJson = Object.keys(extra).length ? JSON.stringify(extra) : null;
193
+
194
+ try {
195
+ await env.DB
196
+ .prepare("INSERT INTO acceptances (doc_ref, name, email, token, captured_json) VALUES (?, ?, ?, ?, ?)")
197
+ .bind(docRef, name, email, token, capturedJson)
198
+ .run();
199
+ } catch (err) {
200
+ // UNIQUE(token) collision = same signer, same document, already recorded. Idempotent: still ok.
201
+ // D1 surfaces the constraint as the stable text "UNIQUE constraint failed"; match the phrase,
202
+ // not the bare word, so an unrelated error never gets swallowed as a duplicate.
203
+ if (String(err).includes("UNIQUE constraint failed")) return json({ ok: true, token, duplicate: true });
204
+ return json({ ok: false, error: "insert failed" }, 500);
205
+ }
206
+ return json({ ok: true, token });
207
+ };
208
+ ```
209
+
210
+ **The single most common breakage — token scope.** A Pages-only token deploys the document and renders the form, but **every POST silently 500s at the insert** because the Function can't write D1. The minted token must carry **both Pages Edit and D1 Edit** (`d1-data-capture.md` § 0). When acceptances stop arriving, this is the first thing to check.
211
+
212
+ ---
213
+
214
+ ## 5. Deploy
215
+
216
+ Deploy the document + Function via `hosting-sites.md`, with the both-Pages-Edit-and-D1-Edit token. The `[[d1_databases]]` binding in `wrangler.toml` is what wires the deployed Function to the `acceptances` database.
217
+
218
+ ---
219
+
220
+ ## 6. Sweep + dispatch
221
+
222
+ When the operator asks for new acceptances (there is no standing cron — see § 8.2 for the reconciliation a scheduled task would run). Two tokens are minted here, both per `api.md` § Minting a narrow token, resolving permission-group ids exactly as `d1-data-capture.md` § 0 does: `${MINTED_D1_READ}` carries only **D1 Read** (for the SELECTs), `${MINTED_D1_EDIT}` carries **D1 Edit** (for the per-row mark-swept). Minting read-scoped where only a read is needed keeps the edit token off every command that does not write.
223
+
224
+ 1. **Read the unswept rows** with `${MINTED_D1_READ}`:
225
+
226
+ ```bash
227
+ CLOUDFLARE_API_TOKEN="${MINTED_D1_READ}" wrangler d1 execute <db-name> --remote --json --command \
228
+ "SELECT id, doc_ref, name, email, token, captured_json, accepted_at
229
+ FROM acceptances WHERE swept = 0 ORDER BY id;"
230
+ ```
231
+
232
+ 2. **For each unswept row, in order — dispatch, then mark that one row swept:**
233
+ - **Render the signed content to PDF.** `browser-navigate` to the deployed document URL, then `browser-pdf-save` to an absolute path under the account directory — the `invoicing.md` step-4 pattern. The PDF is of the live page, which (immutability, § 1) is exactly what was signed.
234
+ - **Send twice via `email-send`**, each carrying `doc_ref`, `name`, `token`, `accepted_at`, and the PDF as an attachment:
235
+ - once to the **business owner's configured address** (read from configured business identity — never hard-code a recipient in this workflow);
236
+ - once to the **signer's captured `email`**.
237
+ - **Mark this row — and only this row — swept, once both sends return a verified result**, keyed by its `id`:
238
+
239
+ ```bash
240
+ CLOUDFLARE_API_TOKEN="${MINTED_D1_EDIT}" wrangler d1 execute <db-name> --remote --command \
241
+ "UPDATE acceptances SET swept = 1 WHERE id = <row-id>;"
242
+ ```
243
+
244
+ Never flip `swept` on assumption. If either `email-send` for a row fails, **skip its UPDATE** and move to the next row; that row stays `swept = 0` so the next sweep (or the reconciliation in § 8.2) re-surfaces it. A bulk `WHERE swept = 0` here is wrong — it would mark rows whose dispatch failed earlier in the same batch; the per-row `WHERE id = <row-id>` is what makes the `swept` flag track verified dispatch exactly.
245
+
246
+ ---
247
+
248
+ ## 7. Outcome contract
249
+
250
+ The acceptance flow is proven end-to-end on the **live** Pages deployment — the D1 row, not the POST status, is the contract:
251
+
252
+ ```bash
253
+ # 1. POST a test acceptance to the live Function.
254
+ curl -sS -X POST -H "Content-Type: application/json" \
255
+ -d '{"name":"verify","email":"test@example.com","token":"QUOTE-LAKESIDE-abcde-zzzzzz","doc_ref":"QUOTE-LAKESIDE"}' \
256
+ "https://<project>.pages.dev/api/accept"
257
+ # expect: {"ok":true,"token":"QUOTE-LAKESIDE-abcde-zzzzzz"}
258
+
259
+ # 2. The row exists in the unswept set. (A 200 with no row here is a FAILURE — see § 8.1.)
260
+ CLOUDFLARE_API_TOKEN="${MINTED_D1_READ}" wrangler d1 execute <db-name> --remote --command \
261
+ "SELECT token FROM acceptances WHERE swept = 0;"
262
+
263
+ # 3. POST the SAME token again → still {"ok":true}; the SELECT shows exactly one row (UNIQUE handled).
264
+
265
+ # 4. Run the sweep (§ 6): both the owner address and test@example.com hold the confirmation email,
266
+ # each with the signed-content PDF attached; re-running the SELECT WHERE swept = 0 no longer returns the row.
267
+ ```
268
+
269
+ Surface every step as verb + target ("inserting a test acceptance", "sweeping unswept rows from `<db-name>`"). **Token values are never printed** to chat or stdout outside the signer-facing confirmation panel.
270
+
271
+ ---
272
+
273
+ ## 8. Failure modes and the signal for each
274
+
275
+ 1. **POST silently 500s at the insert (token missing D1 Edit).** Signal fires *without reproducing end to end*: the test POST returns non-`ok`, and the confirming `SELECT WHERE swept = 0` shows no new row. Fix: re-mint the token confirming **both** Pages Edit and D1 Edit (`d1-data-capture.md` § 0). This is the first thing to check when acceptances stop arriving.
276
+
277
+ 2. **Acceptance recorded but never dispatched (no-event failure).** The sweep never ran, or `email-send` failed after the row existed — this emits no log, because no action was taken. Detection is a **standing reconciliation, not a hung log line**:
278
+
279
+ ```bash
280
+ CLOUDFLARE_API_TOKEN="${MINTED_D1_READ}" wrangler d1 execute <db-name> --remote --command \
281
+ "SELECT count(*), min(accepted_at) FROM acceptances WHERE swept = 0;"
282
+ ```
283
+
284
+ Any row whose `accepted_at` is older than the sweep cadence is an undispatched acceptance. Run this on a schedule (or each time acceptances are reviewed) and treat a non-empty aged result as the failure signal. Because § 6 flips `swept` only after both sends verify, a failed dispatch leaves the row here for the next pass.
285
+
286
+ 3. **Document modified after an acceptance (signed-content drift).** Signal: the dispatched PDF no longer matches what was signed. Detection without reproduction — record the deployed document's content hash at first acceptance; a later mismatch is the failure. The rule (§ 1) is absolute: changed terms require a **new** `doc_ref` and a fresh deploy, never an edit to a signed document.
287
+
288
+ 4. **Duplicate-acceptance row.** Signal: a `token` appears more than once — the UNIQUE handling regressed.
289
+
290
+ ```bash
291
+ CLOUDFLARE_API_TOKEN="${MINTED_D1_READ}" wrangler d1 execute <db-name> --remote --command \
292
+ "SELECT token, count(*) c FROM acceptances GROUP BY token HAVING c > 1;"
293
+ ```
294
+
295
+ Healthy signature: re-POSTing a token leaves its count at one.
296
+
297
+ **Ground-truth lifeline** for one document's whole acceptance lifecycle:
298
+
299
+ ```bash
300
+ CLOUDFLARE_API_TOKEN="${MINTED_D1_READ}" wrangler d1 execute <db-name> --remote --json --command \
301
+ "SELECT id, doc_ref, token, accepted_at, swept FROM acceptances WHERE doc_ref = 'QUOTE-LAKESIDE' ORDER BY id;"
302
+ ```
@@ -0,0 +1,52 @@
1
+ # Managing site form submissions (the published-site → D1 inbound channel)
2
+
3
+ The business's published website has a contact / enquiry form, and that form is a **primary inbound-enquiry channel** — equal to phone and WhatsApp, not a side channel. Submissions do not arrive as messages; they land as rows in a **Cloudflare D1** database (the leads DB created in `plugins/cloudflare/references/d1-data-capture.md`). Nothing surfaces them automatically. This reference is the capability: when the owner asks about new enquiries, read D1 first, then turn each new row into a graph contact routed to the right person.
4
+
5
+ The failure this prevents is concrete: asked "any new sign-ups?", an agent that searches memory or lists public-agent sessions will report "nothing new" while real leads sit unswept in D1. Memory and public-agent sessions are not where site enquiries live. **D1 is.**
6
+
7
+ ---
8
+
9
+ ## "Any new sign-ups / contacts / enquiries?" → read D1 first
10
+
11
+ When the owner asks whether anything new has come in — sign-ups, contacts, enquiries, leads — the **first action is a D1 read of the leads DB**, before `memory_search`, before `contact_lookup` sweeps, before any public-agent `session-list`. The unswept set is the answer:
12
+
13
+ ```bash
14
+ CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --json --command \
15
+ "SELECT id, name, email, message, created_at FROM leads WHERE swept = 0 ORDER BY id;"
16
+ ```
17
+
18
+ This composes `plugins/cloudflare/references/d1-data-capture.md` § 6 (read/sweep) and § 0 (minting the Pages-+-D1 token); the credential and token discipline there is binding. Before this `wrangler` call: run the load-credentials pre-flight from `plugins/cloudflare/references/api.md` (source the secrets file, assert `CLOUDFLARE_API_TOKEN` non-empty), mint the narrow Pages-+-D1 token, and confirm `${MINTED_PAGES_D1}` is non-empty — so the incident's `CLOUDFLARE_API_TOKEN not set` failure cannot reach the leads read. The DB name and the site's project are operator/install data, recorded where the site was set up — not guessed.
19
+
20
+ **Outcome contract for the answer:** the transcript shows a D1 round-trip (`wrangler d1 execute … SELECT … WHERE swept = 0`) as the response to "any new sign-ups?", not a memory search. A `memory_search` or public-agent `session-list` returned as the answer is the defect, not the answer.
21
+
22
+ ---
23
+
24
+ ## Each unswept row → dedupe, create-or-update, then route
25
+
26
+ For every row in the unswept set, in order:
27
+
28
+ 1. **Dedupe against the graph.** Look the person up by the identifiers the row actually carries. The canonical leads table (`plugins/cloudflare/references/d1-data-capture.md` § 3) holds `name, email, message` — so `contact_lookup` on the row's **email**, and on a **phone** only when the form captured one and the SELECT fetched it. Match the columns the form collects; do not look up a field the row does not carry. A match means update; no match means create.
29
+ 2. **Create or update the contact.** No match → `contact_create` with the name and identifiers from the row, `status: enquiry`, `source` naming the site form. Match → `contact_update` to add anything new (email/phone/message context) without clobbering existing fields. Write the enquiry detail into the contact's memory profile as in `references/crm.md`.
30
+ 3. **Route to the install's recorded front-line owner** (below).
31
+ 4. **Sweep the row only after the graph write is confirmed.** Flip `swept = 1` for the ingested rows once the contact create/update has returned successfully — never before. An unconfirmed write followed by a sweep loses the lead silently.
32
+
33
+ ```bash
34
+ CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
35
+ "UPDATE leads SET swept = 1 WHERE swept = 0;"
36
+ ```
37
+
38
+ A row is the durable record until it is swept; the sweep is the commit, and it follows the graph write, not the other way round.
39
+
40
+ ---
41
+
42
+ ## Front-line-owner routing is operator data, never a baked name
43
+
44
+ A new site lead is assigned to the install's **recorded front-line owner** — the person enquiries route to for first contact and follow-up. That ownership is **operator data**: it lives on the graph (a recorded property or relationship on the business/account) or, if not yet recorded, it is **asked of the operator**. It is never a name baked into this reference, and never guessed from context. Assigning a lead to whoever seems plausible — a back-office contact, a name seen earlier in the session — is the defect this rule prevents; an owner the operator then has to correct means the routing was guessed, not read.
45
+
46
+ If the front-line owner is not recorded on the install, ask the operator who new enquiries should go to and record it, then route. (Populating that data is an operator action; this reference states only the routing rule and where the data lives.)
47
+
48
+ ---
49
+
50
+ ## Why D1, not memory
51
+
52
+ Memory profiles and public-agent sessions describe people the business already talks to. A site enquiry is a **new** signal arriving through the website, captured by the form's Pages Function into D1 with a `swept` cursor precisely so "what's new" is a deterministic query rather than a guess. The whole point of the `swept = 0` set is that it is the authoritative, un-missable list of enquiries not yet brought into the business's records. Querying anything else first is how a real lead gets reported as "nothing new".
@@ -41,6 +41,7 @@ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is th
41
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. |
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
+ | [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. |
44
45
  | [dashboard-guide.md](references/dashboard-guide.md) | Click-paths for operations the operator prefers to do by hand — sign in, switch accounts, add a site, edit an apex CNAME, verify nameservers, delete a tunnel, manage CNAME records, author an Access policy. |
45
46
  | [reset-guide.md](references/reset-guide.md) | Decision tree for reset vs. patch, the exact `pkill` incantation for token-mode connectors, and the dashboard cleanup paths for stray records and rogue entries. |
46
47
 
@@ -57,6 +57,18 @@ Load the master for a mint call:
57
57
  set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
58
58
  ```
59
59
 
60
+ ### Load-credentials pre-flight (assert before any `wrangler` / `curl`)
61
+
62
+ Sourcing the file is not proof the token loaded — a missing file, a typo in the path, or an empty value all leave `CLOUDFLARE_API_TOKEN` unset, and the **next** `wrangler` / `curl` then fails with `CLOUDFLARE_API_TOKEN not set` *after* you have already issued the command. Make the load a **hard precondition**: source, then assert the token is non-empty, and stop if it is not. This block precedes every `wrangler d1`, `wrangler pages`, and Cloudflare-API `curl` in this reference and the ones that compose it:
63
+
64
+ ```bash
65
+ set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
66
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded — source the secrets file before any wrangler/curl}"
67
+ : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded — source the secrets file before any wrangler/curl}"
68
+ ```
69
+
70
+ The `:?` form aborts the shell with the given message when the variable is empty or unset, so a credential-unloaded call can never reach the API. The token value is never printed — only the abort message on failure.
71
+
60
72
  ---
61
73
 
62
74
  ## Provisioning the master token (ADVANCED — operator-guided, never automated)
@@ -75,12 +87,14 @@ This is the one manual, operator-driven step. The agent relays the click-path; i
75
87
  5. Click **Continue to summary**, then **Create Token**.
76
88
  6. Copy the token **once** — Cloudflare shows it a single time. Paste it into the secrets file (above). The agent stores it without echoing it back.
77
89
 
78
- Verify the master works (token value never printed — only the verification result):
90
+ Verify the master works (token value never printed — only the verification result). The master provisioned above is an **account-scoped** token (`cfat_…`, scoped to one account under **Account Resources**), so it verifies at the **account** endpoint, not the user endpoint. Verifying a `cfat_…` token at `/user/tokens/verify` returns `Invalid API Token` even when the token is valid:
79
91
 
80
92
  ```bash
81
93
  set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
94
+ : "${CLOUDFLARE_API_TOKEN:?credentials not loaded — source the secrets file first}"
95
+ : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded — source the secrets file first}"
82
96
  curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
83
- "https://api.cloudflare.com/client/v4/user/tokens/verify" | jq '.result.status'
97
+ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens/verify" | jq '.result.status'
84
98
  # expect: "active"
85
99
  ```
86
100
 
@@ -128,7 +142,7 @@ Look up `<pages-edit-permission-group-id>` (and the ids for DNS Edit, D1 Edit, e
128
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.
129
143
 
130
144
  ### Token create / verify
131
- - `GET /user/tokens/verify` — confirm a token is active.
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.
132
146
  - `POST /accounts/${ACC}/tokens` — mint a narrow token (see above).
133
147
  - `GET /accounts/${ACC}/tokens/permission_groups` — resolve permission-group ids for scoping.
134
148
 
@@ -4,6 +4,8 @@ The dashboard is the only surface for operations the CLI cannot perform: signing
4
4
 
5
5
  Anchor on the dashboard's text labels, not pixel positions. Cloudflare reshuffles its UI every few months; when a label has moved or been renamed, the operator will find the new location faster than any stale screenshot would help.
6
6
 
7
+ **Never fabricate a navigation path.** Only relay a click-path that appears verbatim in this file or another verified runbook. When the operator needs a dashboard step that is **not** in a verified path here — or when a relayed path turns out to be wrong ("No such menu") — do **not** invent menu names or guess where Cloudflare put it. Ask the operator what they see on their screen, or ask them to paste the snippet / value you need, and route from there. A guessed nav path that sends the operator hunting for a menu that does not exist is the failure this rule prevents; an honest "I don't have a verified path for that — what do you see under X?" is correct.
8
+
7
9
  ---
8
10
 
9
11
  ## Sign in and confirm which account
@@ -168,6 +170,14 @@ No server restart is needed. Verify with `curl -s https://<domain>/ | head` —
168
170
 
169
171
  ---
170
172
 
173
+ ## Web Analytics — registration is here, but collection is a beacon in the HTML
174
+
175
+ Cloudflare Web Analytics is configured in the dashboard, but registering a site there does **not** start collection — the beacon must be present in the served HTML. The full diagnosis and fix (why the RUM API is blocked, why registered ≠ collecting, why auto-injection is impossible for a Pages-only custom domain, and the inject-rebuild-redeploy path) lives in `references/web-analytics.md`. Read it before answering any "0 visitors / no analytics" question.
176
+
177
+ Because Cloudflare moves this area and the exact menu path varies by account, do not guess the click-path to the Web Analytics page. Ask the operator what they see, or have them paste the site's beacon snippet, then follow `references/web-analytics.md`.
178
+
179
+ ---
180
+
171
181
  ## Where tunnels live in the dashboard (as of 2026-04)
172
182
 
173
183
  Tunnels are under **Zero Trust** → **Networks** → **Tunnels**. This is not in the top-level sidebar — it is under the Zero Trust sub-dashboard, which itself appears in the main sidebar. Cloudflare has moved this location in the past. If **Networks** → **Tunnels** is not visible under Zero Trust, the label has likely been renamed; look for "Tunnels" under Networks or Access.
@@ -0,0 +1,64 @@
1
+ # Cloudflare Web Analytics — beacon-injected, not API-configured
2
+
3
+ Cloudflare Web Analytics is the privacy-first RUM (real-user-monitoring) product that counts visits, page views, and referrers for a site without cookies. On this install it is configured **in the dashboard** and collects data **only when the JS beacon is present in the served HTML**. This reference exists because a 0-visitor reading is almost always a missing beacon, not a registration problem — and because the API path most agents reach for first is structurally blocked here.
4
+
5
+ Pair this with `hosting-sites.md` (how the site is built and deployed) and `dashboard-guide.md` (the click-paths the operator runs when a dashboard step is genuinely required).
6
+
7
+ ---
8
+
9
+ ## The RUM site API is blocked without a RUM permission group
10
+
11
+ The `/accounts/{account_id}/rum/site_info` family of endpoints (list/create/edit RUM sites) requires an **Account · Web Analytics** permission group on the token. That permission group is **not among the master token's rows** provisioned in `api.md` § Provisioning (which owns the authoritative list). A minted token therefore cannot carry it, and every RUM API call returns auth error **10000 ("Authentication error")** regardless of which token is tried.
12
+
13
+ Do not chase 10000 by re-minting. Web Analytics site registration is a **dashboard-only** configuration step on this install: the operator creates/inspects the site under the dashboard, and the agent never drives it via the API. Treat the RUM API as unavailable, not as a token bug to fix.
14
+
15
+ ---
16
+
17
+ ## Registered ≠ collecting — 0 visitors means the beacon is missing
18
+
19
+ A site can show as **registered/enabled** in Web Analytics and still report **0 visitors**. Registration only allocates a site tag in the dashboard; it does not place anything on the page. Collection begins only when the beacon `<script>` is actually loaded by the browser, which requires the snippet to be present in the **served HTML**.
20
+
21
+ So when the operator asks "why are there no analytics / 0 visitors" on a site that is registered:
22
+
23
+ - The diagnosis is **not** "registration failed" and **not** "the dashboard is wrong".
24
+ - The diagnosis is "the beacon JS is not in the HTML". Confirm it directly against the live site:
25
+
26
+ ```bash
27
+ curl -s https://<host>/ | grep cloudflareinsights || echo "BEACON ABSENT"
28
+ ```
29
+
30
+ If `cloudflareinsights` does not appear, the beacon is missing and no amount of dashboard reconfiguration will start collection.
31
+
32
+ ---
33
+
34
+ ## Auto-injection needs a proxied DNS zone — Pages-only custom domains do not have one
35
+
36
+ Cloudflare can **auto-inject** the beacon at the edge, but only for hostnames served through a **Cloudflare-proxied DNS zone** (orange-cloud), where Cloudflare controls the HTML response on the way out. A **Pages-only custom domain** — e.g. a `*.pages.dev` project mapped to a custom domain that is **not** a CF-managed zone (such as `realagent.network`, which is Pages-only with no zone) — has no proxied zone in the path, so edge auto-injection is **structurally impossible**. There is no flag that turns it on; the prerequisite (a zone) does not exist.
37
+
38
+ Do not claim auto-injection "would work if the domain were a zone" as a fix — for a Pages-only custom domain the domain is not a zone, so the only path is manual injection into the site's own HTML.
39
+
40
+ ---
41
+
42
+ ## The working path: inject the beacon into the site HTML, rebuild, redeploy
43
+
44
+ For a Pages-only custom domain the beacon must be placed in the source HTML the site renders, then built and deployed:
45
+
46
+ 1. Get the site's beacon snippet from the dashboard (the `<script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token":"<site-tag>"}'></script>` block, with the site's own token).
47
+ 2. Add it to the rendered HTML — for a Next.js app, the document `<head>` in `app/layout.tsx` (or the framework's equivalent root layout) so every route carries it.
48
+ 3. Rebuild the site and `wrangler pages deploy` per `hosting-sites.md`.
49
+ 4. Confirm the beacon is live (outcome contract below).
50
+
51
+ This is exactly the fix applied to `realagent.network` in the session that sourced this reference: the snippet was added to `app/layout.tsx`, rebuilt, redeployed, and confirmed in the served HTML.
52
+
53
+ ---
54
+
55
+ ## Outcome contract
56
+
57
+ Web Analytics is collecting when the beacon appears in the **live served HTML**, not when the dashboard says "enabled":
58
+
59
+ ```bash
60
+ curl -s https://<host>/ | grep cloudflareinsights
61
+ # a match (the beacon.min.js script line) is the proof
62
+ ```
63
+
64
+ A registered site in the dashboard is **not** the contract; a present beacon in the response is. Visitor counts then appear in the dashboard after real traffic — first data is delayed, so the grep, not an immediate visitor number, is what closes the task.
@@ -15,6 +15,7 @@ This is the entry point for every Cloudflare task on the install. Pick the opera
15
15
  | Call the Cloudflare API (DNS, zones, tunnels, Access, token mint) | `references/api.md` | the API call returns success and the change is observable |
16
16
  | Deploy a static / Next.js site to Cloudflare Pages | `references/hosting-sites.md` | the deployed URL serves the new build |
17
17
  | Capture form/waitlist submissions into a database | `references/d1-data-capture.md` | a test POST appears in `SELECT … WHERE swept = 0` |
18
+ | 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`) |
18
19
 
19
20
  ## Two auth paths, by operation class
20
21
 
@@ -90,6 +90,7 @@ There is no per-account purchase record; the brand decides the shipping set.
90
90
  | `real-agent` | Bundle (13 sub-plugins) | UK estate agency skills — sales, listings, vendor management, buyer management, lead generation, coaching, business operations, teaching, Loop CRM (five value pillars: auto-respond, viewing lifecycle, pipeline mining, listings prospecting, maintenance & preferences), PropertyData market analytics (valuation, sold prices, £/sqft baselines, £/sqft growth, demand-rent, area risk, planning precedent, UPRN matching, property-type distribution), gov.uk EPC floor-area lookup, property brochures, social-share image cards, A4 market reports, and single-address preval packs (full UK address → 4-page A4 PDF covering valuation, area, and demand). 3 specialist roles (negotiator, valuer, compliance) | 4 sub-plugins (estate-sales, buyers, estate-coaching, estate-teaching) |
91
91
  | `writer-craft` | Skills + Agent | Manuscript review and writing craft — story architecture, reader engagement, prose craft, editorial practice, and multi-level review | No — writing craft serves the author |
92
92
  | `venture-studio` | Skills + Agent | Founding-a-business workflow — office-hours discovery, brand pack, zero-to-prototype validation, and the full investor data room (business plan, prospectus, term sheet, deck blueprint, A4 print pipeline). Pre-seeds a `Project` with one `Task` per artefact so nothing gets forgotten. | No — founder-facing only |
93
+ | `management-consulting` | Skills | 21 MBB-style strategy skills for client engagements — situation assessment, growth-barrier and assumption diagnosis, market mapping, competitive intel, segmentation, profit-pool analysis, strategic options, pricing, business cases, portfolio review, operating-model design, initiative prioritization, transformation roadmaps, war-gaming, risk registers, KPI design, value realization, stakeholder alignment, executive narrative, and decision memos. No MCP server. | No — consultant-facing only |
93
94
 
94
95
  **How it works:** Every boot {{productName}} delivers the brand's premium plugins from staging into `platform/plugins/` and stamps `enabledPlugins` against what is actually on disk. No conversation needed — the brand's full set is active from the first turn after install. Updates and reinstalls re-deliver from staging.
95
96
 
@@ -5,6 +5,13 @@ export interface InboundPayload {
5
5
  text: string;
6
6
  /** The WhatsApp message id, for dedup/observability. */
7
7
  waMessageId: string;
8
+ /** Task 694 — inbound non-audio media downloaded to /tmp/maxy-media/. The
9
+ * admin agent gets the path woven into the turn so it can Read the file.
10
+ * Audio is excluded by the weaver: its transcript already rode in as `text`
11
+ * (manager.ts STT step). Undefined when no media or for audio. */
12
+ mediaPath?: string;
13
+ mediaType?: 'image' | 'audio' | 'video' | 'document' | 'sticker';
14
+ mediaMimetype?: string;
8
15
  }
9
16
  /**
10
17
  * Shape an inbound WhatsApp payload as a Claude Code channel notification.
@@ -1 +1 @@
1
- {"version":3,"file":"notification.d.ts","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,cAAc;;;;;EAOzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvG;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAE3C;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAChF,OAAO,CAAC,OAAO,CAAC,CAMlB"}
1
+ {"version":3,"file":"notification.d.ts","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB;;;uEAGmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAA;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,cAAc;;;;;EAOzD;AAwBD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvG;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAE3C;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAChF,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -16,10 +16,31 @@ export function buildInboundNotification(p) {
16
16
  return {
17
17
  method: 'notifications/claude/channel',
18
18
  params: {
19
- content: p.text,
19
+ content: composeAdminContent(p),
20
20
  },
21
21
  };
22
22
  }
23
+ /**
24
+ * Task 694 — weave inbound non-audio media into the turn for the admin sender.
25
+ * Only admin senders reach the native channel (public WhatsApp was removed in
26
+ * Task 677 P2), so the only branch needed is the admin one: hand the agent the
27
+ * `/tmp` path and tell it to Read the file. Wording is kept identical to the
28
+ * legacy bridge's `composeTurn` admin branch (claude-bridge.ts) so behaviour
29
+ * matches archived Task 637. Audio is excluded: its transcript already arrived
30
+ * as `p.text`. Only the `/tmp` file path is added to Claude's context — never
31
+ * the sender number, preserving the data-minimisation invariant above.
32
+ */
33
+ function composeAdminContent(p) {
34
+ const hasFileMedia = !!p.mediaPath && p.mediaType !== 'audio';
35
+ if (!hasFileMedia)
36
+ return p.text;
37
+ const caption = p.text.trim();
38
+ const type = p.mediaType ?? 'file';
39
+ const note = `[Inbound WhatsApp ${type}: ${p.mediaPath}` +
40
+ `${p.mediaMimetype ? ` (${p.mediaMimetype})` : ''}. ` +
41
+ `Read this file to see what the sender shared.]`;
42
+ return caption ? `${caption}\n\n${note}` : note;
43
+ }
23
44
  /**
24
45
  * Defence in depth: this server serves exactly one sender. Even though the
25
46
  * gateway only streams that sender's messages, refuse anything else before
@@ -1 +1 @@
1
- {"version":3,"file":"notification.js","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AAWzE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,CAAiB;IACxD,OAAO;QACL,MAAM,EAAE,8BAAuC;QAC/C,MAAM,EAAE;YACN,OAAO,EAAE,CAAC,CAAC,IAAI;SAChB;KACF,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,CAAuB;IACvE,OAAO,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAA;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,IAAY;IACjE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAA;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,WAAmB;IAEnB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,CAAA;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAA+B,EAC/B,IAAiF;IAEjF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,OAAO,EAAE;YAAE,OAAO,IAAI,CAAA;QAChC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
1
+ {"version":3,"file":"notification.js","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AAkBzE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,CAAiB;IACxD,OAAO;QACL,MAAM,EAAE,8BAAuC;QAC/C,MAAM,EAAE;YACN,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;SAChC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,mBAAmB,CAAC,CAAiB;IAC5C,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,KAAK,OAAO,CAAA;IAC7D,IAAI,CAAC,YAAY;QAAE,OAAO,CAAC,CAAC,IAAI,CAAA;IAChC,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,MAAM,CAAA;IAClC,MAAM,IAAI,GACR,qBAAqB,IAAI,KAAK,CAAC,CAAC,SAAS,EAAE;QAC3C,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI;QACrD,gDAAgD,CAAA;IAClD,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,CAAuB;IACvE,OAAO,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAA;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,IAAY;IACjE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAA;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,WAAmB;IAEnB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,CAAA;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAA+B,EAC/B,IAAiF;IAEjF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,OAAO,EAAE;YAAE,OAAO,IAAI,CAAA;QAChC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -20,7 +20,7 @@ When a request needs a skill and you do not already know its exact slug, resolve
20
20
 
21
21
  You may not act on a request until you know what is being asked, what is in scope and what is out, and what rules apply. When the operator's words are precise, all three are obvious and you act. When any is imprecise, stop and ask. Insist on the operator being precise and concise.
22
22
 
23
- Your roster of installed specialists is loaded alongside this file as part of your first-turn reconnaissance. The test for any deliverable is whether an installed specialist owns its type, not whether you judge the work short or single-step. A request to author or produce a document, report, marketing or other public-facing copy, or a published page is owned by content-producer when it is installed; dispatch it with the Agent tool and the matching subagent_type, and never write it inline from this seat while content-producer is installed. Other deliverable types route the same way to whichever specialist's dispatch rule names them. Writing or editing files directly from this seat is reserved for work no installed specialist's dispatch rule covers.
23
+ Your roster of installed specialists is loaded alongside this file as part of your first-turn reconnaissance. The test for any deliverable is whether an installed specialist owns its type, not whether you judge the work short or single-step. A request to author or produce a document, report, marketing or other public-facing copy, or a published page is owned by content-producer when it is installed; dispatch it with the Agent tool and the matching subagent_type, and never write it inline from this seat while content-producer is installed. Ownership covers origination, not just the final render. You may not draft the copy here and then hand content-producer only the formatting, the voice pass, or the PDF step; the words themselves originate in the specialist. The brief you hand it states the outcome and the binding constraints, never the finished prose. Other deliverable types route the same way to whichever specialist's dispatch rule names them. Writing or editing files directly from this seat is reserved for work no installed specialist's dispatch rule covers.
24
24
 
25
25
  The canonical dispatch routes you should reach for first: graph writes (creating or trashing nodes, batch property changes across nodes) go to the specialist that owns graph writes; arbitrary file edits, shell work, and code changes go to coding-assistant or content-producer depending on whether the artifact is code or prose; web research and outside lookups go to research-assistant. The reason is context economy and tool specialization, not platform refusal; the platform stopped blocking these tools from this seat, so the bias has to come from you. A one-line property update against a node already in context, or a single Read against a file you need for routing the next dispatch, are the only in-context exceptions; everything more goes to a specialist.
26
26
 
@@ -62,7 +62,7 @@ The skill is the authority for the precision pass. The inline rules above under
62
62
 
63
63
  ## Writing in the operator's voice
64
64
 
65
- When prose will go out under the operator's name (a document, public-facing copy, anything they will send onward), the operator's voice profile is applied before the first sentence is written, not as a second pass after they object. Run `skill-load skillName=voice-mirror` to retrieve the profile and condition the draft on it. When you author the prose yourself, condition it before you return it; when a specialist authors it, the voice condition is part of the brief you hand them. This is the operator's own authorial voice, used for work written in their name; it is separate from your voice file above, which is how you sound when you speak as yourself.
65
+ When prose will go out under the operator's name (a document, public-facing copy, anything they will send onward), the operator's voice profile is applied before the first sentence is written, not as a second pass after they object. Run `skill-load skillName=voice-mirror` to retrieve the profile and condition the draft on it. When content-producer or another specialist owns the deliverable, the voice condition is part of the brief you hand them, never a draft you pre-write and pass on. Authoring the prose yourself and conditioning it before you return it is reserved for the case no installed specialist owns, the same boundary the dispatch rule above draws. This is the operator's own authorial voice, used for work written in their name; it is separate from your voice file above, which is how you sound when you speak as yourself.
66
66
 
67
67
  ## Access
68
68