Website · Getting started · Docs · Set up with your AI assistant
@nebulr-group/bridge-cli sets up and manages your Bridge app from the terminal: apps, sign-in methods, roles, plans and prices, feature flags and API tokens. Built for AI coding agents (Claude Code, Cursor, Copilot) as much as for people.
The Bridge is a hosted backend for SaaS apps. It gives you sign-in (passwords, magic links, passkeys, social login and SSO), multi-tenant workspaces with roles, Stripe subscriptions with plan limits, and feature flags, all managed from one dashboard. Your AI coding assistant can set it up for you through the Bridge MCP server.
npm install -g @nebulr-group/bridge-cliOr run directly with npx:
npx @nebulr-group/bridge-cli tenant listbridge-cli supports two authentication paths.
bridge auth loginOpens your default browser, runs through a PKCE-secured loopback flow (RFC 8252), and stores a 10-day token at ~/.config/bridge/credentials.json (mode 0600). After that, every subsequent bridge command picks the token up automatically — no env vars needed.
bridge auth status # show every stored app, and which one is active
bridge auth logout # revoke the active token and remove it locallyBy default the consent screen preselects Every app in <workspace>, so one
login covers all your apps — the same way the MCP server does. The app you pin
at login (--app) is the home app; switch between the others locally:
bridge auth login # Logged in as you@acme.com. Workspace: every app (home app: Acme)
bridge app list # every app, marking home and current
bridge app use "Acme Stage" # the default for every command, no re-login
bridge app create --name "Acme Prod" --use
bridge --app 66f0c0ffee0000000000abcd role list # one command on another appPrecedence: --app > bridge app use > the home app. --profile still picks
which stored login; --app picks the app within it. Acting on a non-home
app prints bridge: Acting on app <name> (<id>) to stderr. Per-app tokens live
only in memory for that one command and are never written to disk.
bridge auth login --single-app asks for a login that covers only the home app
(the user can still change the choice on the consent screen). On such a login,
--app / bridge app use for another app fail and tell you to log in again.
BRIDGE_API_KEY is always bound to its one app.
The credentials file holds one credential per app, so you can log in to your local, stage and prod apps and move between them without a browser round-trip — the tokens are already on disk with days left on them.
bridge auth login --label northwhistle-local # once per app
bridge auth login --label northwhistle-prod
bridge auth status # lists both, marks the active one
bridge auth use northwhistle-local # switch the default, offline
bridge --profile northwhistle-prod app get # target ONE command, default untouchedA profile is selected by its --label, its app id, or its app name, in that
order. An ambiguous name is an error rather than a guess. BRIDGE_PROFILE does
the same thing as --profile for a whole shell session.
Prefer --profile over bridge auth use in scripts and long-running agent
sessions: use mutates state another process may be reading, --profile does
not.
Every command prints one line to stderr naming the app that answered:
bridge: northwhistle-prod (606b4416fabdc800087d09ec) · https://api.thebridge.dev · via saved default
stdout stays pure JSON, so this does not disturb anything parsing it. Silence it
with BRIDGE_NO_BANNER=true if you are already certain which app you are on.
bridge auth logout removes the active app only. Use --profile <name> for a
specific one, or --all to clear the file.
Existing single-app credentials files keep working and are upgraded in place the next time something writes.
| Flag | Description |
|---|---|
--app <id|name> |
Pin the home app, skipping the picker on the consent screen. |
--single-app |
Preselect "only this app" instead of "every app in the workspace" on the consent screen. |
--label <text> |
Friendly label stored on the token (default: bridge-cli). Useful when listing CLI tokens at app.thebridge.dev/keys. |
--no-browser |
Print the authorization URL instead of opening a browser. Use this on headless boxes or over SSH. |
When the token expires (10 days), the next command fails with a friendly Token expired. Run bridge auth login to re-authenticate. message.
The credentials file location honors XDG_CONFIG_HOME:
$XDG_CONFIG_HOME/bridge/credentials.json # if XDG_CONFIG_HOME is set
~/.config/bridge/credentials.json # otherwiseFor non-interactive contexts (CI/CD pipelines, Docker images, headless agents) set the API key directly in the environment:
export BRIDGE_API_KEY=<your-api-token>If you have logged in via bridge auth login, the credentials file always wins — a fresh login takes effect immediately, even when BRIDGE_API_KEY is still exported in your shell. BRIDGE_API_KEY is only used when no credentials file is present (the typical CI runner shape). To switch back to env-var auth on a developer machine, run bridge auth logout --all first.
Because that trips people up, the CLI now says so at the moment it happens: with
both present you get a line on stderr telling you BRIDGE_API_KEY is set and
being ignored, and what to do instead. To point the CLI at a different app, use
--profile, not the env vars.
BRIDGE_APP_ID has no effect on any path. The app is carried by the
credential itself — a login token and an API key are both already scoped to one
app, so there is nothing for it to change. It is called out on stderr whenever it
is set, rather than being quietly ignored.
Optional configuration (applies to both auth paths):
export BRIDGE_BASE_URL=https://api.thebridge.dev # default
export BRIDGE_TENANT_ID=<tenant-id> # for user commands
export BRIDGE_DEBUG=true # enable debug loggingTwo exports, then log in as usual — the browser flow works against a local stack the same way it does against production:
export BRIDGE_BASE_URL=http://localhost:3200 # bridge-api
export BRIDGE_AUTH_BASE_URL=http://localhost:3091 # consent screen (cloud-views)
bridge auth loginBRIDGE_BASE_URL is where commands send their API calls; BRIDGE_AUTH_BASE_URL
is where bridge auth login opens the consent screen. Set the second one too,
or you will authenticate against production and then issue commands against
your local API with a token it does not accept.
There is deliberately no config file to source (TBP-121). A sourceable file
mixed CLI settings with two demo apps' settings, pinned an undocumented app id,
and kept a long-lived API key on disk in plaintext; two exports cover the same
ground without any of that. For CI, use BRIDGE_API_KEY as described above.
One more, for working on the integration guides themselves:
export BRIDGE_GUIDE_LOCAL_DIR=/path/to/thebridge-platform/bridge-pluginsbridge guide then reads prompts from that directory
instead of fetching them from GitHub (or the copies bundled with the CLI), so edits show up without a release.
All output is JSON by default for management commands. AI agents parse it directly; humans can pipe through jq. The bridge auth status and bridge auth login commands print human-readable text, since their primary audience is a human in a terminal.
bridge <command> <subcommand> [options]Every command that acts on a single resource takes either its opaque id
or its human-readable identifier — you never have to run a list first just
to look up an id.
| Noun | Human-readable option | Id option |
|---|---|---|
| flag | --key <key> (or a positional <key> on get / eval / schedule) |
--id |
| role | --key <key> (or a positional <idOrKey> on get) |
--id |
| plan | positional <key> — plans are key-native, no id anywhere |
— |
| tenant | --name <name> |
--id |
| user | --email <email> |
--user-id |
| token | --name <name> |
--id |
Rules, the same for every noun:
- Pass exactly one. Both together, or neither, is an error.
- An identifier that matches nothing fails and names the
listcommand to run. - An identifier that matches more than one resource fails, lists the
candidates, and changes nothing. The CLI never picks one for you — pass
--idto disambiguate.
--name and --email exist because tenants, users and tokens have no key
field. Of those, only a user's email is unique; tenant and token names are free
text, which is exactly why the ambiguity check is a hard failure.
bridge auth login # default: open browser, complete PKCE flow
bridge auth login --app acme # pin to a specific app
bridge auth login --label "work laptop"
bridge auth login --no-browser # print the URL (headless / SSH)
bridge auth status # show every stored app, active one marked
bridge auth use <label|app-id> # switch the default app, no browser
bridge auth logout # revoke + remove the active app
bridge auth logout --all # ...or every stored app
bridge --profile <label> <cmd> # target one stored login for one command
bridge app list # apps this login covers (home + current marked)
bridge app use <id|name> # switch app within a workspace login, no browser
bridge app create --name <n> [--callback-url <url>] [--use]
bridge --app <id|name> <cmd> # one command on another app of the workspacebridge auth config
bridge auth mfa --enabled true
bridge auth password-policy --access-token-ttl 3600bridge app get
bridge app update --name "My App" --mfa-enabled truebridge tenant list
bridge tenant get --name "Acme Corp" # or --id <tenant-id>
bridge tenant create --owner-email admin@acme.com --name "Acme Corp" --plan enterprise
bridge tenant update --name "Acme Corp" --locale sv
bridge tenant update --name "Acme Corp" --new-name "Acme Corp Updated"
bridge tenant delete --name "Acme Corp" # or --id <tenant-id>Tenants have no key field, so they are addressed by --name. Names are not
enforced unique — if two tenants share one, the command fails and lists both
rather than guessing. Use --id to disambiguate.
tenant update --name addresses the tenant when --id is absent; passed
alongside --id it keeps its original meaning and renames the tenant.
--new-name always renames, whichever way you addressed it.
Requires tenant context via --tenant-id or BRIDGE_TENANT_ID.
bridge user list --tenant-id <tenant-id>
bridge user get --email alice@acme.com --tenant-id <tenant-id> # or --user-id <user-id>
bridge user invite --email alice@acme.com --role ADMIN --tenant-id <tenant-id>
bridge user update --email alice@acme.com --role OWNER --tenant-id <tenant-id>
bridge user remove --email alice@acme.com --tenant-id <tenant-id>Users have no key field either; --email is the human-readable address and is
unique within a tenant (matched case-insensitively).
bridge role list
bridge role get ADMIN
bridge role create --name Editor --key editor --privileges READ,WRITE
bridge role update --key editor --privileges READ,WRITE,DELETE # or --id <role-id>
bridge role delete --key editor # or --id <role-id>bridge flag list
bridge flag create --key dark-mode --description "Dark mode UI" --enabled
bridge flag update --key dark-mode --state on # or --id <flag-id>
bridge flag update --key dark-mode --new-key dark-theme # rename
bridge flag toggle --key dark-mode --enabled true # or --id <flag-id>
bridge flag delete --key dark-mode # or --id <flag-id>flag update --key addresses the flag when --id is absent; passed alongside
--id it keeps its original meaning and renames the flag. --new-key always
renames, whichever way you addressed it.
bridge branding get
bridge branding update --bg-color "#ffffff" --text-color "#000000"bridge plan list
# A plan needs at least one price — `create` takes the first one.
# A free tier is a zero-amount price: --amount 0
bridge plan create --key pro --name "Pro Plan" --amount 29 --interval month
bridge plan update --key pro --name "Pro Plan v2"
# Prices — a plan can have several (one per currency + interval). Idempotent.
bridge plan price set pro --amount 290 --currency usd --interval year
# Removing the last price is refused: a priceless plan can't be assigned.
bridge plan price rm pro --interval year
# Usage quotas (hard | metered caps)
# hard = block at the cap; metered = bill overage per unit (needs --price-amount)
bridge plan quota set pro --metric ai_completions --limit 1000 --policy hard
# Metered: first 1000 free, then $0.002/unit (currency from the plan's price)
bridge plan quota set pro --metric ai_completions --limit 1000 --policy metered --price-amount 0.002
# Pure per-unit (billed from unit 1)
bridge plan quota set pro --metric api_calls --limit 0 --policy metered --price-amount 0.01 --price-currency usd
bridge plan quota rm pro --metric ai_completions
# Included on/off features — each is `bridge:billing.entitlement.<key>` in flag rules
bridge plan feature add pro analytics --name "Analytics"
bridge plan feature list pro
bridge plan feature remove pro analytics
bridge plan create --key team --name Team --amount 0 --interval month --features "analytics,sso:Single sign-on"bridge token list
bridge token create --name "CI Token" --privileges USER_READ,TENANT_READ
bridge token revoke --name "CI Token" # or --id <token-id>Token names are free text and not enforced unique — a duplicate name fails and lists the candidates instead of revoking one at random.
bridge event list
bridge event list --type USER_CREATED --since 24h --limit 50bridge setup sso --provider google --client-id <id> --client-secret <secret>
bridge setup sso --provider azure --client-id <id> --client-secret <secret> --tenant-id <entra-directory-id>
bridge setup payments --stripe-key sk_test_xxx
bridge setup communication --from-address noreply@acme.com --from-name "Acme"setup sso supports google, github, linkedin, facebook and azure (azure also
needs the Entra ID Directory (tenant) ID). SAML and OIDC are not supported by
the Bridge API; set them up in the Bridge admin UI. The command prints the
callback URL to register in the provider's console.
setup communication sets only the sender name and address. Bridge sends all
email through its own provider, so there is no provider or API key to
configure. A new sender address is verified by email before Bridge uses it.
bridge info app
bridge info auth-config
bridge info flags
bridge info plans
bridge info rolesThree master prompts orchestrate the per-framework guides:
bridge guide # Bridge Auth master prompt (auth, RBAC, tenants)
bridge guide flags # Feature Flags 2.0 master prompt
bridge guide billing # Billing 2.0 master prompt (subscriptions, quotas, webhooks)One page explains the mechanisms all of them build on — the server decides limits and the client decorates, a POST increments the limit, counter vs gauge, the three ways to show a limit in the UI, and the four levels of customising Bridge's pages:
bridge guide mechanismsPer-framework guides:
bridge guide list
bridge guide <framework> # auth guide (svelte | react | nextjs | angular | nestjs | express)
bridge guide <framework> sdk-auth # in-app auth UI (frontend frameworks only)
bridge guide flags --framework <name> # flags-specific guide
bridge guide billing --framework <name> # billing-specific guide
bridge guide custom # REST API fallback for any languagebridge guide flags and bridge guide billing auto-detect the framework from package.json when --framework is omitted — they fall back to the generic master prompt if no framework signal is found.
Success:
{
"success": true,
"data": { ... }
}Error:
{
"success": false,
"error": {
"code": "TENANT_HAS_ACTIVE_SUBSCRIPTION",
"message": "Cannot delete tenant with active subscription.",
"details": { ... }
}
}bridge auth login, bridge auth logout, and bridge auth status print plain text (they're the only commands aimed primarily at humans).
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Client error (4xx), or bridge auth login failed/cancelled |
| 2 | Server error (5xx) |
| 3 | Configuration error (missing API key, expired credentials, etc.) |
bridge auth loginuses RFC 8252 loopback PKCE — the loopback URL is alwayshttp://127.0.0.1:<random-port>/callback(neverlocalhost, to avoid DNS spoofing).- The loopback HTTP server handles a single request, then closes — no port stays bound.
- The CSRF
stateparameter is verified on the callback before the code is exchanged. - The credentials file is written with mode
0600(owner-only). Its parent directory is created with mode0700. - The CLI never logs the JWT or the PKCE
code_verifier.
# Install dependencies
npm install
# Build
npm run build
# Run locally
node bridge-cli/dist/bin.js --help
# Test
npm test- Getting started
- Set up Bridge with your AI assistant
- Feature flags
- Subscriptions and plan limits
- API reference
| Package | For |
|---|---|
@nebulr-group/bridge-svelte |
SvelteKit |
@nebulr-group/bridge-react |
React |
@nebulr-group/bridge-nextjs |
Next.js |
@nebulr-group/bridge-angular |
Angular |
@nebulr-group/bridge-nestjs |
NestJS |
@nebulr-group/bridge-express |
Express |
@nebulr-group/bridge-auth-core |
Any JavaScript app (core) |
MIT © Nebulr. Built by The Bridge.
