---
name: cloudflare
description: Cloudflare operations for the install — tunnel setup/diagnosis/reset, DNS edits, Pages hosting, and D1 data capture. The agent drives `cloudflared` and `wrangler` directly via Bash and reads the matching reference before each operation class; success is always a live behavioural signal (external HTTP 200, a deployed URL, a D1 round-trip), never an exit code.
---

# Cloudflare operations

This is the entry point for every Cloudflare task on the install. Pick the operation class, read the matching reference, run the command via Bash, relay the literal output, and prove the outcome with a live signal. There are no Cloudflare MCP tools; the plugin registers none. The agent never browser-automates the dashboard — the operator clicks where a click is genuinely required.

| You want to… | Read | Outcome contract |
|---|---|---|
| Stand up / diagnose / reset a tunnel so a hostname is reachable | `references/manual-setup.md` | external `curl -I https://<hostname>` → `200` |
| Switch accounts, edit an apex CNAME by hand, or other dashboard-only clicks | `references/dashboard-guide.md` | the click-path completed in the operator's browser |
| Tear down corrupt tunnel state and start clean | `references/reset-guide.md` | a fresh `manual-setup.md` run reaches `200` |
| Call the Cloudflare API (DNS, zones, tunnels, Access, token mint) | `references/api.md` | the API call returns success and the change is observable |
| Deploy a static / Next.js site to Cloudflare Pages | `references/hosting-sites.md` | the deployed URL serves the new build |
| Capture form/waitlist submissions into a database | `references/d1-data-capture.md` | a test POST appears in `SELECT … WHERE swept = 0` |

## Two auth paths, by operation class

- **Tunnel auth is OAuth.** `cloudflared tunnel login` writes `cert.pem`; the operator clicks the linkified OAuth URL in their own browser. This is the only path for tunnel create/route/run. See `references/manual-setup.md`.
- **API auth is the master token.** The operator provisions one fully-scoped master token once (an **advanced**, operator-guided dashboard step — the agent does not automate it) and stores it at the account-scoped secrets file. The agent reads the master only to **mint a narrowly-scoped, short-lived token** for each operation — a single-zone DNS edit, a one-project Pages deploy, a D1 query — and exports that ephemeral token for the one call. See `references/api.md` for the storage convention, the mint flow, and the binding redaction discipline.

Neither the master token nor any minted token is ever written into a project tree, committed, or echoed into chat.

## Outcome contracts — what "done" means

Every Cloudflare operation proves itself with a live behavioural signal surfaced verbatim in chat:

- **Tunnel:** `curl -I https://<hostname>` from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`). No state file, no `result=ok` line, no "service is active" claim substitutes. If the curl returns anything else, diagnose with `cloudflared tunnel info <tunnelId>` and `systemctl --user status ${BRAND}-cloudflared.service` and recover per `references/manual-setup.md`.
- **Hosting:** the deployed Pages URL returns the new build (`curl -s https://<project>.pages.dev/ | head` shows the new markup).
- **D1 capture:** a test POST to the live form appears in `wrangler d1 execute <db> --remote --command "SELECT … WHERE swept = 0"`.

## Inputs (tunnel)

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.

## Execution

The agent reads the relevant reference and executes its steps with Bash. Every `cloudflared` / `wrangler` / `curl` invocation's stdout and stderr are relayed into chat verbatim — minus any secret, which is always redacted. 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.

Tunnel mutations the agent performs from `references/manual-setup.md` (each one only after reading the relevant step):

- `cloudflared tunnel login` — emits the OAuth URL; cert lands at `~/.cloudflared/cert.pem` after the operator authorises.
- `cloudflared tunnel --origincert <cert> create <name>` — produces `<tunnelId>.json` credentials.
- Write `~/.${BRAND}/cloudflared/config.yml` (ingress block per the runbook).
- `cloudflared tunnel --origincert <cert> route dns <tunnelId> <hostname>` — one call per non-apex hostname.
- For non-admin hostnames not starting with `public.`, append to `~/.${BRAND}/alias-domains.json` so `isPublicHost()` treats them as public.
- Install + start the `${BRAND}-cloudflared.service` user unit (`systemctl --user daemon-reload && systemctl --user enable --now ${BRAND}-cloudflared.service`).

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.

## Apex hostnames

The `cloudflared` CLI cannot route apex records (e.g. `maxy.chat`) — standard DNS forbids a CNAME at the zone apex. There are now two ways to create the apex record: the operator edits the apex CNAME in the dashboard (`references/dashboard-guide.md` § "Edit an apex CNAME"), or the agent creates it via the Cloudflare API with a minted DNS-scoped token (`references/api.md` § DNS records). Either reaches the same flattened-CNAME result; pick the dashboard path when the operator prefers to click, the API path when an end-to-end agent run is wanted.

## Reset

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.

## Tool discipline — binding

When the operator's request touches Cloudflare, the agent's permitted actions are:

- Invoke `cloudflared` via Bash, following `references/manual-setup.md`.
- Invoke `wrangler` and the Cloudflare API via Bash, following `references/api.md`, `references/hosting-sites.md`, and `references/d1-data-capture.md`, using a freshly-minted narrow token per operation.
- Write `config.yml` and `alias-domains.json` per the runbook.
- Install and start the brand's cloudflared user service.
- Quote `references/manual-setup.md`, `references/reset-guide.md`, or `references/dashboard-guide.md` verbatim when a dashboard click-path is the right tool.
- Verify reachability via `curl -I https://<hostname>` and surface the response.

The agent does not drive the dashboard via Playwright or Chrome DevTools, and does not browser-automate master-token creation. It does not synthesise `cloudflared` flag combinations from web search or training — every command comes from the runbook. It never writes a token to disk in a project tree, commits one, or prints one into chat. When a step fails, the agent reports the exact output, names the recovery step from `references/reset-guide.md`, and stops. Improvisation — "let me try a different flag" or "let me drive the dashboard myself" — is the behaviour this rule exists to prevent.
