@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,175 +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 native Claude Code PTY linkifies bare URLs, so the `OAUTH_URL: <https…>` line surfaces in chat as a clickable link the operator opens in a new tab of their 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 the admin chat PTY itself plus their own local browser. 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 PTY (the script's stdout, including its `OAUTH_URL` line) and pages the operator opened from links rendered in that chat. 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
- 0. **Derive `BRAND` from disk.** `BRAND=$(jq -r .hostname ~/.<configDir>/brand.json)` — never hardcode `maxy` or any literal brand name. The same install can host different brands on different ports; the `brand.json.hostname` field is the only authoritative source. Substituting the wrong literal here cascades into a script writing to the wrong `~/.${BRAND}/` tree and restarting the wrong service.
36
- 1. **Set the admin password only if not configured.** Gate on `[ -s "${HOME}/.${BRAND}/remote-password" ]` first — when the file is non-empty, a password already exists from a prior onboarding run. Skip the set step entirely. Only when the file is missing or empty, prompt the operator for the password and POST it to `curl -X POST http://127.0.0.1:${PORT}/api/remote-auth/set-password -H 'Content-Type: application/json' -d '{"password":"…"}'` (`${PORT}` from `brand.json`). Rotation requires the operator to ask for it explicitly — never re-set on every retry.
37
- 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.
38
- 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. `ACCOUNT_DIR` is resolved from the `${HOME}/.${BRAND}/` layout.
39
- 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`):
40
-
41
- ```bash
42
- FILE=~/.${BRAND}/alias-domains.json
43
- mkdir -p "$(dirname "$FILE")"
44
- [ -s "$FILE" ] || echo '[]' > "$FILE"
45
- jq --arg h "$HOST" '. + [$h] | unique' "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
46
- ```
47
-
48
- 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`.
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.
49
17
 
50
- ### Invocation shape (reference)
18
+ ## Execution
51
19
 
52
- ```
53
- ~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]
54
- ```
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.
55
21
 
56
- Examples placeholders show the substitution shape; the literal `BRAND` always derives from `brand.json.hostname`:
22
+ Mutations the agent performs from the runbook (each one only after reading the relevant step):
57
23
 
58
- ```
59
- BRAND=$(jq -r .hostname ~/.maxy/brand.json) # e.g. yields "maxy"
60
- ~/setup-tunnel.sh "$BRAND" 19200 admin.maxy.bot public.maxy.bot maxy.chat
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`).
61
30
 
