{"id":50706107,"url":"https://github.com/zemse/domain-utils","last_synced_at":"2026-06-09T12:01:16.543Z","repository":{"id":362990633,"uuid":"1261554265","full_name":"zemse/domain-utils","owner":"zemse","description":"Keyless domain toolkit CLI: availability, WHOIS/RDAP, DNS, pricing, TLS, DNSSEC, and more.","archived":false,"fork":false,"pushed_at":"2026-06-06T21:58:04.000Z","size":116,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T23:09:12.086Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zemse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T21:07:30.000Z","updated_at":"2026-06-06T21:58:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zemse/domain-utils","commit_stats":null,"previous_names":["zemse/domain-utils"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/zemse/domain-utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zemse%2Fdomain-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zemse%2Fdomain-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zemse%2Fdomain-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zemse%2Fdomain-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zemse","download_url":"https://codeload.github.com/zemse/domain-utils/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zemse%2Fdomain-utils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34105565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T12:01:15.948Z","updated_at":"2026-06-09T12:01:16.415Z","avatar_url":"https://github.com/zemse.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# domain-utils\n\n[![crates.io](https://img.shields.io/crates/v/domain-utils.svg)](https://crates.io/crates/domain-utils)\n[![CI](https://github.com/zemse/domain-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/zemse/domain-utils/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA small, fast CLI toolkit for domains: **check availability**, look up\n**WHOIS / RDAP registration data**, see **registration pricing**, and inspect\n**DNS records**, **email-security records**, **TLS certificates**, **DNSSEC**,\n**reverse DNS**, **HTTP/redirect/HSTS**, and **DNS propagation** across public\nresolvers — across multiple backends, keyless by default.\n\nThe default backend, **`auto`**, is **keyless** — no signup, no API key. It uses\nRDAP where a TLD supports it and falls back to port-43 WHOIS otherwise, so it\ncovers every TLD. Backends can be selected explicitly with `--backend`.\n\nThe crate is published as **`domain-utils`**; it installs a binary named **`domain`**.\n\n## Install\n\n```sh\ncargo install domain-utils   # or: cargo install --path .\n```\n\n## Usage\n\n```sh\n# Quick lookup (no subcommand): checks availability, and for any registered\n# name also prints its full WHOIS/registration record. Add --pricing to also\n# show the registration price for available names.\ndomain example.com\ndomain example.com getme.dev acme.io\ndomain example.com --pricing\n\n# Check availability only (default backend: auto = RDAP→WHOIS, keyless)\ndomain check example.com\ndomain check example.com getme.dev acme.io\n\n# WHOIS / registration data\ndomain whois example.com\n\n# Availability + price together (--pricing is opt-in; the pricing endpoint is slow)\ndomain check mystartup --category popular --pricing\ndomain price com io dev ai          # registration pricing for TLDs\n\n# DNS records (A, AAAA, MX, NS, TXT by default)\ndomain dns example.com\ndomain dns example.com --type MX,TXT      # only these record types\ndomain ns example.com                     # nameservers (shortcut for `dns -t NS`)\n\n# Pick a backend explicitly\ndomain check example.com --backend whois\n\n# DNS health\ndomain dnssec example.com                  # DNSSEC status (DS / DNSKEY / AD bit)\ndomain ptr 8.8.8.8 1.1.1.1                 # reverse DNS (PTR) for IPs\ndomain propagation example.com -t A        # compare a record across resolvers\n\n# HTTP reachability: redirect chain, final status, HSTS header\ndomain http example.com\n\n# Expiry watch: only domains expiring within a window, soonest first\ndomain whois --file portfolio.txt --expiring-within 30d\n\n# List backends and whether each needs an API key\ndomain backends\n\n# Shell completions (bash, zsh, fish, powershell, elvish)\ndomain completions zsh \u003e ~/.zfunc/_domain\n```\n\n### Multi-TLD checks \u0026 categories\n\nCheck one name across many TLDs at once. Pick TLDs explicitly, by curated\n**category**, or all of them:\n\n```sh\ndomain check mystartup --tlds com,io,dev,ai      # explicit TLDs\ndomain check mystartup --category finance        # all finance TLDs\ndomain check mystartup -C tech,popular           # multiple categories\ndomain check mystartup --all-tlds                # every TLD (~1400; slow)\n\ndomain tlds                                      # list categories\ndomain tlds finance                              # TLDs in a category\ndomain tlds all                                  # every known TLD\n```\n\nThe full IANA TLD list and the category map are baked into the binary (no\nnetwork needed); a test keeps every category entry pinned to a real delegation.\n\n### Email security\n\n`domain email \u003cdomain\u003e` inspects a domain's mail-security posture over DNS\n(keyless): MX records, SPF (with the `all` policy), DMARC (with the `p=`\npolicy), and DKIM. DKIM has no DNS discovery, so a set of common selectors is\nprobed — \"none found\" means none of those common selectors, not \"no DKIM\".\n\n```text\n$ domain email github.com\ngithub.com\n  ✓ MX      1 record(s)\n  ✓ SPF     v=spf1 ... ~all  (softfail)\n  ✓ DMARC   p=quarantine\n  ✓ DKIM    selectors: google, k1, s1, selector1\n```\n\n### Pricing\n\n`domain price \u003ctld|domain\u003e...` shows registration / renewal / transfer prices\nvia Porkbun's public, keyless pricing endpoint (USD; these are Porkbun's retail\nprices — indicative, not a market minimum). Works by TLD, by `--category`, or\n`--all`. Add `--pricing` to `check` (or any lookup) to show the registration\nprice next to each available domain.\n\nPricing is **opt-in** because Porkbun's keyless endpoint is slow (~15s) and has\nno per-TLD variant — it only serves the full ~900-TLD table. To keep that cost\noff the common path, the table is fetched only with `--pricing`, downloaded\nconcurrently with the availability lookups, and cached on disk for 24h\n(`~/Library/Caches/domain-utils/` on macOS, `$XDG_CACHE_HOME`/`~/.cache/`\nelsewhere) — so the first priced run is slow but subsequent ones are instant.\n\n```text\n$ domain price io dev ai\nPorkbun pricing (USD/yr):\n  .io             reg $28.12  renew $51.80  transfer $51.80\n  .dev            reg $10.81  renew $12.87  transfer $12.87\n  .ai             reg $82.70  renew $82.70  transfer $165.09\n\n$ domain check mystartup --category popular --pricing\n✓ mystartup.io  available  $28.12/yr (porkbun)\n✓ mystartup.dev  available  $10.81/yr (porkbun)\n...\n```\n\n### TLS certificate\n\n`domain tls \u003cdomain\u003e` opens a TLS connection and shows the leaf certificate:\nsubject, issuer, validity window, days-to-expiry (highlighted when expiring\nsoon or expired), and SANs. Trust is intentionally not verified, so expired or\nself-signed certs are still inspected and flagged. Use `--port` for non-443.\n\n```text\n$ domain tls github.com\ngithub.com:443\n  subject:      github.com\n  issuer:       Sectigo Public Server Authentication CA DV E36\n  not after:    Aug  2 23:59:59 2026 +00:00\n  expiry:       57 days left\n  SANs:         github.com, www.github.com\n```\n\n### DNSSEC, reverse DNS \u0026 propagation\n\n`domain dnssec \u003cdomain\u003e` reports whether the parent zone publishes `DS` records\n(a secure delegation), how many `DNSKEY` records the zone serves, and whether the\nresolver set the `AD` (Authenticated Data) bit — i.e. it DNSSEC-validated the\nanswer.\n\n```text\n$ domain dnssec cloudflare.com\ncloudflare.com  signed \u0026 validated\n  DS records:   1\n           2371 13 2 32996839A6D808AFE3EB4A...\n  DNSKEY:       2\n  AD bit:       set\n```\n\n`domain ptr \u003cip\u003e...` does a reverse-DNS (PTR) lookup for one or more IPv4/IPv6\naddresses. `domain propagation \u003cdomain\u003e` queries the same record (default `A`,\noverride with `-t`) on several public resolvers (Google, Cloudflare, AdGuard,\ndns.sb) and flags whether they agree — useful right after a DNS change.\n\n```text\n$ domain ptr 8.8.8.8\n8.8.8.8  dns.google.\n\n$ domain propagation example.com -t A\nexample.com (A)  consistent\n  google       104.20.23.154, 172.66.147.243\n  cloudflare   104.20.23.154, 172.66.147.243\n  adguard      104.20.23.154, 172.66.147.243\n  dns.sb       104.20.23.154, 172.66.147.243\n```\n\n(Quad9 and OpenDNS are omitted from propagation: their public DoH endpoints\nserve wire-format only, with no JSON variant.)\n\n### HTTP / redirects / HSTS\n\n`domain http \u003curl|domain\u003e` traces the redirect chain hop by hop and reports the\nfinal status plus the `Strict-Transport-Security` (HSTS) and `Server` headers. A\nbare host defaults to `https://`.\n\n```text\n$ domain http github.com\nhttps://github.com/\n  200 https://github.com/\n  HSTS:         max-age=31536000; includeSubdomains; preload\n  server:       github.com\n```\n\n### Expiry watch\n\nAdd `--expiring-within \u003cDURATION\u003e` to `whois` to keep only domains whose\nexpiry falls within a window, sorted soonest-first — pair it with `tls`'s\ndays-to-expiry to watch renewals. Durations accept a bare number (days) or a\n`d`/`w`/`m`/`y` suffix (e.g. `30`, `30d`, `6w`, `3m`, `1y`).\n\n```sh\ndomain whois --file portfolio.txt --expiring-within 30d\n```\n\n### Shell completions\n\n`domain completions \u003cshell\u003e` writes a completion script to stdout for `bash`,\n`zsh`, `fish`, `powershell`, or `elvish`.\n\n```sh\ndomain completions zsh \u003e ~/.zfunc/_domain     # then ensure ~/.zfunc is on $fpath\ndomain completions bash \u003e /etc/bash_completion.d/domain\n```\n\n### JSON output\n\nAdd `--json` to any command for machine-readable output (a JSON array), e.g.\n`domain check example.com --json` or `domain dns example.com --json`. Pipe it to\n`jq` to script around it.\n\n### DNS records\n\n`domain dns` fetches live records over DNS-over-HTTPS (keyless, no resolver\nsetup). Default types are `A,AAAA,MX,NS,TXT`; override with `--type` (`-t`),\ncomma-separated or repeated. `domain ns` is a shortcut for nameservers. Both\naccept multiple domains / `--file` / stdin and run concurrently.\n\n```text\n$ domain dns example.com -t A,NS\nexample.com\n  A      104.20.23.154  ttl 300\n  NS     hera.ns.cloudflare.com.  ttl 21600\n  NS     elliott.ns.cloudflare.com.  ttl 21600\n```\n\n### Batch checks\n\nDomains can be passed as arguments, read from a file, and/or piped on stdin —\nthey're merged, de-duplicated, and looked up concurrently. Results print in\ninput order, followed by a summary line.\n\n```sh\n# Many domains at once\ndomain check example.com google.com acme.io rust-lang.org\n\n# From a file (one per line, whitespace-separated; `#` starts a comment)\ndomain check --file domains.txt\n\n# From stdin (no args, or `--file -`)\ncat domains.txt | domain check\n\n# Tune how many run concurrently (default: 10)\ndomain check --file domains.txt --concurrency 20\n```\n\n```text\n$ domain check example.com google.com freeme-zxqw12345.com\n✗ example.com  registered  (RESERVED-Internet Assigned Numbers Authority)\n✗ google.com  registered  (MarkMonitor Inc.)\n✓ freeme-zxqw12345.com  available\n— 1 available · 2 registered\n```\n\n### Example\n\n```text\n# Default lookup: availability, plus WHOIS for registered names. (Add --pricing\n# to also show the price next to available names.)\n$ domain example.com freeme-zxqw12345.com\n✗ example.com  registered  (RESERVED-Internet Assigned Numbers Authority)\nexample.com\n  status:       registered\n  registrar:    RESERVED-Internet Assigned Numbers Authority\n  expires:      2026-08-13T04:00:00Z\n  source:       rdap\n\n✓ freeme-zxqw12345.com  available\n— 1 available · 1 registered\n```\n\n```text\n$ domain whois example.com\nexample.com\n  status:       registered\n  registrar:    RESERVED-Internet Assigned Numbers Authority\n  registered:   1995-08-14T04:00:00Z\n  expires:      2026-08-13T04:00:00Z\n  nameservers:  a.iana-servers.net, b.iana-servers.net\n  epp status:   client delete prohibited, client transfer prohibited\n  source:       rdap\n```\n\n## How availability is determined (and a pitfall it avoids)\n\nThe default `auto` backend covers **every TLD** by combining two keyless sources:\n\n1. **RDAP** maps the TLD to its registry RDAP server via the IANA bootstrap and\n   queries it: **200 → registered**, **404 → available**. Crucially, a `404` is\n   trusted as \"available\" **only** because the TLD was confirmed to have an RDAP\n   service first. (A naive \"404 = available\" check would wrongly report\n   registered `.io`/`.co` domains as free.)\n2. **Port-43 WHOIS** handles the ~180 ccTLDs with no RDAP service (`.io`, `.co`,\n   `.de`, `.me`, `.us`, …). The authoritative WHOIS server is discovered via\n   IANA referral, then the free-text response is parsed heuristically. Because\n   WHOIS formats vary by registry, this is best-effort and may return\n   **unknown** for an unrecognized response (rather than guessing).\n\n`auto` picks RDAP when the TLD supports it, otherwise WHOIS.\n\n## Backends\n\n| Backend | Key required | Covers | Notes |\n|---------|--------------|--------|-------|\n| `auto`  | no (keyless) | all TLDs | **Default.** RDAP where available, else port-43 WHOIS. |\n| `rdap`  | no (keyless) | gTLDs + RDAP ccTLDs | Structured, authoritative. Errors on non-RDAP TLDs. |\n| `whois` | no (keyless) | all TLDs | Port-43 WHOIS via IANA referral. Free-text, best-effort. |\n\nPlanned: keyed registrar backends (Porkbun, AWS Route 53, Gandi, Name.com) for\npricing. See `RESEARCH.md`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzemse%2Fdomain-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzemse%2Fdomain-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzemse%2Fdomain-utils/lists"}