# @kentcdodds/cloudflare ## Intent Account-aware Cloudflare helpers for Kody workflows: API v4 (JSON, multipart, and raw bodies), public static-site publishing to Cloudflare Pages, GraphQL analytics, Rulesets/WAF, Log Explorer, and developer docs. Token aliases (`default`, `kody`, `pages`) pick the right secret and — when known — the right Cloudflare account id, so agents do not hardcode credentials. ## What it does - Publish a static site to Cloudflare Pages in one call (`publish-static`) - Call Cloudflare API v4 for zones, Workers, D1, R2, KV, Pages, and related resources (`api-v4`, with `form` / `rawBody` when JSON is not enough) - Auto-paginate list endpoints (`api-v4-list`; Pages projects max out at `per_page=10`) - Discover which token alias and account id to use (`accounts`, `verify`) - Summarize zone HTTP traffic; search Log Explorer; preview or apply WAF rules - Fetch allowlisted Cloudflare developer documentation - PUT binary objects into R2 (`r2-put-object`) ## Publishing a static site `publish-static` is the general path for public HTML/CSS/JS hosting. It runs Cloudflare Pages **Direct Upload** (the same flow as `wrangler pages deploy`): 1. Resolve the Cloudflare account id (from the alias, or `accountId`). 2. Get or optionally create the Pages project (Direct Upload only — never Git). 3. Scan text files for forbidden content (built-in credential patterns + your `scan.forbid`) and refuse when something matches. 4. Fetch a short-lived upload JWT, upload only missing files, and upsert hashes. 5. Create a multipart deployment (`manifest` + `_headers` / `_redirects`). 6. Wait until the deployment is live (opt out with `wait: false`). Pass the site as a `files` map (text or base64) and/or a public `sourceUrl` to a `.zip` / `.tar` / `.tar.gz`. Prefer the `pages` account alias for Kent's personal (KCD) Pages projects. Custom domains and Cloudflare Access are out of scope for this helper: the Pages token usually cannot edit DNS. Point DNS at the returned `*.pages.dev` hostname yourself (or with a token that has Zone DNS edit). Deployment list / rollback / prune and project delete are also deferred. ## Prerequisites / setup Three saved Cloudflare API token secrets (user scope), one per account alias: | Account | Secret | Account id | Use when | | --- | --- | --- | --- | | `default` | `cloudflareApiToken` | *(none — spans accounts)* | Routine API, analytics, logs, and WAF on accounts the primary token covers | | `kody` | `cloudflareApiTokenKodyAccount` | Kody production (baked in) | Kody Cloudflare account resources when the default token is unauthorized | | `pages` | `cloudflarePagesApiToken` | KCD / personal (baked in) | Cloudflare Pages Direct Upload and Pages project management | 1. Save each token you need in Kody (never paste values in chat). 2. Approve host `api.cloudflare.com` for each secret you use. 3. Grant firewall/ruleset edit permission only if you will call `rulesets` with `apply: true`. 4. Agents: see [`AGENTS.md`](./AGENTS.md) for import paths, dry-run snippets, and account-id discovery notes. Authenticated exports accept optional `account` (default `'default'`). For a token outside the table, pass `apiTokenSecret` with the Kody secret **name** (never a raw token). Do not pass both `account` and `apiTokenSecret`. ### Finding the Cloudflare account id - `accounts()` returns each alias's known `accountId` (`pages` → KCD, `kody` → Kody; `default` → `null` because that token covers multiple accounts). - `verify({ account })` returns `accountId` when it is unambiguous, plus `accessibleAccounts` from `GET /accounts`. - The `pages` token cannot list accounts (`GET /accounts` is empty even when valid) — that is why its account id is baked into the alias metadata. - Otherwise copy the id from the Cloudflare dashboard URL (`dash.cloudflare.com//…`) and pass `accountId`. ### Verifying tokens Cloudflare has two verify endpoints. `/user/tokens/verify` alone often returns `401 Invalid API Token` for a valid **account**-scoped token. Prefer this package's `verify` export (tries user, then account when an account id is known). After verify succeeds: `403` = lacks permission; `400`/`404` on a mutating call against a bad/missing target = authorized for that operation. ## Done when - Needed token secrets are saved and `api.cloudflare.com` is approved - `verify` for the intended account returns `ok: true` (and an `accountId` when you need one) - `publish-static` with `dryRun: true` shows the plan; a real run returns `live: true` and a `*.pages.dev` URL - Rulesets stay dry-run until an explicit `apply: true` after review - `r2-put-object` with `confirm: true` returns `ok: true` for a test object