62
- BRAND=$(jq -r .hostname ~/.maxy-code/brand.json) # e.g. yields "maxy-code"
63
- ~/setup-tunnel.sh "$BRAND" 19200 admin.maxy.bot public.maxy.bot maxy.chat
64
- ```
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.
65
32
 
66
- ### Optional SSH and SMB ingress
33
+ ## Apex hostnames
67
34
 
68
- The same script also wires the off-LAN SSH and SMB ingress when the
69
- corresponding hostnames are passed via environment variables (NOT positional
70
- argv — the positional contract is preserved for the form/endpoint caller):
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.
71
36
 
72
- ```
73
- SSH_HOSTNAME=ssh.maxy.bot \
74
- SMB_HOSTNAME=smb.maxy.bot \
75
- OPERATOR_EMAIL=joel@example.com \
76
- ~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot
77
- ```
37
+ ## Reset
78
38
 
79
- Behaviour:
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.
80
40
 
81
- - HTTPS hostnames are routed and config.yml rewritten FIRST. SSH and SMB
82
- DNS routes happen in a second pass; a failure on SSH or SMB emits
83
- `[tunnel-install] {ssh,smb}-ingress-deferred` and leaves the HTTPS
84
- ingress durable (no rollback, no `exit 1`).
85
- - `SSH_HOSTNAME` adds an ingress entry `service: ssh://localhost:22`.
86
- - `SMB_HOSTNAME` adds `service: tcp://localhost:445`, gated's
87
- Samba stanza being present in `/etc/samba/smb.conf`. Absent stanza →
88
- `[tunnel-install] smb-ingress-skipped reason=samba-not-provisioned` and
89
- the SMB pass is skipped entirely.
90
- - Re-run with env vars unset rehydrates `sshHostname` / `smbHostname` from
91
- `tunnel.state` so previously configured ingress is not silently dropped.
92
- - The Cloudflare Zero Trust Access policy that gates these hostnames is
93
- authored by the operator in the dashboard — the script prints an
94
- `ACTION REQUIRED` click-path with the resolved hostnames and operator
95
- email (CF API is banned per `feedback_cf_api_total_eradication`). Phase
96
- lines: `[tunnel-install] ssh-access-policy-required` and
97
- `[tunnel-install] smb-access-policy-required` (named `-required`, not
98
- `-set`, because the script does not create the policy).
99
- - Dry-run: `SETUP_TUNNEL_DRY_RUN=1` short-circuits before any cloudflared
100
- mutation and prints the rendered `config.yml` and `tunnel.state` so the
101
- operator can preview changes.
102
-
103
- The YAML and JSON rendering live in a pure Node helper at
104
- `platform/plugins/cloudflare/scripts/tunnel-ingress.ts`, unit-tested under
105
- `scripts/__tests__/tunnel-ingress.test.ts` and invoked from the shell via
106
- `node --experimental-strip-types`.
107
-
108
- 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.
109
-
110
- ### Narrating OAuth progress
111
-
112
- The script prints structured phase lines to stdout (`[setup-tunnel] step=<phase> <key=value …>`) — these stream straight into the PTY chat verbatim. The `OAUTH_URL: <https…>` line is on a line by itself; the native Claude Code terminal renderer linkifies it so the operator clicks the link directly from chat and authorises in their own browser. The agent's narration job is to let that link do the work and not claim where any tab is open.
113
-
114
- - **When `step=oauth-url-extracted url_extracted=1` appears,** the `OAUTH_URL: …` line above it is already clickable in the operator's chat. The agent says one short line — "click the Authorize link above 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.
115
- - **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.
116
- - **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.
117
- - **`result=ok` on a script-internal phase is not narration evidence.** The agent narrates from operator-action outcomes — link clicked, cert landed — not from internal phase ticks.
118
-
119
- ### When the script exits non-zero
120
-
121
- 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.
122
-
123
- 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.
124
-
125
- ---
41
+ ## Dashboard guidance
126
42
 
127
- ## 2. Manual fallback `references/manual-setup.md`
128
-
129
- 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.
130
-
131
- 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.
132
-
133
- 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.
134
-
135
- ---
136
-
137
- ## 3. Reset path — `reset-tunnel.sh` + `references/reset-guide.md`
138
-
139
- 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}`.
140
-
141
- ### Invocation
142
-
143
- ```
144
- ~/reset-tunnel.sh <brand>
145
- ```
146
-
147
- Example:
148
-
149
- ```
150
- ~/reset-tunnel.sh maxy
151
- ```
152
-
153
- `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.
154
-
155
- ---
156
-
157
- ## 4. Dashboard guidance — `references/dashboard-guide.md`
158
-
159
- 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.
160
-
161
- 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.
162
-
163
- ---
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.
164
44
 
165
45
  ## Tool discipline — binding
166
46
 
167
- When the operator's request touches Cloudflare, the agent's permitted actions are exactly:
168
-
169
- - Invoke `setup-tunnel.sh` or `reset-tunnel.sh` via Bash.
170
- - Quote `references/manual-setup.md`, `references/reset-guide.md`, or `references/dashboard-guide.md`.
171
- - Verify reachability via plain HTTP (`curl -I https://<hostname>`).
47
+ When the operator's request touches Cloudflare, the agent's permitted actions are:
172
48
 
173
- 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.
174
54
 
175
- 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
 
@@ -216,7 +216,7 @@ Two endpoints, two surfaces, two restart-survival roles:
216
216
  liveness. `processStartedAt` resets on every brand-service restart;
