← All posts

Domains without the dashboard

· 6 min read
clidomainsgo

Most of what talks to a service now isn’t a person. It’s a pipeline, a cron job, or an agent that read the help text thirty seconds ago and is already three commands in. A command line is the one interface all three can drive, and a well-built one is now a better way into a service than its dashboard.

So I wrote one: namecom, a command line for name.com that covers the registrar’s full surface.

Then there is the part where it spends money. Registering a domain is a purchase, and of everything that might invoke this, only the caller holding the keyboard hesitates before one. An agent passes --yes because --yes makes the prompt go away. Dry runs and confirmation prompts are things you can hand-roll per script, and things you will get subtly wrong per script.

The flow

The whole thing is one command

domain check prints availability and a price. Then, if it checked exactly one domain and a human is actually at the terminal, it offers to register it right there, so there is no second command to type and no name to retype.

$ namecom domain check driftwoodco.com

DOMAIN            AVAILABILITY   PRICE       PREMIUM
driftwoodco.com   ✓ available    $12.99/yr
(1 result)

Register driftwoodco.com for $12.99/yr? [y/N] y

✓ Registered driftwoodco.com (order #1180492, total $12.99)
→ Run 'namecom dns list driftwoodco.com' to add DNS records
→ Run 'namecom domain autorenew on driftwoodco.com' to enable auto-renewal

$ namecom dns create driftwoodco.com --type A --answer 1.2.3.4

✓ Created A record (id 82551037)
→ Run 'namecom dns list driftwoodco.com' to see all records
Registering and pointing a domain. Order and record ids are illustrative; everything else is what the tool prints.

That offer has two rules worth stating. It never appears under --dry-run, because there is no meaningful preview of an interactive purchase. And it never accepts --yes as the answer. --yes is what you pass when you have stopped reading the prompts, and it is the first thing an agent reaches for; honoring it here would turn a read-only availability check into a purchase, with nothing between the two.

Install

Two lines to install

brew tap patramsey/tap
brew install namecom

There are signed release binaries for macOS, Linux and Windows if you would rather not use Homebrew. go install works too, with one wrinkle: Go names the binary after the module path, so it lands as namecom-cli and you will want to rename it.

Surface

Twelve groups, sixty-odd verbs

Anything the web dashboard does, with the exception of billing.

Group What it covers
domain list, search, check, register, renew, lock, privacy, nameservers, contacts, transfer auth codes, pricing
dns records, plus export and import in JSON or BIND zone format
dnssec DS record management
transfer inbound and internal transfers, eligibility, cancellation
email url forwarding, both kinds
vanity-ns your own branded nameservers
auth config credentials and named profiles
api raw authenticated passthrough, for whatever I haven’t wrapped yet

namecom status is the one I actually run most days. It fans out across the account and prints the portfolio in about ten lines: how many domains, what expires soon, what transfers are still pending.

Automation

It knows when it isn’t a terminal

The default output format is a colored table when stdout is a terminal and JSON when it is not. You never pass --output json in a pipeline. It is already JSON, because nothing was watching. Neither does an agent, which is the case that matters more: it would not have known to pass the flag, and it would have parsed the table.

Two separate checks drive that, which matters more than it sounds. Stdout decides the format. Stdin decides whether prompting is even possible. A command with its output redirected but a human still attached can still ask before it deletes something, and a command in CI, or inside an agent loop, never blocks on a question nobody is there to answer.

# Every domain expiring in the next 60 days
namecom domain list --output json \
  | jq -r '.data[] | select(.expireDate < "2026-10-14") | .domainName'

# One name per line, straight into xargs
namecom domain list -q | xargs -I{} namecom dns create {} --type A --answer 1.2.3.4

# Show me the request you would send, then send it
namecom dns create acme.io --type TXT --answer "v=spf1 include:sendgrid.net ~all" --dry-run

--dry-run prints the request instead of sending it, and the dry run is tested against the real one, so the preview cannot drift from what actually goes over the wire. Credentials come from a profile, an environment variable, or a token_cmd that shells out to your password manager and keeps the token off disk entirely.

The point

Quiet, financial, late

Say a registration goes out and nothing comes back: no order number, no error, no way to tell from where you are sitting whether you now own the name. The only thing to do is run it again. Two mechanisms are supposed to make that retry safe, and both of them were mine to get right. My HTTP layer will not retry a POST on its own, because it cannot know the endpoint is safe to repeat. Registration sends an idempotency key, so that if the first request did land, the second is recognized as the same purchase rather than a new one.

Both correct, and for a while both useless: the code that attached the key ran after the code that built the request, and overwrote whatever the caller had set with a fresh value. Every retry carried a new key. Nothing downstream had any way to tell the attempts apart, because I had removed the one thing that would have let it.

Nothing about that was visible from the outside. Both halves looked right in review, the tests passed, and the only symptom would have been a duplicate order on someone’s invoice. That is the shape of the bugs worth being afraid of here: quiet, financial, and discovered long after you wrote them.

Caveats

Where it will bite you

It is beta, and the version number means it. The sandbox exists for a reason: point a profile at api.dev.name.com, register nonsense there, and find out what a command prints before you run it against a real account.

A couple of things will refuse you on purpose. Registering a name under a trademark claim requires its own explicit acknowledgement, and --yes will not supply it, for the same reason it cannot answer the register prompt. Bulk dns import validates the entire file before writing a single record, because the writes are not transactional and a half-applied zone is worse than a rejected one.

The web UI is better than this at the things a web UI is good at. It is very good at showing you one domain. It is not good at showing you sixty, or at doing the same small thing to all of them on a schedule, and nothing but a person can drive it at all. Those are the jobs I built this for, and they are the jobs where the difference between a careful client and a shell script is a line item on a bill.