# names.poweredbyotm.com Free business-name finder: type a few keywords, get brandable name ideas, and see which domains are actually free — checked against the registries themselves. - **Stack:** Next.js 15 (App Router, `output: "standalone"`), React 18, no DB, no auth, public. Name generation runs **client-side**; availability is a server route because browsers can't do DNS and RDAP servers send no CORS headers. - **Repo:** `tonym/names` on `git.poweredbyotm.com`. - **Deploy:** OTM platform — first-party app (`/apps`), built + deployed via the platform MCP, not docker-compose-from-this-repo. ## How availability works — and what it costs (nothing) There is no paid domain API here and no key to manage. 1. **RDAP** (RFC 7482), the registries' own replacement for WHOIS. IANA publishes a bootstrap file at `data.iana.org/rdap/dns.json` mapping every TLD to its authoritative RDAP server. We cache it for 24h, resolve the TLD ourselves, and query that server directly — `404` means unregistered, `200` means registered. Covers ~1,200 TLDs including `.com/.net/.org/.ai/.dev/.app`. 2. **DNS `NS` lookup**, for the TLDs that publish no RDAP server at all — `.io`, `.co`, `.me`, `.sh`, `.gg`, `.us`, `.de`. NXDOMAIN strongly suggests unregistered, but a registered domain with no delegated nameservers looks identical. These report `unverified-available` and the UI shows them in amber as *probably free*, never as a confirmed green. **Do not "simplify" this by pointing everything at `rdap.org`.** Its redirector returns a bare `404` both for "this domain is free" and for "I have no RDAP server for this TLD" — the two cases are indistinguishable from the status code, which is exactly the bug that would report every `.io` as available. Manners, since we're an anonymous client against other people's public endpoints: 60 domains max per request, 8-wide concurrency, a per-IP token bucket, and a 10-minute result cache. ## Name generation `src/lib/generate.ts` is pure and deterministic — the same `(keywords, style, seed)` always yields the same list, so re-renders don't reshuffle results and a shared URL reproduces what the sender saw. The finder shows 36 names per page and **Show me more appends**: the names already on screen are passed as `exclude`, dropped before ranking, so each page is genuinely new (the seed alone would mostly reshuffle the same pool), and only the new domains get looked up. Changing keywords, style or extensions starts a fresh list; **start over** clears it. When the pool runs dry the app says so. Eight strategies (compound, suffix-word, action, blend, coined, clipped, root, domain hack) over the vocabulary in `src/lib/wordbank.ts`. The strategies are cheap and dumb on purpose — **quality comes from `scoreName`**, so tune the scorer before adding more words. Three guards exist because of specific bad output, and removing them brings it straight back: - `expandKeywords` matches category hints by **prefix, never substring** — plain containment let `repair` match the `ai` category (rep-**ai**-r) and drag neural-network vocabulary into an auto-shop brief. - `syncopate` only drops the schwa from a final `-er` (`flicker` → `flickr`). The original "drop the last vowel" produced `crema` → `crem`, `kettle` → `kettl`, `security` → `securit`. - `isTruncation` rejects any candidate that is a strict prefix of a word the generator knows. This is what keeps stumps out of the results. `diversify()` caps each strategy at ~28% of the returned list. Without it the top 20 came back as eighteen coinages — scoring alone clumps badly. ## AI names Sign in with a free OTM account (the link sits next to the generate button) and an **AI names** toggle appears: Claude-generated ideas are merged in ahead of the built-in list, checked for availability like everything else. The call goes `browser → /api/ai-names → OTM's metered Anthropic gateway → Anthropic`; the app holds a per-app gateway token, never a real Anthropic key, and the route refuses anonymous requests and caps each account at roughly 20 requests an hour. The platform's monthly AI cap is the backstop. There's no chat: the AI is briefed from the same keywords box and Style chip as the built-in generator (`src/lib/ai-prompt.ts` is the rulebook), and its names are merged in front of the built-in list, tagged **AI**, and checked like everything else. When AI is on, a **Tell the AI more** field appears — tone, words to include or avoid, audience — which is appended to that brief (capped at 300 chars, enforced client- and server-side). The built-in generator ignores it. Sign-in is OTM SSO: `/sso/authorize?aud=app:names` on the control plane hands back a 60-second single-use ticket to `/api/auth/otm-sso`, which mints a small HMAC-signed cookie (`names.session`). There are no user accounts in this app. ## Bring-your-own AI key Optional. `/settings` stores an Anthropic or Gemini key in `localStorage` (`names.aiKey`) and the browser calls that provider **directly** — the key never touches our server. Same pattern as `rpo`'s Google Vision key. It overrides the platform path above and works without signing in. The `anthropic-dangerous-direct-browser-access` header is correct here and would be wrong in a product that owns the key. The distinction is whose key it is. ## Saved names Every card has a ☆. Pressing it keeps the name in a **Saved** panel above the results, so you can shortlist while you keep generating. The list is `localStorage` only (`names.wishlist`, capped at 60) — there are no accounts and this doesn't need one. Each entry snapshots the availability it last saw, and the panel's **Re-check** button re-queries every saved domain (batched through the same `/api/availability` route, so it obeys the same manners) and **Copy list** puts a plain-text `domain — status` list on the clipboard. ## Registrar links `src/lib/registrars.ts`. Links are plain today — every `affiliateCode` is `""`. Fill one in and `HAS_AFFILIATE_LINKS` flips, which turns on the footer disclosure automatically. Keeping both in one file is deliberate: a disclosure that can drift out of sync with whether links actually pay is worse than none. ## Local development ```bash npm install npm run dev # http://localhost:3000 npm run typecheck npm run build ``` No env vars are needed to run it. Sign-in and platform AI light up only when the platform injects `AUTH_SECRET`, `OTM_SSO_SECRET`, `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY`; without them `/api/auth/session` reports `platformAi:false` and the UI hides the feature. Tailwind is present **only** to style the shared `@otm/account-panel` widgets (the bottom-right feedback chat-pop) — the app's own UI is handwritten CSS, so `preflight` is off and the config scans `node_modules/@otm/account-panel/src`. The app is always dark, so `` pins the widget's `dark:` variants on. The suggestions lightbulb degrades to a quiet "not configured" state unless `GITEA_URL` / `GITEA_TOKEN` / `GITEA_REPO` are set (the platform injects those at deploy time).