217
217
  Neo4j probe is bounded to 1 s and reports
218
218
  `conversationDb: 'ok' | 'error'`. Use this to confirm the brand
219
- process came back after `setup-tunnel.sh`'s armed restart.
219
+ process came back after a Cloudflare-setup armed restart.
220
220
  - `GET /api/admin/version` (maxy-edge) — installer / brand version
221
221
  string. Hosted on `maxy-edge.service` so the Software Update modal
222
222
  can read it while the brand service is mid-restart.
@@ -1,6 +1,6 @@
1
1
  # Cloudflare Tunnel — the dashboard is the source of truth
2
2
 
3
- Each installation has its own Cloudflare account. Sign-in is OAuth: the setup script prints the Cloudflare Authorize URL into the admin chat as a clickable link; click it, authorize in the new tab, and `cloudflared` writes `cert.pem` to the brand's config directory. The agent never reads or mutates Cloudflare account state directly — whatever you see in your logged-in dashboard is the single source of truth. When something needs doing on the account side (adding a domain, deleting a stray entry, switching accounts), the agent relays the click-paths; you run them in your browser.
3
+ Each installation has its own Cloudflare account. Sign-in is OAuth: the agent invokes `cloudflared tunnel login` via Bash; the Cloudflare Authorize URL streams into the admin chat PTY and the native terminal renders it as a clickable link. Click it, authorise in your own browser, and `cloudflared` writes `cert.pem` to the brand's config directory. The agent never reads or mutates Cloudflare account state directly — whatever you see in your logged-in dashboard is the single source of truth. When something needs doing on the account side (adding a domain, deleting a stray entry, switching accounts), the agent relays the click-paths; you run them in your browser.
4
4
 
5
5
  ## Identity model
6
6
 
@@ -8,34 +8,25 @@ Each installation has its own Cloudflare account. Sign-in is OAuth: the setup sc
8
8
  |------|--------|
9
9
  | **Product identity** (Maxy vs Real Agent) | `brand.json` (`productName`, `configDir`) — known at install. |
10
10
  | **Cloudflare account identity** | `cert.pem` from OAuth. One account per brand per device. |
11
- | **Domain scope** (which zones the operator can route) | Live Cloudflare dashboard at script-invocation time via `list-cf-domains.sh`, not `brand.json`. Brand identity has no authority over which domains the operator's CF account holds. When the scrape returns an unexpected count (e.g. 1 on a two-zone account), the stream log's per-poll `phase=dom-scrape-poll n=<k> count=<n> domains=[…]` trajectory + the on-disk HTML dump at `~/{configDir}/logs/list-cf-domains-<ts>-count<n>-<mode>-pid<pid>.html` (earlier platform fixes — written on every scrape outcome, not just empty ones) give the operator everything they need to triage the cause without re-running. |
12
- | **CDP port the scrape attaches to** | `brand.json` (`cdpPort`, stamped at install time) — the same brand-scoped port `vnc.sh` binds Chromium to. `list-cf-domains.sh <brand>` requires the brand arg; the helper reads `${MAXY_PLATFORM_ROOT}/config/brand.json` (no silent default). Missing brand arg, missing brand.json, or missing `cdpPort` field each exit 1 with one of three named reasons (`brand-arg-missing`, `brand-config-missing`, `cdp-port-unresolved`); the agent reads the exit-code reason from stderr, names the brand, and stops — retrying would re-fire the same wrongly-resolved spawn. |
13
- | **Local tunnel state** | `~/{configDir}/cloudflared/` — `cert.pem`, `<UUID>.json`, `config.yml`, `tunnel.state`, `alias-domains.json`. |
11
+ | **Domain scope** (which zones the operator can route) | Live Cloudflare dashboard the operator picks the zone in the dashboard during OAuth or names it in chat. The agent does not enumerate zones programmatically. |
12
+ | **Local tunnel state** | `~/{configDir}/cloudflared/` `cert.pem`, `<UUID>.json`, `config.yml`, `alias-domains.json`. |
14
13
 
