@rubytech/create-maxy-code 0.1.109 → 0.1.111

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 (32) hide show
  1. package/dist/index.js +50 -150
  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/neo4j/schema.cypher +6 -3
  6. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  7. package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
  8. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  9. package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +34 -0
  10. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +9 -16
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +7 -12
  13. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -3
  15. package/payload/platform/plugins/cloudflare/references/manual-setup.md +19 -54
  16. package/payload/platform/plugins/cloudflare/references/reset-guide.md +29 -28
  17. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +29 -149
  18. package/payload/platform/plugins/docs/references/admin-session.md +1 -1
  19. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  20. package/payload/platform/plugins/docs/references/cloudflare.md +20 -29
  21. package/payload/platform/plugins/docs/references/platform.md +1 -1
  22. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  23. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  24. package/payload/platform/scripts/check-no-task-id-leaks.mjs +1 -1
  25. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  26. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  27. package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +0 -241
  28. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +0 -98
  29. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +0 -715
  30. package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +0 -107
  31. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +0 -852
  32. package/payload/platform/plugins/cloudflare/scripts/tunnel-ingress.ts +0 -291
@@ -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: `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). 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 native Claude Code PTY linkifies bare URLs so the operator clicks it from chat and authorizes Cloudflare in 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
 
@@ -540,20 +505,20 @@ sudo cloudflared service uninstall
540
505
  If `admin.<yourdomain>` is rendering the public-agent UI, the tunnel is fine but the platform UI is treating the admin hostname as public. The platform UI classifies a host as public when either:
541
506
 
542
507
  - the hostname starts with `public.`, or
543
- - the hostname appears in `${CFG_DIR}/alias-domains.json`.
508
+ - the hostname appears in `${HOME}/.${BRAND}/alias-domains.json` (note: brand root, **not** `${CFG_DIR}` — `alias-domains.json` lives one level above `cloudflared/`, because that's where the platform UI watches).
544
509
 
545
510
  Pre-Task-548 sessions populated this file via the now-deleted `tunnel-add-hostname` MCP tool, which wrote every routed hostname — including `admin.*` — into the alias set. The pollution persists across installs.
546
511
 
547
512
  **Diagnose:**
548
513
 
549
514
  ```
550
- cat "${CFG_DIR}/alias-domains.json"
515
+ cat "${HOME}/.${BRAND}/alias-domains.json"
551
516
  ```
552
517
 
553
518
  If `admin.<yourdomain>` appears in the array, remove it:
554
519
 
555
520
  ```
556
- jq --arg H "admin.<yourdomain>" 'map(select(. != $H))' "${CFG_DIR}/alias-domains.json" > /tmp/alias.json && mv /tmp/alias.json "${CFG_DIR}/alias-domains.json"
521
+ jq --arg H "admin.<yourdomain>" 'map(select(. != $H))' "${HOME}/.${BRAND}/alias-domains.json" > /tmp/alias.json && mv /tmp/alias.json "${HOME}/.${BRAND}/alias-domains.json"
557
522
  ```
558
523
 
559
524
  Replace `<yourdomain>` with your actual domain. The platform UI watches the file — no restart required; the next request to the admin hostname routes to the admin surface.
@@ -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).
@@ -87,16 +86,18 @@ When the operator is unsure which records are stray, have them list the zone's C
87
86
 
88
87
  ### Remove a rogue entry from `alias-domains.json`
89
88
 
90
- `alias-domains.json` controls which hostnames the platform UI classifies as public (serving the public agent) rather than admin. A prior setup flow may have written an `admin.*` hostname into this file by mistake. Remove it manually:
89
+ `alias-domains.json` controls which hostnames the platform UI classifies as public (serving the public agent) rather than admin. It lives at `${HOME}/.${BRAND}/alias-domains.json` the brand root, **not** `${CFG_DIR}` (which is `${HOME}/.${BRAND}/cloudflared`). The platform UI watches the brand root; writing to `${CFG_DIR}/alias-domains.json` is invisible to the watcher.
90
+
91
+ A prior setup flow may have written an `admin.*` hostname into this file by mistake. Remove it manually:
91
92
 
92
93
  ```
93
- cat "${CFG_DIR}/alias-domains.json"
94
+ cat "${HOME}/.${BRAND}/alias-domains.json"
94
95
  ```
95
96
 
96
97
  If `admin.<yourdomain>` is listed, strip it:
97
98
 
98
99
  ```
99
- jq --arg H "admin.<yourdomain>" 'map(select(. != $H))' "${CFG_DIR}/alias-domains.json" > /tmp/alias.json && mv /tmp/alias.json "${CFG_DIR}/alias-domains.json"
100
+ jq --arg H "admin.<yourdomain>" 'map(select(. != $H))' "${HOME}/.${BRAND}/alias-domains.json" > /tmp/alias.json && mv /tmp/alias.json "${HOME}/.${BRAND}/alias-domains.json"
100
101
  ```
101
102
 
102
103
  Replace `<yourdomain>` with the actual domain. The platform UI watches the file and reloads without a restart.
@@ -109,10 +110,10 @@ If a hostname's CNAME is pointing at the wrong tunnel (e.g. after an account swi
109
110
  cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" <hostname>
110
111
  ```
111
112
 
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").
113
+ 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
114
 
114
115
  ---
115
116
 
116
117
  ## Failure discipline
117
118
 
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.
119
+ 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.