@rubytech/create-maxy-code 0.1.108 → 0.1.110

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 (124) hide show
  1. package/dist/index.js +11 -125
  2. package/dist/snap-chromium.js +1 -2
  3. package/dist/uninstall.js +10 -9
  4. package/package.json +1 -1
  5. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  6. package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
  7. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +34 -0
  9. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/cloudflare/PLUGIN.md +9 -16
  11. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +7 -12
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -3
  14. package/payload/platform/plugins/cloudflare/references/manual-setup.md +16 -51
  15. package/payload/platform/plugins/cloudflare/references/reset-guide.md +24 -25
  16. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +29 -144
  17. package/payload/platform/plugins/docs/references/admin-session.md +1 -1
  18. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  19. package/payload/platform/plugins/docs/references/cloudflare.md +20 -29
  20. package/payload/platform/plugins/docs/references/platform.md +4 -22
  21. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  22. package/payload/platform/plugins/docs/references/troubleshooting.md +4 -316
  23. package/payload/platform/plugins/venture-studio/PLUGIN.md +35 -4
  24. package/payload/platform/plugins/venture-studio/bin/scaffold.sh +104 -0
  25. package/payload/platform/plugins/venture-studio/skills/investor-data-room/SKILL.md +3 -1
  26. package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.d.ts +2 -0
  27. package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.d.ts.map +1 -0
  28. package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.js +78 -0
  29. package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.js.map +1 -0
  30. package/payload/platform/scripts/check-no-task-id-leaks.mjs +1 -1
  31. package/payload/platform/scripts/vnc.sh +4 -3
  32. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  33. package/payload/premium-plugins/venture-studio/PLUGIN.md +35 -4
  34. package/payload/premium-plugins/venture-studio/bin/scaffold.sh +104 -0
  35. package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +3 -1
  36. package/payload/server/{chunk-AGFS3TVN.js → chunk-FSPPVVWM.js} +1303 -496
  37. package/payload/server/maxy-edge.js +21 -259
  38. package/payload/server/public/assets/{ChatInput-DJsqm_Gf.js → ChatInput-CsnIedhS.js} +1 -5
  39. package/payload/server/public/assets/{Checkbox-DGZG9BKc.js → Checkbox-CWugFyFT.js} +1 -1
  40. package/payload/server/public/assets/admin-D3gZuyUn.js +217 -0
  41. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Dw16BhiX.js → architectureDiagram-Q4EWVU46-CeTRKWDb.js} +1 -1
  42. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DIOpmf5Y.js → blockDiagram-DXYQGD6D-DeeIX5U3.js} +1 -1
  43. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Tdb_HZeX.js → c4Diagram-AHTNJAMY-CFsqZuil.js} +1 -1
  44. package/payload/server/public/assets/channel-CrSx5mnG.js +1 -0
  45. package/payload/server/public/assets/{chunk-336JU56O-CebpwDDe.js → chunk-336JU56O-DpIXuFM0.js} +2 -2
  46. package/payload/server/public/assets/{chunk-426QAEUC-BtRCmfDU.js → chunk-426QAEUC-Qk8qqrvA.js} +1 -1
  47. package/payload/server/public/assets/{chunk-4TB4RGXK-BZ3GEWs3.js → chunk-4TB4RGXK-DtM8-CUn.js} +1 -1
  48. package/payload/server/public/assets/{chunk-5FUZZQ4R-iDI6Xu0U.js → chunk-5FUZZQ4R-CrSQ4ySU.js} +1 -1
  49. package/payload/server/public/assets/{chunk-5PVQY5BW-SQD_EpYa.js → chunk-5PVQY5BW-Bn2nQwdj.js} +1 -1
  50. package/payload/server/public/assets/{chunk-EDXVE4YY-CtCcA7_e.js → chunk-EDXVE4YY-CzCPnR0P.js} +1 -1
  51. package/payload/server/public/assets/{chunk-ENJZ2VHE-pXVGVCbb.js → chunk-ENJZ2VHE-CgZj9RoG.js} +1 -1
  52. package/payload/server/public/assets/{chunk-ICPOFSXX-Dkzg9o2N.js → chunk-ICPOFSXX-wy-eNjwW.js} +1 -1
  53. package/payload/server/public/assets/{chunk-OYMX7WX6-1ZZWzf9F.js → chunk-OYMX7WX6-CMmJtL8S.js} +1 -1
  54. package/payload/server/public/assets/{chunk-U2HBQHQK-CpQ3kzO0.js → chunk-U2HBQHQK-CFCW7OaT.js} +1 -1
  55. package/payload/server/public/assets/{chunk-X2U36JSP-C2LkxroC.js → chunk-X2U36JSP-Bgh-CJSN.js} +1 -1
  56. package/payload/server/public/assets/{chunk-YZCP3GAM-Bl5jBOt5.js → chunk-YZCP3GAM-BXKwZ4vN.js} +1 -1
  57. package/payload/server/public/assets/{chunk-ZZ45TVLE-CHtnptPS.js → chunk-ZZ45TVLE-BiOuK5NP.js} +1 -1
  58. package/payload/server/public/assets/classDiagram-6PBFFD2Q-C3IDJsqN.js +1 -0
  59. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BOIrJ5Zb.js +1 -0
  60. package/payload/server/public/assets/clone-P8Fkz7JD.js +1 -0
  61. package/payload/server/public/assets/{dagre-BziN0Nkh.js → dagre-DyCxa9Q2.js} +1 -1
  62. package/payload/server/public/assets/{dagre-KV5264BT-B7OG1g1Y.js → dagre-KV5264BT-Cffo_hmf.js} +1 -1
  63. package/payload/server/public/assets/data-DRfwJPja.js +1 -0
  64. package/payload/server/public/assets/{diagram-5BDNPKRD-Bf31nIDs.js → diagram-5BDNPKRD-Bt93Du3V.js} +1 -1
  65. package/payload/server/public/assets/{diagram-G4DWMVQ6-DQu85hhH.js → diagram-G4DWMVQ6-v4r-tBsC.js} +1 -1
  66. package/payload/server/public/assets/{diagram-MMDJMWI5-5tstbs4y.js → diagram-MMDJMWI5-DKrVPOP-.js} +1 -1
  67. package/payload/server/public/assets/{diagram-TYMM5635--MOV1U4o.js → diagram-TYMM5635-B1PzWQb9.js} +1 -1
  68. package/payload/server/public/assets/{erDiagram-SMLLAGMA-BtAnOJmd.js → erDiagram-SMLLAGMA-ClQzsQAs.js} +1 -1
  69. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-CybiCIih.js → flowDiagram-DWJPFMVM-h3IbpN_7.js} +1 -1
  70. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-B44jN7Cy.js → ganttDiagram-T4ZO3ILL-ClaxOJi8.js} +1 -1
  71. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CT7iYcwg.js → gitGraphDiagram-UUTBAWPF-pM-U4ClD.js} +1 -1
  72. package/payload/server/public/assets/graph-B4FFYwus.js +1 -0
  73. package/payload/server/public/assets/graph-labels-Cpk9Ktt0.js +1 -0
  74. package/payload/server/public/assets/{graphlib-DP4o0pYL.js → graphlib-CwimOv_M.js} +1 -1
  75. package/payload/server/public/assets/{infoDiagram-42DDH7IO-CvxFpFHN.js → infoDiagram-42DDH7IO-DcO3Giwe.js} +1 -1
  76. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CUwWLPeR.js → ishikawaDiagram-UXIWVN3A-BcdSL5VS.js} +1 -1
  77. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-y-OnGgLi.js → journeyDiagram-VCZTEJTY-CYahHYzf.js} +1 -1
  78. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BhrSe8R4.js → kanban-definition-6JOO6SKY-Cl5KhoS4.js} +1 -1
  79. package/payload/server/public/assets/lib--yuBd0Xi.js +33 -0
  80. package/payload/server/public/assets/{line-C9uS7z4J.js → line-d_2oTxLp.js} +1 -1
  81. package/payload/server/public/assets/{mermaid-parser.core-Cg4ZdKp-.js → mermaid-parser.core-CMMDGv3x.js} +1 -1
  82. package/payload/server/public/assets/{mermaid.core-BHdKOsex.js → mermaid.core-DK9ENGbr.js} +3 -3
  83. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-NBYiXHo7.js → mindmap-definition-QFDTVHPH-mA3x3MFG.js} +1 -1
  84. package/payload/server/public/assets/page-CgDmg_fX.js +1 -0
  85. package/payload/server/public/assets/{page-D9YpwhIu.js → page-D9lluVl7.js} +2 -2
  86. package/payload/server/public/assets/{pieDiagram-DEJITSTG-CbojC64C.js → pieDiagram-DEJITSTG-CW1nGFlY.js} +1 -1
  87. package/payload/server/public/assets/public-DdLMkNdS.js +7 -0
  88. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CnnoZXcL.js → quadrantDiagram-34T5L4WZ-DI5igJhR.js} +1 -1
  89. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DFRFRtyJ.js → requirementDiagram-MS252O5E-DyVeI4e_.js} +1 -1
  90. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DU5gcpzO.js → sankeyDiagram-XADWPNL6-CCTTBYKY.js} +1 -1
  91. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BN7HZ6Hq.js → sequenceDiagram-FGHM5R23-hVVf35ly.js} +1 -1
  92. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DG6cCjvg.js → stateDiagram-FHFEXIEX-CSLuQDUX.js} +1 -1
  93. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-C5JejhQU.js +1 -0
  94. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-qmV-8SEV.js → timeline-definition-GMOUNBTQ-sxC3W8Q4.js} +1 -1
  95. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-BMXQ1s3n.js → vennDiagram-DHZGUBPP-Ro-ton6Z.js} +1 -1
  96. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-CoAdP1Gw.js → wardleyDiagram-NUSXRM2D-T-dKD0Zx.js} +1 -1
  97. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CZlfGTEn.js → xychartDiagram-5P7HB3ND-CfProPts.js} +1 -1
  98. package/payload/server/public/data.html +4 -4
  99. package/payload/server/public/graph.html +5 -5
  100. package/payload/server/public/index.html +7 -7
  101. package/payload/server/public/public.html +4 -4
  102. package/payload/server/server.js +299 -1392
  103. package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +0 -241
  104. package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +0 -154
  105. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +0 -98
  106. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +0 -749
  107. package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +0 -107
  108. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +0 -854
  109. package/payload/platform/plugins/cloudflare/scripts/tunnel-ingress.ts +0 -291
  110. package/payload/server/chunk-BDFOTLPW.js +0 -759
  111. package/payload/server/chunk-JRBCOVA4.js +0 -1305
  112. package/payload/server/cloudflare-task-tracker-M5ONAGUT.js +0 -22
  113. package/payload/server/public/assets/admin-BvzMvMGo.js +0 -217
  114. package/payload/server/public/assets/channel-DThrH4QF.js +0 -1
  115. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BC6oGTNX.js +0 -1
  116. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-COwC5Umh.js +0 -1
  117. package/payload/server/public/assets/clone-Chp7hvnA.js +0 -1
  118. package/payload/server/public/assets/data-BAgaPs4j.js +0 -1
  119. package/payload/server/public/assets/graph-Cma7EArf.js +0 -1
  120. package/payload/server/public/assets/graph-labels-DyKk6Sxf.js +0 -1
  121. package/payload/server/public/assets/lib-CpkYtEDz.js +0 -29
  122. package/payload/server/public/assets/page-B4IWl3aZ.js +0 -1
  123. package/payload/server/public/assets/public-BtOXjy3A.js +0 -8
  124. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CvVd1Q_q.js +0 -1
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: cloudflare
3
- description: Cloudflare Tunnel operations — setup, reset, dashboard guidance. Zero agent-facing MCP tools; every operation is a shell script or a dashboard click-path the operator performs themselves.
3
+ description: Cloudflare Tunnel operations — setup, reset, dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` directly via Bash or quoting a dashboard click-path the operator performs themselves.
4
4
  tools: []
5
5
  mcp-manifest: skip
6
6
  ---
7
7
 
8
8
  # Cloudflare Tunnel
9
9
 
10
- Each installation has its own Cloudflare account. The operator signs in with OAuth via `cloudflared tunnel login` (driven by `setup-tunnel.sh`); `cloudflared` writes `cert.pem` to the brand-scoped config directory. The Cloudflare dashboard is the single source of truth for which domains, addresses, and tunnels exist; the plugin never reads or mutates account state via any API path — only `cloudflared` CLI shell-outs the scripts execute, and DNS + HTTPS probes against public surfaces.
10
+ Each installation has its own Cloudflare account. The operator signs in with OAuth via `cloudflared tunnel login` (issued by the agent through Bash); `cloudflared` writes `cert.pem` to the brand-scoped config directory. The Cloudflare dashboard is the single source of truth for which domains, addresses, and tunnels exist; the plugin never reads or mutates account state via any API path — only `cloudflared` CLI shell-outs the agent runs, and DNS + HTTPS probes against public surfaces.
11
11
 
12
12
  ## When to activate
13
13
 
@@ -25,41 +25,34 @@ Each installation has its own Cloudflare account. The operator signs in with OAu
25
25
 
26
26
  ## Operator-facing surface
27
27
 
28
- The plugin registers no agent-facing MCP tools. Every Cloudflare operation is driven through one of four sanctioned surfaces `setup-tunnel.sh`, `reset-tunnel.sh`, `references/manual-setup.md`, or `references/dashboard-guide.md`. See the skill below for the discipline rule that binds the agent to these four.
29
-
30
- ### Scripts
31
-
32
- | Script | Purpose |
33
- |---|---|
34
- | [`scripts/setup-tunnel.sh`](scripts/setup-tunnel.sh) | Autonomous end-to-end setup: OAuth login, tunnel resolve (operator-supplied identity), DNS route, config + state, service restart, post-restart verification. Invocation: `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]`. Required env: `STREAM_LOG_PATH`, `ACCOUNT_DIR`, AND exactly one of `TUNNEL_ID` (operator selected an existing tunnel — the agent enumerates them via `cloudflared tunnel list --output json` and presents the list in chat) or `TUNNEL_NAME` (operator typed a name to create) per the operator-selected-tunnel fix. The pre-fix derivation `${BRAND}-$(hostname -s)` is removed — the operator's logged-in Cloudflare account is the source of truth for which tunnel exists. Apex hostnames print an `ACTION REQUIRED` block for the dashboard record the CLI cannot create. Step 1 (wrappers faithfully relay third-party CLI) spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and prints it as `OAUTH_URL: <url>` on its own stdout — the admin UI's `ActionLogPanel` renders that line as a clickable Authorize link with `target=_blank` so the operator authorizes Cloudflare in a new tab of their own browser. The script does not spawn a browser of its own; cloudflared's OAuth callback writes `~/.cloudflared/cert.pem` regardless of which browser completed the Authorize click. 180 s budget with a 2-second `step=oauth-login result=awaiting-cert` heartbeat. No CDP auto-click, no DOM matcher. |
35
- | [`scripts/reset-tunnel.sh`](scripts/reset-tunnel.sh) | Deletes every tunnel on the brand's CF account and wipes `${CFG_DIR}`. Does not touch the platform service, stray CNAMEs, or token-mode connectors — those require dashboard cleanup or `pkill`. Invocation: `~/reset-tunnel.sh <brand>`. No polling blocks — every long-wait is bounded by `cloudflared`'s network round-trip, so no heartbeat contract applies. |
28
+ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is the agent invoking `cloudflared` directly via Bash, following the numbered steps in `references/manual-setup.md`, with the cloudflared stdout/stderr streamed verbatim into the PTY chat. The OAuth URL printed by `cloudflared tunnel login` is linkified by the native PTY; the operator clicks it in their own browser. There is no shell-script wrapper, no orchestrator state machine, no MCP-tool surface.
36
29
 
37
30
  ### Skills
38
31
 
39
32
  | Skill | Purpose |
40
33
  |---|---|
41
- | [setup-tunnel/SKILL.md](skills/setup-tunnel/SKILL.md) | Names the four sanctioned surfaces (autonomous / manual / reset / dashboard), the inputs to collect before invoking `setup-tunnel.sh`, and the tool-discipline rule that binds the agent. |
34
+ | [setup-tunnel/SKILL.md](skills/setup-tunnel/SKILL.md) | Names the outcome contract (external HTTP 200 to the configured hostname), the inputs to collect, the runbook to execute, and the tool-discipline rule that binds the agent. |
42
35
 
43
36
  ### References
44
37
 
45
38
  | Reference | Topics |
46
39
  |---|---|
47
- | [manual-setup.md](references/manual-setup.md) | Step-by-step human runbook — Steps 0–7 with isolated command blocks. Used when diagnosing a failing scripted step or working on a device where the scripts are not yet deployed. |
40
+ | [manual-setup.md](references/manual-setup.md) | Step-by-step runbook — Steps 0–7 with isolated `cloudflared` command blocks. The agent reads the relevant step before issuing each command. |
48
41
  | [dashboard-guide.md](references/dashboard-guide.md) | Click-paths for the operations only the Cloudflare dashboard can perform — sign in, switch accounts, add a site, edit an apex CNAME, verify nameservers, delete a tunnel, manage CNAME records. |
49
42
  | [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. |
50
43
 
51
44
  The agent loads these references on demand via `plugin-read` as the conversation requires. They are not auto-injected into the system prompt.
52
45
 
53
- ### Error envelope contract
46
+ ### Success contract
54
47
 
55
- `setup-tunnel.sh` emits structured `[tunnel-install] step=… result=… …` phase lines on stdout throughout its run and exits non-zero on failure. The agent streams stdout into chat verbatim as it arrives, names the literal exit code on failure, and cites `references/reset-guide.md` for the next action. The agent never paraphrases the script's output, never summarises an `ACTION REQUIRED` block, and never re-solicits values the operator already provided — re-soliciting is a doctrine violation. The general form of the post-deterministic-error reply contract (literal-error + held-values restatement) lives in IDENTITY.md § "Post-deterministic-error reply contract" and `.docs/agents.md` § "Intent Gate — post-deterministic-error reply contract"; admin-reply token scrub at `platform/ui/app/lib/admin-reply-scrub.ts` catches narration leakage under `llm-narrates-internal-state`.
48
+ The setup-done claim only fires when `curl -I https://<hostname>` issued from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`) and the response is surfaced verbatim in chat. No state file, no service-active claim, no `cloudflared` exit code substitutes for the live HTTP response. When the curl returns anything else, the agent diagnoses with `cloudflared tunnel info <tunnelId>` and `systemctl --user status ${BRAND}-cloudflared.service`, cites the relevant step in `references/reset-guide.md`, and stops.
56
49
 
