@norbix.ai/cli
v1.26.0
Published
Norbix CLI — manage your Norbix projects from the terminal. Built on the official TypeScript SDK.
Readme
Norbix CLI
Manage your Norbix projects from the terminal. Built on top of the official
@norbix.ai/ts SDK, so every
command talks to the same API the SDK does.
Works on macOS, Linux and Windows (Node.js 22+).
Install
npm install -g @norbix.ai/cli
# or run without installing:
npx @norbix.ai/cli --helpQuick start
# 1. Sign in through the browser (the Norbix dashboard asks which roles the CLI gets)
norbix login # norbix.ai
norbix login --host cloud.example.com # your own Norbix: just the dashboard address
# or, for CI and scripts, with an API key:
norbix login --api-key nbsu_... --project <projectId> --profile ci
# 2. Check everything is wired up
norbix whoami
# 3. Use it
norbix db find orders --filter '{"status":"paid"}' --page-size 20
norbix logs list --level Error
norbix scheduler listCommands
| Command | What it does |
| --- | --- |
| norbix login / logout / whoami | Authenticate and inspect the current context |
| norbix config list/get/set/unset | Read and change one profile in ~/.norbix/config (--profile, default [default]) |
| norbix db ... | Database records (find/get/count/insert/update/replace/delete/aggregate/change-owner), schemas (create/update/publish/delete/versions/diff), schema triggers, integrations, taxonomies and terms, indexes, test-data seed — see docs/database.md |
| norbix files list/info/get-by-id/upload/download/sign/delete/publish/unpublish | Upload, download, manage and publish files |
| norbix files integrations test <id> | Check that a files integration really works (live upload/read/list/delete probe) |
| norbix users list/get/invite/block/unblock/delete | Manage project users (membership) |
| norbix env list/use/delete | Manage project environments |
| norbix logs list/trail | Read project logs, follow one correlation ID |
| norbix scheduler list/get/save/enable/disable/delete | Scheduler tasks — a cron that sends an email campaign; every scheduler endpoint, see docs/scheduler.md |
| norbix apikeys list/create/regenerate/revoke | Service-user API keys for scripts, CI, SDKs and agents |
| norbix webhooks show/secret/enable/disable/remove | Inspect the webhook integration |
| norbix email ... | Email module, integrations, validation, footers, signatures, templates, campaigns, preview — every Email endpoint a developer calls, see docs/email.md |
| norbix push ... | Push module, integrations, devices, templates, campaigns — every push endpoint, see docs/push.md |
| norbix sms ... | SMS module, integrations, templates, campaigns — every SMS endpoint, see docs/sms.md |
| norbix account profile/status/usage/projects/team/regions/billing-portal/me | Account-level info; account team pages and filters, account me set-phone saves the phone "Account users" SMS campaigns use — see docs/account.md |
| norbix project ... | One project's settings — name, look, languages, regions, CORS origins, admin portal, legal documents, AI chat and assistants, see docs/project.md |
| norbix ai ... | LLM and MCP server integrations, AI service users and their keys, see docs/ai.md |
| norbix payments integrations/triggers/trigger/enable/disable | Payment integrations and triggers |
| norbix code functions/qualifiers/invoke/event-sample/runs | Cloud functions (AWS Lambda, Azure Functions, Google Cloud Functions): list what a code integration may call, aliases and versions, call one by hand, a sample envelope for a local run, the last runs of a trigger or task — see docs/code.md |
| norbix code marketplace listings/connections/functions/runs | Marketplace: browse listings, enable a listing as a connection, validate it, add functions (db.find, chat.postMessage, …), call one by hand, the last runs of a trigger or task — see docs/code.md |
| norbix integrations <module> | List integrations of any module |
| norbix module enable/disable <name> | Turn a whole project module on or off |
| norbix hub <module> <words...> | Call ANY hub endpoint with plain words (see below) |
| norbix api <module> <words...> | Same for the data-plane API |
| norbix raw <path> | Low-level HTTP escape hatch (hub by default, --api for API) |
| norbix schema [command] | Describe every command as data (--json) — for scripts and agents |
| norbix ai init | Set up a project for Claude Code / Cursor / AGENTS.md agents |
Plain-word access to every endpoint
norbix hub and norbix api resolve SDK methods from plain words — every
current and future SDK method is callable without waiting for a dedicated
command:
norbix hub # list modules
norbix hub database # list database methods
norbix hub database aggregates get # plural = list
norbix hub database aggregate get maggr_123 # singular = one item
norbix hub membership role 66b2f0a1c3d4e5f6 # no verb + an id = get one (getRole)
norbix hub membership role # no verb, no id = the list (getRoles)
norbix hub database aggregates delete maggr_123 --schemaId sch_456
norbix hub scheduler tasks get --pageSize 100
norbix hub email templates get # email/sms/push route into notificationsThe first positional value becomes id; --field value flags become request
fields, typed from the SDK's request type (norbix hub <module> --help lists
every method with its fields; --json returns them as data). Force a type
with --field:str, --field:num, --field:bool or --field:json, or pass
the whole request with --body '<json>'. Destructive verbs (delete, remove,
stop, disable, block, regenerate, rotate) ask for confirmation unless
--yes — and exit 3 without a terminal. Add --dry-run to preview the exact
HTTP request.
Words without a verb that fit several methods (membership role fits
createRole, deleteRole and getRole) read: with an id the get of one item,
without one the list. A write or destructive verb is never picked unless you
type it; when no single read method fits, the CLI still stops and lists the
candidates.
--dry-run resolves auth, region and project and prints the request; it
sends nothing, so the server does not check the values. One exception:
norbix hub database schema bundle apply --bundleJson '<IF json>' --dry-run
also sends the bundle to the Hub's read-only schema check
(account.validateSchema, it writes nothing), prints the issues, and exits 6
when the bundle is invalid. A bundle given only as catalog --entities is
not checked.
norbix schema --json describes every command (args, flags, examples,
destructive, dry-run) for scripts and coding agents; norbix autocomplete
sets up shell tab-completion (bash/zsh).
Run norbix <topic> --help for flags and examples.
Configuration: hosts, profiles and sessions
The CLI needs one address: the host — your Norbix dashboard or Hub
(cloud.example.com, hub.example.com), default hub.norbix.ai. It asks
the host where its Hub is (/.well-known/norbix.json) and the Hub for
everything else (/echo: the Api address, versions, regions, sign-in
URLs), and caches the answer for 24 hours in ~/.norbix/hosts. Give it with
--host, NORBIX_HOST, or host in a profile. https is the default; plain
http only for localhost and *.test names — local development is
--host localhost:5001, a local Norbix stack --host <item>.norbix.test:5001.
Profiles (AWS-style) — one INI file at ~/.norbix/config:
[default] # no host = hub.norbix.ai
project_id = ...
[finlo]
host = cloud.finlo.space
project_id = ...
[finlo-ci]
host = hub.finlo.space
api_key = nbsu_...
project_id = ...norbix configure # writes the [default] profile
norbix configure --profile finlo # a named profile
norbix db find orders --profile finloconfigure asks for the host (empty = hub.norbix.ai), a service-user API
key (empty = use the browser sign-in of that host), project ID, and optional
account ID, environment (empty = PROD) and region. Keys are spelled
snake_case in the file (project_id); norbix config set / get /
unset take that spelling or the camelCase one (projectId) and write the
[default] profile, or the one named with --profile:
norbix config set host cloud.example.com --profile example
norbix config set region nb-eu-germany --profile ci
norbix config get project_idapi_url / hub_url in a profile and NORBIX_API_URL / NORBIX_HUB_URL
still work in this release, with a warning on stderr; replace them with
host (norbix config set host … removes them). The old
~/.config/norbix/config.json is still read as a last fallback, never
written; norbix config list shows what is left in it.
Sessions — one browser sign-in per Hub, in
~/.norbix/sessions/<hub>.json. cloud.example.com and hub.example.com
lead to the same Hub, so they share one sign-in. Every terminal window, and
every profile of that host without an api_key, uses it. norbix logout
removes all of them; norbix logout --host h or --profile p only that
host's. Profiles are never touched by logout. After norbix login --host h
with no [default] profile yet, the CLI saves host = h there so plain
commands use the sign-in; an existing [default] for another host is left
alone and the CLI prints how to switch.
Which credential wins: --api-key / NORBIX_API_KEY → the profile's
api_key → the browser sign-in of the profile's host. A profile is used
only for its own host: --host for another Hub leaves [default] out, so a
key is never sent to another host. norbix whoami shows the host, Hub, Api,
profile and auth source that won, and checks them against the server.
Region — on norbix.ai calls go to <region>.api.norbix.ai. With no
region set, the CLI asks the Hub for the project's primary region once and
caches it; if the key or sign-in may not read the project, pass --region
(or region in the profile). On your own Norbix the Hub lists its regions,
and the region is optional.
Signing in: norbix login
norbix login signs in through the browser (the OAuth device flow) to the
host's Hub — --host, --profile, or hub.norbix.ai:
- The CLI prints a one-time code (
BCDF-GHJK) and, after ENTER, opens the Norbix dashboard on the sign-in page. Over SSH (on any system), on a machine without a desktop (a container), or with--no-browser, it does not open a browser: open the printed link on any device, and the CLI picks the sign-in up by itself. - In the dashboard you check the code, pick the roles the CLI gets — account roles and project roles, the same pickers as when an AI tool connects over OAuth — and press Allow. You can never give more than you have.
- The CLI works as an AI service user named
Norbix CLI (<computer name>)with exactly those roles.norbix whoamishows it. Signing in again from the same computer with the same roles reuses the same user.
The access token lasts one hour and is refreshed by itself; the refresh
token lasts 30 days from its last use. To end the sign-in, run
norbix logout (it also revokes the refresh token on the Hub) or remove the
user in the dashboard under Account → AI service users; the next
command then exits 4 and asks you to run norbix login again.
| Situation | What happens | Exit |
| --- | --- | --- |
| You press Deny | Sign-in was denied in the browser. (ACCESS_DENIED) | 4 |
| You press Allow, but the Hub refuses (e.g. the account email is not verified, the account is blocked or closed) | Sign-in was refused: <the Hub's message> (ACCESS_DENIED, context.reason in --json); for an unverified email the hint says: verify it in the dashboard (banner → Resend), then run norbix login again | 4 |
| Nobody approves within 10 minutes | The sign-in code expired before it was approved. (EXPIRED_TOKEN) | 4 |
| The Hub refuses the code (already used, unknown, the AI service user deleted) | The Hub's reason is shown (INVALID_GRANT / INVALID_REQUEST) | 4 |
| login --wait and nobody approved yet | Not approved yet. — run it again (AUTHORIZATION_PENDING) | 4 |
| The sign-in was removed or ran out | The stored tokens are cleared (SESSION_EXPIRED) | 4 |
| No terminal, and no --api-key / --no-browser / --wait | Usage error that names --no-browser | 2 |
| The Hub is older than the browser sign-in | Usage error: sign in with --api-key | 2 |
| --host cloud.example.com serves its web page for /.well-known/norbix.json and has no Hub at /v3 | NOT_A_HUB: discovery is not routed to the Hub (ingress rule missing, or the installation predates discovery) — use --host hub.example.com | 2 |
Project after sign-in. When the sign-in carries no project, the CLI reads
the account's projects: exactly one is saved into the sign-in and named;
with several, they are listed with how to choose one (norbix config set
project_id <id>, --project <id> or NORBIX_PROJECT_ID). A command that
needs a project and has none stops with No project ID configured. and the
account's projects in the hint; with one project and a browser sign-in, that
project is saved into the sign-in and the next run uses it (the failed run
is not repeated by itself, so nothing is sent twice). On norbix.ai the list
is read from the account Hub (hub.norbix.ai), so no --region is needed
for it, and the saved project keeps its primary region with it. A
self-hosted Hub needs no region, so none is saved there.
There is no user + password sign-in. CI and scripts use a service-user API key, never a browser sign-in:
# environment only
export NORBIX_HOST=hub.example.com # omit for norbix.ai
export NORBIX_API_KEY=nbsu_...
export NORBIX_PROJECT_ID=...
norbix db count orders
# or a profile
norbix login --api-key nbsu_... --project <id> --host hub.example.com --profile ciThe Hub version in every path (/v3/...) comes from the Hub's /echo; set
NORBIX_HUB_VERSION or hub_version in a profile only to override it. The
full design is in AUTH_DESIGN.md.
JSON output
Every command supports --json and prints clean JSON on stdout, so you can
pipe into jq:
norbix db find orders --json | jq '.list.items[] | ._id'Errors and exit codes
A failed call prints the message, the details the server gave (code, HTTP status, URL, trace id, field errors) and what to do next — on stderr, so stdout stays clean for data:
$ norbix files info a/b.txt
Error: File not found: "a/b.txt" does not exist in Local (nbin_1).
code: CM-ERRORS-FILES-016
status: 404
url: https://nb-eu-germany.api.norbix.ai/v3/files/...
Hint: Check the id, the --env and the --project. List items first (e.g. `norbix files list --json`).
Docs: norbix files info --helpWith --json the same information is one JSON document on stdout:
{"error": {"code", "message", "status", "exit", "fieldErrors", "url", "traceId", "hint", "docs"}}.
The exit code tells a script or an agent what went wrong, with or without
--json:
| code | meaning |
| --- | --- |
| 0 | success |
| 1 | unexpected / internal error |
| 2 | usage: bad flags or arguments, invalid JSON input, unknown command, missing config |
| 3 | confirmation required — a destructive command ran without a terminal and without --yes |
| 4 | not authenticated / auth rejected (401, 403, no key) |
| 5 | not found (404) |
| 6 | validation rejected by the server (400 / 422) |
| 7 | network, timeout, endpoint unreachable |
| 8 | rate limited or server error (429, 5xx) |
| 9 | cancelled by you at a prompt |
Destructive commands (delete, stop, regenerate, block, disable,
--many updates) ask for confirmation in a terminal. In a script, in CI or
under a coding agent there is nobody to ask, so they exit 3 and send
nothing unless you pass --yes. Preview any of them with --dry-run:
the request is built with your real auth, region and project, printed, and
not sent — so the values are not checked by the server (except a schema
bundle, see norbix hub above).
Use with Claude Code / AI agents
The CLI is built to be driven by a coding agent from the shell — no MCP
server needed: one JSON document per call (--json), documented exit codes
(0–9), nothing destructive without --yes, --dry-run on every change, and
norbix schema --json to discover every command. Set a project up once:
norbix ai init # writes .claude/skills/norbix/SKILL.md + a CLAUDE.md block
norbix ai init --target all # also AGENTS.md (Codex, OpenCode) and a Cursor ruleThe agent signs in by itself, in two steps (its shell commands time out after ~2 minutes; the code lives 10 minutes):
norbix login --no-browser --json # → {"status":"pending","userCode":"BCDF-GHJK","verificationUriComplete":"https://…/device?code=…","expiresIn":600,…}
norbix login --wait --json # waits up to 90 s; exit 4 AUTHORIZATION_PENDING → run it againIt shows you the link and the code; you open it, check the code, pick the
roles it gets, and press Allow. It then works as an AI service user you can
remove any time (Account → AI service users). Not on norbix.ai? It adds
--host <your dashboard> to both steps. You can also sign in yourself with
norbix login. The written rules also tell the agent to start with
norbix whoami --json, to pass --json to everything it parses, to run
every change with --dry-run first and show it to you, and never to print
or ask for keys or tokens. In CI, give the agent NORBIX_HOST +
NORBIX_API_KEY + NORBIX_PROJECT_ID instead.
The recipe the agent follows is in docs/AGENTS.md; the guarantees it relies on are in docs/agent-contract.md.
Versioning
The major version is frozen at v1 until the public launch.
- A breaking change is released as a minor (for example v1.2.0 → v1.3.0), never as a new major.
- Write it as
feat(<scope>): <what>and add a lineBreaking: <what changed and what callers must do>in plain words, in the pull-request body and in the commit message. - Never mark it the conventional-commits way: no
!in the title (feat!:), no BREAKING CHANGE footer. ThePR titlecheck fails a pull request that does. - As a safety net, the release config (
.releaserc.json→releaseRules) maps breaking commits to a minor, so one that slips through still does not bump the major.
Development
npm install
npm run build # compile TypeScript to dist/
./bin/run.js --help # run the local buildCommands live in src/commands/<topic>/<name>.ts — one file per command
(oclif convention). Shared logic (auth resolution, config store, output) is in
src/base.ts and src/lib/.
Files: pick your integration once
File commands need a files integration ID. Set it once:
norbix config set files_integration_id <id>
norbix files upload ./invoice.pdf invoices/2026/invoice.pdf
norbix files download invoices/2026/invoice.pdfOr pass --integration <id> (env var: NORBIX_FILES_INTEGRATION_ID).
The file commands
| Command | What it does |
| --- | --- |
| norbix files list [path] | List the files and folders under a path. No path lists the root. |
| norbix files info <path> | Show one file's details: size, type, when it changed. |
| norbix files get-by-id <id> | Show one file by its stable id — the value a file field of a record stores (db find --expand returns it as the reference's id). |
| norbix files sign <path> [--expires <seconds>] | Get a temporary web address for the file that anyone with the link can open. |
| norbix files upload <local> [remote] | Upload a file. Without remote it keeps its own name in the root folder. |
| norbix files download <remote> [local] | Download a file. Without local it keeps its own name in the current folder. |
| norbix files delete <path> [--yes] | Delete one file. Asks first unless you pass --yes; without a terminal it exits 3 unless --yes. |
| norbix files publish <path> [--folder] | Give the file — or the whole folder — a link anyone can open, and print it. |
| norbix files unpublish <path> [--folder] | Take that link away again. |
| norbix files integrations test <id> | Check that the storage behind an integration really works. See below. |
Upload and download take more than one step, so the file bytes never pass through Norbix:
- upload — ask for a short-lived upload address, send the bytes straight to storage, then tell Norbix the upload finished.
- download — ask for a short-lived download address, then fetch the bytes from storage.
--content-type overrides the type on upload; without it the type is guessed
from the file name.
Publishing a file
sign and publish both give you a web address, and they are not the same
thing:
signhands out a temporary address that expires by itself. Good for a download button on a page somebody is already signed in to.publishmakes the file public until you say otherwise. The address has no expiry and no sign-in: it works in an e-mail, in an<img src>, or in a browser on somebody else's phone.
norbix files publish invoices/2026/invoice.pdf
# https://api.norbix.ai/v3/files/public/nbpf_7hK2abc/invoice.pdf
norbix files unpublish invoices/2026/invoice.pdf
# invoices/2026/invoice.pdf is private again — its link now gives a 404--folder does the same for a whole folder. That is one record however
many files sit under it, and every file inside becomes readable by putting its
path inside the folder after the link:
norbix files publish invoices --folder
# https://api.norbix.ai/v3/files/public/nbpf_folder1/
# → .../nbpf_folder1/2026/invoice.pdfFour rules worth knowing:
- Publishing the same thing twice gives the same link back — the first one is already in somebody's hands.
- A file cannot be unpublished on its own while a folder above it is public. The command says so and names the folder to switch off instead.
- Unpublishing a folder takes back every link inside it, per-file links included.
- Every dead link answers the same plain
404: unknown, renamed, unpublished, deleted. A more precise answer would tell a stranger that the file is there.
Testing an integration
norbix files integrations test <id> runs a live probe against the storage
behind the integration: it uploads a small file, reads it, lists its folder and
deletes it again. One line per step:
norbix files integrations test 55555555-5555-5555-5555-555555555555
# UploadFile OK
# GetFile FAILED
# - NoSuchKey: the object was not found
# GetAllFiles NOT_TESTED
# DeleteFile NOT_TESTED
# › Error: The files integration test failed: GetFile FAILED (2 later steps not tested).NOT_TESTED means the step was skipped because an earlier one failed. The
command exits 0 only when every step is OK, so it works in a script or a
CI job. --json prints the steps as data and keeps the same exit code. The
probe writes to the storage, so the key needs the files:create permission.
Tests
npm testThe tests build the CLI and run each command through oclif with fetch
replaced, so nothing leaves the machine and no account is needed. HOME points
at an empty temporary folder during the run, so your own ~/.norbix settings
and login session are never read.
Roadmap
- Standalone binaries (no Node needed): Homebrew, curl installer, Scoop/winget
- OS keychain storage for tokens
norbix logs list --follow(live tail via SSE)
See RESEARCH.md for the full background on these choices.