15
- There is no token-based auth for the operator-owned path (Mode A). To switch Cloudflare accounts, run `reset-tunnel.sh` (which deletes the cert and every tunnel on the current account), then run `setup-tunnel.sh` again — `cloudflared tunnel login` inside the setup script will pick a fresh account when you sign in.
14
+ There is no token-based auth for the operator-owned path (Mode A). To switch Cloudflare accounts, the agent runs the reset flow from `plugins/cloudflare/references/reset-guide.md` (deletes the cert and every tunnel on the current account), then the manual-setup flow again — `cloudflared tunnel login` picks a fresh account when you sign in.
16
15
 
17
16
  ## Setup flow
18
17
 
19
- Ask the agent to set up Cloudflare. The agent confirms the domain is already on your Cloudflare account (if not, it quotes the dashboard click-path — see below) and then collects the inputs in plain chat:
18
+ Ask the agent to set up Cloudflare. The agent confirms the domain is already on your Cloudflare account (if not, it quotes the dashboard click-path — see below) and collects the inputs in plain chat:
20
19
 
21
20
  - **Admin address** — the hostname that will serve the admin chat (e.g. `admin.yourdomain.com`).
22
21
  - **Public address** — optional hostname for the public agent (e.g. `public.yourdomain.com` or `chat.yourdomain.com`).
23
22
  - **Proxy apex** — optional bare-domain hostname (e.g. `yourdomain.com`) that should also serve the public agent.
24
23
  - **Admin password** — the password used to gate remote access to the admin surface.
25
24
 
26
- Once the agent has the four inputs, it sets the admin password via `curl -X POST http://127.0.0.1:${PORT}/api/remote-auth/set-password` (the same endpoint onboarding uses), optionally enumerates existing tunnels via `cloudflared tunnel list --output json` so you can pick from the logged-in account, and then invokes `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]` via the Bash tool with either `TUNNEL_ID=<uuid>` (selected) or `TUNNEL_NAME=<name>` (new) in env. After the script returns, the agent appends each non-`public.*` public or apex hostname to `~/{configDir}/alias-domains.json` so `isPublicHost()` classifies it as public.
25
+ The agent then sets the admin password via `curl -X POST http://127.0.0.1:${PORT}/api/remote-auth/set-password` (same endpoint the local onboarding form uses), and works through `plugins/cloudflare/references/manual-setup.md` Steps 1–7 directly via the Bash tool. `cloudflared`'s stdout streams into the PTY verbatim. The OAuth URL is linkified by the terminal; click it in your own browser to authorise. After the tunnel is up, the agent appends each non-`public.*` public or apex hostname to `~/{configDir}/alias-domains.json` so `isPublicHost()` classifies it as public, and starts the brand's cloudflared user service.
27
26
 
28
- The script runs end-to-end:
27
+ If any step's `cloudflared` invocation exits non-zero, the agent names the literal exit code, surfaces the stderr verbatim, and cites `reset-guide.md` for the next action — no retry under a different flag, no Playwright-driven dashboard inspection.
29
28
 