57
50
  ## Identity model
58
51
 
59
52
  - **Product identity** (Maxy vs Real Agent) — known from `brand.json` (`productName`, `configDir`).
60
53
  - **Cloudflare account identity** — `cert.pem` from OAuth. One account per brand per device.
61
- - **Account binding drift** — `~/{configDir}/cloudflared/account-binding.json` is a historical drift marker. Reset via `reset-tunnel.sh` when switching accounts.
54
+ - **Account binding drift** — `~/{configDir}/cloudflared/account-binding.json` is a historical drift marker. Reset by `rm -rf ~/.${BRAND}/cloudflared/` per `references/reset-guide.md` when switching accounts.
62
55
 
63
56
  ## Discipline
64
57
 
65
- Loaded into IDENTITY.md § Cloudflare operations at install time. The short form: the agent's permitted surfaces are the two scripts, the three reference files, and plain `curl` reachability checks — everything else (Playwright, WebSearch-for-CF-recipes, Cloudflare API / SDK, ad-hoc `cloudflared` flag invention, direct edits to cert.pem / tunnel.state / config.yml / alias-domains.json) is out of bounds. Sanctioned-surface failures are reported with evidence and cited against `reset-guide.md`, not improvised around.
58
+ Loaded into IDENTITY.md § Cloudflare operations at install time. The short form: the agent's permitted surfaces are direct `cloudflared` invocations via Bash following the runbook, the three reference files, and `curl -I` reachability checks — everything else (Playwright, WebSearch-for-CF-recipes, Cloudflare API / SDK, ad-hoc `cloudflared` flag invention not in the runbook) is out of bounds. When a step fails, the agent reports the exact `cloudflared` output, cites the recovery step from `references/reset-guide.md`, and stops.
@@ -2,18 +2,13 @@ import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
2
2
  initStderrTee("cloudflare");
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- // the Cloudflare plugin exposes zero agent-facing tools. Every
6
- // Cloudflare operation is driven by shell scripts (setup-tunnel.sh,
7
- // reset-tunnel.sh) invoked via Bash, plus dashboard click-paths relayed
8
- // verbatim from the skill's reference files. The MCP server process
9
- // still spawns for lifecycle and stderr-tee consistency with other
10
- // plugins, but its tool list is empty by design.
11
- //
12
- // See platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md and
13
- // platform/plugins/cloudflare/references/ for the operator-facing
14
- // surface. `lib/cloudflared.ts` and `lib/setup-orchestrator.ts` are
15
- // retained as private implementation layers that nothing currently
16
- // imports; a follow-up task will delete them once that is confirmed.
5
+ // The Cloudflare plugin exposes zero agent-facing tools. Every
6
+ // Cloudflare operation is the agent invoking `cloudflared` directly via
7
+ // the Bash tool, following the runbook in
8
+ // platform/plugins/cloudflare/references/manual-setup.md, with
9
+ // cloudflared stdout/stderr streamed verbatim into the PTY. The MCP
10
+ // server process still spawns for lifecycle and stderr-tee consistency
11
+ // with other plugins, but its tool list is empty by design.
17
12
  const server = new McpServer({
18
13
  name: "cloudflare",
19
14
  version: "0.4.0",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,aAAa,CAAC,YAAY,CAAC,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,+DAA+D;AAC/D,oEAAoE;AACpE,wEAAwE;AACxE,oEAAoE;AACpE,mEAAmE;AACnE,iDAAiD;AACjD,EAAE;AACF,mEAAmE;AACnE,kEAAkE;AAClE,oEAAoE;AACpE,mEAAmE;AACnE,qEAAqE;AAErE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,aAAa,CAAC,YAAY,CAAC,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,+DAA+D;AAC/D,wEAAwE;AACxE,0CAA0C;AAC1C,+DAA+D;AAC/D,oEAAoE;AACpE,uEAAuE;AACvE,4DAA4D;AAE5D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -58,7 +58,7 @@ If the account shown in the top-left is not the one that owns the zone you need,
58
58
 
59
59
  ## Edit an apex CNAME (bare-domain hostname)
60
60
 
61
- Apex hostnames — a record at the zone root, e.g. `maxy.chat` rather than `www.maxy.chat` — cannot be routed by `cloudflared tunnel route dns` because standard DNS forbids CNAMEs at zone apex. Cloudflare's workaround (CNAME flattening) is only exposed via the dashboard. When `setup-tunnel.sh` prints an `ACTION REQUIRED` block for an apex, these are the steps:
61
+ Apex hostnames — a record at the zone root, e.g. `maxy.chat` rather than `www.maxy.chat` — cannot be routed by `cloudflared tunnel route dns` because standard DNS forbids CNAMEs at zone apex. Cloudflare's workaround (CNAME flattening) is only exposed via the dashboard. When the agent prints an `ACTION REQUIRED` block for an apex (per `references/manual-setup.md` § Step 4), these are the steps:
62
62
 
63
63
  1. Click **Websites** in the sidebar, then click the apex zone (e.g. `maxy.chat`).
64
64
  2. Click **DNS** in the sidebar, then **Records**.
@@ -105,8 +105,8 @@ Use this when you need to audit which hostnames are pointing at which `<UUID>.cf
105
105
 
106
106
  ## Author an Access policy for SSH or SMB
107
107
 
108
- When `setup-tunnel.sh` adds an SSH or SMB ingress hostname, it prints an
109
- `ACTION REQUIRED` block naming this click-path. The script does NOT
108
+ When the agent adds an SSH or SMB ingress hostname (per `references/manual-setup.md`), it surfaces an
109
+ `ACTION REQUIRED` block naming this click-path. The agent does NOT
110
110
  create the Access policy — Cloudflare API/SDK is banned
111
111
  (`feedback_cf_api_total_eradication`) and `cloudflared` CLI has no
112
112
  Access-application create subcommand — so the operator must author it
@@ -39,43 +39,13 @@ Mode B skips Steps 1–5 of this runbook entirely — the user receives a token
39
39
 
40
40
  ---
41
41
 
42
- ## Automation setup-tunnel.sh and reset-tunnel.sh
42
+ ## How this runbook is used
43
43
 
44
- The manual walkthrough below exists so an operator can execute every step by hand when the system is broken or the automation is absent. For a normal setup and for validating that the runbook's steps produce a working tunnel end-to-enduse the two scripts at `platform/plugins/cloudflare/scripts/`:
44
+ This runbook is the contract the agent follows when setting up a Cloudflare tunnel. The agent reads the step it is about to execute, runs the `cloudflared` command (or writes the config / starts the service) via the Bash tool, and streams the literal output back to the operator. There is no shell-script wrapper, no orchestrator state machine, no MCP-tool surface the PTY itself is the operator-visible surface, and cloudflared's stdout/stderr is the evidence.
45
45
 
46
- > **`list-cf-domains` is brand-arg + brand.json driven.** `list-cf-domains.sh` requires the brand name as `$1`; the.ts helper reads `cdpPort` from `${MAXY_PLATFORM_ROOT}/config/brand.json` (no silent default). Missing brand arg, missing brand.json, or missing `cdpPort` field each exit 1 with a named `reason=` token, and the route maps them to `field=config` (no Retry button fix the install instead).
46
+ The setup is done when, and only when, `curl -I https://<admin-hostname>` issued from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`). The agent must paste the curl response into chat before claiming success. No service-active or `cloudflared exit 0` claim substitutes for that response.
47
47
 
48
- ```
49
- setup-tunnel.sh <brand> <port> <hostname> [<hostname> ...]
50
- ```
51
-
52
- Example:
53
-
54
- ```
55
- ~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot maxy.chat
56
- ```
57
-
58
- Mirrors this runbook verbatim (Steps 0–5b), calls `systemctl --user restart "${BRAND}.service"` at the end to spawn the connector via `resume-tunnel.sh`, then polls each subdomain hostname (up to 60s per host) for a non-530 response. Apex hostnames cannot be routed via CLI (see §Step 4); the script prints an explicit ACTION REQUIRED block naming the dashboard record to edit or add.
59
-
60
- ```
61
- reset-tunnel.sh <brand>
62
- ```
63
-
64
- Example:
65
-
66
- ```
67
- ~/reset-tunnel.sh maxy
68
- ```
69
-
70
- Deletes every tunnel on the brand's Cloudflare account (via the brand's cert) and wipes `${CFG_DIR}`. Does **not** touch the platform service or any stray misrouted CNAMEs in DNS — those require a manual dashboard delete.
71
-
72
- After manual transfer to a device, run `chmod +x ~/setup-tunnel.sh ~/reset-tunnel.sh` before the first invocation.
73
-
74
- **Walk through manually (instead of scripting) when:**
75
- - Diagnosing a step that's failing under the script.
76
- - Recovering a partial state where the scripts assume a clean start.
77
- - Validating runbook changes before re-baking them into the scripts.
78
- - Working on a device where the scripts aren't deployed yet.
48
+ Apex hostnames (e.g. `maxy.chat`) cannot be routed by `cloudflared tunnel route dns` — see §Step 4. The agent quotes the ACTION REQUIRED block from §Step 4 verbatim and the operator edits the apex CNAME in the dashboard.
79
49
 
80
50
  ---
81
51
 
@@ -222,7 +192,7 @@ cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns
222
192
  2. Overwritten: `Added CNAME <hostname> which will route to this tunnel` (same shape as new; `--overwrite-dns` makes overwrite transparent)
223
193
  3. Idempotent no-op: `<timestamp> INF <hostname> is already configured to route to your tunnel tunnelID=<UUID>`
224
194
 
225
- Shape 3 is what a second clean run of setup-tunnel.sh against an already-configured hostname emits. Historically the shell script's stdout parser rejected this shape and exited 1 on the idempotent case; the script now relies on cloudflared's exit code exclusively.
195
+ Shape 3 is what a repeat run against an already-configured hostname emits `cloudflared` exits 0 and the agent treats it as a no-op success.
226
196
 
227
197
  **If it fails with `zone not found`:** the hostname's parent domain isn't on this brand's Cloudflare account. Either add it in the dashboard (Websites → Add a site) and re-run, or sign into the account that already owns the domain.
228
198
 
@@ -306,12 +276,10 @@ cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns
306
276
  cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" smb.maxy.bot
307
277
  ```
308
278
 
309
- **Order matters in the script path:** `setup-tunnel.sh` routes HTTPS
310
- hostnames first and rewrites `config.yml` only after the HTTPS pass is
311
- durable, so a failure on `ssh` or `smb` route DNS does not nuke the
312
- HTTPS ingress; the failing hostname is logged as
313
- `[tunnel-install] {ssh,smb}-ingress-deferred` and config.yml is rendered
314
- without it.
279
+ **Order matters:** route HTTPS hostnames first and rewrite `config.yml`
280
+ only after the HTTPS pass is durable, so a failure on `ssh` or `smb` route
281
+ DNS does not nuke the HTTPS ingress; the failing hostname is logged and
282
+ config.yml is rendered without it.
315
283
 
316
284
  **SMB depends.** Before adding the SMB ingress, confirm the
317
285
  brand stanza exists in `/etc/samba/smb.conf`:
@@ -355,7 +323,7 @@ cloudflared access tcp --hostname smb.<brand>.<rootdomain> --url tcp://localhost
355
323
 
356
324
  ## Step 5b — Write tunnel.state
357
325
 
358
- `resume-tunnel.sh` (the `ExecStartPre=` in the brand's service) reads `${CFG_DIR}/tunnel.state` to discover which tunnel to run. **Without this file the script exits 0 silently and no connector is spawned** — see lines 34-37 of `platform/scripts/resume-tunnel.sh`. The old MCP tool `tunnel-create` used to write this file via `saveTunnelIdentity()` in `cloudflared.ts`; with the raw CLI flow in this runbook nothing does, so we write it explicitly here.
326
+ `resume-tunnel.sh` (the `ExecStartPre=` in the brand's service) reads `${CFG_DIR}/tunnel.state` to discover which tunnel to run. **Without this file the script exits 0 silently and no connector is spawned** — see lines 34-37 of `platform/scripts/resume-tunnel.sh`. The raw CLI flow in this runbook does not write `tunnel.state` automatically, so write it explicitly here:
359
327
 
360
328
  Re-run the Step 5a fail-fast guard if your shell is new or if Step 5a was a while ago:
361
329
 
@@ -381,8 +349,8 @@ EOF
381
349
 
382
350
  Omit `sshHostname` and `smbHostname` when those ingresses are not in
383
351
  use. The fields are additive; the script's re-run path rehydrates them
384
- on every invocation so an operator running `setup-tunnel.sh` without
385
- `SSH_HOSTNAME` / `SMB_HOSTNAME` env vars does not silently drop the
352
+ on every invocation so a repeat setup run without
353
+ `SSH_HOSTNAME` / `SMB_HOSTNAME` set does not silently drop the
386
354
  SSH/SMB ingress.
387
355
 
388
356
  **Why each field:** `resume-tunnel.sh` reads `tunnelId`, `domain`, `configPath` (all used; domain is for logging, tunnelId for the log line, configPath is passed as `--config` to cloudflared). `credentialsPath` and `tunnelName` are not read by `resume-tunnel.sh` itself but are consumed by other tools (e.g. `tunnel-status` in the cloudflared plugin), so write them anyway.
@@ -403,23 +371,20 @@ Prints the UUID from Step 3. If it prints empty or null, the heredoc's env expan
403
371
 
404
372
  You do **not** run `cloudflared` manually. The brand's existing user-space systemd unit (`~/.config/systemd/user/${BRAND}.service`) declares `ExecStartPre=/home/<user>/${BRAND}/platform/scripts/resume-tunnel.sh`, and that pre-start script reads `${CFG_DIR}/tunnel.state` and `${CFG_DIR}/config.yml` (the files Steps 5 and 5b just wrote) and spawns the connector in the user's cgroup. Restarting the brand service is what picks up the new config.
405
373
 
406
- > **Note:** When walking through by hand you run this step yourself. The automation script `platform/plugins/cloudflare/scripts/setup-tunnel.sh` runs it for you — with a critical twist documented below. If you used the script, this step is already done and the service will restart a few seconds after the script exits.
407
-
408
-
409
374
  ```
410
375
  systemctl --user restart "${BRAND}.service"
411
376
  ```
412
377
 
413
- **Why the script dispatches the restart via `systemd-run` instead of a direct `systemctl restart`:** when the admin agent invokes `setup-tunnel.sh` via the Bash tool, the script runs *inside* `${BRAND}.service`'s cgroup. A direct `systemctl --user restart ${BRAND}.service` from that cgroup tells systemd to SIGTERM the entire cgroup — the node server, the claude subprocess, the Bash child, and the script itself all die simultaneously. cgroup membership is inherited: `setsid`, `nohup`, `disown`, and `&` all stay in the caller's cgroup, and `systemd-run --scope` runs in the caller's scope. Only `systemd-run --user --unit=<name> --on-active=<N>s` creates a genuinely new transient unit with its own cgroup. The script uses that primitive to arm the restart a few seconds after its own exit:
378
+ **Cgroup trap when the admin agent issues this command via the Bash tool, the Bash subprocess runs *inside* `${BRAND}.service`'s cgroup.** A direct `systemctl --user restart ${BRAND}.service` from that cgroup tells systemd to SIGTERM the entire cgroup — the node server, the claude subprocess, the Bash child, and the agent itself all die simultaneously. cgroup membership is inherited: `setsid`, `nohup`, `disown`, and `&` all stay in the caller's cgroup, and `systemd-run --scope` runs in the caller's scope. Only `systemd-run --user --unit=<name> --on-active=<N>s` creates a genuinely new transient unit with its own cgroup. The agent uses that primitive to arm the restart a few seconds after the Bash call returns:
414
379
 
415
380
  ```
416
- systemd-run --user --unit=maxy-tunnel-restart-<nonce>.service --on-active=3s --collect \
381
+ systemd-run --user --unit=maxy-tunnel-restart-$(date +%s).service --on-active=3s --collect \
417
382
  /bin/systemctl --user restart "${BRAND}.service"
418
383
  ```
419
384
 
420
- The script then emits `[script:setup-tunnel] step=service-restart-dispatched` and `step=service-restart-armed exit=0` in the per-conversation stream log so operators see exactly when the restart was scheduled, exits 0, and the transient timer fires from outside the service's cgroup — semantically identical to this manual runbook's `systemctl --user restart`. (The `script:` prefix is the chat-surface namespace — see `_stream-log.sh` header.)
385
+ The transient timer fires from outside the service's cgroup — semantically identical to a direct `systemctl --user restart`, but the agent survives.
421
386
 
422
- When walking through manually you do **not** need `systemd-run` — your SSH shell already lives in a separate user-scope cgroup (`user@<uid>.service`), so the direct `systemctl restart` does not kill the caller. The script's extra indirection only matters when the caller *is* the service being restarted.
387
+ When walking through manually from an SSH shell you do **not** need `systemd-run` — your SSH shell already lives in a separate user-scope cgroup (`user@<uid>.service`), so the direct `systemctl restart` does not kill the caller. The extra indirection only matters when the caller *is* inside the service being restarted (the admin agent's case).
423
388
 
424
389
  **Why:** `resume-tunnel.sh` is the deterministic, brand-scoped spawner. Running `cloudflared` manually duplicates the connector (two processes for one tunnel) and races the brand service on every service restart. The service path is the only correct production path.
425
390
 
@@ -1,6 +1,6 @@
1
1
  # Cloudflare reset — when and how
2
2
 
3
- Reset is a heavier action than recovery. `reset-tunnel.sh` deletes every tunnel on the brand's Cloudflare account and wipes the on-disk cloudflared config directory. Use it when the state is genuinely corrupt or when the operator wants a known-good baseline; use patching (targeted cleanup) when the state is mostly correct but one record or process is wrong.
3
+ Reset is a heavier action than recovery. A reset deletes every tunnel on the brand's Cloudflare account and wipes the on-disk cloudflared config directory. Use it when the state is genuinely corrupt or when the operator wants a known-good baseline; use patching (targeted cleanup) when the state is mostly correct but one record or process is wrong.
4
4
 
5
5
  ---
6
6
 
@@ -17,51 +17,50 @@ Ask three questions in order. The first `yes` determines the action.
17
17
  3. **Is the only problem a stray CNAME in the dashboard, a rogue token-mode connector process, or an out-of-date `alias-domains.json` entry?**
18
18
  → Patch (see § Patching below). Reset would destroy correct local state alongside the bad record.
19
19
 
20
- When no question reaches `yes`, the state is probably recoverable per-step via `references/manual-setup.md`. Invoke `setup-tunnel.sh` again before reaching for reset.
20
+ When no question reaches `yes`, the state is probably recoverable per-step via `references/manual-setup.md`. Re-run the relevant manual setup steps before reaching for reset.
21
21
 
22
22
  ---
23
23
 
24
- ## Full reset — `reset-tunnel.sh`
24
+ ## Full reset
25
25
 
26
26
  ### What it does
27
27
 
28
- - Reads cert.pem from `${HOME}/.${BRAND}/cloudflared/cert.pem`.
29
- - Lists every tunnel on the account the cert authorises.
30
- - Deletes every one of those tunnels via `cloudflared tunnel delete`.
31
- - Removes `${HOME}/.${BRAND}/cloudflared/` entirely.
28
+ The agent issues these commands in order via Bash, against the brand cert:
32
29
 
33
- ### What it does not do
34
-
35
- - It does not touch the platform service (`${BRAND}.service`). Restart the service separately with `systemctl --user restart "${BRAND}.service"` once a fresh tunnel is set up.
36
- - It does not touch DNS records. Records pointing at `<UUID>.cfargotunnel.com` from a deleted tunnel become stray; delete them in the dashboard (see § Patching below).
37
- - It does not stop token-mode connector processes. If the device is running `cloudflared ... tunnel run --token <X>` for a tunnel the brand's cert does not own, that connector keeps running.
38
- - It does not switch accounts. If the bound account was wrong, a fresh `cloudflared tunnel login` from inside `setup-tunnel.sh` on the next run will pick a new one — but the operator must sign in with the correct account in the browser.
30
+ ```
31
+ BRAND=$(jq -r .hostname ~/.<configDir>/brand.json)
32
+ CFG_DIR="${HOME}/.${BRAND}/cloudflared"
39
33
 
40
- ### Invocation
34
+ for t in $(cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel list --output json | jq -r '.[].id'); do
35
+ cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel delete -f "$t"
36
+ done
41
37
 
42
- ```
43
- ~/reset-tunnel.sh <brand>
38
+ rm -rf "${CFG_DIR}"
44
39
  ```
45
40
 
46
- Example:
41
+ Then stop the brand's cloudflared user service so the now-deleted tunnel's connector exits:
47
42
 
48
43
  ```
49
- ~/reset-tunnel.sh maxy
44
+ systemctl --user stop "${BRAND}-cloudflared.service" 2>/dev/null || true
50
45
  ```
51
46
 
52
- The script exits with the number of tunnels deleted. Relay the output verbatim. If the script exits non-zero (e.g. cert missing, cloudflared not installed), quote the error and stop — do not synthesise a recovery path.
47
+ ### What it does not do
48
+
49
+ - It does not touch DNS records. Records pointing at `<UUID>.cfargotunnel.com` from a deleted tunnel become stray; delete them in the dashboard (see § Patching below).
50
+ - It does not stop token-mode connector processes. If the device is running `cloudflared ... tunnel run --token <X>` for a tunnel the brand's cert does not own, that connector keeps running. Use `pkill` per § Patching.
51
+ - It does not switch accounts. A fresh `cloudflared tunnel login` after reset picks a new one — but the operator must sign in with the correct account in the browser.
53
52
 
54
53
  ### After reset
55
54
 
56
- Invoke `setup-tunnel.sh` with the operator's chosen hostnames (see `SKILL.md` for the input-collection questions). The script re-runs `cloudflared tunnel login`; the operator authorises a fresh cert from the correct account in the VNC browser.
55
+ Re-run the setup flow from `references/manual-setup.md` § Step 1 with the operator's chosen hostnames (see `SKILL.md` for the input-collection questions). `cloudflared tunnel login` runs again; the operator authorises a fresh cert from the correct account in their own browser via the linkified OAuth URL.
57
56
 
58
57
  ---
59
58
 
60
59
  ## Patching — targeted cleanup without reset
61
60
 
62
- ### Stop a token-mode connector the reset script cannot see
61
+ ### Stop a token-mode connector
63
62
 
64
- Token-mode tunnels are created by a central operator (e.g. Maxy-provided subdomains under `maxy.bot`). The connector runs with `--token <X>` and does not consult `cert.pem`, so `reset-tunnel.sh` (which authorises via cert.pem) cannot stop or delete it. Kill the process directly:
63
+ Token-mode tunnels are created by a central operator (e.g. Maxy-provided subdomains under `maxy.bot`). The connector runs with `--token <X>` and does not consult `cert.pem`, so the full-reset flow above (which authorises via cert.pem) cannot stop or delete it. Kill the process directly:
65
64
 
66
65
  ```
67
66
  pkill -f 'cloudflared.*tunnel run --token'
@@ -77,7 +76,7 @@ No lines with `--token` should remain. If multiple token-mode connectors are run
77
76
 
78
77
  ### Delete a stray CNAME that survived a tunnel delete
79
78
 
80
- When `reset-tunnel.sh` deletes a tunnel, the DNS CNAMEs that pointed at `<UUID>.cfargotunnel.com` remain in the zone — Cloudflare deliberately does not cascade-delete DNS when you delete a tunnel. Clean up in the dashboard:
79
+ When a tunnel is deleted, the DNS CNAMEs that pointed at `<UUID>.cfargotunnel.com` remain in the zone — Cloudflare deliberately does not cascade-delete DNS when you delete a tunnel. Clean up in the dashboard:
81
80
 
82
81
  1. See `references/dashboard-guide.md` § "Delete a stray CNAME" for the click-path.
83
82
  2. Verify the CNAME's target is the tunnel you just deleted (not a live one).
@@ -109,10 +108,10 @@ If a hostname's CNAME is pointing at the wrong tunnel (e.g. after an account swi
109
108
  cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" <hostname>
110
109
  ```
111
110
 
112
- This runs the same logic `setup-tunnel.sh` runs internally for each subdomain. Apex hostnames do not work here — the dashboard is the only path (see `references/dashboard-guide.md` § "Edit an apex CNAME").
111
+ This is the same call the manual-setup runbook makes for each subdomain. Apex hostnames do not work here — the dashboard is the only path (see `references/dashboard-guide.md` § "Edit an apex CNAME").
113
112
 
114
113
  ---
115
114
 
116
115
  ## Failure discipline
117
116
 
118
- When `reset-tunnel.sh` or a patch command exits non-zero, report the failure with the exact output. Cite the relevant section above for the next action. Do not chain alternative recovery attempts — the agent's job ends at the boundary of a sanctioned surface, and improvisation outside that boundary is the discipline violation IDENTITY.md § Cloudflare operations exists to prevent.
117
+ When a reset or patch command exits non-zero, report the failure with the exact output. Cite the relevant section above for the next action. Do not chain alternative recovery attempts — the agent's job ends at the boundary of a sanctioned surface, and improvisation outside that boundary is the discipline violation `SKILL.md` § "Tool discipline" exists to prevent.
@@ -1,170 +1,55 @@
1
1
  ---
2
2
  name: setup-tunnel
3
- description: Cloudflare Tunnel operations — setup, diagnose, reset, dashboard guidance. Every path runs through one of four sanctioned surfaces; the agent never improvises outside them.
3
+ description: Cloudflare Tunnel setup, diagnosis, and reset the agent drives cloudflared directly via Bash, following the manual runbook step by step, and proves success with an external HTTP 200.
4
4
  ---
5
5
 
6
6
  # Cloudflare operations
7
7
 
8
- Every Cloudflare task — setup, diagnosis, reset, DNS edit — lands on exactly one of four sanctioned surfaces. The agent's job is to pick the right surface, collect the inputs it needs, invoke it, and relay the structured result verbatim.
8
+ Every Cloudflare task — setup, diagnosis, reset, DNS edit — is driven by the agent invoking `cloudflared` directly via the Bash tool, following the numbered steps in `references/manual-setup.md`. There are no Cloudflare MCP tools; the plugin registers none. There is no shell-script wrapper, no state machine, no orchestrator. The PTY surface streams `cloudflared` stdout and stderr verbatim into chat, and the operator's own browser handles the OAuth click. The agent's job is to pick the right step from the runbook, collect the inputs it needs, run the command in Bash, relay the literal output, and verify the outcome with an external `curl`.
9
9
 
10
- There are no Cloudflare MCP tools. The plugin registers none. The only agent-facing surfaces for Cloudflare work are:
10
+ ## Outcome contract what "done" means
11
11
 
12
- 1. **Autonomous path**run `setup-tunnel.sh` via the Bash tool.
13
- 2. **Manual fallback** — quote steps from `references/manual-setup.md`.
14
- 3. **Reset path** — run `reset-tunnel.sh` via Bash, then follow `references/reset-guide.md`.
15
- 4. **Dashboard guidance** — relay click-paths from `references/dashboard-guide.md`.
12
+ A Cloudflare setup is done when, and only when, `curl -I https://<hostname>` issued from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`) and the response is surfaced verbatim in chat. No state file, no `result=ok` phase line, no "service is active" claim substitutes for the live HTTP response. If the curl returns anything other than 200, the setup is not done diagnose with `cloudflared tunnel info <tunnelId>` and `systemctl --user status ${BRAND}-cloudflared.service` and recover per `references/manual-setup.md`.
16
13
 
17
- Any Cloudflare action outside these four surfaces is a discipline violation — see § Tool discipline below.
14
+ ## Inputs
18
15
 
19
- ---
20
-
21
- ## 1. Autonomous path — `setup-tunnel.sh`
22
-
23
- Use this when the operator wants Cloudflare set up (or re-set up) end-to-end on the device. The script handles OAuth login, tunnel creation, DNS routing for each subdomain, config.yml + tunnel.state, and dispatches the `${BRAND}.service` restart to a transient `systemd-run` unit — all in one invocation. The restart fires a few seconds after the script exits so the script does not kill its own cgroup when invoked via the Bash tool; the chat UI receives a `server_shutdown` SSE frame and reconnects automatically. Post-restart hostname verification is out of scope for the script (connector is not up when the script exits) — verify via the next admin turn or manually with `curl -I https://<hostname>`. Apex hostnames cannot be routed by the CLI; when one is passed, the script prints an `ACTION REQUIRED` block naming the exact dashboard record to edit.
24
-
25
- Step 1's OAuth flow is a state machine over two observable variables: the brand-scoped cert path (`${CFG_DIR}/cert.pem`) and the OAuth-default cert path (`~/.cloudflared/cert.pem`). When the brand-scoped cert is missing but the default-path cert is present from any prior partial run, the wrapper promotes it (`mv`) and emits `step=oauth-login result=ok reason=cert-promoted-from-default-path` without re-spawning cloudflared. When both are missing, the wrapper spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and prints it on its own stdout as `OAUTH_URL: <url>` plus a `step=oauth-url-extracted url_extracted=1` phase line. The admin UI's `ActionLogPanel` renders that `OAUTH_URL` line as a clickable Authorize link with `target=_blank`, opening a new tab in the operator's own browser — the canonical operator-visible OAuth surface. The wrapper does not spawn a browser of its own; cloudflared's OAuth callback polls Cloudflare server-to-server and writes `~/.cloudflared/cert.pem` regardless of which browser completed the Authorize click. The wrapper's cert-poll (180 s budget) picks the cert up and `mv`s it to the brand-scoped path. There is no CDP auto-click, no DOM matcher, no consent-page driver — the wrapper's job is to faithfully relay `cloudflared tunnel login` and surface the URL for the operator to click.
26
-
27
- **Operator-visible-surface doctrine.** The operator's visible surface is their local browser — the one already showing the admin UI. The brand VNC iframe is a derived, optional surface that the operator may or may not have open. Any agent claim that ties a UI outcome to a Pi-side surface (a brand VNC chromium tab, a `:N` display) is wrong by construction — the operator may not be watching that surface. Surfaces the agent can trust as operator-visible: the admin chat itself, the admin UI's ActionLogPanel banner, and links the operator clicked from either of those two. Everything else is best-effort.
28
-
29
- ### How inputs reach the script
30
-
31
- The agent collects inputs in plain chat, then invokes the script via Bash. Four inputs are needed: admin FQDN, optional public FQDN, optional apex FQDN, and the admin password. Ask in a single combined message when all four are already known; ask one question at a time when anything is ambiguous.
32
-
33
- Sequence:
34
-
35
- 1. **Set the admin password** before invoking the script. Use the same endpoint onboarding uses — `curl -X POST http://127.0.0.1:${PORT}/api/remote-auth/set-password -H 'Content-Type: application/json' -d '{"password":"…"}'` — where `${PORT}` is read from `brand.json`.
36
- 2. **(Optional) enumerate existing tunnels** when the operator wants to pick from the logged-in account rather than create a new one. Invoke `cloudflared tunnel list --output json` via Bash, present the result as a numbered list, and let the operator type the number (use that tunnel's `id`) or a fresh name.
37
- 3. **Invoke the script** with `~/setup-tunnel.sh <brand> <port> <admin-fqdn> [<public-fqdn>] [<apex-fqdn>]`. Pass either `TUNNEL_ID=<uuid>` (operator selected) or `TUNNEL_NAME=<name>` (operator named a new one) in env — exactly one. `STREAM_LOG_PATH` and `ACCOUNT_DIR` are resolved from `brand.json` and the `${HOME}/.${BRAND}/` layout.
38
- 4. **Alias-domain classification.** For each non-admin hostname that does not start with `public.`, append it to `~/{configDir}/alias-domains.json` so `isPublicHost()` treats it as public. The platform server watches that file (`watchFile`, ~2 s pickup) — no restart needed. Use this idempotent Bash command per hostname (HOST is the FQDN, FILE is `~/.${BRAND}/alias-domains.json`):
39
-
40
- ```bash
41
- FILE=~/.${BRAND}/alias-domains.json
42
- mkdir -p "$(dirname "$FILE")"
43
- [ -s "$FILE" ] || echo '[]' > "$FILE"
44
- jq --arg h "$HOST" '. + [$h] | unique' "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
45
- ```
46
-
47
- Repeated calls with the same hostname are a no-op (the `| unique` filter dedupes). Mirrors the in-process `addAliasDomain()` helper at `platform/ui/app/lib/alias-domains.ts`.
48
-
49
- ### Invocation shape (reference)
50
-
51
- ```
52
- ~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]
53
- ```
54
-
55
- Example ({{productName}} on `maxy.bot` with a public subdomain and the `maxy.chat` apex):
56
-
57
- ```
58
- ~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot maxy.chat
59
- ```
60
-
61
- ### Optional SSH and SMB ingress
62
-
63
- The same script also wires the off-LAN SSH and SMB ingress when the
64
- corresponding hostnames are passed via environment variables (NOT positional
65
- argv — the positional contract is preserved for the form/endpoint caller):
66
-
67
- ```
68
- SSH_HOSTNAME=ssh.maxy.bot \
69
- SMB_HOSTNAME=smb.maxy.bot \
70
- OPERATOR_EMAIL=joel@example.com \
71
- ~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot
72
- ```
73
-
74
- Behaviour:
75
-
76
- - HTTPS hostnames are routed and config.yml rewritten FIRST. SSH and SMB
77
- DNS routes happen in a second pass; a failure on SSH or SMB emits
78
- `[tunnel-install] {ssh,smb}-ingress-deferred` and leaves the HTTPS
79
- ingress durable (no rollback, no `exit 1`).
80
- - `SSH_HOSTNAME` adds an ingress entry `service: ssh://localhost:22`.
81
- - `SMB_HOSTNAME` adds `service: tcp://localhost:445`, gated's
82
- Samba stanza being present in `/etc/samba/smb.conf`. Absent stanza →
83
- `[tunnel-install] smb-ingress-skipped reason=samba-not-provisioned` and
84
- the SMB pass is skipped entirely.
85
- - Re-run with env vars unset rehydrates `sshHostname` / `smbHostname` from
86
- `tunnel.state` so previously configured ingress is not silently dropped.
87
- - The Cloudflare Zero Trust Access policy that gates these hostnames is
88
- authored by the operator in the dashboard — the script prints an
89
- `ACTION REQUIRED` click-path with the resolved hostnames and operator
90
- email (CF API is banned per `feedback_cf_api_total_eradication`). Phase
91
- lines: `[tunnel-install] ssh-access-policy-required` and
92
- `[tunnel-install] smb-access-policy-required` (named `-required`, not
93
- `-set`, because the script does not create the policy).
94
- - Dry-run: `SETUP_TUNNEL_DRY_RUN=1` short-circuits before any cloudflared
95
- mutation and prints the rendered `config.yml` and `tunnel.state` so the
96
- operator can preview changes.
16
+ Four inputs come from the operator in chat: the admin FQDN, optional public FQDN, optional apex FQDN, and the admin password. `BRAND` is derived from disk — never hardcoded — with `BRAND=$(jq -r .hostname ~/.<configDir>/brand.json)`. The same install can host different brands on different ports; `brand.json.hostname` is the only authoritative source.
97
17
 
98
- The YAML and JSON rendering live in a pure Node helper at
99
- `platform/plugins/cloudflare/scripts/tunnel-ingress.ts`, unit-tested under
100
- `scripts/__tests__/tunnel-ingress.test.ts` and invoked from the shell via
101
- `node --experimental-strip-types`.
18
+ ## Execution
102
19
 
103
- The agent invokes the script directly via the Bash tool there is no form, no endpoint relay. Stream the script's stdout into chat verbatim as it arrives; if an `ACTION REQUIRED` block appears, quote it exactly the operator needs the specific dashboard instructions it contains.
20
+ The agent reads `references/manual-setup.md` and executes its steps with Bash. Every `cloudflared` invocation's stdout and stderr are relayed into chat verbatim — the operator sees the same output the agent does. The OAuth URL printed by `cloudflared tunnel login` is linkified by the native PTY; the operator clicks it in their own browser. The agent does not spawn a browser, automate the Cloudflare consent page, or drive the dashboard via Playwright or Chrome DevTools.
104
21
 
105
- ### Narrating OAuth progress
22
+ Mutations the agent performs from the runbook (each one only after reading the relevant step):
106
23
 
107
- The script tees structured phase lines into the stream log (`[setup-tunnel] step=<phase> <key=value …>`). The admin UI renders the `OAUTH_URL` stdout line as a clickable Authorize link directly above the log panel; the operator clicks it and authorizes in a new tab of their own browser. The agent's narration job is to let that link do the work and not claim where any tab is open.
24
+ - `cloudflared tunnel login` emits the OAuth URL; cert lands at `~/.cloudflared/cert.pem` after the operator authorises.
25
+ - `cloudflared tunnel --origincert <cert> create <name>` — produces `<tunnelId>.json` credentials.
26
+ - Write `~/.${BRAND}/cloudflared/config.yml` (ingress block per the runbook).
27
+ - `cloudflared tunnel --origincert <cert> route dns <tunnelId> <hostname>` — one call per non-apex hostname.
28
+ - For non-admin hostnames not starting with `public.`, append to `~/.${BRAND}/alias-domains.json` so `isPublicHost()` treats them as public.
29
+ - Install + start the `${BRAND}-cloudflared.service` user unit (`systemctl --user daemon-reload && systemctl --user enable --now ${BRAND}-cloudflared.service`).
108
30
 
109
- - **When `step=oauth-url-extracted url_extracted=1` appears,** the URL is already surfaced as a clickable link in the operator's chat. The agent says one short line — "click the Authorize link and I'll pick up the cert once you've authorised" — and waits. No claim about which device or screen the link opens on; the operator's browser handles that.
110
- - **When `step=oauth-login result=ok reason=cert-promoted-from-default-path` appears,** a prior run already completed OAuth — Step 1 short-circuits to the existing cert. No operator action is needed.
111
- - **When `step=oauth-login result=error reason=<token>` appears,** the agent restates the literal `reason=…` and any `last_line=…` field and stops per the discipline rule below. The reasons that can fire on this path are `cloudflared-exited-before-url`, `url-not-extracted`, `timeout-waiting-cert`, `cloudflared-exited-no-cert`, and `cert-promote-failed`. `timeout-waiting-cert` specifically means the operator did not click Authorize within 180 s; the remediation is a fresh `~/setup-tunnel.sh` invocation, which will land on the cert-promotion pre-flight if the operator authorised after the timeout.
112
- - **Stream-log `result=ok` on its own steps is not narration evidence.** The agent narrates from operator-action outcomes — link clicked, cert landed — not from script-internal phase ticks.
31
+ After the service is up, the agent runs `curl -I https://<admin-hostname>` and pastes the response. The setup-done claim only fires when a `200` line appears in that response.
113
32
 
114
- ### When the script exits non-zero
33
+ ## Apex hostnames
115
34
 
116
- Relay the script's stdout to the operator verbatim, name the literal exit code, and cite `references/reset-guide.md` for the next action. Do not attempt a second invocation under a different flag combination, a Playwright-driven dashboard inspection, or an alternative `cloudflared` command sequence. The discipline rule below applies.
35
+ The CLI cannot route apex records (e.g. `maxy.chat`). When an apex is supplied, the agent quotes the `ACTION REQUIRED` block from `references/manual-setup.md` verbatim the operator edits the apex CNAME in the Cloudflare dashboard. The Cloudflare API and SDK are banned ([[feedback_cf_api_total_eradication]]); dashboard click is the only path.
117
36
 
118
- When the failure reason is `timeout-waiting-cert` (operator did not click Authorize within the 180 s budget), the page is still on the Pi VNC; the operator can click Authorize there and a fresh `~/setup-tunnel.sh` invocation will complete via the cert-promotion pre-flight (the cert lands in `~/.cloudflared/cert.pem` after consent, and the wrapper's `mv` runs on the next invocation). Do not suggest `~/reset-tunnel.sh` — the cert path is intact and a fresh attempt is the only remediation needed.
37
+ ## Reset
119
38
 
120
- ---
121
-
122
- ## 2. Manual fallback — `references/manual-setup.md`
123
-
124
- Use this when the operator is diagnosing a step that is failing under the script, recovering from a partial state the script does not expect, or working on a device where the scripts are not yet deployed. The runbook covers Steps 0 through 7 with isolated command blocks, success conditions, and troubleshooting per step.
125
-
126
- The agent's role in manual mode is to read the relevant step, quote the commands the operator runs, and relay their output back through the runbook's decision tree. Do not paraphrase the commands — the operator pastes them verbatim.
127
-
128
- Cross-reference: every scripted step in `setup-tunnel.sh` mirrors a numbered step in `references/manual-setup.md`, so an operator debugging a script failure can pick up exactly where the script left off.
129
-
130
- ---
131
-
132
- ## 3. Reset path — `reset-tunnel.sh` + `references/reset-guide.md`
133
-
134
- Use this when the local Cloudflare state is corrupt, the operator wants a fresh start on a known-good cert, or the bound account has been scrubbed or rotated. `reset-tunnel.sh` deletes every tunnel on the brand's Cloudflare account and wipes `${CFG_DIR}`.
135
-
136
- ### Invocation
39
+ When local Cloudflare state is corrupt or the operator wants to start from a known-good cert, follow `references/reset-guide.md`. The agent issues the relevant `cloudflared tunnel delete` and `rm -rf ~/.${BRAND}/cloudflared/` commands in Bash, then re-runs the setup steps above.
137
40
 
138
- ```
139
- ~/reset-tunnel.sh <brand>
140
- ```
41
+ ## Dashboard guidance
141
42
 
142
- Example:
143
-
144
- ```
145
- ~/reset-tunnel.sh maxy
146
- ```
147
-
148
- `reset-tunnel.sh` cannot stop a token-mode connector process or delete stray misrouted CNAMEs in the dashboard. `references/reset-guide.md` names the decision tree (reset vs. patch), the exact `pkill` incantation for token-mode connectors, and the dashboard cleanup paths for stray records.
149
-
150
- ---
151
-
152
- ## 4. Dashboard guidance — `references/dashboard-guide.md`
153
-
154
- Use this when the operator needs to do something only the Cloudflare dashboard can do: sign in, switch accounts, add a site, edit an apex CNAME, verify zone nameservers, delete a tunnel after stopping its replicas. The guide has one numbered click-path per operation. Quote the relevant click-path verbatim — the operator follows it in the browser. The agent does not drive dashboard mutations via Playwright or Chrome DevTools.
155
-
156
- The single exception is `list-cf-domains.sh`, which reads the domains attached to the logged-in account so the agent can present them in chat (e.g. when the operator needs to pick which domain to route a hostname under). The agent invokes the script directly via Bash with `list-cf-domains.sh <brand>` — no route wrapper. The script is deterministic (bash + raw CDP, no LLM in the decision path) and produces only a JSON `string[]` on stdout; no dashboard state is changed. Any dashboard scrape that is not this exact script is forbidden — the agent does not extend this carve-out to new scripts it writes, hypothesises, or finds. Adding a new sanctioned scrape surface requires a code change reviewed as a doctrine change, not an inline agent decision.
157
-
158
- ---
43
+ For operations only the Cloudflare dashboard can do (sign in, switch accounts, add a site, edit an apex CNAME, verify zone nameservers, delete stale CNAMEs), quote the relevant click-path from `references/dashboard-guide.md` verbatim. The operator follows it in their browser. The agent does not drive the dashboard programmatically.
159
44
 
160
45
  ## Tool discipline — binding
161
46
 
162
- When the operator's request touches Cloudflare, the agent's permitted actions are exactly:
163
-
164
- - Invoke `setup-tunnel.sh` or `reset-tunnel.sh` via Bash.
165
- - Quote `references/manual-setup.md`, `references/reset-guide.md`, or `references/dashboard-guide.md`.
166
- - Verify reachability via plain HTTP (`curl -I https://<hostname>`).
47
+ When the operator's request touches Cloudflare, the agent's permitted actions are:
167
48
 
168
- The agent does not drive Cloudflare dashboard mutations via Playwright or Chrome DevTools. The single sanctioned read-only scrape is `list-cf-domains.sh`, invoked directly via Bash the LLM is not in its decision path. No other dashboard-automation surface is permitted; the agent does not generalise this exception to new scripts. The agent does not synthesise `cloudflared` flag combinations from web search or prior training. The agent does not call Cloudflare API or SDK from any language. The agent does not write or mutate `cert.pem`, `tunnel.state`, `config.yml`, or `alias-domains.json` directly — `setup-tunnel.sh` and the operator manage those files.
49
+ - Invoke `cloudflared` via Bash, following the numbered steps in `references/manual-setup.md`.
50
+ - Write `config.yml` and `alias-domains.json` per the runbook.
51
+ - Install and start the brand's cloudflared user service.
52
+ - Quote `references/manual-setup.md`, `references/reset-guide.md`, or `references/dashboard-guide.md` verbatim when guidance is needed.
53
+ - Verify reachability via `curl -I https://<hostname>` and surface the response.
169
54
 
170
- When a sanctioned surface fails, the agent reports the failure with the exact output, cites the recovery step from `references/reset-guide.md`, and stops. Improvisation — "let me try a different flag" or "let me check the dashboard myself" — is the behaviour this rule exists to prevent. See IDENTITY.md § Cloudflare operations for the unconditional form.
55
+ The agent does not call any Cloudflare API or SDK. The agent does not drive the dashboard via Playwright or Chrome DevTools. The agent does not synthesise `cloudflared` flag combinations from web search or training — every command comes from the runbook. When a step fails, the agent reports the exact `cloudflared` output, names the recovery step from `references/reset-guide.md`, and stops. Improvisation — "let me try a different flag" or "let me check the dashboard myself" — is the behaviour this rule exists to prevent.
@@ -1,6 +1,6 @@
1
1
  # Admin Session — restart survival and SDK-resume contract
2
2
 
3
- The admin PIN-gated session-store is the in-memory `Map<sessionKey, Session>` at [`platform/ui/app/lib/claude-agent/session-store.ts`](../../../ui/app/lib/claude-agent/session-store.ts). Every `systemctl --user restart {brand}.service` (notably the one `setup-tunnel.sh` arms 3 s after `step=done` via `systemd-run --on-active=3s`) wipes that Map. This reference documents how an admin session survives the restart without forcing PIN re-entry, and how the SDK conversation chain is preserved across the gap.
3
+ The admin PIN-gated session-store is the in-memory `Map<sessionKey, Session>` at [`platform/ui/app/lib/claude-agent/session-store.ts`](../../../ui/app/lib/claude-agent/session-store.ts). Every `systemctl --user restart {brand}.service` (notably the one the agent arms 3 s after Cloudflare-setup completion via `systemd-run --on-active=3s` to avoid the cgroup trap) wipes that Map. This reference documents how an admin session survives the restart without forcing PIN re-entry, and how the SDK conversation chain is preserved across the gap.
4
4
 
5
5
  ## Signed sessionKey
6
6