@blueforge-studio/fauth
forge-auth CLI — sign in with the device flow and inspect your forge-auth identity

Package Info
Versions
| Version | Published |
|---|---|
| 0.4.0 | 2026-10-13 |
| 0.3.0 | 2026-10-13 |
| 0.2.0 | 2026-09-25 |
Per-version supply chain
| Version | Sig | SBOM | Att |
|---|---|---|---|
| 0.4.0 | |||
| 0.3.0 | |||
| 0.2.0 |
Click a version to view its details.
Supply chain
Provenance
- Published by
- web:14
- When
- 2026-10-13T11:06:02Z
- sha256 (registry-computed)
- 45eb560c1e123858f0ff2290caa2de7e6d6b54a9659eb858bb3a1ce1474756d3
Observed by the registry at publish.
Claimed by the build
The publisher claimed nothing about its build.
Attachments
- Signature
- none
- SBOM
- none
Dependencies
README
fauth — forge-auth CLI
fauth is the forge-auth member of the federation CLI family, alongside fgit (forge-git), forge (forge-control) and packr-cli. It signs in with forge-auth's device flow and tells you who you are, so you no longer need to read the production database to find your own user id.
fauth login # device flow for app "fauth": open the URL, enter the code
fauth whoami # user id, email, name, org memberships
fauth whoami --json
fauth orgs list | show <idOrSlug> | members <idOrSlug>
fauth orgs create --name "Acme" --slug acme --yes
fauth logout # revokes the key on the server, deletes the local file
Commands
| Command | What it does |
|---|---|
fauth login [--app-id <id or slug>] | Runs the device flow (POST /api/auth/device, then polls /api/auth/device/token). The app defaults to the slug fauth (FAUTH_APP_ID overrides). Prints the verification URL and code, waits for browser approval, then stores the minted key. |
fauth login --admin --email <e> [--app <slug>] [--days <n>] [--json] | Agentic login with no browser and no prompts; see Agentic login. |
fauth admin device-approve <code> --as <email or id> [--json] | Approves someone else's waiting device login (a host with no browser) as that existing user, using the admin key (same sources as --admin). The waiting login then gets its key, which carries the user's own scopes. |
fauth admin service-account-delete <id> [--json] | Deletes a service account by id with the admin key (same sources as --admin). The server answers 204 even for an unknown id, so a repeat is a no-op; it does not say whether the id existed. |
fauth admin apps create --name <n> --slug <s> [--redirect <url>]... [--webhook-url <u>] [--show-secret] [--json] | Registers an app with the admin key. Idempotent: if the slug exists it prints the existing app (id included) and exits 0, changing nothing (a differing --redirect set only prints a note). The generated webhook secret is never printed unless --show-secret. |
fauth admin apps show <slug> [--json] | Prints an app's non-secret fields (id, slug, name, enabled, redirect URLs, webhook URL) so consumers can set e.g. FORGE_AUTH_APP_ID without hand-copying. Exits 2 for an unknown slug. |
fauth admin mint-key --email <e> --app <idOrSlug> --scopes <a,b> [--days N] [--name n] (--print-key | --out <file>) [--json] | Admin-mints a user-bound key with explicit scopes (the server rejects unknown ones; admin mint may grant any catalogue scope). Unlike login --admin it does not touch your stored login. The key is shown once: --print-key writes only the key to stdout (summary on stderr), --out writes a new mode-0600 file and refuses to overwrite. |
fauth login --key-stdin | Stores an existing user-bound fk_… key read from stdin (for example fauth login --key-stdin < file). The key is checked before it is saved, and the command refuses to read from a terminal. |
fauth logout | Revokes the stored key on the server (DELETE /api/v1/me/key), then deletes the local file. If revocation fails, the file is still deleted and a warning says the key may still be valid. |
fauth whoami [--json] | User id, email, name, and org memberships (org id, slug, name, role) plus the default org. |
fauth orgs list [--json] | The organizations you belong to. |
fauth orgs show <idOrSlug> [--json] | One organization you belong to (for non-members it answers exactly as if the org didn't exist). |
fauth orgs members <idOrSlug> [--json] | Its members (user id, email, name, role). |
fauth orgs create --name <n> --slug <s> --yes [--json] | Creates an org with you as owner. Without --yes, prints what would be created and exits 2. A taken slug exits 1. |
Global option: --base-url <url> (env FAUTH_BASE_URL), default https://forge-auth.blueforge.studio. The URL must use https; plain http is allowed only for localhost.
Exit codes
| Code | Meaning |
|---|---|
| 0 | ok |
| 1 | error (network, server, unexpected response, credential file I/O, slug taken, org not found) |
| 2 | usage (bad flags or arguments, unknown app, base-URL mismatch, orgs create without --yes) |
| 3 | not logged in (no credential, or the server rejected it) |
Agentic login
Use this when an agent or script needs to be the operator without a browser. It follows the same pattern as fgit and packr: a secret in ~/.forge-secrets, one command, no TTY. The device flow is still the path for humans.
fauth login --admin --email you@example.com # app "fauth", key valid for 30 days
fauth login --admin --email you@example.com --days 7 --json
FAUTH_EMAIL=you@example.com fauth login --admin --app fauth
Steps:
-
Reads the forge-auth admin API key (the server's
FORGE_GIT_AUTH_ADMIN_KEY), trying these sources in order:- the
FAUTH_ADMIN_KEYenv var ~/.forge-secrets/forge-auth-admin.key, which must be mode 0600. fauth refuses a group- or world-readable file and prints achmod 600hint.- on macOS, the keychain item
forge-auth-admin-key(security find-generic-password -s forge-auth-admin-key -w)
The key is never read from argv.
- the
-
Calls
POST /api/admin/users/keyswith{ email, app, expiresInDays }. The server looks up the existing user by email (case-insensitive; it never creates users) and mints a key tied to that user and app, carrying the user's own scope set (user.scopes), as a device-flow key would. It uses the same code path as the device flow, so every/api/v1route accepts the key the same way. -
Checks the new key with
GET /api/v1/me. If that fails, fauth revokes the key and saves nothing. -
Saves the key to
~/.forge-secrets/forge-auth.json(0600) in the same form as the device flow, plusexpiresAt, then printswhoami(or--json).
The email comes from --email, otherwise FAUTH_EMAIL, otherwise the command exits 2. --days accepts 1–90 and defaults to 30.
Security. The admin key can mint a key for any user. Keep it only in
~/.forge-secrets/forge-auth-admin.key(mode 0600) or the keychain. Never commit it, paste it into a chat, or pass it as a command-line argument. fauth never prints the admin key or the minted key. Each mint writes an audit line on the server (forge_auth.admin.user_key_minted: target user, app, key id, expiry; never the key), andfauth logoutrevokes the minted key.
Storing the admin key once, without it touching argv or shell history:
pbpaste > ~/.forge-secrets/forge-auth-admin.key && chmod 600 ~/.forge-secrets/forge-auth-admin.key
# or: security add-generic-password -s forge-auth-admin-key -a "$USER" -w (prompts for the value)
Credentials
- The credential is stored in
~/.forge-secrets/forge-auth.jsonwith mode0600, inside a0700directory. It is written to a temp file and then renamed into place.FORGE_SECRETS_DIRoverrides the directory. - The file holds
{ baseUrl, token, appId, userId, email, savedAt }. The token is never printed. - A stored key is only ever sent to the
baseUrlit was issued by. If--base-urlnames a different server, the command exits with 2 and makes no request. FAUTH_TOKEN(env) takes precedence over the file. This matches howFORGE_GIT_TOKENworks for fgit.- Secrets never go in argv. The key is sent only in a JSON body (
/api/v1/users/resolve) or anAuthorization: Bearerheader, never in a URL. - If the file is group- or world-readable, fauth prints a warning.
Before first login: register the fauth app (once)
forge-auth's device flow issues its key for a registered app (POST /api/auth/device { appId }). appId can be the app's id or its slug, and an unknown app returns 404 APP_NOT_FOUND. fauth login defaults to the slug fauth, so an admin registers that app once (safe to repeat):
fauth admin apps create --name "fauth CLI" --slug fauth
fauth admin apps show fauth # prints the app_… id
Until then, pass any existing app id or slug with --app-id. The key is bound to its user, and the /api/v1 routes don't care which app it came from.
Server endpoints
The /api/v1 routes authenticate with Authorization: Bearer fk_…, and the admin mint uses X-Admin-Api-Key. The key must be user-bound (an app/admin key with no user gets 401) and unexpired (expires_at is honored). api_key rows have no scopes yet, so there is no scope check: a user-bound key acts as its user for these self-service routes. When key scopes land, the routes map to the scopes in the table. Every route is rate-limited in memory per key.
| Endpoint | Scope (future) | Limit | Answers |
|---|---|---|---|
GET /api/v1/me | read:user | 120/min | { user: {id,email,name}, memberships, activeOrganizationId, organizationsEnabled, membershipsTruncated }; flag off → memberships: [] |
DELETE /api/v1/me/key | write:user | 10/min | revokes exactly the presented key → { revoked } |
GET /api/v1/orgs | read:organization | 120/min | the caller's memberships (same claims as get-session) |
POST /api/v1/orgs {name, slug} | write:organization | 5/min | 201 via auth.api.createOrganization (caller = owner); 409 SLUG_TAKEN; 403 for guests or at the org limit; 404 ORGANIZATIONS_DISABLED when the flag is off |
POST /api/admin/users/keys {email, app?, name?, expiresInDays?, scopes?} | X-Admin-Api-Key (not a user key) | 20/min | 201 { key, keyId, userId, email, appId, expiresAt, scopes } (the key is shown once; scopes omitted → the user's row); 404 USER_NOT_FOUND/APP_NOT_FOUND, 403 APP_DISABLED, 400 bad input, 401 bad admin key. Audited. |
GET /api/admin/apps, POST /api/admin/apps {name, slug, redirectUrls?, webhookUrl?, includeSecret?} | X-Admin-Api-Key | list apps; 201 { app } (webhook secret only with includeSecret: true); 400 on a duplicate slug. | |
POST /api/admin/device/approve {code, user} | X-Admin-Api-Key | 20/min | 200 { ok, userId, email, appId }; 404 USER_NOT_FOUND/DEVICE_REQUEST_NOT_FOUND, 409 ALREADY_APPROVED, 400, 401. Audited. |
GET /api/v1/orgs/:idOrSlug | read:organization | 120/min | org + members, members only; a non-member gets the same 404 as a missing org |
If a server predates these routes, whoami falls back to POST /api/v1/users/resolve (id + email only), and the orgs commands report that the server needs upgrading. An admin user lookup (fauth users find) is not built yet.
Build, install, publish
This directory is not a pnpm workspace member. commander and esbuild are devDependencies of the forge-auth root, and the bundle includes everything, so the published package has no runtime dependencies.
# from the forge-auth repo root
pnpm install
pnpm fauth:bundle # → packages/fauth/dist/fauth.mjs
pnpm fauth whoami # or run from source via tsx
# install the local build globally (before it is on packr)
cd packages/fauth && npm pack --pack-destination /tmp && npm install -g /tmp/blueforge-studio-fauth-$(node -p "require('./package.json').version").tgz
To publish to packr (operator only; not automated yet):
pnpm fauth:bundle
cd packages/fauth
npm view @blueforge-studio/fauth@$(node -p "require('./package.json').version") --registry https://api.packr.blueforge.studio/ version \
&& echo "already published — bump the version first" # packr answers 409 to an overwrite
npm publish --registry https://api.packr.blueforge.studio/ # auth: PACKR_TOKEN via ~/.npmrc
# consumers: npm install -g @blueforge-studio/fauth --registry https://api.packr.blueforge.studio/
Never probe packr with curl -X PUT, because an empty PUT creates an empty version. The same rule is documented in fgit's scripts/publish-fgit.sh.