30
- - `cloudflared tunnel login` OAuth sign-in. The script prints the Cloudflare authorize URL; the admin UI surfaces it as a clickable link above the log. Click the link, pick the account that owns your domain, click Authorize. `cert.pem` lands; the script picks it up via its 180 s cert-poll regardless of which browser tab completed the click.
31
- - Tunnel resolution from operator-supplied identity (operator-selected-tunnel fix). The script requires EXACTLY ONE of `TUNNEL_ID` or `TUNNEL_NAME` in env and exits 1 with a literal error on either-both-set or neither-set. Pre-fix the script derived `${BRAND}-$(hostname -s)` locally; that broke the operator-state-is-authoritative doctrine and silently created orphan tunnels whenever the device hostname changed. Stream log emits `step=tunnel-resolve source=operator-selected|operator-created tunnel_id=… tunnel_name=…` once the UUID is known.
32
- - **Zone pre-flight** — for every non-apex hostname the script queries `1.1.1.1` for the registrable parent's NS records and refuses the whole run if they don't point at Cloudflare. Stream log: `step=zone-preflight result=ok|error zones_on_account=… missing_parent_for=…`. Catches "domain not on Cloudflare"; does not catch "domain on a different Cloudflare account than `cert.pem` is bound to" — that case surfaces later via `tunnel-status`.
33
- - `cloudflared tunnel route dns` for each subdomain hostname. Apex hostnames cannot be routed this way — the script prints an **ACTION REQUIRED** block naming the exact dashboard record to add or edit. Stream log emits `step=route-dns hostname=… tunnel_id=…` before the call and `step=route-dns hostname=… result=ok|apex-skip|error` after; on error the bounded cloudflared stderr (≤400 chars) rides in the same phase line. **The script does not parse cloudflared's stdout** — exit code is the sole decision signal, so all three legitimate cloudflared output shapes (new record, overwrite, idempotent "already configured") are treated as success.
34
- - `config.yml` and `tunnel.state` written under `${CFG_DIR}`.
35
- - `systemctl --user restart ${BRAND}.service` — restarts the platform service so the new tunnel spawns via the service's `ExecStartPre=resume-tunnel.sh`. 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.
36
- - Post-restart verification — `ps -ef | grep '[c]loudflared'` confirms the connector is alive, then `curl -I https://<hostname>` against each subdomain (up to 60 s per host) confirms a non-530 response.
37
-
38
- The agent streams the script's stdout into chat verbatim as it arrives, including any `ACTION REQUIRED` block. If the script exits non-zero, the agent names the literal exit code and cites `reset-guide.md` for the next action — no retry under a different flag, no Playwright-driven dashboard inspection.
29
+ The setup-done claim only fires after the agent runs `curl -I https://<admin-hostname>` from outside the local network and the response shows a `200` line. That HTTP response is the only success terminal.
39
30
 
40
31
  ## Getting a domain on Cloudflare
41
32
 
@@ -49,19 +40,19 @@ Existing website traffic continues to work during and after the switch. Only DNS
49
40
 
50
41
  ## Reset / account switch
51
42
 
52
- Ask the agent to reset Cloudflare. The agent invokes `reset-tunnel.sh` on the device:
43
+ Ask the agent to reset Cloudflare. The agent executes the reset flow from `plugins/cloudflare/references/reset-guide.md`:
53
44
 
54
45
  - Deletes every tunnel on the brand's current Cloudflare account (via the bound cert).
55
46
  - Wipes the brand's `${CFG_DIR}`.
56
- - Exits with the number of tunnels deleted.
47
+ - Stops the brand's cloudflared user service.
57
48
 
58
- The script does **not** stop token-mode connector processes, delete stray misrouted CNAMEs in the dashboard, or touch the platform service. If any of those apply (agent will flag them), the agent guides you through the manual cleanup — `pkill -f 'cloudflared.*tunnel run --token'` on the device, or deleting the stray CNAME in the dashboard.
49
+ The agent does **not** stop token-mode connector processes or delete stray misrouted CNAMEs in the dashboard. If any of those apply, the agent guides you through the manual cleanup — `pkill -f 'cloudflared.*tunnel run --token'` on the device, or deleting the stray CNAME in the dashboard.
59
50
 
60
51
  After reset, run setup again. The fresh `cloudflared tunnel login` will pick whichever Cloudflare account you sign into.
61
52
 
62
53
  ## Manual runbook
63
54
 
