npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hasna/domains

v0.3.3

Published

Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL

Readme

@hasna/domains

Domain portfolio, registrar, marketplace, and DNS management for AI agents. The package ships a CLI, MCP server, authenticated HTTP API, generated SDK, and library exports. Every client surface resolves its credential and authority through the one shared @hasna/contracts resolver (macOS Keychain, ~/.hasna/domains/config/credentials, or HASNA_DOMAINS_API_KEY, with the fleet gateway https://api.hasna.com/domains as the default authority), and data commands fail closed when no credential resolves: local SQLite path settings are rejected; domains-serve connects directly to cloud Postgres.

Features

  • Domain portfolio management — track registrar, expiry, SSL, nameservers, pricing, lifecycle status, and notes
  • Registrar sync and purchase flows — direct Route 53 and Namecheap registration plus GoDaddy and Brandsight provider surfaces through a shared provider registry
  • Nameserver delegation — Route 53 and Namecheap adapters can update registrar nameservers when the provider API supports it
  • DNS provider management — create or reuse Cloudflare or Route 53 zones and manage DNS records through the DNS provider layer
  • DNS record CRUD — A, AAAA, CNAME, MX, TXT, NS, SRV records
  • DNS desired state — plan/diff/apply provider DNS records from JSON with explicit delete opt-in and post-apply verification
  • Sedo marketplace tools — optional search, portfolio/listing management, blacklist checks, and recorded Sedo purchases
  • AWS diagnostics — sync Route 53 registered domains and hosted zones without printing secret values
  • Credential diagnostics — redacted checks for Route 53, Cloudflare, Namecheap, GoDaddy, Brandsight, and Sedo
  • Expiry alerts — set alerts for domain expiry and SSL certificate expiry
  • WHOIS lookup — query and store registrar/expiry info from WHOIS
  • SSL certificate check — verify SSL issuer and expiry
  • DNS propagation check — query Google, Cloudflare, Quad9, OpenDNS
  • Zone file export/import — BIND-format zone files
  • Subdomain discovery — via crt.sh certificate transparency logs
  • DNS validation — detect CNAME conflicts, missing MX, and more
  • Portfolio export — CSV or JSON with all domain data
  • Brand monitoring — optional typosquat/threat detection via Brandsight API
  • MCP server — Model Context Protocol support for AI agents
  • MCP safe mode — expose read-only tools only with DOMAINS_MCP_SAFE_MODE=1
  • HTTP API + SDK — authenticated /v1 API through domains-serve and a typed @hasna/domains/sdk client
  • Interactive TUI — optional portfolio browser in the terminal with domains interactive

Installation

npm install -g @hasna/domains

All normal clients use the shared account API. Saved credentials live in the macOS Keychain or ~/.hasna/domains/config/credentials; HASNA_HOME and HASNA_CONFIG_HOME can select the credential root. Existing SQLite files are preserved for verified migration and are never opened by the CLI, MCP or SDK factory.

Optional Command Groups

The default CLI keeps core portfolio, registrar, DNS, provider, Route 53, doctor, MCP, and server commands loaded. Optional groups are enabled per invocation:

domains extras
DOMAINS_COMMAND_GROUPS=marketplace,owner domains --help
DOMAINS_ENABLE_EXTRAS=1 domains --help

Available groups: brandsight, events, history, interactive, marketplace, outreach, owner, provision, research, wallet.

Quick Start

# Set defaults used by availability, buy, setup, DNS, and doctor commands
domains config set default-registrar route53
domains config set default-dns cloudflare
domains config set purchase-aws-profile production-domains

# Add registrant contact defaults used by registrar purchase APIs
domains config set contact.first_name Jane
domains config set contact.last_name Example
domains config set contact.email [email protected]
domains config set contact.phone +1.5555555555
domains config set contact.address_line_1 "1 Main St"
domains config set contact.city "New York"
domains config set contact.state NY
domains config set contact.country_code US
domains config set contact.zip_code 10001

# Check local health without printing secrets
domains doctor

Provider Matrix

| Provider | Type | Availability | Buy/Register | Renew | Nameservers/DNS | Notes | |----------|------|--------------|--------------|-------|-----------------|-------| | route53 | registrar + DNS | yes | yes | no | nameservers + Route 53 hosted zones | Primary self-serve registration path. Use AWS_PROFILE or AWS keys. | | namecheap | registrar + DNS | yes | yes | yes | nameservers + Namecheap DNS records | Requires Namecheap API access and whitelisted client IP. | | godaddy | registrar + DNS | gated | no direct CLI purchase | gated | DNS records when API access qualifies | Availability remains threshold-gated; DNS/domain management is available for qualifying accounts. | | brandsight | registrar + DNS | yes | gated by contract | yes when contract allows | nameservers + DNS records | GoDaddy Corporate Domains / Brandsight v2 API; enterprise-contract-only. | | cloudflare | DNS + inventory | no | no | no | DNS zones and records | Preferred DNS provider; zone inventory syncs domains but does not prove registrar ownership. | | sedo | marketplace | marketplace search/status | recorded purchase only | no | no | Sedo is marketplace/listing/portfolio, not registrar DNS in this CLI. |

CLI Usage

# Portfolio management
domains domain add --name example.com --registrar Namecheap --expires-at 2027-01-01
domains domain list
domains domain list --status active --registrar Namecheap
domains domain get example.com
domains domain update <id> --notes "renewed"
domains domain delete <id>
domains domain search example
domains domain stats

# Expiry monitoring
domains domain expiring --days 30
domains ssl expiring --days 30

# WHOIS / SSL checks
domains domain whois example.com
domains ssl check example.com
domains domain check example.com example.net

# Portfolio export
domains domain export --format csv > portfolio.csv
domains domain export --format json

# Provider registry and diagnostics
domains providers
domains provider list
domains provider test route53
domains provider test cloudflare
domains doctor

# Registrar sync
domains sync --provider route53
domains sync --provider cloudflare
domains sync --provider namecheap
domains sync --provider godaddy
domains sync --provider brandsight
domains sync --all

# Availability and renewals
domains check example.com
domains check --provider route53 example.com
domains check --provider namecheap example.com
domains renew example.com --provider namecheap --years 1

# Purchases and setup
domains domain buy example.com --provider route53 --wait
domains domain buy example.com --provider namecheap --wait
domains domain buy premium.example --registrar sedo --price 2500 --expires 2027-01-01
domains domain setup example.com --registrar route53 --dns cloudflare --wait

# DNS record management
domains dns list <domain-id>
domains dns add --domain <id> --type A --name @ --value 1.2.3.4
domains dns update <record-id> --value 5.6.7.8
domains dns remove <record-id>
domains dns check-propagation example.com --record A
domains dns export <domain-id>
domains dns import <domain-id> --file zone.txt
domains dns discover-subdomains example.com
domains dns validate <domain-id>
domains dns pull example.com --provider cloudflare
domains dns push <domain-id> --provider cloudflare

# Create one new live DNS-only CNAME, without changing existing records
domains dns create example.com --provider cloudflare --zone-id <exact-zone-id> \
  --type CNAME --name new.example.com --value target.example.net \
  --ttl 300 --proxied false --yes --json

# Change one existing live record in place, by record ID (no delete, no recreate)
domains dns set example.com --provider cloudflare --zone-id <exact-zone-id> \
  --record-id <exact-record-id> --content new-target.example.net \
  --ttl 60 --proxied false --yes --json

# Read or change one allowlisted provider zone setting (Cloudflare)
domains zone setting get <exact-zone-id> --provider cloudflare --setting email_obfuscation
domains zone setting set <exact-zone-id> --provider cloudflare --setting email_obfuscation --value off --yes

# Desired DNS state against a provider zone
domains dns plan example.com --provider cloudflare --file dns.example.json
domains dns diff example.com --provider cloudflare --file dns.example.json
domains dns apply example.com --provider cloudflare --file dns.example.json --yes
# Delete plans require --allow-delete and may still be refused before writes
# when the provider path cannot guarantee safe convergence.

# Optional Sedo marketplace
DOMAINS_COMMAND_GROUPS=marketplace domains sedo search example
DOMAINS_COMMAND_GROUPS=marketplace domains sedo status example.com
DOMAINS_COMMAND_GROUPS=marketplace domains sedo portfolio --limit 25
DOMAINS_COMMAND_GROUPS=marketplace domains sedo add example.com --price 2500
DOMAINS_COMMAND_GROUPS=marketplace domains sedo edit example.com --price 3000
DOMAINS_COMMAND_GROUPS=marketplace domains sedo remove example.com
DOMAINS_COMMAND_GROUPS=marketplace domains sedo buy example.com --price 2500 --order-id SEDO-ORDER-ID

# Optional brand monitoring
DOMAINS_COMMAND_GROUPS=brandsight domains monitor watch mybrand
DOMAINS_COMMAND_GROUPS=brandsight domains monitor similar example.com
DOMAINS_COMMAND_GROUPS=brandsight domains monitor threats example.com

# Optional interactive portfolio browser
DOMAINS_COMMAND_GROUPS=interactive domains interactive
DOMAINS_COMMAND_GROUPS=interactive domains interactive --status active

dns add/update/remove changes the hosted portfolio. dns create instead contacts Cloudflare through its configured provider credential and creates exactly one previously absent DNS-only CNAME. Both the zone ID and zone name must match an active provider zone. Names and targets must be fully qualified; underscores in certificate-validation names are supported. An existing record of any type, including an identical CNAME, is refused without mutation.

The create command performs one POST and checks the returned record ID through both exact-ID and exact-name provider reads. It never updates, deletes, rolls back, or automatically retries a write. If a connection fails or readback does not match, inspect current provider state before deciding whether to try again. Provider CNAME collision handling protects competing address/CNAME creation; this operation is not a general compare-and-swap API for existing records.

dns set changes one existing live record in place by its record ID: it pins the zone, reads the record first for the exact pre-image, then issues one PATCH of only the fields given and verifies the readback. The record ID, type and name are preserved — nothing is deleted or recreated, and no retry is attempted. --expect-content refuses the change before any write unless the record still carries the reviewed content. It refuses without --yes, on any readback mismatch, when the record identity does not match, and on any id that is not an exact Cloudflare zone or record id.

zone setting get reads one allowlisted provider zone setting and zone setting set changes exactly that setting with one PATCH and a matching readback. The allowlist is closed (email_obfuscation, values on/off, today): unknown settings, unknown values, providers without settings support and a readback that does not match the requested value are refused, and no other zone setting is touched.

Compact Output For Agents

List, search, status, history, discovery, and portfolio commands are compact by default. Human output shows essential fields, caps the first page to 20 rows unless a command already has a smaller explicit limit, truncates long notes/record values, and prints a hint for the next detail path.

Use these flags to disclose more when needed:

domains domain list --limit 50 --offset 50
domains domain list --all
domains domain list --verbose
domains domain get example.com
domains domain list --json

--verbose adds human-readable detail columns such as registrar, notes, owner contact fields, or provider metadata. get, info, and other detail commands show one record at a time. --json remains the machine-readable detail path and preserves full records for existing automation unless a command already documents an explicit API/provider limit.

MCP list-style tools follow the same gradual disclosure model. Defaults return compact JSON with count, total, limit, offset, has_more, next_offset, compact, and hint. Pass limit/offset for paging, all: true for all returned matches, or verbose: true for full records:

{ "name": "list_domains", "arguments": { "limit": 50, "offset": 50 } }
{ "name": "list_domains", "arguments": { "verbose": true, "limit": 5 } }
{ "name": "get_domain", "arguments": { "id": "example.com" } }

MCP provider sync tools return count/error summaries by default; pass verbose: true only when an agent needs provider-specific arrays or full sync diagnostics.

Prefer the domains CLI over raw registrar CLIs for Route 53 availability, registration status, local portfolio updates, and DNS delegation. The CLI applies configured defaults, records outcomes in the local portfolio DB, and keeps behavior consistent across providers.

Desired DNS state files are JSON:

{
  "domain": "example.com",
  "records": [
    { "type": "A", "name": "@", "value": "192.0.2.10", "ttl": 300 },
    { "type": "MX", "name": "@", "value": "mail.example.com", "ttl": 300, "priority": 10 },
    { "type": "TXT", "name": "@", "value": "v=spf1 -all", "ttl": 300 }
  ]
}

dns apply refuses to run without --yes. If the plan includes deletes, it also requires --allow-delete, then refuses before writing unless the provider path can guarantee delete convergence without partial mutation. Non-delete applies re-read provider records after writing and fail if the live zone still differs from the desired file.

AWS Domain Discovery

# Sync registered domains and hosted zones from specific AWS profiles
AWS_PROFILE=production-domains domains sync --provider route53
AWS_PROFILE=shared-dns domains sync --provider route53

# Check all configured providers and redacted credential status
domains providers --json
domains doctor --json

Route 53 sync imports registered domains when the selected AWS account permits route53domains:ListDomains, and hosted zones when the account permits Route 53 hosted-zone reads. Domain-looking names from unrelated systems such as SSM parameters or Secrets Manager should only be imported after review because they do not prove registrar ownership.

Storage

Every client surface — the CLI, the MCP server and the ./sdk — resolves its credential and authority through the ONE shared @hasna/contracts resolver, fresh on every call:

  1. an explicit --api-key / --profile argument (code-level)
  2. a deliberate env pointer — HASNA_DOMAINS_API_KEY_OVERRIDE, HASNA_PROFILE, HASNA_DOMAINS_API_KEY_REF
  3. the macOS Keychain — item hasna.credentials.domains.api-key / .api-url, account HASNA_STATION, else the short hostname, else $USER
  4. disk, read at call time — ~/.hasna/domains/config/credentials (owner-only 0600; HASNA_HOME / HASNA_CONFIG_HOME override the root)
  5. HASNA_DOMAINS_API_KEY in the process env — a legitimate tier

The authority follows the same ladder — HASNA_DOMAINS_API_URL, the Keychain api-url item, the credentials file — and defaults to the fleet gateway https://api.hasna.com/domains once a credential resolves, so a key alone is a complete configuration. The unprefixed DOMAINS_API_URL / DOMAINS_API_KEY aliases are accepted by the resolver for one release; the canonical HASNA_DOMAINS_* names always win. Retired locations (~/.hasna/fleet-env, the cloud dirs under ~/.hasna, ~/.config/hasna, $XDG_CONFIG_HOME) are never read, and no *_MODE / *_STORAGE_MODE variable selects anything.

Fail closed. A hosted run with no credential exits non-zero naming the canonical env pair — never a silent fallback to SQLite, never a *-local-fallback event. A database DSN is never exposed to clients.

SQLite client selection is retired. Unset HASNA_DOMAINS_DB_PATH, DOMAINS_DB_PATH, HASNA_DOMAINS_DIR and DOMAINS_DIR before using normal clients. These settings are rejected even when no API key is configured. Preserve existing databases until all records have been reconciled and verified in the shared account; removing a path setting does not migrate data.

domains serve and domains-serve start the same authenticated PostgreSQL service. Both require a server database DSN (HASNA_DOMAINS_DATABASE_URL) and signing key (HASNA_DOMAINS_API_SIGNING_KEY); a client API key cannot authorize a listener. Data routes use /v1 and validate each caller's key and revocation state. Apply owner-role migrations first with domains db migrate.

MCP Server

domains-mcp

MCP list/search tools are compact by default for agent context safety; use limit, offset, all, and verbose arguments, or one-record detail tools such as get_domain, when more detail is needed.

Add to your Claude/agent config:

{
  "mcpServers": {
    "domains": {
      "command": "domains-mcp",
      "args": ["--stdio"]
    }
  }
}

MCP Transports

Streamable HTTP is the default transport for shared agent sessions and binds to 127.0.0.1:

domains-mcp                     # default port 8859
domains-mcp --port 9000
MCP_HTTP_PORT=9000 domains-mcp
  • GET /health returns {"status":"ok","name":"domains"}
  • POST /mcp is the Streamable HTTP MCP endpoint

Use stdio for clients that launch one MCP child process per session:

domains-mcp --stdio

For read-only agent sessions:

DOMAINS_MCP_SAFE_MODE=1 domains-mcp
DOMAINS_MCP_SAFE_MODE=1 domains-mcp --http

Safe mode registers only read-only/list/check/export tools. Mutating tools such as domain creation, DNS writes, provider sync, and Route 53 registration are withheld.

HTTP API And SDK

domains-serve exposes public health, readiness, version, and OpenAPI endpoints plus API-key-authenticated /v1 routes. Portfolio reads require domains:read; ordinary writes require domains:write; registrar purchases and provisioning require the separate domains:purchase scope. Send keys through x-api-key.

The hosted provisioning API is the production authority for availability, registration, Cloudflare zone creation, registrar nameserver delegation, bounded DNS reconciliation, and target readiness. Targets are generic: the existing shortlinks Worker binding and a website_origin profile that accepts a validated AWS ALB hostname. Website-origin jobs default to origin_tls_mode: "strict"; the weaker "full" mode must be explicit in each purchase or adoption request and becomes part of the durable idempotency identity. The service sets only the Cloudflare zone SSL setting, reads the exact value back before creating web records, and refuses to weaken an existing strict or stronger origin_pull zone. Unknown SSL values fail before mutation. Automatic SSL/TLS is a separate Cloudflare setting and is not changed. Full mode encrypts the Cloudflare-to-origin connection but does not authenticate the origin certificate.

  • POST /v1/availability — live Route 53 availability with registration and renewal prices.
  • POST /v1/provisioning — idempotently reserve and enqueue a capped purchase for a target.
  • POST /v1/provisioning/adopt — adopt an already-owned portfolio domain without purchasing it again.
  • GET /v1/provisioning/:id and GET /v1/provisioning/by-name/:name — read the durable state machine by job or canonical domain.
  • POST /v1/provisioning/by-name/:name/dns-reconcile — idempotently reconcile 1–20 exact TXT, CNAME, or MX records after provider readback.
  • POST /v1/provisioning/:id/advance — bounded operator recovery; the server worker normally advances jobs automatically.

Every purchase requires an explicit total-charge ceiling (max_price_usd), auto_renew, and idempotency key. The service rechecks availability and the total multi-year price immediately before registrar submission. An ambiguous registration submission enters manual_review and is never retried automatically, preventing duplicate purchases. Adoption requires registrar ownership readback plus an existing portfolio row; target changes conflict instead of silently repointing a domain. Route 53 and Brandsight portfolio registrations keep their registrar provenance. Before changing nameservers, the worker preflights the requested origin TLS mode, copies supported registrar DNS groups (A, AAAA, CAA, CNAME, MX, delegated NS, and TXT) into Cloudflare, reads them back exactly, and records a stable checkpoint. Unsupported or lossy DNS shapes enter manual_review; MX, TXT, CAA, and unrelated groups are never discarded to make a web route pass. A registrar readback also reconciles an applied-but-timed-out nameserver update without repeating it. Ready jobs return a provider-neutral result with an opaque zone reference, nameservers, web records, and the target check time.

Before registration, the durable job records the exact set of matching public Route 53 hosted-zone IDs. Cleanup can target only one newly appeared zone, and only when its current record set is exactly the apex NS/SOA pair, its delegation set matches those NS records, the registrar has delegated elsewhere, and Cloudflare is authoritative. Cleanup never deletes record sets; a concurrent record addition makes Route 53 reject the final zone deletion safely.

Hosted provisioning runtime requirements:

  • AWS task-role permissions for Route 53 Domains registration/status/delegation and safe hosted-zone cleanup.
  • Every account searched for adoption (including assumed registrar roles) needs route53domains:ListDomains and route53domains:GetDomainDetail. Absence means a completed paginated inventory without that domain; authorization, transport, and detail-read errors always stop discovery. This also preserves the inventory permission already needed by domain-detail enrichment.
  • Optional server-only DOMAINS_ROUTE53_REGISTRAR_ACCOUNTS_JSON maps up to 16 account references to registrar role ARNs. New adoptions persist their selected account and role fingerprint before provider mutations. Clients cannot select an account or inject a role.
  • DOMAINS_REGISTRANT_SOURCE_DOMAIN naming an existing Route 53 domain whose registrant contact can be reused in-process; contact data is never accepted from or returned to API clients.
  • CLOUDFLARE_ACCOUNT_ID plus CLOUDFLARE_API_TOKEN, scoped to zone management, DNS writes, Cloudflare's Zone Settings Write permission for the explicit SSL-mode readback contract, and the configured target bindings. Hosted provisioning deliberately rejects Cloudflare global API key/email authentication.
  • BRANDSIGHT_API_KEY, BRANDSIGHT_API_SECRET, and BRANDSIGHT_CUSTOMER_ID when an existing Brandsight / GoDaddy Corporate Domains portfolio registration is adopted. A new adoption request with missing credentials fails before reserving a hosted job or creating a Cloudflare zone.
  • Optional DOMAINS_PROVIDER_HTTP_TIMEOUT_MS (default 30000, maximum 120000) bounds Cloudflare and Brandsight Domain API requests. DOMAINS_PROVIDER_MAX_RESPONSE_BYTES (default 1048576, maximum 4194304) bounds Cloudflare responses; Brandsight Domain API responses have a fixed 1 MiB bound.
  • Optional DOMAINS_PROVISIONING_INTERVAL_MS (default 5000) for the durable background worker.
  • Optional DOMAINS_PROVISIONING_MAX_ATTEMPTS (default 17280) bounds consecutive polls within one state; successful state transitions reset the counter so normal registrar/DNS/certificate waits do not consume the whole job budget.

Consumers must call this API (normally through @hasna/domains/sdk) rather than holding registrar or Cloudflare purchase credentials themselves.

The protected deployment lane uses two stages on one candidate image digest and registrar map. First it loads binding-aware code with all existing entries retained and DOMAINS_ROUTE53_ADOPTION_PAUSED=true: new Route 53 adoptions cannot discover or persist bindings, existing bound jobs can resume, and legacy unbound jobs remain on the default account. It then requires an exclusively converged service and two complete, stable task censuses, including desired-stopped tasks that may still be running or stopping, before unpausing adoption. Both stages know the same map, so draining stage-one workers can safely process jobs admitted by stage two. The deploy role needs ecs:ListTasks and ecs:DescribeTasks for the service's full task census. Existing mapped deployments require the lane's digest-bound compatibility labels. Unknown provenance, mapping removal or retargeting, pending tasks, changed service state, and incomplete pagination fail closed. The lane never removes roles or restarts an old runtime as recovery. Once a mapping-bearing service mutation may have begun, backward rollback, including manual rollback, is forbidden even when job and lease counts are zero. Reconcile uncertain outcomes read-only and prepare separately reviewed binding-aware forward repair. Deployment remains subject to its normal release and owner authority.

domains-serve --host 0.0.0.0 --port 8080
curl http://127.0.0.1:8080/health
curl -H "x-api-key: $HASNA_DOMAINS_API_KEY" http://127.0.0.1:8080/v1/domains

The package also exports the generated client, wired to the same shared resolver the CLI and MCP server use — fresh on every request, so a key rotation heals a long-lived client:

import { createDomainsClientFromEnv } from "@hasna/domains/sdk";

const domains = createDomainsClientFromEnv();
const portfolio = await domains.listDomains({ status: "active" });

An explicit baseUrl with no apiKey builds a client pinned to that authority that never picks up an ambient fleet key; without a resolvable credential the SDK throws — it never degrades to an anonymous client or to local data.

domains serve is an alias for the authenticated server runtime, preserving its CLI host/port defaults. Its account data routes are /v1/*; the former unauthenticated /domains proxy has been removed.

Environment Variables

| Variable | Description | |----------|-------------| | HASNA_DOMAINS_API_KEY | Hosted API key (canonical env tier of the shared resolver; below the Keychain and the credential file) | | HASNA_DOMAINS_API_URL | Optional hosted API base URL — defaults to the fleet gateway https://api.hasna.com/domains once a key resolves | | HASNA_DOMAINS_API_KEY_OVERRIDE | Deliberate per-run key override that outranks every other tier | | HASNA_DOMAINS_API_KEY_REF | Deliberate secrets-vault pointer resolved through the @hasna/secrets SDK at request time | | HASNA_PROFILE | Global identity profile pointer (credentials-<profile> beside the credential file) | | HASNA_HOME | Shared root override for the credential/config root; normal clients do not open local data | | HASNA_CONFIG_HOME | Config-root override for the resolver's credential file | | HASNA_DOMAINS_DB_PATH | Retired client setting; migrate and verify existing data before removal | | DOMAINS_DB_PATH | Retired legacy client setting; rejected | | HASNA_DOMAINS_DIR | Retired client setting; rejected | | DOMAINS_DIR | Retired legacy client setting; rejected | | HASNA_DOMAINS_HOME, DOMAINS_HOME | Retired client selectors; rejected by hosted client surfaces | | HASNA_DOMAINS_CONFIG_PATH, DOMAINS_CONFIG_PATH | Override the settings config file path | | DOMAINS_CONFIG_DIR | Override the settings config directory | | DOMAINS_COMMAND_GROUPS | Comma-separated optional command groups to load, or all | | DOMAINS_ENABLE_EXTRAS | Set to 1 to load all optional command groups | | DOMAINS_MCP_SAFE_MODE | Set to 1 to expose only read-only MCP tools | | HASNA_DOMAINS_DATABASE_URL | Server-side PostgreSQL DSN used by domains-serve and DB migrations; required for server storage | | HASNA_DOMAINS_API_SIGNING_KEY | HMAC signing secret used by domains-serve to verify API keys | | AWS_PROFILE | AWS profile for Route 53 Domains and hosted zones | | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN | AWS credential fallback | | DOMAINS_PURCHASE_AWS_PROFILE | Purchase profile fallback when config has no purchase_aws_profile | | CLOUDFLARE_API_TOKEN | Cloudflare API token | | CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL | Cloudflare global key fallback | | CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID for zone creation | | DOMAINS_PROVIDER_HTTP_TIMEOUT_MS | Cloudflare and Brandsight Domain API request timeout in milliseconds, 1–120000 (default 30000) | | DOMAINS_PROVIDER_MAX_RESPONSE_BYTES | Cloudflare response-size limit in bytes, 1–4194304 (default 1048576) | | NAMECHEAP_API_KEY | Namecheap API key | | NAMECHEAP_USERNAME | Namecheap account username | | NAMECHEAP_CLIENT_IP | Namecheap whitelisted IP | | NAMECHEAP_SANDBOX | Use Namecheap sandbox API | | GODADDY_API_KEY, GODADDY_API_SECRET | GoDaddy API credentials | | BRANDSIGHT_API_KEY, BRANDSIGHT_API_SECRET, BRANDSIGHT_CUSTOMER_ID | Brandsight / GoDaddy Corporate Domains credentials | | BRANDSIGHT_DEMO_STUBS, BRANDSIGHT_ALLOW_STUBS | Set either to 1 to allow demo stub responses when the Brandsight API is unreachable | | SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD | Sedo marketplace API credentials |

Hosted storage is authoritative

The CLI, MCP server and SDK factory are hosted clients. They resolve the owner-only credential from the shared resolver and send requests to https://api.hasna.com/domains/v1. HASNA_DOMAINS_DB_PATH, DOMAINS_DB_PATH, HASNA_DOMAINS_DIR, DOMAINS_DIR and HASNA_DOMAINS_HOME are retired client selectors and are rejected; they never select SQLite or create a local portfolio.

If the hosted credential or authority cannot be resolved, data commands fail closed and report the configuration error. domains doctor reports the resolved hosted authority and credential source without exposing the credential. domains-serve and domains db migrate are operator surfaces and require PostgreSQL through HASNA_DOMAINS_DATABASE_URL (or the documented owner-role migration variable).

License

Apache-2.0

Preserving mail DNS

For SES-style authorization, use the SDK's reconcileProvisionedDomainEmailDns (POST /v1/provisioning/by-name/{name}/dns-reconcile/mail) with a stable idempotency key. The domain must already have a ready provisioning job. This route preserves existing DMARC content and TTL, keeps unrelated TXT records, and retains existing SPF authorization and terminal policy when adding required includes. Conflicting DKIM or mail-from records require review rather than replacement. records binds the requested intent; result.records contains the effective provider values after readback, including preserved automatic TTL.

The generic /dns-reconcile route still replaces explicitly named record groups. Neither route is a client compare-and-swap API. Both routes share a provider-zone PostgreSQL advisory lock on a dedicated connection across read, merge, write, readback and receipt. A busy zone returns retryable HTTP 503. Attempt receipts remain autocommitted on that connection; uncertain lock acquisition or release discards the connection. Attempt leases also fence mail writes. These controls do not lock out external DNS administrators. Mail writes refuse observed SPF drift, never delete existing record groups, and reject ambiguous SPF or DMARC policy sets on readback. An ambiguous provider outcome requires readback before any deliberate recovery.