skills/cloudflare-dns/SKILL.md
Manage Cloudflare DNS records via the API — add, update, or delete subdomains (A, AAAA, CNAME, TXT, MX). Use when user wants to add a subdomain, point a DNS record, create/update/delete DNS entries on Cloudflare, or set up a custom domain for a hosted app (Fly, Vercel, Render, etc.).
npx skillsauth add RonanCodes/ronan-skills cloudflare-dnsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage DNS records on Cloudflare via the API v4. Typical use: pointing a subdomain at a Fly.io / Vercel / Render hostname.
/ro:cloudflare-dns add api.myapp.com CNAME myapp.fly.dev --proxied
/ro:cloudflare-dns list myapp.com
/ro:cloudflare-dns update <record-id> <zone-id> --content new-target.fly.dev
/ro:cloudflare-dns delete api.myapp.com
Check in order for CLOUDFLARE_API_TOKEN (required) and optionally CLOUDFLARE_ZONE_ID, CLOUDFLARE_ACCOUNT_ID:
${CLAUDE_PLUGIN_DATA}/.env (preferred — plugin-wide, survives updates)~/.config/ro/.env.envExpected shape:
CLOUDFLARE_API_TOKEN=... # required
CLOUDFLARE_ZONE_ID=... # optional: skips lookup in step 2
CLOUDFLARE_ACCOUNT_ID=... # optional: needed for tunnels/workers
If token missing: direct user to https://dash.cloudflare.com/profile/api-tokens → Create Token → template "Edit zone DNS" (scopes: Zone:DNS:Edit, Zone:Zone:Read; add Tunnel:Edit if you also plan to use tunnels). Save:
mkdir -p "$CLAUDE_PLUGIN_DATA" && chmod 700 "$CLAUDE_PLUGIN_DATA"
cat >> "$CLAUDE_PLUGIN_DATA/.env" <<'EOF'
CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ZONE_ID=...
CLOUDFLARE_ACCOUNT_ID=...
EOF
chmod 600 "$CLAUDE_PLUGIN_DATA/.env"
If CLOUDFLARE_ZONE_ID is already in the env and matches the apex of the record you're editing, skip the lookup. Otherwise resolve it:
ZONE_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones?name=myapp.com" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result[0].id')
Quick sanity check when using the env zone ID:
curl -s "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result.name'
If null: the domain isn't on Cloudflare or the token lacks Zone:Zone:Read. Stop and tell the user.
Base URL: https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records
Add (POST):
curl -s -X POST "$BASE" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"CNAME","name":"api","content":"myapp.fly.dev","ttl":1,"proxied":true}'
ttl: 1 = autoproxied: true = orange cloud (Cloudflare in front). Use false for Fly.io custom domains that need direct TLS termination on Fly.List (GET): curl -s "$BASE?name=api.myapp.com" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" — returns record id + current values.
Update (PATCH $BASE/<record_id>): send only changed fields.
Delete (DELETE $BASE/<record_id>): always confirm with the user first — deleting DNS can drop production traffic.
After a write, re-fetch the record and show the user the final name, type, content, proxied, ttl. Mention propagation: Cloudflare is fast (~seconds) but downstream resolvers may cache for up to TTL.
When chaining with /ro:fly-deploy:
flyctl certs create api.myapp.com -a <app> → fly returns required DNS records_acme-challenge.api.myapp.com → <app>.fly.dev) with proxied: falseapi.myapp.com record as either:
<app>.fly.dev (proxied: false — let Fly terminate TLS), ORflyctl ips list -a <app> (proxied: true OK)flyctl certs show api.myapp.com -a <app> — wait for Issued statusZone:DNS:Edit scopeerrors[].code: 81057: Record already exists — list first, then PATCH instead of POSTerrors[].code: 1004: Bad DNS content (e.g. CNAME pointing to raw IP)success: false: Surface the errors[] array verbatim to the userCLOUDFLARE_API_TOKEN valuedevelopment
Close the loop on a Linear ticket when its work ships - move the status and post a deploy comment with the PR link, what shipped, and a try-it link, mentioning the collaborator. Used as the tail of /ro:linear-nightshift for every merged mirror, or manually after an ad-hoc build. Triggers on "linear update", "update the linear ticket", "mark NUT-x done", "tell eoin it shipped", "/ro:linear-update".
devops
Run a night-shift against a collaborator's Linear board. Pulls the team's Grilled tickets (/ro:linear-grill moves a ticket to Grilled once its questions are answered), VERIFIES the questions were actually answered (unanswered → bounce the ticket to the "Question for <name>" state), mirrors verified tickets to ephemeral GitHub issues with ready-for-agent, then runs the standard /ro:night-shift machinery on GitHub. Tail-calls /ro:linear-update for everything that merged + deployed. Triggers on "linear nightshift", "nightshift linear", "drain the linear board", "run the shift off linear", "/ro:linear-nightshift".
development
Grill a collaborator's Linear tickets and move every processed ticket to where it belongs. Resolves the board from the repo's .ro-linear.json, reads the collaborator's Backlog / Ready-for-agent issues, then per ticket either posts 3-5 decision-extracting questions (state moves to "Question for <name>") or confirms it build-ready (state moves to "Grilled", the gate /ro:linear-nightshift consumes); shipped-and-confirmed tickets close as Done. The async-collaborator counterpart of /ro:day-shift for people who never touch GitHub. Triggers on "grill linear", "grill eoin's tickets", "linear grill", "add questions to the linear tickets", "/ro:linear-grill".
development
--- name: about-page description: Add a standard About page to any web app, what it is, the tech stack, and an FAQ, wired into a footer link with a sticky footer. Built with Spartan + Tailwind (the canonical component layer) and falls back to semantic HTML so it ships reliably. Use whenever building, polishing, or shipping an app, every app should have one. Triggers on "add an about page", "about page", "footer about link", or as a standard step in app build/polish. category: frontend argument-h