skills/domain/SKILL.md
Search, price, register, and manage domains via the Porkbun API. Use when the user wants to find a cheap domain, check availability across TLDs, buy a domain, list their domains, update nameservers, or manage URL forwarding. Porkbun is the backend; cheap pricing, full API coverage, no IP whitelisting.
npx skillsauth add RonanCodes/ronan-skills domainInstall 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.
Cheap domains + a clean REST API. Covers search, pricing, registration, DNS, and nameservers. After buying, you usually want /ro:cloudflare-dns to run DNS on Cloudflare (set Porkbun nameservers to Cloudflare's).
/ro:domain search <name> [--tlds .com,.app,.dev,.io,.xyz] # availability + price across TLDs
/ro:domain search-bulk name1,name2,name3 [--tlds ...] # matrix scan: names × TLDs, ranked by cost
/ro:domain prices [--tld .com] # live price list (no auth needed)
/ro:domain check <domain> # single availability + exact price
/ro:domain register <domain> [--years 1] [--no-privacy] # buy it (confirms cost first)
/ro:domain list # all domains in the account
/ro:domain ns <domain> # show nameservers
/ro:domain ns <domain> <ns1> <ns2> [<ns3> <ns4>] # update nameservers
/ro:domain ns-cloudflare <domain> # end-to-end: create CF zone, push NS to Porkbun
/ro:domain bootstrap <domain> [--worker <name>] # buy + CF zone + NS switch + optional Worker attach
/ro:domain forward <domain> <target-url> # add URL forwarding
/ro:domain dns-list <domain> # list DNS records on Porkbun's NS
Important Porkbun quirk: the account must have at least one domain previously registered before domain/create will succeed via API. The first domain has to be bought through the web UI. After that, API registration works.
mkdir -p "$CLAUDE_PLUGIN_DATA" && chmod 700 "$CLAUDE_PLUGIN_DATA"
cat >> "$CLAUDE_PLUGIN_DATA/.env" <<'EOF'
PORKBUN_API_KEY=pk1_...
PORKBUN_SECRET_API_KEY=sk1_...
EOF
chmod 600 "$CLAUDE_PLUGIN_DATA/.env"
The skill loads from (in order): $CLAUDE_PLUGIN_DATA/.env, ~/.claude/.env, project root .env, shell env.
curl -sX POST https://api.porkbun.com/api/json/v3/ping \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_API_KEY\"}" \
| jq .
Expect {"status":"SUCCESS","yourIp":"..."}. If you get Invalid API Key, the key pair is wrong or API access is disabled on all domains.
https://api.porkbun.com/api/json/v3{"apikey": "...", "secretapikey": "...", ...other fields}POST /pricing/getBuild a reusable body template once per session:
AUTH="\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_API_KEY\""
Then inject with -d "{$AUTH}" or -d "{$AUTH,\"field\":\"value\"}".
prices — live TLD pricingPublic endpoint, no auth:
curl -sX POST https://api.porkbun.com/api/json/v3/pricing/get | jq '.pricing'
Returns {tld: {registration, renewal, transfer}}. Filter and sort:
curl -sX POST https://api.porkbun.com/api/json/v3/pricing/get \
| jq -r '.pricing | to_entries | map({tld:.key, reg:(.value.registration|tonumber)}) | sort_by(.reg) | .[0:20][] | "\(.tld): $\(.reg)"'
Top-20 cheapest by first-year registration. Good for finding .xyz-style cheap TLDs the user might not have considered.
search — single name across TLDsDefault TLDs if user doesn't pass --tlds: .com,.app,.dev,.io,.xyz,.co,.net,.org.
For each TLD, hit checkDomain:
curl -sX POST "https://api.porkbun.com/api/json/v3/domain/checkDomain/$NAME.$TLD" \
-H 'Content-Type: application/json' \
-d "{$AUTH}" | jq '{domain: "'$NAME.$TLD'", available: .response.avail, price: .response.price, premium: .response.premium}'
response.avail is "yes" / "no". Premium domains cost multiples of the base price; flag them so the user doesn't accidentally buy a $500 .com.
Rate limit: checkDomain is capped at 1 request per 10 seconds per API key (Porkbun, as of 2026-04). Sleep 11s between calls or the server returns "1 out of 1 checks within 10 seconds used". For bulk scans, first filter with whois / dig NS (free, no rate limit) to a shortlist, then run checkDomain on the survivors for exact pricing. Never run in parallel; you'll get throttled immediately.
search-bulk — matrix scanNames × TLDs, filter to available, rank by price ascending. Useful for "give me the cheapest available from this shortlist." Example output:
AVAILABLE (sorted by first-year price):
$2.30 connpal.xyz
$9.73 connpal.com
$11.99 connpal.app
$12.12 connpal.dev
TAKEN:
hintleaf.com, hintleaf.app, ...
PREMIUM (skip unless you love this name):
$499 wordgrid.com
check — single domain, exact price + premium flagUse checkDomain as above but print the full response. Show the user registration, renewal, and first-year cost separately (Porkbun always shows both).
register — BUYS THE DOMAIN. Costs money.Always confirm with the user before calling domain/create. Show the exact cost (in dollars), the years, and the privacy setting.
curl -sX POST "https://api.porkbun.com/api/json/v3/domain/create/$DOMAIN" \
-H 'Content-Type: application/json' \
-d "{$AUTH,\"years\":1,\"agreeToTerms\":\"yes\",\"whoisPrivacy\":1,\"cost\":\"$COST_USD\"}"
Params:
years — integer, default 1agreeToTerms — must be "yes" or "1"whoisPrivacy — 1 (default, free at Porkbun) or 0 (expose registrant info in WHOIS)cost — string of the exact first-year price in dollars (e.g. "9.73"); must match the current price or Porkbun rejectsOrder of operations:
checkDomain to get the current price (prices change)"Register foo.com for $9.73/year (renewal $11.28/year), WHOIS privacy on. Proceed?"domain/create with the exact cost from step 1https://porkbun.com/account/domainsErrors to expect:
"We were unable to process..." → account email/phone not verified, or no prior registration on the account"Price mismatch" → price changed between check and create; re-run check and retry"Insufficient funds" → top up account credit at https://porkbun.com/account/credit (Porkbun requires pre-paid credit, not a card-on-file for API buys)list — owned domainscurl -sX POST https://api.porkbun.com/api/json/v3/domain/listAll \
-H 'Content-Type: application/json' \
-d "{$AUTH,\"includeLabels\":\"yes\"}" | jq '.domains[] | {domain, status, expireDate, autoRenew}'
ns — nameserversRead:
curl -sX POST "https://api.porkbun.com/api/json/v3/domain/getNs/$DOMAIN" \
-H 'Content-Type: application/json' -d "{$AUTH}" | jq '.ns'
Write (replace all):
curl -sX POST "https://api.porkbun.com/api/json/v3/domain/updateNs/$DOMAIN" \
-H 'Content-Type: application/json' \
-d "{$AUTH,\"ns\":[\"$NS1\",\"$NS2\"]}"
ns-cloudflare — hand DNS over to Cloudflare (automated)End-to-end: create the Cloudflare zone via API, then push the per-zone nameservers to Porkbun. Requires CLOUDFLARE_API_TOKEN_ZONE_CREATE (the account-owned token — user tokens cannot create zones). If it's not set, stop and point at /ro:cloudflare-setup.
source "$(ro context env)"
# 1. Create the zone
ZONE_JSON=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ZONE_CREATE" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$DOMAIN\",\"account\":{\"id\":\"$CLOUDFLARE_ACCOUNT_ID\"},\"type\":\"full\"}")
ok=$(echo "$ZONE_JSON" | jq -r '.success')
if [ "$ok" != "true" ]; then
echo "$ZONE_JSON" | jq '.errors'
# Common errors:
# - 1061 "zone already exists" → zone is already in the account, fetch it with GET /zones?name=$DOMAIN
# - 10000 "Authentication error" → token lacks Zone Write, re-run /ro:cloudflare-setup
exit 1
fi
NS1=$(echo "$ZONE_JSON" | jq -r '.result.name_servers[0]')
NS2=$(echo "$ZONE_JSON" | jq -r '.result.name_servers[1]')
ZONE_ID=$(echo "$ZONE_JSON" | jq -r '.result.id')
# 2. Push those NS to Porkbun
curl -sX POST "https://api.porkbun.com/api/json/v3/domain/updateNs/$DOMAIN" \
-H 'Content-Type: application/json' \
-d "{$AUTH,\"ns\":[\"$NS1\",\"$NS2\"]}" | jq '{status, message}'
# 3. Tell the user: propagation usually takes 5-60 min for a brand new domain.
# Activation status can be polled via:
# curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ADMIN" | jq '.result.status'
# Expect "pending" → "active" once Cloudflare sees the NS change.
Verify after a few minutes: dig NS $DOMAIN +short @1.1.1.1 — should show the two *.ns.cloudflare.com hostnames.
Manual fallback (if the account-owned token isn't available): have the user add the zone via dashboard → https://dash.cloudflare.com → Add a site → $DOMAIN → Free plan. The dashboard prints the per-zone NS pair; run /ro:domain ns $DOMAIN <ns1> <ns2> with those values.
bootstrap — buy a domain and put it on Cloudflare in one flowComposes register + ns-cloudflare + an optional Worker attach step. Use when the user says "buy X and point it at my Worker" — it's the common end-to-end.
# 1. check + confirm + register (see `register` subcommand)
# 2. ns-cloudflare (see above) — creates zone + swaps NS
# 3. if --worker <name> passed: add two routes on the Worker
# for $DOMAIN and www.$DOMAIN via wrangler.jsonc, and run `wrangler deploy`.
# Requires CLOUDFLARE_API_TOKEN_ADMIN for the custom_domain attach.
Report at the end: zone ID, nameservers, Worker bindings (if any), and whether the zone is pending or already active. Remind the user that until the zone is active, HTTPS on the new domain won't serve traffic — but it usually flips within minutes for a brand-new domain (slower for moves from an existing registrar-hosted DNS).
forward — URL forwardingPorkbun can redirect a bare domain to a URL (useful for parking before you deploy something). Survives without setting up DNS or a server.
curl -sX POST "https://api.porkbun.com/api/json/v3/domain/addUrlForward/$DOMAIN" \
-H 'Content-Type: application/json' \
-d "{$AUTH,\"subdomain\":\"\",\"location\":\"$TARGET_URL\",\"type\":\"temporary\",\"includePath\":\"no\",\"wildcard\":\"yes\"}"
Use "type":"permanent" for a 301; "temporary" for 302.
dns-list — DNS records (only useful if Porkbun is your NS)curl -sX POST "https://api.porkbun.com/api/json/v3/dns/retrieve/$DOMAIN" \
-H 'Content-Type: application/json' -d "{$AUTH}" | jq '.records'
If Cloudflare is the nameserver, use /ro:cloudflare-dns list instead.
If the user doesn't yet have Porkbun credentials, fall back to whois or dig for a rough first pass:
whois example.com | head -5 # "No match" / "NOT FOUND" => likely available
dig NS example.com +short # empty output => likely available (but some domains have no NS)
These give a signal, not a guarantee. Premium / reserved names can show as available in WHOIS but be un-buyable at normal prices. Always verify with check once keys are in place.
register spends real money. Never call it without an explicit user "yes" and showing the exact cost. Don't auto-retry on failure (the user may not want to re-attempt at a new price).ns / updateNs can break email and websites. Show the current NS before overwriting, warn on production domains, and recommend lowering TTLs to 300 before the switch if the domain is in active use.Insufficient funds means the user needs to pre-pay at https://porkbun.com/account/credit. Don't try to top up from the skill, it's manual.PORKBUN_SECRET_API_KEY. When echoing a curl command for the user to re-run, replace the key with $PORKBUN_SECRET_API_KEY.domain/create fails immediately after account creation, the fix is to buy one domain through the web UI first./ro:domain register mydomain.com — buy/ro:domain ns-cloudflare mydomain.com — create CF zone + move DNS/ro:cloudflare-dns add @ A <ip> or whatever records you need/ro:cf-ship --domain mydomain.com (if deploying a Worker) — attach custom domainOr collapse steps 1-2 into /ro:domain bootstrap mydomain.com --worker my-worker.
ns-cloudflare and bootstrap need two Cloudflare tokens because of how Cloudflare's permission model splits up — zone creation is not available on user tokens, only account-owned tokens.
CLOUDFLARE_API_TOKEN_ZONE_CREATE (account-owned, prefix cfat_) — for creating the zoneCLOUDFLARE_API_TOKEN_ADMIN (user, prefix cfut_) — for DNS, Worker attach, redirect rulesCLOUDFLARE_ACCOUNT_ID — used in the zone-create payloadIf any are missing, run /ro:cloudflare-setup first. That skill walks the user through minting both tokens with the right scopes.
/ro:cloudflare-setup — one-time Cloudflare token minting (prereq for ns-cloudflare / bootstrap)/ro:cloudflare-dns — DNS management once nameservers are on Cloudflare/ro:cf-ship — deploy a Worker with a custom domaindevelopment
--- name: worktree description: Coordinate multiple agents on one repo via a worktree-lock pool, so two agents never clobber each other's working tree. Acquire the first free slot (main, then beta/gamma… worktrees, created on demand), work there on your own branch, release when you've pushed. Use before modifying any repo that might be in use by another agent (factory, dataforce, etc.), or whenever you're told a repo is being worked on. Backed by `ro worktree`. category: development argument-hin
testing
--- name: ship description: Ship a feature branch the local-CI-first way — run the full local gate, push, open a PR, squash-merge, then deploy, without waiting on GitHub Actions. Use when a branch is ready for main and you want it merged and deployed now. Reads CI policy from `ro ci` (default skips remote CI because GitHub Actions billing keeps hitting limits). Sibling to /ro:gh-ship (waits on GitHub checks) and /ro:cf-ship (the deploy half). Triggers on "ship it", "ship this", "merge and deploy
testing
--- name: setup-logging description: Set up (or audit) the observability stack in a TanStack Start + Cloudflare Workers app so it is "diagnosable by default" — structured logging (logtape) with a request context carrying trace_id + userId + tenant/orgId, a trace_id propagated FE→BE→logs→Sentry→PostHog, Cloudflare Workers observability enabled, and Sentry + PostHog wired. Two modes: `setup` (wire it into an app) and `audit` (check an existing app + report gaps). Use when scaffolding a new app, wh
development
Manage credentials INSIDE the active ~/.claude/.env file — read which token/account to use for a given app (Simplicity vs Dataforce vs Ronan-personal), add or update a secret WITHOUT it passing through the chat (an interactive Terminal window prompts for it), and track secrets that were exposed in a transcript so they get rotated. Sibling to /ro:context (which switches WHICH env file is active). Use when the user wants to add an API key/token/secret, asks "which credential do I use for X", needs the env organized/labelled, or a secret was pasted into the chat and should be rotated.