@rubytech/create-maxy-code 0.1.109 → 0.1.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +11 -125
- package/dist/snap-chromium.js +1 -2
- package/dist/uninstall.js +10 -9
- package/package.json +1 -1
- 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 +16 -51
- package/payload/platform/plugins/cloudflare/references/reset-guide.md +24 -25
- 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/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,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cloudflare
|
|
3
|
-
description: Cloudflare Tunnel operations — setup, reset, dashboard guidance. Zero agent-facing MCP tools; every operation is
|
|
3
|
+
description: Cloudflare Tunnel operations — setup, reset, dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` directly via Bash or quoting a dashboard click-path the operator performs themselves.
|
|
4
4
|
tools: []
|
|
5
5
|
mcp-manifest: skip
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Cloudflare Tunnel
|
|
9
9
|
|
|
10
|
-
Each installation has its own Cloudflare account. The operator signs in with OAuth via `cloudflared tunnel login` (
|
|
10
|
+
Each installation has its own Cloudflare account. The operator signs in with OAuth via `cloudflared tunnel login` (issued by the agent through Bash); `cloudflared` writes `cert.pem` to the brand-scoped config directory. The Cloudflare dashboard is the single source of truth for which domains, addresses, and tunnels exist; the plugin never reads or mutates account state via any API path — only `cloudflared` CLI shell-outs the agent runs, and DNS + HTTPS probes against public surfaces.
|
|
11
11
|
|
|
12
12
|
## When to activate
|
|
13
13
|
|
|
@@ -25,41 +25,34 @@ Each installation has its own Cloudflare account. The operator signs in with OAu
|
|
|
25
25
|
|
|
26
26
|
## Operator-facing surface
|
|
27
27
|
|
|
28
|
-
The plugin registers no agent-facing MCP tools. Every Cloudflare operation is
|
|
29
|
-
|
|
30
|
-
### Scripts
|
|
31
|
-
|
|
32
|
-
| Script | Purpose |
|
|
33
|
-
|---|---|
|
|
34
|
-
| [`scripts/setup-tunnel.sh`](scripts/setup-tunnel.sh) | Autonomous end-to-end setup: OAuth login, tunnel resolve (operator-supplied identity), DNS route, config + state, service restart, post-restart verification. Invocation: `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]`. Required env: `ACCOUNT_DIR`, AND exactly one of `TUNNEL_ID` (operator selected an existing tunnel — the agent enumerates them via `cloudflared tunnel list --output json` and presents the list in chat) or `TUNNEL_NAME` (operator typed a name to create). Apex hostnames print an `ACTION REQUIRED` block for the dashboard record the CLI cannot create. Step 1 (wrappers faithfully relay third-party CLI) spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and prints it as `OAUTH_URL: <url>` on its own stdout — the native Claude Code PTY linkifies bare URLs so the operator clicks it from chat and authorizes Cloudflare in their own browser. The script does not spawn a browser of its own; cloudflared's OAuth callback writes `~/.cloudflared/cert.pem` regardless of which browser completed the Authorize click. 180 s budget with a 2-second `step=oauth-login result=awaiting-cert` heartbeat. No CDP auto-click, no DOM matcher. |
|
|
35
|
-
| [`scripts/reset-tunnel.sh`](scripts/reset-tunnel.sh) | Deletes every tunnel on the brand's CF account and wipes `${CFG_DIR}`. Does not touch the platform service, stray CNAMEs, or token-mode connectors — those require dashboard cleanup or `pkill`. Invocation: `~/reset-tunnel.sh <brand>`. No polling blocks — every long-wait is bounded by `cloudflared`'s network round-trip, so no heartbeat contract applies. |
|
|
28
|
+
The plugin registers no agent-facing MCP tools. Every Cloudflare operation is the agent invoking `cloudflared` directly via Bash, following the numbered steps in `references/manual-setup.md`, with the cloudflared stdout/stderr streamed verbatim into the PTY chat. The OAuth URL printed by `cloudflared tunnel login` is linkified by the native PTY; the operator clicks it in their own browser. There is no shell-script wrapper, no orchestrator state machine, no MCP-tool surface.
|
|
36
29
|
|
|
37
30
|
### Skills
|
|
38
31
|
|
|
39
32
|
| Skill | Purpose |
|
|
40
33
|
|---|---|
|
|
41
|
-
| [setup-tunnel/SKILL.md](skills/setup-tunnel/SKILL.md) | Names the
|
|
34
|
+
| [setup-tunnel/SKILL.md](skills/setup-tunnel/SKILL.md) | Names the outcome contract (external HTTP 200 to the configured hostname), the inputs to collect, the runbook to execute, and the tool-discipline rule that binds the agent. |
|
|
42
35
|
|
|
43
36
|
### References
|
|
44
37
|
|
|
45
38
|
| Reference | Topics |
|
|
46
39
|
|---|---|
|
|
47
|
-
| [manual-setup.md](references/manual-setup.md) | Step-by-step
|
|
40
|
+
| [manual-setup.md](references/manual-setup.md) | Step-by-step runbook — Steps 0–7 with isolated `cloudflared` command blocks. The agent reads the relevant step before issuing each command. |
|
|
48
41
|
| [dashboard-guide.md](references/dashboard-guide.md) | Click-paths for the operations only the Cloudflare dashboard can perform — sign in, switch accounts, add a site, edit an apex CNAME, verify nameservers, delete a tunnel, manage CNAME records. |
|
|
49
42
|
| [reset-guide.md](references/reset-guide.md) | Decision tree for reset vs. patch, the exact `pkill` incantation for token-mode connectors, and the dashboard cleanup paths for stray records and rogue entries. |
|
|
50
43
|
|
|
51
44
|
The agent loads these references on demand via `plugin-read` as the conversation requires. They are not auto-injected into the system prompt.
|
|
52
45
|
|
|
53
|
-
###
|
|
46
|
+
### Success contract
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
The setup-done claim only fires when `curl -I https://<hostname>` issued from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`) and the response is surfaced verbatim in chat. No state file, no service-active claim, no `cloudflared` exit code substitutes for the live HTTP response. When the curl returns anything else, the agent diagnoses with `cloudflared tunnel info <tunnelId>` and `systemctl --user status ${BRAND}-cloudflared.service`, cites the relevant step in `references/reset-guide.md`, and stops.
|
|
56
49
|
|
|
57
50
|
## Identity model
|
|
58
51
|
|
|
59
52
|
- **Product identity** (Maxy vs Real Agent) — known from `brand.json` (`productName`, `configDir`).
|
|
60
53
|
- **Cloudflare account identity** — `cert.pem` from OAuth. One account per brand per device.
|
|
61
|
-
- **Account binding drift** — `~/{configDir}/cloudflared/account-binding.json` is a historical drift marker. Reset
|
|
54
|
+
- **Account binding drift** — `~/{configDir}/cloudflared/account-binding.json` is a historical drift marker. Reset by `rm -rf ~/.${BRAND}/cloudflared/` per `references/reset-guide.md` when switching accounts.
|
|
62
55
|
|
|
63
56
|
## Discipline
|
|
64
57
|
|
|
65
|
-
Loaded into IDENTITY.md § Cloudflare operations at install time. The short form: the agent's permitted surfaces are the
|
|
58
|
+
Loaded into IDENTITY.md § Cloudflare operations at install time. The short form: the agent's permitted surfaces are direct `cloudflared` invocations via Bash following the runbook, the three reference files, and `curl -I` reachability checks — everything else (Playwright, WebSearch-for-CF-recipes, Cloudflare API / SDK, ad-hoc `cloudflared` flag invention not in the runbook) is out of bounds. When a step fails, the agent reports the exact `cloudflared` output, cites the recovery step from `references/reset-guide.md`, and stops.
|
|
@@ -2,18 +2,13 @@ import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
|
|
|
2
2
|
initStderrTee("cloudflare");
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
//
|
|
6
|
-
// Cloudflare operation is
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// See platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md and
|
|
13
|
-
// platform/plugins/cloudflare/references/ for the operator-facing
|
|
14
|
-
// surface. `lib/cloudflared.ts` and `lib/setup-orchestrator.ts` are
|
|
15
|
-
// retained as private implementation layers that nothing currently
|
|
16
|
-
// imports; a follow-up task will delete them once that is confirmed.
|
|
5
|
+
// The Cloudflare plugin exposes zero agent-facing tools. Every
|
|
6
|
+
// Cloudflare operation is the agent invoking `cloudflared` directly via
|
|
7
|
+
// the Bash tool, following the runbook in
|
|
8
|
+
// platform/plugins/cloudflare/references/manual-setup.md, with
|
|
9
|
+
// cloudflared stdout/stderr streamed verbatim into the PTY. The MCP
|
|
10
|
+
// server process still spawns for lifecycle and stderr-tee consistency
|
|
11
|
+
// with other plugins, but its tool list is empty by design.
|
|
17
12
|
const server = new McpServer({
|
|
18
13
|
name: "cloudflare",
|
|
19
14
|
version: "0.4.0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,aAAa,CAAC,YAAY,CAAC,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,+DAA+D;AAC/D,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,aAAa,CAAC,YAAY,CAAC,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,+DAA+D;AAC/D,wEAAwE;AACxE,0CAA0C;AAC1C,+DAA+D;AAC/D,oEAAoE;AACpE,uEAAuE;AACvE,4DAA4D;AAE5D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -58,7 +58,7 @@ If the account shown in the top-left is not the one that owns the zone you need,
|
|
|
58
58
|
|
|
59
59
|
## Edit an apex CNAME (bare-domain hostname)
|
|
60
60
|
|
|
61
|
-
Apex hostnames — a record at the zone root, e.g. `maxy.chat` rather than `www.maxy.chat` — cannot be routed by `cloudflared tunnel route dns` because standard DNS forbids CNAMEs at zone apex. Cloudflare's workaround (CNAME flattening) is only exposed via the dashboard. When
|
|
61
|
+
Apex hostnames — a record at the zone root, e.g. `maxy.chat` rather than `www.maxy.chat` — cannot be routed by `cloudflared tunnel route dns` because standard DNS forbids CNAMEs at zone apex. Cloudflare's workaround (CNAME flattening) is only exposed via the dashboard. When the agent prints an `ACTION REQUIRED` block for an apex (per `references/manual-setup.md` § Step 4), these are the steps:
|
|
62
62
|
|
|
63
63
|
1. Click **Websites** in the sidebar, then click the apex zone (e.g. `maxy.chat`).
|
|
64
64
|
2. Click **DNS** in the sidebar, then **Records**.
|
|
@@ -105,8 +105,8 @@ Use this when you need to audit which hostnames are pointing at which `<UUID>.cf
|
|
|
105
105
|
|
|
106
106
|
## Author an Access policy for SSH or SMB
|
|
107
107
|
|
|
108
|
-
When
|
|
109
|
-
`ACTION REQUIRED` block naming this click-path. The
|
|
108
|
+
When the agent adds an SSH or SMB ingress hostname (per `references/manual-setup.md`), it surfaces an
|
|
109
|
+
`ACTION REQUIRED` block naming this click-path. The agent does NOT
|
|
110
110
|
create the Access policy — Cloudflare API/SDK is banned
|
|
111
111
|
(`feedback_cf_api_total_eradication`) and `cloudflared` CLI has no
|
|
112
112
|
Access-application create subcommand — so the operator must author it
|
|
@@ -39,43 +39,13 @@ Mode B skips Steps 1–5 of this runbook entirely — the user receives a token
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## How this runbook is used
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
This runbook is the contract the agent follows when setting up a Cloudflare tunnel. The agent reads the step it is about to execute, runs the `cloudflared` command (or writes the config / starts the service) via the Bash tool, and streams the literal output back to the operator. There is no shell-script wrapper, no orchestrator state machine, no MCP-tool surface — the PTY itself is the operator-visible surface, and cloudflared's stdout/stderr is the evidence.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
The setup is done when, and only when, `curl -I https://<admin-hostname>` issued from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`). The agent must paste the curl response into chat before claiming success. No service-active or `cloudflared exit 0` claim substitutes for that response.
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
setup-tunnel.sh <brand> <port> <hostname> [<hostname> ...]
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Example:
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot maxy.chat
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Mirrors this runbook verbatim (Steps 0–5b), calls `systemctl --user restart "${BRAND}.service"` at the end to spawn the connector via `resume-tunnel.sh`, then polls each subdomain hostname (up to 60s per host) for a non-530 response. Apex hostnames cannot be routed via CLI (see §Step 4); the script prints an explicit ACTION REQUIRED block naming the dashboard record to edit or add.
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
reset-tunnel.sh <brand>
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Example:
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
~/reset-tunnel.sh maxy
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Deletes every tunnel on the brand's Cloudflare account (via the brand's cert) and wipes `${CFG_DIR}`. Does **not** touch the platform service or any stray misrouted CNAMEs in DNS — those require a manual dashboard delete.
|
|
71
|
-
|
|
72
|
-
After manual transfer to a device, run `chmod +x ~/setup-tunnel.sh ~/reset-tunnel.sh` before the first invocation.
|
|
73
|
-
|
|
74
|
-
**Walk through manually (instead of scripting) when:**
|
|
75
|
-
- Diagnosing a step that's failing under the script.
|
|
76
|
-
- Recovering a partial state where the scripts assume a clean start.
|
|
77
|
-
- Validating runbook changes before re-baking them into the scripts.
|
|
78
|
-
- Working on a device where the scripts aren't deployed yet.
|
|
48
|
+
Apex hostnames (e.g. `maxy.chat`) cannot be routed by `cloudflared tunnel route dns` — see §Step 4. The agent quotes the ACTION REQUIRED block from §Step 4 verbatim and the operator edits the apex CNAME in the dashboard.
|
|
79
49
|
|
|
80
50
|
---
|
|
81
51
|
|
|
@@ -222,7 +192,7 @@ cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns
|
|
|
222
192
|
2. Overwritten: `Added CNAME <hostname> which will route to this tunnel` (same shape as new; `--overwrite-dns` makes overwrite transparent)
|
|
223
193
|
3. Idempotent no-op: `<timestamp> INF <hostname> is already configured to route to your tunnel tunnelID=<UUID>`
|
|
224
194
|
|
|
225
|
-
Shape 3 is what a
|
|
195
|
+
Shape 3 is what a repeat run against an already-configured hostname emits — `cloudflared` exits 0 and the agent treats it as a no-op success.
|
|
226
196
|
|
|
227
197
|
**If it fails with `zone not found`:** the hostname's parent domain isn't on this brand's Cloudflare account. Either add it in the dashboard (Websites → Add a site) and re-run, or sign into the account that already owns the domain.
|
|
228
198
|
|
|
@@ -306,12 +276,10 @@ cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns
|
|
|
306
276
|
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" smb.maxy.bot
|
|
307
277
|
```
|
|
308
278
|
|
|
309
|
-
**Order matters
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
`[tunnel-install] {ssh,smb}-ingress-deferred` and config.yml is rendered
|
|
314
|
-
without it.
|
|
279
|
+
**Order matters:** route HTTPS hostnames first and rewrite `config.yml`
|
|
280
|
+
only after the HTTPS pass is durable, so a failure on `ssh` or `smb` route
|
|
281
|
+
DNS does not nuke the HTTPS ingress; the failing hostname is logged and
|
|
282
|
+
config.yml is rendered without it.
|
|
315
283
|
|
|
316
284
|
**SMB depends.** Before adding the SMB ingress, confirm the
|
|
317
285
|
brand stanza exists in `/etc/samba/smb.conf`:
|
|
@@ -355,7 +323,7 @@ cloudflared access tcp --hostname smb.<brand>.<rootdomain> --url tcp://localhost
|
|
|
355
323
|
|
|
356
324
|
## Step 5b — Write tunnel.state
|
|
357
325
|
|
|
358
|
-
`resume-tunnel.sh` (the `ExecStartPre=` in the brand's service) reads `${CFG_DIR}/tunnel.state` to discover which tunnel to run. **Without this file the script exits 0 silently and no connector is spawned** — see lines 34-37 of `platform/scripts/resume-tunnel.sh`. The
|
|
326
|
+
`resume-tunnel.sh` (the `ExecStartPre=` in the brand's service) reads `${CFG_DIR}/tunnel.state` to discover which tunnel to run. **Without this file the script exits 0 silently and no connector is spawned** — see lines 34-37 of `platform/scripts/resume-tunnel.sh`. The raw CLI flow in this runbook does not write `tunnel.state` automatically, so write it explicitly here:
|
|
359
327
|
|
|
360
328
|
Re-run the Step 5a fail-fast guard if your shell is new or if Step 5a was a while ago:
|
|
361
329
|
|
|
@@ -381,8 +349,8 @@ EOF
|
|
|
381
349
|
|
|
382
350
|
Omit `sshHostname` and `smbHostname` when those ingresses are not in
|
|
383
351
|
use. The fields are additive; the script's re-run path rehydrates them
|
|
384
|
-
on every invocation so
|
|
385
|
-
`SSH_HOSTNAME` / `SMB_HOSTNAME`
|
|
352
|
+
on every invocation so a repeat setup run without
|
|
353
|
+
`SSH_HOSTNAME` / `SMB_HOSTNAME` set does not silently drop the
|
|
386
354
|
SSH/SMB ingress.
|
|
387
355
|
|
|
388
356
|
**Why each field:** `resume-tunnel.sh` reads `tunnelId`, `domain`, `configPath` (all used; domain is for logging, tunnelId for the log line, configPath is passed as `--config` to cloudflared). `credentialsPath` and `tunnelName` are not read by `resume-tunnel.sh` itself but are consumed by other tools (e.g. `tunnel-status` in the cloudflared plugin), so write them anyway.
|
|
@@ -403,23 +371,20 @@ Prints the UUID from Step 3. If it prints empty or null, the heredoc's env expan
|
|
|
403
371
|
|
|
404
372
|
You do **not** run `cloudflared` manually. The brand's existing user-space systemd unit (`~/.config/systemd/user/${BRAND}.service`) declares `ExecStartPre=/home/<user>/${BRAND}/platform/scripts/resume-tunnel.sh`, and that pre-start script reads `${CFG_DIR}/tunnel.state` and `${CFG_DIR}/config.yml` (the files Steps 5 and 5b just wrote) and spawns the connector in the user's cgroup. Restarting the brand service is what picks up the new config.
|
|
405
373
|
|
|
406
|
-
> **Note:** When walking through by hand you run this step yourself. The automation script `platform/plugins/cloudflare/scripts/setup-tunnel.sh` runs it for you — with a critical twist documented below. If you used the script, this step is already done and the service will restart a few seconds after the script exits.
|
|
407
|
-
|
|
408
|
-
|
|
409
374
|
```
|
|
410
375
|
systemctl --user restart "${BRAND}.service"
|
|
411
376
|
```
|
|
412
377
|
|
|
413
|
-
**
|
|
378
|
+
**Cgroup trap — when the admin agent issues this command via the Bash tool, the Bash subprocess runs *inside* `${BRAND}.service`'s cgroup.** A direct `systemctl --user restart ${BRAND}.service` from that cgroup tells systemd to SIGTERM the entire cgroup — the node server, the claude subprocess, the Bash child, and the agent itself all die simultaneously. cgroup membership is inherited: `setsid`, `nohup`, `disown`, and `&` all stay in the caller's cgroup, and `systemd-run --scope` runs in the caller's scope. Only `systemd-run --user --unit=<name> --on-active=<N>s` creates a genuinely new transient unit with its own cgroup. The agent uses that primitive to arm the restart a few seconds after the Bash call returns:
|
|
414
379
|
|
|
415
380
|
```
|
|
416
|
-
systemd-run --user --unit=maxy-tunnel-restart
|
|
381
|
+
systemd-run --user --unit=maxy-tunnel-restart-$(date +%s).service --on-active=3s --collect \
|
|
417
382
|
/bin/systemctl --user restart "${BRAND}.service"
|
|
418
383
|
```
|
|
419
384
|
|
|
420
|
-
The
|
|
385
|
+
The transient timer fires from outside the service's cgroup — semantically identical to a direct `systemctl --user restart`, but the agent survives.
|
|
421
386
|
|
|
422
|
-
When walking through manually you do **not** need `systemd-run` — your SSH shell already lives in a separate user-scope cgroup (`user@<uid>.service`), so the direct `systemctl restart` does not kill the caller. The
|
|
387
|
+
When walking through manually from an SSH shell you do **not** need `systemd-run` — your SSH shell already lives in a separate user-scope cgroup (`user@<uid>.service`), so the direct `systemctl restart` does not kill the caller. The extra indirection only matters when the caller *is* inside the service being restarted (the admin agent's case).
|
|
423
388
|
|
|
424
389
|
**Why:** `resume-tunnel.sh` is the deterministic, brand-scoped spawner. Running `cloudflared` manually duplicates the connector (two processes for one tunnel) and races the brand service on every service restart. The service path is the only correct production path.
|
|
425
390
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cloudflare reset — when and how
|
|
2
2
|
|
|
3
|
-
Reset is a heavier action than recovery.
|
|
3
|
+
Reset is a heavier action than recovery. A reset deletes every tunnel on the brand's Cloudflare account and wipes the on-disk cloudflared config directory. Use it when the state is genuinely corrupt or when the operator wants a known-good baseline; use patching (targeted cleanup) when the state is mostly correct but one record or process is wrong.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -17,51 +17,50 @@ Ask three questions in order. The first `yes` determines the action.
|
|
|
17
17
|
3. **Is the only problem a stray CNAME in the dashboard, a rogue token-mode connector process, or an out-of-date `alias-domains.json` entry?**
|
|
18
18
|
→ Patch (see § Patching below). Reset would destroy correct local state alongside the bad record.
|
|
19
19
|
|
|
20
|
-
When no question reaches `yes`, the state is probably recoverable per-step via `references/manual-setup.md`.
|
|
20
|
+
When no question reaches `yes`, the state is probably recoverable per-step via `references/manual-setup.md`. Re-run the relevant manual setup steps before reaching for reset.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
## Full reset
|
|
24
|
+
## Full reset
|
|
25
25
|
|
|
26
26
|
### What it does
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
- Lists every tunnel on the account the cert authorises.
|
|
30
|
-
- Deletes every one of those tunnels via `cloudflared tunnel delete`.
|
|
31
|
-
- Removes `${HOME}/.${BRAND}/cloudflared/` entirely.
|
|
28
|
+
The agent issues these commands in order via Bash, against the brand cert:
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- It does not touch DNS records. Records pointing at `<UUID>.cfargotunnel.com` from a deleted tunnel become stray; delete them in the dashboard (see § Patching below).
|
|
37
|
-
- It does not stop token-mode connector processes. If the device is running `cloudflared ... tunnel run --token <X>` for a tunnel the brand's cert does not own, that connector keeps running.
|
|
38
|
-
- It does not switch accounts. If the bound account was wrong, a fresh `cloudflared tunnel login` from inside `setup-tunnel.sh` on the next run will pick a new one — but the operator must sign in with the correct account in the browser.
|
|
30
|
+
```
|
|
31
|
+
BRAND=$(jq -r .hostname ~/.<configDir>/brand.json)
|
|
32
|
+
CFG_DIR="${HOME}/.${BRAND}/cloudflared"
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
for t in $(cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel list --output json | jq -r '.[].id'); do
|
|
35
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel delete -f "$t"
|
|
36
|
+
done
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
~/reset-tunnel.sh <brand>
|
|
38
|
+
rm -rf "${CFG_DIR}"
|
|
44
39
|
```
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
Then stop the brand's cloudflared user service so the now-deleted tunnel's connector exits:
|
|
47
42
|
|
|
48
43
|
```
|
|
49
|
-
|
|
44
|
+
systemctl --user stop "${BRAND}-cloudflared.service" 2>/dev/null || true
|
|
50
45
|
```
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
### What it does not do
|
|
48
|
+
|
|
49
|
+
- It does not touch DNS records. Records pointing at `<UUID>.cfargotunnel.com` from a deleted tunnel become stray; delete them in the dashboard (see § Patching below).
|
|
50
|
+
- It does not stop token-mode connector processes. If the device is running `cloudflared ... tunnel run --token <X>` for a tunnel the brand's cert does not own, that connector keeps running. Use `pkill` per § Patching.
|
|
51
|
+
- It does not switch accounts. A fresh `cloudflared tunnel login` after reset picks a new one — but the operator must sign in with the correct account in the browser.
|
|
53
52
|
|
|
54
53
|
### After reset
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
Re-run the setup flow from `references/manual-setup.md` § Step 1 with the operator's chosen hostnames (see `SKILL.md` for the input-collection questions). `cloudflared tunnel login` runs again; the operator authorises a fresh cert from the correct account in their own browser via the linkified OAuth URL.
|
|
57
56
|
|
|
58
57
|
---
|
|
59
58
|
|
|
60
59
|
## Patching — targeted cleanup without reset
|
|
61
60
|
|
|
62
|
-
### Stop a token-mode connector
|
|
61
|
+
### Stop a token-mode connector
|
|
63
62
|
|
|
64
|
-
Token-mode tunnels are created by a central operator (e.g. Maxy-provided subdomains under `maxy.bot`). The connector runs with `--token <X>` and does not consult `cert.pem`, so
|
|
63
|
+
Token-mode tunnels are created by a central operator (e.g. Maxy-provided subdomains under `maxy.bot`). The connector runs with `--token <X>` and does not consult `cert.pem`, so the full-reset flow above (which authorises via cert.pem) cannot stop or delete it. Kill the process directly:
|
|
65
64
|
|
|
66
65
|
```
|
|
67
66
|
pkill -f 'cloudflared.*tunnel run --token'
|
|
@@ -77,7 +76,7 @@ No lines with `--token` should remain. If multiple token-mode connectors are run
|
|
|
77
76
|
|
|
78
77
|
### Delete a stray CNAME that survived a tunnel delete
|
|
79
78
|
|
|
80
|
-
When
|
|
79
|
+
When a tunnel is deleted, the DNS CNAMEs that pointed at `<UUID>.cfargotunnel.com` remain in the zone — Cloudflare deliberately does not cascade-delete DNS when you delete a tunnel. Clean up in the dashboard:
|
|
81
80
|
|
|
82
81
|
1. See `references/dashboard-guide.md` § "Delete a stray CNAME" for the click-path.
|
|
83
82
|
2. Verify the CNAME's target is the tunnel you just deleted (not a live one).
|
|
@@ -109,10 +108,10 @@ If a hostname's CNAME is pointing at the wrong tunnel (e.g. after an account swi
|
|
|
109
108
|
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" <hostname>
|
|
110
109
|
```
|
|
111
110
|
|
|
112
|
-
This
|
|
111
|
+
This is the same call the manual-setup runbook makes for each subdomain. Apex hostnames do not work here — the dashboard is the only path (see `references/dashboard-guide.md` § "Edit an apex CNAME").
|
|
113
112
|
|
|
114
113
|
---
|
|
115
114
|
|
|
116
115
|
## Failure discipline
|
|
117
116
|
|
|
118
|
-
When
|
|
117
|
+
When a reset or patch command exits non-zero, report the failure with the exact output. Cite the relevant section above for the next action. Do not chain alternative recovery attempts — the agent's job ends at the boundary of a sanctioned surface, and improvisation outside that boundary is the discipline violation `SKILL.md` § "Tool discipline" exists to prevent.
|
|
@@ -1,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.
|