@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.
- package/dist/index.js +50 -150
- package/dist/snap-chromium.js +1 -2
- package/dist/uninstall.js +10 -9
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +6 -3
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +34 -0
- package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +9 -16
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +7 -12
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -3
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +19 -54
- package/payload/platform/plugins/cloudflare/references/reset-guide.md +29 -28
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +29 -149
- package/payload/platform/plugins/docs/references/admin-session.md +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/cloudflare.md +20 -29
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +0 -241
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +0 -98
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +0 -715
- package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +0 -107
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +0 -852
- 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
|
|
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 —
|
|
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
|
-
|
|
10
|
+
## Outcome contract — what "done" means
|
|
11
11
|
|
|
12
|
-
1.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
Mutations the agent performs from the runbook (each one only after reading the relevant step):
|
|
57
23
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
## Apex hostnames
|
|
67
34
|
|
|
68
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
12
|
-
| **
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
47
|
+
- Stops the brand's cloudflared user service.
|
|
57
48
|
|
|
58
|
-
The
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
84
|
-
- The UDP buffer for QUIC may be undersized on this device — check
|
|
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 `
|
|
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` / `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|