64
- When the scripted flow fails partway, or you want to walk through every step yourself, there's a step-by-step runbook at `plugins/cloudflare/references/manual-setup.md`. The runbook mirrors `setup-tunnel.sh` verbatim every scripted step corresponds to a numbered runbook step with isolated command blocks, success conditions, and troubleshooting. Pick up the runbook at whichever step the script failed in.
55
+ The step-by-step runbook at `plugins/cloudflare/references/manual-setup.md` is the contract the agent follows. It is also what an operator runs by hand when needed every numbered step is an isolated `cloudflared` command block with success conditions and troubleshooting.
65
56
 
66
57
  ## Dashboard operations the CLI cannot do
67
58
 
@@ -71,17 +62,17 @@ The CLI cannot add a domain, switch accounts, edit an apex CNAME, or delete stra
71
62
 
72
63
  ### Tunnel won't start
73
64
 
74
- Ask the agent to check. The agent reads `systemctl --user status ${BRAND}.service` and `~/{configDir}/cloudflared/logs/cloudflared.log`. Common states:
65
+ Ask the agent to check. The agent reads `systemctl --user status ${BRAND}-cloudflared.service` and the cloudflared log under `~/{configDir}/cloudflared/`. Common states:
75
66
 
76
- - **No cloudflared process running** — the service's `ExecStartPre=resume-tunnel.sh` exited cleanly but did not spawn the connector. Usually means `tunnel.state` is missing or `config.yml` was written with empty variables. The agent will re-run `setup-tunnel.sh`.
77
- - **`tunnel not found`** — the UUID in `config.yml` does not match any tunnel on the currently-bound account. Usually follows an account switch that didn't reset local state. The agent invokes `reset-tunnel.sh` and then a fresh setup.
67
+ - **No cloudflared process running** — the cloudflared service exited or never started. The agent runs the manual-setup flow to re-issue tunnel creation.
68
+ - **`tunnel not found`** — the UUID in `config.yml` does not match any tunnel on the currently-bound account. Usually follows an account switch that didn't reset local state. The agent runs the reset flow and then a fresh setup.
78
69
 
79
70
  ### URL returns 530
80
71
 
81
72
  DNS propagation or account mismatch. Wait 30–60 seconds and retry first. If the 530 persists:
82
73
 
83
- - The domain may be on a Cloudflare account different from the one `cert.pem` is bound to — re-running `setup-tunnel.sh` re-validates.
84
- - The UDP buffer for QUIC may be undersized on this device — check `cloudflared.log` for `failed to sufficiently increase receive buffer size`.
74
+ - The domain may be on a Cloudflare account different from the one `cert.pem` is bound to — the agent re-runs the manual setup steps to re-validate.
75
+ - The UDP buffer for QUIC may be undersized on this device — check the cloudflared log for `failed to sufficiently increase receive buffer size`.
85
76
 
86
77
  ### URL returns connection refused
87
78
 
@@ -104,8 +95,8 @@ The most common cause is wrong nameservers on the domain. The domain must use Cl
104
95
 
105
96
  ## What the agent does and does not do
106
97
 
107
- **Does:** invokes `setup-tunnel.sh` / `reset-tunnel.sh` via Bash, quotes click-paths from the reference files verbatim, verifies external reachability with `curl -I`.
98
+ **Does:** invokes `cloudflared` directly via Bash, following `plugins/cloudflare/references/manual-setup.md` step by step; quotes click-paths from the reference files verbatim; verifies external reachability with `curl -I` and surfaces the response.
108
99
 
109
- **Does not:** drive the Cloudflare dashboard via Playwright, synthesise alternative `cloudflared` flag sequences, call any Cloudflare API or SDK, write or edit `cert.pem` / `tunnel.state` / `config.yml` / `alias-domains.json` directly.
100
+ **Does not:** drive the Cloudflare dashboard via Playwright, synthesise alternative `cloudflared` flag sequences not in the runbook, call any Cloudflare API or SDK, write or edit `cert.pem` / `config.yml` directly outside the runbook's instructions.
110
101
 
