cmdflare
v0.2.0
Published
Smart, complete Cloudflare API command line: every endpoint of the Cloudflare SDK as commands, with interactive (human) and non-interactive (CI) modes.
Maintainers
Readme
cmdflare
The Cloudflare API, as a command. List DNS, purge cache, ship Workers — from a terminal or from CI. 2,500+ commands, generated from the official SDK.
$ cmdflare dns records list --zone example.com
id name type content proxied ttl
3f1c… www.example.com A 203.0.113.10 true 1
a8b2… example.com MX mail.example false 3600
$ cmdflare dns records create --zone example.com --type A --name api --content 203.0.113.20 --proxied --ttl 1 --json
{ "id": "…", "name": "api.example.com", "type": "A", … }
$ cmdflare workers scripts list -A "My Account" --all -q '[*].id'Try it
npm i -g cmdflare # Node.js ≥ 20; or: bunx cmdflare / npx cmdflare
cmdflare auth login # paste a Cloudflare API token
cmdflare zones listCreate a token at API Tokens (user) or Manage Account → Account API Tokens (account-owned, cfat_…). Then:
cmdflare dns records list --zone example.com
cmdflare cache purge --zone example.com --purge-everything --yesOr skip login and export CLOUDFLARE_API_TOKEN (also CLOUDFLARE_TOKEN / CF_API_TOKEN).
Interactive
Run cmdflare with no arguments. Type dns records, pick a zone by name, fill the rest. It prints the equivalent one-liner so you can paste it into a script later.
Same idea for a partial path: cmdflare dns records opens that menu. On a TTY, missing flags are prompted instead of failing — cmdflare dns records get will ask for the record and the zone.
For agents
cmdflare is not in model training data. Discover at runtime — do not scrape TTY help:
cmdflare search dns records --json
cmdflare dns records list --help --json
cmdflare dns records list --zone example.com --json --no-inputFull instructions: cmdflare skill (also skills/cmdflare/SKILL.md, llms.txt, AGENTS.md).
Commands
cmdflare <resource> [<subresource>...] <command> [arguments] [flags]Resources match the API: zones, dns records, workers scripts, kv namespaces values, r2 buckets, zero-trust access applications, rulesets, … Commands are SDK methods (list, get, create, update, edit, delete, plus purge, verify, rotate, …). kebab-case and camelCase both work; ls / rm / show / add are aliases.
cmdflare --help
cmdflare dns --help
cmdflare dns records create --help
cmdflare search purge cache
cmdflare help --tree zero-trustPath ids are positional (cmdflare dns records get <dns-record-id>). Everything else is a --flag named after the API field. Zones and accounts accept names: --zone example.com, -A "Acme Corp" (or CLOUDFLARE_ZONE_ID / CLOUDFLARE_ACCOUNT_ID, or a profile default).
Bulk export
A few commands are whole workflows rather than one API call.
cmdflare stream export ./backup # every Stream video + a CSV manifest
cmdflare stream export ./backup --compress # …and ./backup.tar.gz
cmdflare stream export ./backup --dry-run # what it would download, without touching anything
cmdflare stream export ./backup --retry-failed # re-attempt only what failed last timestream export walks the whole library, saves each video as <id>.mp4 and each caption track as
<id>.<language>.vtt, and writes videos.csv with every video's id, title, description, public
URLs, flags, dates, captions, the full custom meta object and the relative path to each file.
Existing files are skipped, so an interrupted export resumes. Every MP4 render is requested up front
(--request-concurrency, default 16) so Cloudflare renders the library in parallel, then files are
downloaded as they become ready (--concurrency, default 4) — a big library takes about as long as
one render, not one per batch. The generated MP4s are kept on your account (they count towards Stream
storage; remove them with cmdflare stream downloads delete <id>). Use --metadata-only for the CSV
alone, --limit / --creator / --search / --start / --end to export a subset.
If a run ends with failures (exit 1, an error column in the CSV), --retry-failed reads the
manifest and re-attempts only those videos — no re-listing, no other rows touched — then updates
their rows in place.
Scripts and CI
JSON when piped, tables on a TTY. Destructive commands ask on a TTY and need --yes otherwise. --no-input (or CI=true) never prompts.
- run: bunx cmdflare cache purge --zone example.com --purge-everything --yes
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}Exit codes: 0 ok · 1 API/other · 2 usage · 3 auth · 4 not found · 5 rate limited · 130 cancelled.
Output
| Flag | Effect |
| --- | --- |
| -o table (TTY default) | Columns picked automatically, fits the terminal |
| -o json / --json (piped default) | Pretty JSON (--compact for one line) |
| -o yaml, -o csv, -o tsv, -o ndjson, -o raw, -o id | … |
| -q, --query <path> | Select data: [*].name, [?status==active].id |
| --fields id,name,account.name | Restrict columns / keys |
| --all / --limit <n> | Every page / cap items |
| --dry-run / --curl | Print the request (or a curl) without sending it |
Stdout is data. Hints, spinners, and errors go to stderr.
Flags in more detail
- Types come from the SDK: booleans (
--proxied,--proxied=false), numbers, enums, arrays (--tags a,bor JSON or repeated flags), objects (--settings '{"ipv4_only":true}'or--settings.ipv4-only true), files (--value ./file.bin,@-for stdin). --description @notes.txtand--data @-read a file or stdin.-d/--data '{…}'(JSON or YAML) sets the whole body;--set key.path=valuepatches nested fields; flags win over--data.- If a parameter shares a name with a global flag (
--dataon DNS create,--limiton some lists), the parameter wins. Use-d/-q/-oor--cf-<flag>for the global.--helpsays which. --include-metawraps{result, result_info, has_more}.--raw-responseprints the API envelope.--output-filewrites downloads.
Auth and profiles
cmdflare auth login
cmdflare auth login --token "$CF_TOKEN" -p ci
cmdflare auth status
cmdflare auth whoami
cmdflare auth logoutLookup order: --token → CLOUDFLARE_API_TOKEN (CLOUDFLARE_TOKEN, CF_API_TOKEN) → CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL → the active profile.
User tokens (cfut_… or older unprefixed strings) and account-owned tokens (cfat_…) both go out as Authorization: Bearer. Global API keys (cfk_…) still need --api-key and --email.
Config is ~/.config/cmdflare/config.json (cmdflare config path). A profile stores the token plus optional default account/zone:
cmdflare config set account_id 8c7434c9906dab3c23d2dd5d46fbb649
cmdflare config set zone_id example.com # names are resolved on use
cmdflare config set output yaml
cmdflare config use staging # or -p staging / CMDFLARE_PROFILERaw API
Any REST path, including endpoints the SDK does not model yet:
cmdflare api /zones -P per_page=5
cmdflare api GET '/zones/{zone_id}/dns_records' -Z example.com --paginate -q '[*].name'
cmdflare api POST '/zones/{zone_id}/purge_cache' -Z example.com -d '{"purge_everything":true}'
cmdflare api DELETE /zones/<id>/dns_records/<rid> --yes{account_id} / {zone_id} are filled from context. -P is query or body; -H adds headers; --paginate follows pages.
Completion
echo 'eval "$(cmdflare completion bash)"' >> ~/.bashrc
echo 'eval "$(cmdflare completion zsh)"' >> ~/.zshrc
cmdflare completion fish > ~/.config/fish/completions/cmdflare.fishEnvironment
| Variable | Meaning |
| --- | --- |
| CLOUDFLARE_API_TOKEN (CLOUDFLARE_TOKEN, CF_API_TOKEN) | API token |
| CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL | Legacy global API key |
| CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ZONE_ID | Default account / zone (id or name) |
| CLOUDFLARE_BASE_URL | API base (default https://api.cloudflare.com/client/v4) |
| CMDFLARE_PROFILE | Config profile |
| CMDFLARE_CONFIG_DIR, CMDFLARE_CACHE_DIR | Config / cache locations |
| CMDFLARE_NO_INPUT, CI | Never prompt |
| NO_COLOR, FORCE_COLOR | Color |
Development
From a clone (Bun ≥ 1.1):
bun install
bun run build # manifest + dist/cli.js
bun link # `cmdflare` on your PATH
bun src/cli.ts --help # or run TypeScript directly
bun test
bun run typecheck
bun run dev -- zones listscripts/gen-manifest.ts walks the cloudflare package and writes src/generated/. Re-run bun run gen after upgrading the SDK — new endpoints become commands. Runtime loads only the SDK module for the command you ran.
License
GPL-3.0 — see LICENSE.
