@rubytech/create-realagent-code 0.1.23 → 0.1.26

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 (208) hide show
  1. package/dist/index.js +63 -16
  2. package/package.json +1 -1
  3. package/payload/platform/plugins/admin/PLUGIN.md +50 -23
  4. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +47 -0
  5. package/payload/platform/plugins/admin/skills/commitment-followthrough/SKILL.md +60 -0
  6. package/payload/platform/plugins/admin/skills/file-presentation/SKILL.md +67 -0
  7. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +111 -126
  8. package/payload/platform/plugins/admin/skills/session-management/SKILL.md +62 -0
  9. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +37 -0
  10. package/payload/platform/plugins/cloudflare/references/manual-setup.md +81 -1
  11. package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +241 -0
  12. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +267 -28
  13. package/payload/platform/plugins/cloudflare/scripts/tunnel-ingress.ts +291 -0
  14. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +42 -0
  15. package/payload/platform/plugins/contacts/PLUGIN.md +18 -9
  16. package/payload/platform/plugins/deep-research/.claude-plugin/plugin.json +1 -1
  17. package/payload/platform/plugins/deep-research/PLUGIN.md +7 -1
  18. package/payload/platform/plugins/deep-research/recipes/README.md +36 -0
  19. package/payload/platform/plugins/deep-research/skills/academic-verify/SKILL.md +75 -0
  20. package/payload/platform/plugins/deep-research/skills/book-mirror/SKILL.md +68 -0
  21. package/payload/platform/plugins/deep-research/skills/data-research/SKILL.md +108 -0
  22. package/payload/platform/plugins/deep-research/skills/strategic-reading/SKILL.md +69 -0
  23. package/payload/platform/plugins/docs/references/deployment.md +3 -2
  24. package/payload/platform/plugins/docs/references/platform.md +2 -0
  25. package/payload/platform/plugins/docs/references/troubleshooting.md +12 -0
  26. package/payload/platform/plugins/email/PLUGIN.md +18 -9
  27. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts +17 -0
  28. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts.map +1 -0
  29. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js +185 -0
  30. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js.map +1 -0
  31. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +1 -1
  32. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +34 -111
  33. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +1 -1
  34. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts +7 -2
  35. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts.map +1 -1
  36. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +7 -2
  37. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -1
  38. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -0
  39. package/payload/platform/plugins/memory/PLUGIN.md +64 -29
  40. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts +3 -1
  41. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -1
  42. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +105 -4
  43. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +16 -3
  46. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  47. package/payload/platform/plugins/memory/skills/archive-crawler/SKILL.md +67 -0
  48. package/payload/platform/plugins/memory/skills/concept-synthesis/SKILL.md +80 -0
  49. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +2 -0
  50. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +2 -0
  51. package/payload/platform/plugins/outlook/PLUGIN.md +14 -7
  52. package/payload/platform/plugins/replicate/PLUGIN.md +6 -3
  53. package/payload/platform/plugins/scheduling/PLUGIN.md +19 -8
  54. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +7 -3
  55. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts.map +1 -1
  56. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +7 -3
  57. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  58. package/payload/platform/plugins/scheduling/skills/briefing/SKILL.md +75 -0
  59. package/payload/platform/plugins/scheduling/skills/daily-prep/SKILL.md +61 -0
  60. package/payload/platform/plugins/tasks/PLUGIN.md +28 -14
  61. package/payload/platform/plugins/waitlist/PLUGIN.md +12 -6
  62. package/payload/platform/plugins/whatsapp/PLUGIN.md +25 -13
  63. package/payload/platform/plugins/workflows/PLUGIN.md +16 -8
  64. package/payload/platform/scripts/conversation-id-allowlist.txt +0 -1
  65. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  66. package/payload/platform/services/claude-session-manager/dist/http-server.js +27 -2
  67. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  68. package/payload/platform/services/claude-session-manager/dist/index.js +27 -0
  69. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  70. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +36 -0
  71. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  72. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +41 -3
  73. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  74. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +25 -1
  75. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  76. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +54 -3
  77. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  78. package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts +25 -0
  79. package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts.map +1 -0
  80. package/payload/platform/services/claude-session-manager/dist/tool-surface.js +149 -0
  81. package/payload/platform/services/claude-session-manager/dist/tool-surface.js.map +1 -0
  82. package/payload/platform/templates/agents/admin/IDENTITY.md +38 -284
  83. package/payload/platform/templates/agents/admin/SOUL.md +4 -4
  84. package/payload/platform/templates/specialists/agents/content-producer.md +24 -69
  85. package/payload/platform/templates/specialists/agents/database-operator.md +49 -155
  86. package/payload/platform/templates/specialists/agents/personal-assistant.md +27 -177
  87. package/payload/platform/templates/specialists/agents/project-manager.md +29 -96
  88. package/payload/platform/templates/specialists/agents/research-assistant.md +36 -78
  89. package/payload/premium-plugins/real-agency/agents/compliance.md +14 -0
  90. package/payload/premium-plugins/real-agency/agents/negotiator.md +22 -0
  91. package/payload/premium-plugins/real-agency/agents/valuer.md +16 -0
  92. package/payload/premium-plugins/real-agency/plugins/estate-business/.claude-plugin/plugin.json +1 -1
  93. package/payload/premium-plugins/real-agency/plugins/estate-business/PLUGIN.md +44 -13
  94. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/commission-calculator/SKILL.md +40 -0
  95. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/month-end-close/SKILL.md +69 -0
  96. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/payment-batch-stager/SKILL.md +42 -0
  97. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/period-reconciler/SKILL.md +42 -0
  98. package/payload/premium-plugins/real-agency/plugins/estate-sales/.claude-plugin/plugin.json +1 -1
  99. package/payload/premium-plugins/real-agency/plugins/estate-sales/PLUGIN.md +32 -13
  100. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/chase-progression/SKILL.md +107 -0
  101. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/risk-scorer/SKILL.md +42 -0
  102. package/payload/premium-plugins/real-agency/plugins/leads/.claude-plugin/plugin.json +1 -1
  103. package/payload/premium-plugins/real-agency/plugins/leads/PLUGIN.md +40 -10
  104. package/payload/premium-plugins/real-agency/plugins/leads/skills/chain-progression-tracker/SKILL.md +51 -0
  105. package/payload/premium-plugins/real-agency/plugins/leads/skills/diary-builder/SKILL.md +38 -0
  106. package/payload/premium-plugins/real-agency/plugins/leads/skills/enquiry-triage/SKILL.md +36 -0
  107. package/payload/premium-plugins/real-agency/plugins/leads/skills/morning-round/SKILL.md +72 -0
  108. package/payload/premium-plugins/real-agency/plugins/listings/.claude-plugin/plugin.json +1 -1
  109. package/payload/premium-plugins/real-agency/plugins/listings/PLUGIN.md +82 -12
  110. package/payload/premium-plugins/real-agency/plugins/listings/skills/comparable-finder/SKILL.md +52 -0
  111. package/payload/premium-plugins/real-agency/plugins/listings/skills/epc-checker/SKILL.md +38 -0
  112. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-copy-writer/SKILL.md +55 -0
  113. package/payload/premium-plugins/real-agency/plugins/listings/skills/local-market-stats/SKILL.md +33 -0
  114. package/payload/premium-plugins/real-agency/plugins/listings/skills/new-instruction/SKILL.md +78 -0
  115. package/payload/premium-plugins/real-agency/plugins/listings/skills/particulars-builder/SKILL.md +48 -0
  116. package/payload/premium-plugins/real-agency/plugins/listings/skills/portal-launch-scheduler/SKILL.md +49 -0
  117. package/payload/premium-plugins/real-agency/plugins/listings/skills/pricing-scenario-builder/SKILL.md +35 -0
  118. package/payload/premium-plugins/real-agency/plugins/listings/skills/supplier-booker/SKILL.md +39 -0
  119. package/payload/premium-plugins/real-agency/plugins/listings/skills/talk-track-composer/SKILL.md +36 -0
  120. package/payload/premium-plugins/real-agency/plugins/listings/skills/terms-of-business-drafter/SKILL.md +54 -0
  121. package/payload/premium-plugins/real-agency/plugins/listings/skills/valuation-prep/SKILL.md +69 -0
  122. package/payload/premium-plugins/real-agency/plugins/loop/PLUGIN.md +35 -0
  123. package/payload/premium-plugins/real-agency/plugins/loop/skills/compliance-flag-checker/SKILL.md +53 -0
  124. package/payload/premium-plugins/real-agency/plugins/loop/skills/priority-ranker/SKILL.md +40 -0
  125. package/payload/premium-plugins/real-agency/plugins/loop/skills/tone-matched-drafter/SKILL.md +53 -0
  126. package/payload/premium-plugins/real-agency/plugins/loop/skills/variance-narrator/SKILL.md +50 -0
  127. package/payload/premium-plugins/real-agency/plugins/loop/skills/vendor-research/SKILL.md +54 -0
  128. package/payload/server/{chunk-2ZNKHCQB.js → chunk-2MRZBQMH.js} +1 -1
  129. package/payload/server/{chunk-GPUCA2RQ.js → chunk-NL7QLVAD.js} +0 -192
  130. package/payload/server/{chunk-IDKWGLM5.js → chunk-YPZFYTYP.js} +1 -247
  131. package/payload/server/{cloudflare-task-tracker-LYI5BTYI.js → cloudflare-task-tracker-QVOGHKWV.js} +2 -2
  132. package/payload/server/maxy-edge.js +2 -2
  133. package/payload/server/package.json +0 -2
  134. package/payload/server/public/assets/{Checkbox-D1OQD43b.js → Checkbox-YIF0payo.js} +1 -1
  135. package/payload/server/public/assets/{admin-czNBxWor.js → admin-DW8IJcLc.js} +1 -1
  136. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-BcwgT80u.js → architectureDiagram-Q4EWVU46-Bz8mlxZZ.js} +1 -1
  137. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BMSyZUQA.js → blockDiagram-DXYQGD6D-DwV8Z8-i.js} +1 -1
  138. package/payload/server/public/assets/{brand-2cku8WFs.css → brand-DqiRNMlu.css} +1 -1
  139. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-DPRGY1jJ.js → c4Diagram-AHTNJAMY-DiUTejMp.js} +1 -1
  140. package/payload/server/public/assets/channel-PtVtoBEL.js +1 -0
  141. package/payload/server/public/assets/{chunk-336JU56O-B7oQ3g1c.js → chunk-336JU56O-4mHZpBXe.js} +2 -2
  142. package/payload/server/public/assets/{chunk-426QAEUC-C1P0yFXw.js → chunk-426QAEUC-Cbv0vrN9.js} +1 -1
  143. package/payload/server/public/assets/{chunk-4TB4RGXK-LI7kOJd0.js → chunk-4TB4RGXK-BvLhId_2.js} +1 -1
  144. package/payload/server/public/assets/{chunk-5FUZZQ4R-CXQRGTQE.js → chunk-5FUZZQ4R-bBafOTkw.js} +1 -1
  145. package/payload/server/public/assets/{chunk-5PVQY5BW-NSyzpXRy.js → chunk-5PVQY5BW-B0NqBKVy.js} +1 -1
  146. package/payload/server/public/assets/{chunk-EDXVE4YY-voNwxbDs.js → chunk-EDXVE4YY-CFd4SqI6.js} +1 -1
  147. package/payload/server/public/assets/{chunk-ENJZ2VHE-CMEMPzYY.js → chunk-ENJZ2VHE-ajf2sb6c.js} +1 -1
  148. package/payload/server/public/assets/{chunk-ICPOFSXX-hEbwu-pe.js → chunk-ICPOFSXX-pWg6bug7.js} +1 -1
  149. package/payload/server/public/assets/{chunk-OYMX7WX6-DxskDrLs.js → chunk-OYMX7WX6-OjEd-17c.js} +1 -1
  150. package/payload/server/public/assets/{chunk-U2HBQHQK-D7TKgUo0.js → chunk-U2HBQHQK-DbEFSPoh.js} +1 -1
  151. package/payload/server/public/assets/{chunk-X2U36JSP-BvPUQEPm.js → chunk-X2U36JSP-COdNwrBb.js} +1 -1
  152. package/payload/server/public/assets/{chunk-YZCP3GAM-BY-RWQUW.js → chunk-YZCP3GAM-CHMWuY9B.js} +1 -1
  153. package/payload/server/public/assets/{chunk-ZZ45TVLE-DZvOYDY6.js → chunk-ZZ45TVLE-B-uDLQOB.js} +1 -1
  154. package/payload/server/public/assets/classDiagram-6PBFFD2Q-RVH_SEhY.js +1 -0
  155. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Cm3rAb93.js +1 -0
  156. package/payload/server/public/assets/clone-BjY0Wzht.js +1 -0
  157. package/payload/server/public/assets/{dagre-KV5264BT-Cnj0mUZl.js → dagre-KV5264BT-CMEzmhIL.js} +1 -1
  158. package/payload/server/public/assets/{dagre-Bt-fpckL.js → dagre-bhIG_KnW.js} +1 -1
  159. package/payload/server/public/assets/data-K_kS__sL.js +1 -0
  160. package/payload/server/public/assets/{device-url-actions-Bjz3Xzbm.js → device-url-actions-AcOyLSeF.js} +1 -1
  161. package/payload/server/public/assets/{diagram-5BDNPKRD-DjLzvOlx.js → diagram-5BDNPKRD-6RIoQhIL.js} +1 -1
  162. package/payload/server/public/assets/{diagram-G4DWMVQ6-DTfuRd-T.js → diagram-G4DWMVQ6-BSp36TVv.js} +1 -1
  163. package/payload/server/public/assets/{diagram-MMDJMWI5-BaL2mCnx.js → diagram-MMDJMWI5-D54fo52D.js} +1 -1
  164. package/payload/server/public/assets/{diagram-TYMM5635-C5InWY5R.js → diagram-TYMM5635-CWL8z-Pq.js} +1 -1
  165. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DO7BXTpn.js → erDiagram-SMLLAGMA-AnnHBo3z.js} +1 -1
  166. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-DDdAKfLf.js → flowDiagram-DWJPFMVM-laWmBl5o.js} +1 -1
  167. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-arJD8Utm.js → ganttDiagram-T4ZO3ILL-B94ko8ie.js} +1 -1
  168. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-C55GH-OS.js → gitGraphDiagram-UUTBAWPF-DxzL1fxZ.js} +1 -1
  169. package/payload/server/public/assets/graph-DeEigyO_.js +1 -0
  170. package/payload/server/public/assets/graph-labels-C7I5QvNv.js +1 -0
  171. package/payload/server/public/assets/{graphlib-DL9PM7Ex.js → graphlib-CY-zIElM.js} +1 -1
  172. package/payload/server/public/assets/{infoDiagram-42DDH7IO-BMSGqUbG.js → infoDiagram-42DDH7IO-BMTajIIr.js} +1 -1
  173. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-Dw6BZ6BG.js → ishikawaDiagram-UXIWVN3A-B_QauE5O.js} +1 -1
  174. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DrywUGXw.js → journeyDiagram-VCZTEJTY-DmlqSIih.js} +1 -1
  175. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DuwtVBBc.js → kanban-definition-6JOO6SKY-ZGDQT7xB.js} +1 -1
  176. package/payload/server/public/assets/{line-JAksyKHj.js → line-D13opgep.js} +1 -1
  177. package/payload/server/public/assets/{mermaid-parser.core-BMq-ApBW.js → mermaid-parser.core-C650Sual.js} +1 -1
  178. package/payload/server/public/assets/{mermaid.core-tH4oX0Kh.js → mermaid.core-BqnQoXTp.js} +3 -3
  179. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-D1OiiJga.js → mindmap-definition-QFDTVHPH-BS_8y-tY.js} +1 -1
  180. package/payload/server/public/assets/{page-BZpoS7iR.js → page-B_rpjIRr.js} +1 -1
  181. package/payload/server/public/assets/{page-CkvBvezS.js → page-qSH972X0.js} +1 -1
  182. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Ckwm69PW.js → pieDiagram-DEJITSTG-B5OmNvBO.js} +1 -1
  183. package/payload/server/public/assets/{public-C-dTMgXu.js → public-DDsYgotk.js} +3 -3
  184. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-COw3yZ1j.js → quadrantDiagram-34T5L4WZ-DTYITdNo.js} +1 -1
  185. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DqGzM4K-.js → requirementDiagram-MS252O5E-CRZWxH06.js} +1 -1
  186. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D-l1c_Pl.js → sankeyDiagram-XADWPNL6-DazRENhe.js} +1 -1
  187. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BeIi0DtJ.js → sequenceDiagram-FGHM5R23-BcHTxmPy.js} +1 -1
  188. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-C-jgegLk.js → stateDiagram-FHFEXIEX-DYU7nbqg.js} +1 -1
  189. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BgljVtlp.js +1 -0
  190. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BGFKkYmi.js → timeline-definition-GMOUNBTQ-BKGmqkST.js} +1 -1
  191. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-5NuIhJLS.js → vennDiagram-DHZGUBPP-BXvLPmX7.js} +1 -1
  192. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-Be9ytVut.js → wardleyDiagram-NUSXRM2D-BCclUa3Z.js} +1 -1
  193. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DCyHg41R.js → xychartDiagram-5P7HB3ND-C-Xp-Eoc.js} +1 -1
  194. package/payload/server/public/data.html +5 -5
  195. package/payload/server/public/graph.html +6 -6
  196. package/payload/server/public/index.html +8 -8
  197. package/payload/server/public/public.html +5 -5
  198. package/payload/server/server.js +1152 -2564
  199. package/payload/platform/scripts/check-sdk-oauth.mjs +0 -185
  200. package/payload/server/public/assets/channel-fxEghWew.js +0 -1
  201. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BsWzGW0N.js +0 -1
  202. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BGVa3h90.js +0 -1
  203. package/payload/server/public/assets/clone-Khvocke2.js +0 -1
  204. package/payload/server/public/assets/data-DBd-Buhp.js +0 -1
  205. package/payload/server/public/assets/graph-DUtVdnZ6.js +0 -1
  206. package/payload/server/public/assets/graph-labels-Dxfue-fP.js +0 -1
  207. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BaMs8Znv.js +0 -1
  208. /package/payload/server/public/assets/{brand-CSQuxS9w.js → brand-Bm671owU.js} +0 -0