111
- When a script or command fails, the agent reports the failure and cites the relevant recovery step. It does not improvise.
102
+ When a command fails, the agent reports the failure and cites the relevant recovery step. It does not improvise.
@@ -111,7 +111,7 @@ The Data search panel ranks results by combining vector similarity with keyword
111
111
  Both flows run on the native Claude Code PTY surface in admin chat (Task 287). There is no in-app upgrade modal and no Cloudflare setup form — the agent invokes the relevant Bash command directly and its stdout streams into chat verbatim.
112
112
 
113
113
  - **Software update.** Re-run the installer (`npx -y @rubytech/create-<brand>@latest`) from a shell; HeaderMenu's version row turns sage when `installed === latest`.
114
- - **Cloudflare setup.** Operator asks in chat; the agent invokes `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]` via the Bash tool. The script's `[setup-tunnel] step=<phase> …` phase lines and cloudflared's own stdout stream into the PTY; the `OAUTH_URL: <url>` line is linkified by the terminal so the operator clicks it and authorises Cloudflare in their own browser.
114
+ - **Cloudflare setup.** Operator asks in chat; the agent invokes `cloudflared` directly via the Bash tool, following the numbered steps in `plugins/cloudflare/references/manual-setup.md`. cloudflared's stdout and stderr stream into the PTY; the OAuth URL printed by `cloudflared tunnel login` is linkified by the terminal so the operator clicks it and authorises Cloudflare in their own browser.
115
115
 
116
116
  **Mid-turn stream-drop banners.** If a chat turn ends abruptly the bubble shows one of two messages depending on what actually happened. You see "Server is restarting — reconnect will happen automatically." only when the app server itself emits the restart signal — typically during a Software Update or a Cloudflare setup that re-launches the brand service. You see "Lost connection — retrying." when your browser's connection to the Pi dropped mid-stream while the server was still up — typically a flaky Wi-Fi moment or the tunnel hiccupping. Either way the chat resumes once the connection is back; the previously-rendered messages stay on screen so you don't lose context.
117
117
 
@@ -152,7 +152,7 @@ After this, every `console.error("[your-tool]...")` from any tool in the plugin
152
152
 
153
153
  **How the tee decides which file to write to:** the platform sets `STREAM_LOG_PATH` as an environment variable on every MCP server spawn, pointing to the conversation-scoped stream log. The MCP server does not know about conversations — it just trusts `STREAM_LOG_PATH`. Multiple concurrent conversations produce multiple concurrent MCP server processes, each teeing to its own file; no cross-conversation leakage.
154
154
 
155
- **Bash scripts stream straight into the PTY.** Maxy Code's admin and public chat run on the native Claude Code PTY (Task 287). The per-conversation server-side stream log that the retired web-UI dispatcher tailed is gone; agent-invoked shell scripts (`setup-tunnel.sh`, `reset-tunnel.sh`, `list-cf-domains.sh`) print `[<scope>] <key>=<value>` phase lines directly to stdout, which the PTY renders in chat verbatim.
155
+ **Bash commands stream straight into the PTY.** Maxy Code's admin and public chat run on the native Claude Code PTY (Task 287). The per-conversation server-side stream log that the retired web-UI dispatcher tailed is gone; agent-invoked Bash commands (including direct `cloudflared` invocations for Cloudflare setup Task 288) print their stdout and stderr directly, and the PTY renders the output in chat verbatim.
156
156
 
157
157
  **Retrieve MCP diagnostic lines for a conversation:**
158
158
 
@@ -190,7 +190,7 @@ If the initial Cloudflare login fails during setup, {{productName}} will fall ba
190
190
  Both flows run on the native Claude Code PTY surface in admin chat (Task 287). The retired action-runner / terminal-modal troubleshooting sections that lived here have been removed because those surfaces no longer exist; failures now manifest as plain stderr from the agent-invoked Bash command, visible in chat.
191
191
 
192
192
  - **Software update.** Re-run `npx -y @rubytech/create-<brand>@latest` from a shell; if the installer fails, its stdout is the diagnostic record. HeaderMenu turns sage when `installed === latest`.
