This repository is the Seamless Auth command-line tool (published as seamless-cli, invoked as
seamless or npx create-seamless). It does two things:
- Scaffold a working Seamless Auth project (
seamless init): generates a React frontend, an Express adapter, the auth server, a Docker Compose file, and config. - Verify the whole auth surface (
seamless verify): a cross-package conformance harness that runs an api / adapter / react matrix against the ecosystem.
Use this file as the fast path. The verify harness has its own moving parts under verify/ (a Docker Compose stack plus a Playwright harness).
These rules apply to every repository in the fells-code org. Repo-specific guidance may extend them but must not contradict them.
- Commit and open PRs solely under the repository owner's identity. Never commit under an agent or assistant identity.
- Never attribute work to an AI assistant: no
Co-Authored-By: Claude(or any assistant) trailers, no "Generated with" / "Created with Claude" notes, and no assistant branding or emoji anywhere in commit messages, PR or issue titles and descriptions, changesets, code comments, or docs.
- Comment only when the code genuinely needs explaining: a non-obvious reason, a gotcha, or an invariant. Never narrate what the code plainly does.
- Every
TODO/FIXMEmust reference a ticket, e.g.// TODO(#123): .... Do not leave a bare TODO. If no ticket exists, create one first.
- Conventional Commits (
feat:,fix:,chore:,docs:,ci:,test:). - Descriptive branch names (
feat/...,fix/...); never aclaude/or other tool-generated prefix.
- No em dashes in commit messages, code comments, PR or issue text, changesets, or docs. Use a comma, parentheses, or a separate sentence.
- All code quality checks must pass before you open a PR or call the work done. Run them and report the real output; do not open a PR while any check is failing.
- Commands:
npm run build(runstsc, which type-checks) andnpm test(vitest run);npm run coverageenforces the coverage thresholds. There is no separate lint/format tooling configured yet. Never claim a change works without running these. - Match the surrounding code's style, naming, and comment density.
- Install dependencies:
npm install - Build (type-check and emit):
npm run build(tsc, output indist/) - Run from source:
npm run dev -- <command>(tsx); or after building,node dist/index.js <command> - Commands:
init [name],templates,check,verify [flags],apps, and the instance-management commandsprofile,login,whoami,logout,sessions,config,users,org(all dispatched fromsrc/index.ts)
The entry point is src/index.ts, which dispatches to a command module in
src/commands/.
- init (src/commands/init.ts) scaffolds a project, driven by
src/prompts/. The web and api starters come from the registry-driven template source (src/core/templates.ts): it readsregistry.jsonfrom thefells-code/seamless-templatesmonorepo (pinned bySEAMLESS_TEMPLATES_REFin src/core/images.ts), downloads the selected templates, and applies each template'stemplate.jsonenv contract. The auth, docker, and config pieces are still generated locally insrc/generators/*. Override the template source for development withSEAMLESS_TEMPLATES_DIR(a local checkout) orSEAMLESS_TEMPLATES_REF(a different ref).- A
--<id>or--<alias>flag (e.g.seamless init --react-oauth,seamless init --oauth) preselects the matching template and skips that layer's prompt. Both spellings live in the registry, so no per-flag code.resolveTemplateAliasesruns inrunCLIbefore the project directory is created and before the non-empty-directory confirmation, so an unknown flag can never route through a destructive prompt on its way to an error. --yesruns the whole thing without prompting: every question has a flag (--web,--api,--email,--auth,--admin) and anything unspecified falls back to the option the prompt marks "(recommended)".--yesis never enough for a destructive step: overwriting a non-empty directory and rotating an existing service token both require--force, and choosing between a managed application and a local stack requires--appor--local. Flag parsing lives inparseInitArgs(src/index.ts); everything it produces is validated inrunCLIbefore a directory is created.- Every prompt is fronted by
requireInteractive(src/core/tty.ts), so a run without a TTY on stdin fails naming the flag that answers the question instead of rendering a prompt nobody can answer. This holds across every command, not justinit. When adding a prompt anywhere, guard it the same way. - templates (src/commands/templates.ts) lists the registry
(
seamless templates list [--json]) so those ids and flags are discoverable without a checkout. It reads the same sourceinitdoes and needs no login. - A template can declare
setup.oauthin itstemplate.jsonto trigger the OAuth provider prompts (src/prompts/oauthSetup.ts, catalog in src/core/oauthProviders.ts). The chosen providers are wired into the auth server env (OAUTH_PROVIDERS, per-provider*_CLIENT_SECRET, theoauthlogin method) bybuildAuthEnvin src/generators/docker/docker.ts.
- A
- destructive confirmations go through
confirmDestructive(src/core/confirmAction.ts), which answers itself when--forceis set and otherwise asks.--forceis the standing spelling for "do it without asking";hasForceFlagalso accepts--yesand-y, becauseconfig oauth-providers remove --yesshipped before the convention existed.--yesmeans something narrower oninit(answer the ordinary questions, never the destructive ones), so do not add--yesalone to a destructive step. Cancelling a confirmation reads as declining, not as an error. - check health-checks a running stack (local or managed).
- verify (src/commands/verify.ts) runs the conformance harness (below).
- instance management —
profile(targets, plusprofile login),logout/whoami,sessions,config(system config + OAuth providers),users, andorgall talk to a running instance and are authenticated by the stored session. - help —
seamless --help,seamless <command> -h/--help, andseamless help <command>all render from the single registry in src/commands/helpTopics.ts (src/commands/help.ts does the formatting, andCOMMANDSthere is also the dispatcher's known-command list). Document a new command or flag in that registry, not in the help template.src/index.tsanswers the help flag before a command parses its own args. - portal —
loginsigns in to the Seamless portal, a separate account from any instance profile. Its session lives beside the profile map inconfig.jsonand is the only oneinituses to connect a managed application (src/core/authClient.ts exposescreatePortalClientfor it).
seamless verify stands up the ecosystem with Docker Compose and runs a Playwright matrix, then
prints a flow x layer pass/fail grid (plus JUnit and HTML reports).
- verify/docker-compose.verify.yml: postgres, the auth API, and
both adapters, plus the React starter behind the
reactcompose profile. The mock OIDC provider runs in-process inglobal-setup(it is not a container). - verify/adapter-app (port 3000) and
verify/adapter-fastify-app (port 3001): minimal adopter backends on
@seamless-auth/expressand@seamless-auth/fastify, each with a capture transport so the harness can read OTP / magic-link codes the adapter would otherwise strip. They are deliberately twins: the same routes on the same env contract, so a spec cannot tell which one answered and any difference in behaviour is a real one. Keep them in step when either changes. - verify/harness: the Playwright projects (
api,adapter,adapter-fastify,react),lib/helpers,mock-oidc.ts,global-setup.ts, andlib/matrixReporter.ts(the printed grid). It has its ownnode_modulesand browsers.- The two adapter projects run the same specs from
./adapter; only theadapterUrlproject option differs (lib/fixtures.ts). Adding an adopter framework is a project entry plus a compose service, never a copy of the suite. Because they share a directory,matrixReportertakes the layer from the Playwright project name, not the spec's path.
- The two adapter projects run the same specs from
Modes and sibling repos:
--localbuilds the@seamless-auth/*packages from source (pre-publish contract testing); the default uses the published packages.- The sibling repos are resolved relative to this repo, overridable with
SEAMLESS_API_DIR,SEAMLESS_SERVER_DIR,SEAMLESS_REACT_SDK_DIR(the React SDK), andSEAMLESS_REACT_DIR(thereact-viteweb template, defaulting to../seamless-templates/templates/web/react-vite). - Useful flags:
--api-only,--no-react,--filter=<flow>(the=form; a space-separated--filter <flow>is not parsed),--keep-up.
- src/commands: one file per CLI command
- src/generators: locally generated scaffolding (auth, docker, config)
- src/core: shared helpers (templates, exec, env, fetch, secrets, paths, package manager, output)
- src/prompts: interactive setup prompts (
@clack/prompts) - src/utils: repo and env-file helpers
- verify: the conformance harness (shipped with the package)
Templates are not in this repo — they live in the seamless-templates monorepo
(SEAMLESS_TEMPLATES_REPO) and are fetched at scaffold time.
- TypeScript, ESM (
"type": "module"). Local imports use.jsextensions (NodeNext resolution). - Commit, comment, TODO, and attribution rules live in Working Standards above.
- Releases use Changesets. A user-facing change needs a changeset (
npm run changeset). A push tomainopens a "version packages" PR that bumps the version and writesCHANGELOG.md; merging that PR publishes to npm. Do not hand-edit the version orCHANGELOG.md. - npm publish token. The release workflow publishes with the
NPM_TOKENrepo secret. It must be a classic Automation token (full publish rights, bypasses 2FA) owned by an account with publish access toseamless-cli; a granular token restricted to a package allowlist cannot create or publish it and the registry returns a confusingE404on thePUT. - Templates ref bump. Shipping a change that depends on a new templates release is a two-step,
cross-repo dance: release
seamless-templatesfirst, then bumpSEAMLESS_TEMPLATES_REF(src/core/images.ts) to that tag. - Coverage badge.
README.mdshows a line-coverage badge (resources/coverage-badge.svg) regenerated locally by a Huskypre-commithook (.husky/pre-commit): it runsnpm run coverage(src/**/*.test.tsonly, so it never sweeps the Playwright specs underverify/), thennpm run coverage:badge(scripts/updateCoverageBadge.mjs) to rewrite the SVG fromcoverage/coverage-summary.json, stages it, and rebuilds. We standardized on the pre-commit hook (matchingseamless-auth-api) rather than a CI staleness check, so the committed badge always reflects the latest local run. If you change coverage, let the hook regenerate the badge; do not hand-edit the SVG.
- Run
npm run build(the root package's only build step). - If you touched the harness:
cd verify/harness && npx tsc --noEmit, then runseamless verify(--localto exercise local SDK source, or--api-onlyfor a fast pass). - Add a changeset for any user-facing change.
- Sibling-repo branches: every sibling repo (api, server, react SDK, seamless-templates) is checked out
at its default branch (
main) when no explicit*-refis passed to the verify CI workflow. --localneeds SDK dependencies: it builds the server (pnpm) and the React SDK (npm) from source on the host, so those repos must have their dependencies installed first. CI installs them explicitly.- OAuth mock networking: the in-process mock OIDC is reached by the browser and harness via
localhost, but by the API container viahost.docker.internal, so the provider config splits the authorize URL from the token / userinfo URLs. - Adapter OTP limiter: the adapter funnels all OTP through one client IP, so the API's per-IP OTP limiter (10 per 15 minutes, hardcoded) bounds adapter / react OTP traffic. Keep specs off it where possible (for example, magic-link login instead of a second email-OTP round trip).
- Version pins: verify/adapter-app pins
@seamless-auth/express, verify/adapter-fastify-app pins@seamless-auth/fastify, and thereact-vitetemplate pins@seamless-auth/react. Bump these when new versions publish. - Templates ref: the CLI scaffolds from
seamless-templatesatSEAMLESS_TEMPLATES_REF(src/core/images.ts); bump it when a new templates release publishes.