skills/cf-ship/SKILL.md
--- name: cf-ship description: Ship a TanStack Start / Cloudflare Workers app with pre-flight checks (typecheck + lint + test + D1 migrations + build + deploy). Use when user wants to deploy, ship, release, push, or go live with a Cloudflare Workers app. Handles secrets, D1 migrations, and a post-deploy smoke check. category: deployment argument-hint: [--skip-tests] [--skip-migrations] [--env <name>] allowed-tools: Bash(pnpm *) Bash(wrangler *) Bash(curl *) Bash(git *) Read Edit --- # CF Ship
npx skillsauth add RonanCodes/ronan-skills skills/cf-shipInstall 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.
Deploy a Cloudflare Workers app (TanStack Start on Path C) with a pre-flight gate. Prevents shipping broken code.
/ro:cf-ship # full pipeline
/ro:cf-ship --skip-tests # skip pnpm test (hotfix mode)
/ro:cf-ship --skip-migrations # skip D1 migrations (schema unchanged)
/ro:cf-ship --env staging # deploy to a non-default wrangler env
wrangler authed. Three options in priority order:
~/.claude/.env has CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID from /ro:cloudflare-setup (the default for this user). Source pattern:
set -a && source "$(ro context env)" && set +a
unset GH_TOKEN GITHUB_TOKEN # ~/.claude/.env shadows gh CLI keychain — must unset before any gh call
Always grep this file BEFORE asking the user to paste a token or run wrangler login — for already-onboarded providers, the value is there.
★ Token capability matters: pick by capability, not the first match. On the personal ronanconnolly.dev account (c421a1b2…) there are two token classes and they are NOT interchangeable:
CLOUDFLARE_ACCOUNT_TOKEN_RONAN (a cfat_ account token, dashboard name "porkbun") has FULL access: D1, Containers, R2, KV, Queues, Vectorize, secrets, Workers, Zone. Use it whenever the app has a D1 binding (the deploy validates the binding, which needs D1 read), uses Containers, or you run wrangler d1 … / wrangler secret put.CLOUDFLARE_API_TOKEN_RONAN (= _NUTMEG = _RONANCONNOLLY; a cfut_ user token) is Zone + Workers only (NO D1, NO Containers). Fine for a plain Workers/Pages deploy + DNS, nothing more.Authentication error [10000] on wrangler d1/secrets, or binding … of type d1 must have a valid database_id [10021] on deploy. Switch to the cfat_ account token.cfat_) token for D1/Containers. Memory reference_cloudflare_accounts has the matrix.wrangler login (browser OAuth) — fine for one-off interactive runs
${CLAUDE_PLUGIN_DATA}/.env (legacy, prefer ~/.claude/.env)
wrangler.toml in repo root with a valid main or TanStack Start preset configuredRun in parallel:
git status --short
wrangler whoami
git rev-parse HEAD
If uncommitted changes exist, warn the user — offer to proceed anyway or abort. Print the current commit SHA and branch so there's a paper trail of what's shipping.
Run in this order. Stop on first failure — do not try to "fix forward" without asking.
pnpm typecheck # tsc --noEmit, strict mode
pnpm lint # eslint .
pnpm format:check # prettier --check .
pnpm test # vitest run (skip if --skip-tests)
If any fails, surface the output and stop. Suggest /ro:commit after the fix.
Unless --skip-migrations:
pnpm drizzle-kit generate # generate any pending migrations
wrangler d1 migrations list <db-name> --remote # show what's pending remotely
Read the db name from wrangler.toml ([[d1_databases]] database_name). Show the user the list. If migrations are pending, confirm before applying:
wrangler d1 migrations apply <db-name> --remote
Apply local too (keeps dev DB matching):
wrangler d1 migrations apply <db-name> --local
Schema-assert (do not skip when drizzle/ exists). Applying migrations is not proof the prod DB has the schema the code expects: the apply can no-op against a wrongly-tracked or wrong database. After the remote apply, assert the code-expected schema is actually present on prod. For each table the current Drizzle schema declares, probe the live DB (PRAGMA table_info(<table>) via wrangler d1 execute <db-name> --remote --command=...) and fail the deploy loudly if any table or column is missing. Print exactly what is missing; never swallow it with || true. This gate exists because nutmeg 2026-06-11 shipped code whose schema prod lacked (silently unapplied migrations) and caused a user-facing P1. Full rule: canon/db-deploy-gate.md.
List current secrets and compare against .dev.vars:
wrangler secret list
grep -E '^[A-Z_]+=' .dev.vars 2>/dev/null | cut -d= -f1
If .dev.vars has a key that's NOT in wrangler secret list, warn the user and offer to push it:
wrangler secret put KEY_NAME # prompts for value
Never auto-push secrets without confirmation.
pnpm build
wrangler deploy # add --env <name> if --env passed
Capture the deployed URL from wrangler's output (https://<worker>.<account>.workers.dev or custom domain).
curl -sfI <deployed-url> | head -1 # expect HTTP/2 200 or 301
If it returns non-2xx/3xx, surface it immediately. Offer to tail logs:
wrangler tail
Once the smoke check passes: if docs/infrastructure/ does not exist (this is the first successful deploy), OR this deploy changed bindings/resources, run /ro:infra-docs to generate (or refresh) living architecture documentation — the live resource inventory, C4 + sequence diagrams, security model, and provisioning runbook. Standing up a new app should always leave it documented. The skill is idempotent, so a periodic re-run after notable deploys keeps it honest.
Offer to tag the release (optional):
git tag -a "deploy-$(date +%Y%m%d-%H%M%S)" -m "🚀 deploy: $(git log -1 --pretty=%s)"
Report to user: commit SHA, deployed URL, migrations applied, secrets status, smoke-check result, and whether infra docs were generated/refreshed.
typecheck fails — stop. Fix types, commit via /ro:commit, re-run /ro:cf-ship.test fails — stop. Don't use --skip-tests as a habit; treat red tests as the signal they are.drizzle-kit generate with a clean working DB first.wrangler deploy fails on compatibility_date — bump to today's date in wrangler.toml.wrangler tail immediately; usually a missing secret or bad D1 binding.--skip-tests + --skip-migrations together without explicit user confirmation--env production) without showing current vs new commit firstwrangler delete / wrangler d1 delete are NOT part of this skill — refuse if asked/ro:commit — emoji commits (use before shipping)/ro:new-tanstack-app — scaffold that this skill ships/ro:cloudflare-dns — add a custom domain after first deploytesting
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".