193
- - **Cloudflare setup.** The agent invokes `~/setup-tunnel.sh` via Bash. Failures surface as the script's own `[setup-tunnel] step=… result=error reason=…` lines in chat plus a non-zero exit. Recovery paths live in the cloudflare plugin's `references/reset-guide.md` and `references/manual-setup.md`.
193
+ - **Cloudflare setup.** The agent invokes `cloudflared` directly via Bash, following the cloudflare plugin's `references/manual-setup.md`. Failures surface as cloudflared's literal stderr plus a non-zero exit. Recovery paths live in `references/reset-guide.md` and `references/manual-setup.md`.
194
194
 
195
195
  ## Orphan Account Directory Archived to `.trash/`
196
196
 
@@ -2,7 +2,7 @@
2
2
  // Build-time gate: rejects `(Task NNN)` citations in operator-visible production
3
3
  // strings. Citations are allowed in inline comments and test titles/bodies only.
4
4
  //
5
- // Live repro that motivated the gate: setup-tunnel.sh on realagent 192.168.88.8
5
+ // Live repro that motivated the gate: a setup script on realagent 192.168.88.8
6
6
  // emitted `Re-run the installer to provision Chromium.` to the
7
7
  // operator's chat surface — the citation made the operator chase task
8
8
  // history rather than execute the verb. Operator surfaces strip citations;
@@ -10,7 +10,7 @@ Your job is to do useful work for the operator. How useful you can be depends on
10
10
 
11
11
  ## Before you speak
12
12
 
13
- Before your first message, do reconnaissance. Treat your starting knowledge as whatever your tools can surface, not what the system prompt names. Search the memory graph for what's known about the operator and their business, read the profile, check today's calendar, active projects, open work, and recent conversations. Infer the domain from the plugins and skills installed; they were chosen for a reason and are evidence of what this operation does.
13
+ Before your first message, do reconnaissance. Treat your starting knowledge as whatever your tools can surface, not what the system prompt names. Search the memory graph for what's known about the operator and their business, read the profile, check today's calendar, active projects, open work, and recent conversations. Infer the domain from the plugins and skills installed; they were chosen for a reason and are evidence of what this operation does. Check whether an upgrade is available for this install (the upgrade skill describes the lookup); if so, offer it in your first message.
14
14
 
15
15
  Then lead with what you found. When you have enough knowledge of the operator and their business to make a useful proposal, follow with one suited to where they are in their day; otherwise, gather more before proposing. Ask only the questions you genuinely could not answer through investigation. You are expected to drive the day, not to solicit instructions for it.
16
16
 
@@ -23,7 +23,7 @@ These three rules win when anything else in this prompt conflicts with them.
23
23
  Each domain has a small set of tools and, where it exists, a skill that drives the multi-step flow. Match the brief to the domain, load the skill if one is named, and run the tools the skill prescribes.
24
24
 
25
25
  - **WhatsApp setup or config:** load `skill-load skillName=connect-whatsapp` for QR pairing and admin-phone setup; load `skill-load skillName=manage-whatsapp-config` for DM/group policies and admin-phone management. The skills carry the per-phase flow.
26
- - **Cloudflare tunnel:** load `skill-load skillName=setup-tunnel`. The skill names the four sanctioned surfaces (`setup-tunnel.sh`, `reset-tunnel.sh`, `manual-setup.md`, `dashboard-guide.md`) and the inputs to collect.
26
+ - **Cloudflare tunnel:** load `skill-load skillName=setup-tunnel`. The skill drives `cloudflared` directly via Bash following `references/manual-setup.md`, `references/reset-guide.md`, and `references/dashboard-guide.md`. There is no shell-script wrapper; PTY stdout is the only surface.
27
27
  - **Every other domain** (scheduling, Telegram, email, Outlook, contacts, browser, platform admin) runs through the tool descriptions injected into your system prompt. The rules below apply across these domains regardless of which tool is invoked.
28
28
 
29
29
  ## Cross-domain rules