Captures what isn't derivable from the code: build_app times out at the MCP layer while continuing to build (poll for the image, don't retry), the app doesn't exist to build_app until platform CI goes green, and the three generator guards (prefix-only hint matching, -er-only syncopation, truncation rejection) each exist because of specific bad output. Also records why lookups must not route through rdap.org: its redirector returns a bare 404 both for "unregistered" and "I don't serve this TLD", which would report every .io as available. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JChWdFJPCRxMBxErb8kUVK
4.4 KiB
names — Business Name Finder
Free business-name generator with live domain availability, at names.poweredbyotm.com. A first-party Powered by OTM app.
- Stack: Next.js 15 (App Router,
output: "standalone"), React 18, no DB, no auth, public. Generation runs client-side; availability is a server route (browsers can't do DNS, and RDAP servers send no CORS headers). - Repo:
tonym/namesongit.poweredbyotm.com. - Deploy: platform MCP —
build_app("names")thendeploy_app("names"). NOT docker-compose-from-this-repo. Prod deploys need Tony's explicit OK.
README.md explains the mechanisms in full. This file is the house rules.
Deploy
Registered in platform/apps/otm-admin/lib/first-party-apps.ts as
publicAccess: true, needsDb: false, needsAuthSecret: false,
needsSuggestions: true, uploads: "/app/public/uploads".
Adding this app to the registry required an otm-admin release (0.113.0) — and
because the auto-deploy webhook is gated on green CI, names does not
appear in /apps until platform CI passes and otm-admin redeploys (~2.5 min).
build_app before that point fails on an unknown app id.
build_app times out at the MCP layer and keeps building. That's normal, not
a failure. Poll for the image instead of retrying the call:
ssh debian@51.81.80.250 'docker images platform-names --format "{{.Repository}} {{.CreatedSince}}"'
Verify a deploy against the LIVE site, never the deploy log:
curl -s https://names.poweredbyotm.com/api/health
curl -s -X POST https://names.poweredbyotm.com/api/availability \
-H 'content-type: application/json' \
-d '{"domains":["google.com","zzq7xk4m.io"]}' # expect taken + unverified-available
Availability — the trap
Never route lookups through rdap.org. Its redirector returns a bare 404
both for "this domain is unregistered" and for "I have no RDAP server for this
TLD" — the two are indistinguishable from the status code. Routing through it
would report every .io as available. We resolve the TLD ourselves from IANA's
bootstrap (data.iana.org/rdap/dns.json, cached 24h) and query the
authoritative server directly, so a 404 can only mean unregistered.
The TLDs with no RDAP at all (.io .co .me .sh .gg .us .de) fall back to a DNS
NS lookup. That path can only ever return unverified-available, shown amber
as probably free — a registered-but-undelegated domain looks identical. Do
not "upgrade" it to a green check.
We're an anonymous client against other people's public endpoints. Keep the manners: 60 domains/request, 8-wide concurrency, per-IP token bucket, 10-minute result cache.
Generation — three guards that must not be relaxed
src/lib/generate.ts is pure and deterministic. The strategies are deliberately
cheap and dumb; quality lives in scoreName — tune the scorer before adding
vocabulary. Each of these exists because of specific bad output, and removing it
brings that output straight back:
expandKeywordsmatches category hints by prefix, never substring. Plain containment letrepairmatch theaicategory (rep-ai-r) and dragged neural-network words into an auto-shop brief.syncopateonly drops the schwa from a final-er(flicker→flickr). The original "drop the last vowel" gavecrema→crem,kettle→kettl,security→securit.isTruncationrejects 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 list. Without it the top 20 came
back as eighteen coinages — raw score ranking clumps badly.
Keys stay in the browser
Optional AI names use the visitor's own Anthropic/Gemini key from
localStorage (names.aiKey), called browser-direct — same pattern as rpo's
Google Vision key, for the same reason: the platform has no per-app env tool,
and a free public tool must not hold a metered credential. The
anthropic-dangerous-direct-browser-access header is correct here and would be
wrong in a product that owns the key.
Registrar links
src/lib/registrars.ts. Links are plain today (every affiliateCode is "").
Fill one in and HAS_AFFILIATE_LINKS flips the footer disclosure on
automatically. Keep the code and the disclosure in that one file — a disclosure
that can drift out of sync with whether links actually pay is worse than none.