@@ -0,0 +1,291 @@
1
+ // Task 009 — Pure tunnel-ingress rendering + state I/O.
2
+ //
3
+ // Mirror of `samba-provision.ts`: pure decision functions in this file with
4
+ // no side effects; `setup-tunnel.sh` orchestrates I/O around them. Invoked
5
+ // from bash via `node --experimental-strip-types`. The CLI shim at the
6
+ // bottom of this file takes a JSON spec on argv and prints rendered YAML
7
+ // (or the requested state snippet) on stdout so the shell can `>` it into
8
+ // the brand's config.yml or tunnel.state.
9
+ //
10
+ // Why a pure module:
11
+ // - The existing setup-tunnel.sh rebuilds config.yml from scratch every
12
+ // run. Adding SSH and SMB conditionally inline doubles the rewrite
13
+ // logic and makes it untestable. Extracting to a pure function with a
14
+ // test grid locks the YAML shape against future drift.
15
+ // - tunnel.state needs additive sshHostname / smbHostname fields so a
16
+ // re-run with no env vars rehydrates the ingress instead of silently
17
+ // dropping it. The persistence shape lives here so the test grid
18
+ // covers round-trip parity.
19
+ //
20
+ // Out of scope:
21
+ // - Cloudflare API calls (banned per `feedback_cf_api_total_eradication`).
22
+ // - Access policy creation (operator authors it in the dashboard).
23
+ // - DNS routing (the bash wrapper invokes `cloudflared tunnel route dns`).
24
+
25
+ import { readFileSync, existsSync, appendFileSync } from "node:fs";
26
+
27
+ // Task 054 — STREAM_LOG_PATH writer (Task 598/600 contract). The four CLI
28
+ // subcommands invoked from setup-tunnel.sh (probe-samba, render-config,
29
+ // render-state, action-req) emit a `phase=<cmd>-start` line on entry and
30
+ // `phase=<cmd>-ok` / `phase=<cmd>-fail` on exit. Pattern ported verbatim
31
+ // from list-cf-domains.ts:94-133: sticky-flag on first write failure to
32
+ // avoid stderr spam if STREAM_LOG_PATH is unwritable; observability
33
+ // failure must not mask the subcommand's real signal.
34
+ let streamLogWriteFailed = false;
35
+
36
+ function logPhase(line: string): void {
37
+ const streamLogPath = process.env.STREAM_LOG_PATH;
38
+ if (!streamLogPath || streamLogWriteFailed) return;
39
+ try {
40
+ appendFileSync(
41
+ streamLogPath,
42
+ `[${new Date().toISOString()}] [script:tunnel-ingress] ${line}\n`,
43
+ );
44
+ } catch (err) {
45
+ streamLogWriteFailed = true;
46
+ const detail = (err instanceof Error ? err.message : String(err))
47
+ .slice(0, 200)
48
+ .replace(/"/g, "'");
49
+ process.stderr.write(
50
+ `[tunnel-ingress] phase=stream-log-write-failed detail="${detail}"\n`,
51
+ );
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Types
57
+ // ---------------------------------------------------------------------------
58
+
59
+ export interface IngressSpec {
60
+ tunnelId: string;
61
+ credentialsPath: string;
62
+ httpPort: number;
63
+ httpHostnames: string[];
64
+ sshHostname?: string | null;
65
+ smbHostname?: string | null;
66
+ }
67
+
68
+ export interface TunnelState {
69
+ tunnelId: string;
70
+ tunnelName: string;
71
+ domain: string;
72
+ configPath: string;
73
+ credentialsPath: string;
74
+ sshHostname?: string | null;
75
+ smbHostname?: string | null;
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Pure renderer
80
+ // ---------------------------------------------------------------------------
81
+
82
+ /**
83
+ * Render config.yml from a fully-resolved IngressSpec. Order matters:
84
+ * cloudflared matches ingress rules top-to-bottom, so the catch-all
85
+ * `http_status:404` MUST be last. SSH and SMB are inserted between the
86
+ * HTTPS hostnames and the catch-all — they carry non-HTTP services, so
87
+ * placing them after HTTPS keeps the existing HTTP routing behaviour
88
+ * unchanged when no SSH/SMB hostname is configured.
89
+ */
90
+ export function renderConfigYml(spec: IngressSpec): string {
91
+ const lines: string[] = [];
92
+ lines.push(`tunnel: ${spec.tunnelId}`);
93
+ lines.push(`credentials-file: ${spec.credentialsPath}`);
94
+ lines.push(`ingress:`);
95
+ for (const h of spec.httpHostnames) {
96
+ lines.push(` - hostname: ${h}`);
97
+ lines.push(` service: http://localhost:${spec.httpPort}`);
98
+ }
99
+ if (spec.sshHostname) {
100
+ lines.push(` - hostname: ${spec.sshHostname}`);
101
+ lines.push(` service: ssh://localhost:22`);
102
+ }
103
+ if (spec.smbHostname) {
104
+ lines.push(` - hostname: ${spec.smbHostname}`);
105
+ lines.push(` service: tcp://localhost:445`);
106
+ }
107
+ lines.push(` - service: http_status:404`);
108
+ lines.push(``);
109
+ return lines.join("\n");
110
+ }
111
+
112
+ /**
113
+ * Render the tunnel.state JSON, preserving SSH/SMB hostnames as additive
114
+ * fields so re-runs can rehydrate them. The shape stays a superset of the
115
+ * pre-Task-009 JSON (tunnelId, tunnelName, domain, configPath,
116
+ * credentialsPath) — consumers that don't know about ssh/smb keep working.
117
+ */
118
+ export function renderTunnelState(state: TunnelState): string {
119
+ const out: Record<string, string> = {
120
+ tunnelId: state.tunnelId,
121
+ tunnelName: state.tunnelName,
122
+ domain: state.domain,
123
+ configPath: state.configPath,
124
+ credentialsPath: state.credentialsPath,
125
+ };
126
+ if (state.sshHostname) out.sshHostname = state.sshHostname;
127
+ if (state.smbHostname) out.smbHostname = state.smbHostname;
128
+ return JSON.stringify(out, null, 2) + "\n";
129
+ }
130
+
131
+ /**
132
+ * Read an existing tunnel.state from disk, returning the persisted
133
+ * sshHostname / smbHostname if present. Used to rehydrate on re-runs
134
+ * where the operator did not pass the env vars again. Missing file or
135
+ * malformed JSON returns nulls — the caller decides whether to fail.
136
+ */
137
+ export function readPersistedHostnames(statePath: string): {
138
+ sshHostname: string | null;
139
+ smbHostname: string | null;
140
+ } {
141
+ if (!existsSync(statePath)) return { sshHostname: null, smbHostname: null };
142
+ try {
143
+ const raw = readFileSync(statePath, "utf8");
144
+ const parsed = JSON.parse(raw) as Partial<TunnelState>;
145
+ return {
146
+ sshHostname: typeof parsed.sshHostname === "string" ? parsed.sshHostname : null,
147
+ smbHostname: typeof parsed.smbHostname === "string" ? parsed.smbHostname : null,
148
+ };
149
+ } catch {
150
+ return { sshHostname: null, smbHostname: null };
151
+ }
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Samba presence probe
156
+ // ---------------------------------------------------------------------------
157
+
158
+ /**
159
+ * Probe whether Task 034's per-brand Samba stanza exists in /etc/samba/smb.conf.
160
+ * The brand stanza header is `[<brand>]` at the start of a line (Task 034's
161
+ * `renderBrandStanza` output). Source of truth is the Pi filesystem, not
162
+ * brand.json — Samba is provisioned by the installer's post-install step,
163
+ * not declared up-front.
164
+ */
165
+ export function probeSambaStanza(brand: string, smbConfPath = "/etc/samba/smb.conf"): boolean {
166
+ if (!existsSync(smbConfPath)) return false;
167
+ try {
168
+ const raw = readFileSync(smbConfPath, "utf8");
169
+ const re = new RegExp(`^\\[${escapeRegex(brand)}\\]\\s*$`, "m");
170
+ return re.test(raw);
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+
176
+ function escapeRegex(s: string): string {
177
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // ACTION REQUIRED block
182
+ // ---------------------------------------------------------------------------
183
+
184
+ /**
185
+ * Render the dashboard click-path the operator follows to author the Access
186
+ * policy. Same shape as the existing apex-CNAME ACTION REQUIRED block in
187
+ * setup-tunnel.sh. The script does NOT create the policy — Cloudflare API
188
+ * is banned (`feedback_cf_api_total_eradication`) and `cloudflared` CLI
189
+ * has no Access-application create subcommand. The operator clicks through
190
+ * Zero Trust → Access → Applications → Add → Self-hosted.
191
+ */
192
+ export function renderAccessPolicyActionRequired(input: {
193
+ sshHostname?: string | null;
194
+ smbHostname?: string | null;
195
+ operatorEmail: string;
196
+ }): string {
197
+ const hosts: Array<{ kind: "SSH" | "SMB"; hostname: string }> = [];
198
+ if (input.sshHostname) hosts.push({ kind: "SSH", hostname: input.sshHostname });
199
+ if (input.smbHostname) hosts.push({ kind: "SMB", hostname: input.smbHostname });
200
+ if (hosts.length === 0) return "";
201
+
202
+ const lines: string[] = [];
203
+ lines.push("");
204
+ lines.push("============================================================");
205
+ lines.push("ACTION REQUIRED — Cloudflare Zero Trust Access policy");
206
+ lines.push("============================================================");
207
+ for (const { kind, hostname } of hosts) {
208
+ lines.push(` ${kind}: ${hostname}`);
209
+ lines.push(` Cloudflare dashboard → Zero Trust → Access → Applications`);
210
+ lines.push(` Add → Self-hosted`);
211
+ lines.push(` Application name: ${hostname}`);
212
+ lines.push(` Application domain: ${hostname}`);
213
+ lines.push(` Policy → Action: Allow`);
214
+ lines.push(` Include → Emails: ${input.operatorEmail}`);
215
+ lines.push("");
216
+ }
217
+ lines.push("Until each Access application is created, off-LAN clients see");
218
+ lines.push("Cloudflare's identity-rejection page and bytes never reach the Pi.");
219
+ lines.push("============================================================");
220
+ return lines.join("\n");
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // CLI shim
225
+ // ---------------------------------------------------------------------------
226
+
227
+ // Bash invokes this file with one of three subcommands:
228
+ // render-config <spec.json> → stdout YAML
229
+ // render-state <state.json> → stdout JSON
230
+ // read-state <state-path> → stdout JSON {sshHostname,smbHostname}
231
+ // probe-samba <brand> [smb.conf] → exit 0 if present, 1 if absent
232
+ // action-req <input.json> → stdout ACTION REQUIRED block
233
+
234
+ function readJsonArg(path: string): unknown {
235
+ const raw = readFileSync(path, "utf8");
236
+ return JSON.parse(raw);
237
+ }
238
+
239
+ const isCli =
240
+ typeof process !== "undefined" &&
241
+ Array.isArray(process.argv) &&
242
+ process.argv[1] !== undefined &&
243
+ /tunnel-ingress\.(ts|js|mjs|mts)$/.test(process.argv[1]);
244
+
245
+ if (isCli) {
246
+ const [, , cmd, ...rest] = process.argv;
247
+ // Track which subcommands carry the Task 054 stream-log contract — for these
248
+ // we emit start/ok/fail phase pairs. Unknown subcommands and read-state are
249
+ // out of scope per the task (read-state has no setup-tunnel.sh caller line).
250
+ const instrumented = new Set(["probe-samba", "render-config", "render-state", "action-req"]);
251
+ if (instrumented.has(cmd)) logPhase(`phase=${cmd}-start`);
252
+ try {
253
+ if (cmd === "render-config") {
254
+ const spec = readJsonArg(rest[0]) as IngressSpec;
255
+ process.stdout.write(renderConfigYml(spec));
256
+ logPhase(`phase=${cmd}-ok`);
257
+ } else if (cmd === "render-state") {
258
+ const state = readJsonArg(rest[0]) as TunnelState;
259
+ process.stdout.write(renderTunnelState(state));
260
+ logPhase(`phase=${cmd}-ok`);
261
+ } else if (cmd === "read-state") {
262
+ const persisted = readPersistedHostnames(rest[0]);
263
+ process.stdout.write(JSON.stringify(persisted));
264
+ } else if (cmd === "probe-samba") {
265
+ const brand = rest[0];
266
+ const smbConf = rest[1] ?? "/etc/samba/smb.conf";
267
+ const present = probeSambaStanza(brand, smbConf);
268
+ logPhase(`phase=${cmd}-ok brand=${brand} present=${present}`);
269
+ process.exit(present ? 0 : 1);
270
+ } else if (cmd === "action-req") {
271
+ const input = readJsonArg(rest[0]) as {
272
+ sshHostname?: string | null;
273
+ smbHostname?: string | null;
274
+ operatorEmail: string;
275
+ };
276
+ process.stdout.write(renderAccessPolicyActionRequired(input));
277
+ logPhase(`phase=${cmd}-ok`);
278
+ } else {
279
+ process.stderr.write(`tunnel-ingress: unknown subcommand: ${cmd}\n`);
280
+ process.exit(2);
281
+ }
282
+ } catch (err) {
283
+ const msg = (err as Error).message;
284
+ if (instrumented.has(cmd)) {
285
+ const detail = msg.slice(0, 200).replace(/"/g, "'");
286
+ logPhase(`phase=${cmd}-fail error="${detail}"`);
287
+ }
288
+ process.stderr.write(`tunnel-ingress: ${msg}\n`);
289
+ process.exit(1);
290
+ }
291
+ }
@@ -46,6 +46,48 @@ Example ({{productName}} on `maxy.bot` with a public subdomain and the `maxy.cha
46
46
  ~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot maxy.chat
47
47
  ```
48
48
 
49
+ ### Optional SSH and SMB ingress (Task 009)
50
+
51
+ The same script also wires the off-LAN SSH and SMB ingress when the
52
+ corresponding hostnames are passed via environment variables (NOT positional
53
+ argv — the positional contract is preserved for the form/endpoint caller):
54
+
55
+ ```
56
+ SSH_HOSTNAME=ssh.maxy.bot \
57
+ SMB_HOSTNAME=smb.maxy.bot \
58
+ OPERATOR_EMAIL=joel@example.com \
59
+ ~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot
60
+ ```
61
+
62
+ Behaviour:
63
+
64
+ - HTTPS hostnames are routed and config.yml rewritten FIRST. SSH and SMB
65
+ DNS routes happen in a second pass; a failure on SSH or SMB emits
66
+ `[tunnel-install] {ssh,smb}-ingress-deferred` and leaves the HTTPS
67
+ ingress durable (no rollback, no `exit 1`).
68
+ - `SSH_HOSTNAME` adds an ingress entry `service: ssh://localhost:22`.
69
+ - `SMB_HOSTNAME` adds `service: tcp://localhost:445`, gated on Task 034's
70
+ Samba stanza being present in `/etc/samba/smb.conf`. Absent stanza →
71
+ `[tunnel-install] smb-ingress-skipped reason=samba-not-provisioned` and
72
+ the SMB pass is skipped entirely.
73
+ - Re-run with env vars unset rehydrates `sshHostname` / `smbHostname` from
74
+ `tunnel.state` so previously configured ingress is not silently dropped.
75
+ - The Cloudflare Zero Trust Access policy that gates these hostnames is
76
+ authored by the operator in the dashboard — the script prints an
77
+ `ACTION REQUIRED` click-path with the resolved hostnames and operator
78
+ email (CF API is banned per `feedback_cf_api_total_eradication`). Phase
79
+ lines: `[tunnel-install] ssh-access-policy-required` and
80
+ `[tunnel-install] smb-access-policy-required` (named `-required`, not
81
+ `-set`, because the script does not create the policy).
82
+ - Dry-run: `SETUP_TUNNEL_DRY_RUN=1` short-circuits before any cloudflared
83
+ mutation and prints the rendered `config.yml` and `tunnel.state` so the
84
+ operator can preview changes.
85
+
86
+ The YAML and JSON rendering live in a pure Node helper at
87
+ `platform/plugins/cloudflare/scripts/tunnel-ingress.ts`, unit-tested under
88
+ `scripts/__tests__/tunnel-ingress.test.ts` and invoked from the shell via
89
+ `node --experimental-strip-types`.
90
+
49
91
  The agent does not invoke the script directly during onboarding — the endpoint does. The agent's responsibility is to render the form and relay the endpoint's script output verbatim when `_componentDone` arrives. If an `ACTION REQUIRED` block appears, quote it exactly — the operator needs the specific dashboard instructions it contains.
50
92
 
51
93
  ### When the script exits non-zero
@@ -2,15 +2,24 @@
2
2
  name: contacts
3
3
  description: "CRM contacts plugin. Provides contact-create, contact-lookup, contact-update, contact-delete, contact-list, contact-export, contact-erase, group-create, and group-manage tools for managing the customer contact graph and group conversations."
4
4
  tools:
5
- - contact-create
6
- - contact-lookup
7
- - contact-update
8
- - contact-delete
9
- - contact-list
10
- - contact-export
11
- - contact-erase
12
- - group-create
13
- - group-manage
5
+ - name: contact-create
6
+ publicAllowlist: false
7
+ - name: contact-lookup
8
+ publicAllowlist: false
9
+ - name: contact-update
10
+ publicAllowlist: false
11
+ - name: contact-delete
12
+ publicAllowlist: false
13
+ - name: contact-list
14
+ publicAllowlist: false
15
+ - name: contact-export
16
+ publicAllowlist: false
17
+ - name: contact-erase
18
+ publicAllowlist: false
19
+ - name: group-create
20
+ publicAllowlist: false
21
+ - name: group-manage
22
+ publicAllowlist: false
14
23
  always: false
15
24
  embed: false
16
25
  metadata: {"platform":{}}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deep-research",
3
- "description": "Structured multi-source research workflow query decomposition, search strategy, source evaluation, synthesis, and citation formatting. Uses Claude Code's native WebSearch and WebFetch tools.",
3
+ "description": "Structured multi-source research workflow: query decomposition, search strategy, source evaluation, synthesis, citation formatting, plus four named knowledge-worker skills (book-mirror, strategic-reading, academic-verify, data-research). Uses Claude Code's native WebSearch and WebFetch tools.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,7 +1,13 @@
1
1
  ---
2
2
  name: deep-research
3
- description: "Structured multi-source research workflow query decomposition, search strategy, source evaluation, synthesis, and citation formatting. Uses Claude Code's native WebSearch and WebFetch tools."
3
+ description: "Structured multi-source research workflow: query decomposition, search strategy, source evaluation, synthesis, citation formatting, plus four named knowledge-worker skills (book-mirror, strategic-reading, academic-verify, data-research). Uses Claude Code's native WebSearch and WebFetch tools."
4
4
  tools: []
5
+ skills:
6
+ - skills/deep-research/SKILL.md
7
+ - skills/book-mirror/SKILL.md
8
+ - skills/strategic-reading/SKILL.md
9
+ - skills/academic-verify/SKILL.md
10
+ - skills/data-research/SKILL.md
5
11
  always: false
6
12
  embed: false
7
13
  ---
@@ -0,0 +1,36 @@
1
+ # Recipes
2
+
3
+ Recipes for the `data-research` skill. Each recipe is a YAML file that declares the field-by-field shape of an extraction, the source kinds it accepts, the validations to apply, and the graph target.
4
+
5
+ A recipe is a contract. The owner reads the recipe and knows what `data-research` will write to the graph before it runs.
6
+
7
+ ## Recipe shape
8
+
9
+ ```yaml
10
+ name: <recipe-name>
11
+ description: One-line description of what this recipe extracts.
12
+ source_kinds:
13
+ - email-thread # one or more of: email-thread, web-page, pdf, text-file, form-submission
14
+ fields:
15
+ - name: <field name>
16
+ type: string | number | date | boolean
17
+ required: true | false
18
+ validation: # optional
19
+ min: <number>
20
+ max: <number>
21
+ pattern: <regex>
22
+ enum: [a, b, c]
23
+ graph_target:
24
+ label: <Node label that must exist in the live Neo4j ontology>
25
+ identity: # property names that together uniquely identify a row
26
+ - <field name>
27
+ - <field name>
28
+ relationships: # optional
29
+ - type: <RELATIONSHIP_TYPE>
30
+ target_label: <Node label>
31
+ target_match: <field name> # the field whose value resolves the target node via memory-search
32
+ ```
33
+
34
+ ## Updating a recipe
35
+
36
+ Recipes are part of the plugin and are committed alongside the plugin. To add or change a recipe, the owner names the extraction in conversation, the operator drafts the YAML, the owner reviews it, and the file is saved here. The `data-research` skill loads recipes by name at run-time; it does not author or modify them inline.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: academic-verify
3
+ description: "Traces an academic or scientific claim through the publication chain to a primary source, checks for retraction, and returns one of four verdicts: verified, contradicted, retracted, or cannot be confirmed. Triggers when the owner says 'verify this study', 'is this paper real', 'check the citation', 'is X actually peer-reviewed'."
4
+ ---
5
+
6
+ # Academic verify
7
+
8
+ This skill is for claims that arrive as "studies show", "research suggests", or a named paper title. It traces the claim to the actual paper, the actual journal, and the actual conclusion of the source. It never says "verified" without a primary source URL.
9
+
10
+ ## When to run
11
+
12
+ Run when the owner asks whether an academic or scientific claim is real. The trigger phrases are above. Also run when the owner repeats a strong claim attributed to "research" or "a study" and asks to check.
13
+
14
+ ## Inputs
15
+
16
+ `claim` (the statement to verify) and optionally `source` (the citation the owner has, if any: a paper title, DOI, author and year, journal name, or a URL).
17
+
18
+ If `claim` is missing, refuse. If `source` is missing, the skill will try to find the source from the claim wording.
19
+
20
+ ## Method
21
+
22
+ 1. **Find the originating paper.** If the owner provided a DOI or URL, fetch it. If they provided a paper title, search via `WebSearch` for the title in quotes, prioritising the journal's own site or a known repository (Google Scholar, PubMed, arXiv, OpenAlex). If they provided only the claim wording, search for the claim's distinctive phrasing in quotes.
23
+ 2. **Confirm the chain.** Pull these four facts from the source page:
24
+ - Paper title, authors, journal name, year.
25
+ - DOI (or arXiv ID, or PubMed ID).
26
+ - Peer-review status (peer-reviewed journal, preprint, working paper, conference abstract).
27
+ - The paper's own abstract or conclusion as written.
28
+ 3. **Check for retraction.** Search Retraction Watch and the journal's own page for a retraction notice. Capture the date and reason if found.
29
+ 4. **Cross-reference the claim against the paper.** Read the paper's abstract or conclusion. Compare the owner's claim wording to what the paper actually says. Decide one of four verdicts:
30
+ - **Verified.** The paper exists, is peer-reviewed (or clearly named as preprint), is not retracted, and the claim wording matches what the paper concludes.
31
+ - **Contradicted by source.** The paper exists but the claim wording overstates, reverses, or generalises beyond what the paper actually concludes.
32
+ - **Retracted.** The paper has been retracted. Name the date and reason.
33
+ - **Cannot be confirmed.** A search could not find a primary source, or the primary source is behind a paywall and only a secondary summary is available.
34
+ 5. **Return the verdict with the chain.** Verdict in the first sentence. Below it, the four facts from step 2. If retracted, the retraction details. If cannot-be-confirmed, name what was searched and where it stopped.
35
+
36
+ ## Output format
37
+
38
+ ```
39
+ **Verdict.** <verified | contradicted by source | retracted | cannot be confirmed>
40
+
41
+ **Source.**
42
+ - Title: <paper title>
43
+ - Authors: <list>
44
+ - Journal: <journal name>, <year>
45
+ - DOI: <DOI or other identifier>
46
+ - Peer-review status: <peer-reviewed | preprint | working paper | conference abstract>
47
+ - Primary source URL: <URL>
48
+
49
+ **The paper concludes.** <one to three sentences, paraphrased tightly or quoted verbatim if wording matters>
50
+
51
+ **The claim says.** <the owner's claim, verbatim>
52
+
53
+ **Match.** <one paragraph on whether the claim faithfully represents the paper's conclusion. If contradicted, name the specific divergence.>
54
+
55
+ (If retracted:)
56
+ **Retraction.** <date, reason, retraction notice URL>
57
+ ```
58
+
59
+ ## Discipline
60
+
61
+ - **Never say "verified" without a primary source URL in the output.** A claim is verified against the actual paper, not against a secondary summary.
62
+ - **Paywalled abstracts.** If the abstract is behind a paywall, the verdict is "cannot be confirmed". Surface the paywall, do not infer from training memory what the paper says.
63
+ - **Preprints.** A preprint is not the same as peer-reviewed. Surface the distinction explicitly; the owner may not want to act on a preprint result.
64
+ - **Press release vs paper.** Press releases routinely overstate the paper's conclusions. If the chain goes paper → press release → claim, the verdict is usually "contradicted by source"; name the divergence.
65
+ - **Author affiliation conflicts.** Surface known conflicts of interest from the paper's disclosure if present. Do not pretend they affect the verdict; they affect the owner's interpretation of the verdict.
66
+
67
+ ## Failure modes
68
+
69
+ - **Search returns no candidates.** Verdict is "cannot be confirmed". Report the search terms tried.
70
+ - **Multiple candidate papers.** Surface the top three and ask the owner to confirm which is the source.
71
+ - **Search returns a retraction watch entry but no journal page.** Use the retraction watch entry as the primary source; the verdict is "retracted".
72
+
73
+ ## What this skill does not do
74
+
75
+ It does not evaluate the paper's methodology or replicate the analysis. It checks whether the claim matches the source, not whether the source is correct. A flawed paper that is correctly cited returns "verified"; the owner is the one who decides whether the paper itself is sound.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: book-mirror
3
+ description: "Reads a book the owner provides (PDF, ePub, web link, or text), walks it chapter by chapter, and produces a personalised mirror that maps each chapter's claims to the owner's known context. Triggers when the owner says 'mirror this book', 'two-column analysis', 'how does this book apply to me', 'personalise this book against my business'."
4
+ ---
5
+
6
+ # Book mirror
7
+
8
+ This skill turns a book into a usable document by walking the chapters one at a time and asking, for each: "what does this say, and how does it apply to this specific owner". The output is a personalised pack the owner can read once and act on. It is grounded in the graph; the right-hand column is always specific to what the graph already knows about the owner.
9
+
10
+ ## When to run
11
+
12
+ Run when the owner asks for a book to be applied to their situation. The trigger phrases are above. Do not run when the owner asks for a simple summary; a summary is what `deep-research` produces, and that is the right skill for that intent.
13
+
14
+ ## Inputs
15
+
16
+ | Input | Meaning |
17
+ |---|---|
18
+ | `source` | The book. A path to a file in `$ACCOUNT_DIR`, a web URL, or pasted text. |
19
+ | `anchor` | A node in the graph the book is being mirrored against. Usually the `LocalBusiness` node; can also be a named `Project` or `Goal`. |
20
+
21
+ If the anchor is missing, ask for it in one sentence: "Mirror against the business, or a specific project?".
22
+
23
+ ## Method
24
+
25
+ 1. **Confirm the source.** If `source` is a file path, read it. If it is a URL, fetch it. If it is pasted text, work from the text directly. Reject sources that cannot be read; do not paraphrase a book the skill has not seen.
26
+ 2. **Identify the chapters.** Look for chapter headings in the text. If the book has none, segment by first-level headings or by every 2000 to 4000 words. Tell the owner the chapter count before starting.
27
+ 3. **For each chapter, build the two columns.**
28
+ - **Left.** A two-to-three-sentence summary of the chapter's actual claim. Quote verbatim only when the wording matters; otherwise paraphrase tightly.
29
+ - **Right.** A two-to-three-sentence note on how the claim applies to the anchor, grounded in graph specifics. Pull from `memory-search` against the anchor: the owner's business stage, recent obstacles, named projects, the LocalBusiness `businessType` schema. Name specific entities. Do not write "this applies to your business in general": that is jargon for "I have nothing specific to say".
30
+ - **Actions.** End each chapter with one or two concrete steps the owner could take this week. Use the imperative ("Call the photographer to confirm the brief for 14 Garth Road" rather than "Consider arranging photography").
31
+ 4. **Save the result.** Compose the whole pack as one `:KnowledgeDocument` with `:Section:Chapter` children per chapter. Link the document to the anchor node via `MIRRORS` (or `REFERENCES` if `MIRRORS` is not in the live ontology: call `maxy-graph-get_neo4j_schema` first).
32
+
33
+ ## Output format
34
+
35
+ ```
36
+ # Mirror: <book title> against <anchor name>
37
+
38
+ ## Chapter 1: <chapter title>
39
+
40
+ **The chapter.** <2-3 sentence summary>
41
+
42
+ **For <anchor>.** <2-3 sentence application, grounded in graph specifics>
43
+
44
+ **This week.**
45
+ - <one concrete action>
46
+ - <one concrete action>
47
+
48
+ ---
49
+
50
+ ## Chapter 2: ...
51
+ ```
52
+
53
+ Render the pack via `render-component name: document-editor` for owner review before saving. The owner can edit any chapter inline before the save. Once approved, write to the graph.
54
+
55
+ ## Length discipline
56
+
57
+ Each chapter must fit on one screen (roughly 30 lines). If a chapter's claims need more, split the application across two or three chapter mirrors; do not pad. The whole pack should be readable in one sitting; a 20-chapter book becomes a 60-page pack only if every chapter genuinely earns its application.
58
+
59
+ ## Failure modes
60
+
61
+ - **No chapters detectable and no headings.** Ask the owner to confirm the chunk size or to provide a chapter list.
62
+ - **Anchor has thin graph context** (LocalBusiness with no recent activity, Project with no Tasks). Surface the gap: "the graph has limited context for <anchor>. The right column will be general where it should be specific. Continue?". Wait for confirmation.
63
+ - **`memory-search` returns no useful results for the anchor**. Stop. The book cannot be mirrored against an empty anchor; tell the owner and recommend running `business-profile` or naming a different anchor.
64
+ - **Source fetch fails.** Surface the error literally. Do not summarise from training memory.
65
+
66
+ ## What this skill does not do
67
+
68
+ It does not summarise a book without an anchor; that is generic content, which `deep-research` can produce. It does not produce a study guide, a literature review, or a comparative essay. It produces one specific output: a personalised application of one book to one named entity in the graph.