Files
names/README.md

136 lines
7.0 KiB
Markdown

# 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 `<html
class="dark">` 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).