Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c7cf498
feat(ai): norbix ai context — the agent's project context, kept in .n…
Domantas Oct 14, 2026
7bc5835
feat(db): db taxonomy create — create a taxonomy without the hub esca…
Domantas Oct 14, 2026
57dd87d
fix(db): every term command takes the taxonomy id or its name (N10)
Domantas Oct 14, 2026
94e4d76
fix(files): files commands use the env's default files integration (N6)
Domantas Oct 14, 2026
796267f
fix(db): db delete --filter alone deletes the one matching record (N15)
Domantas Oct 14, 2026
28c503a
fix(db): schema publish asks only when the Hub reports a breaking cha…
Domantas Oct 14, 2026
4997d12
feat(env): env create — an environment and its database in one comman…
Domantas Oct 14, 2026
36c4b6e
feat(cli): notice when a newer CLI is on npm (N4)
Domantas Oct 14, 2026
d7cd65f
feat(ai): ai init pins the repo to its Norbix in .norbix/project.json…
Domantas Oct 14, 2026
d52ed8b
feat(env): a change never goes to PROD by default (pickio N-21)
Domantas Oct 14, 2026
d5a999f
docs(email,sms): one Fake integration per environment, not per projec…
Domantas Oct 14, 2026
0060419
fix(login): pick up the approval while "Press ENTER" is still shown (N2)
Domantas Oct 14, 2026
34af355
fix(files): take only the env's default files integration, never one …
Domantas Oct 14, 2026
e5a1643
fix(env): aggregates, distinct values and diagnostics are reads for t…
Domantas Oct 14, 2026
5a676b7
docs(env): env create help names the managed-service trial, not every…
Domantas Oct 14, 2026
d152bc1
fix(ai): ai init reports .norbix/project.json with / on Windows too (N3)
Domantas Oct 14, 2026
9ee8eba
fix(cli): encode every / of the package name in the registry URL (N4)
Domantas Oct 14, 2026
2e5cb05
feat(users): users import, repair-roles and repair-logins (N7, N20)
Domantas Oct 14, 2026
e415772
fix(ai): the repo's Norbix joins the intro line of the agent block (N…
Domantas Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ out of reach.
| --- | --- | --- |
| database/collections | 13/15 | find, find-one, count, insert, insert-many, update(+many), replace, delete(+many), distinct, aggregate, execute-aggregate. Missing: find-own, change-responsibility |
| database/schemas | 2/18 | read-only list/get. Schema editing is portal work |
| database/taxonomies | 2/15 | read-only list/get |
| database/taxonomies | 3/15 | list/get, create |
| database/aggregates | 1/5 | list only |
| files/* | 9/23 | upload, download, list, info, sign, delete. Missing: bulk delete, triggers, integration config |
| logs/* | 6/14 | list, trail, settings, module toggle. Missing: clean, integration config |
Expand Down
83 changes: 77 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ norbix scheduler list
| --- | --- |
| `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](docs/database.md) |
| `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 (list/show/create) and terms, indexes, test-data seed — see [docs/database.md](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 users import <file.json\|csv> [--apply]` | Import users with their existing password hashes (CodeMash V1 `servicestack-saltedhash`, Norbix `servicestack-pbkdf2`); checks every row by default, `--apply` writes; 1000 per request |
| `norbix users repair-roles / repair-logins [--apply]` | Admin repairs: contacts left with `[null]` roles, logins stored without a project id; count by default, `--apply` repairs |
| `norbix env list/create/use/delete` | Manage project environments; `env create TEST --connection-string-env VAR` (or `--managed`) creates one with its database |
| `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](docs/scheduler.md) |
| `norbix apikeys list/create/regenerate/revoke` | Service-user API keys for scripts, CI, SDKs and agents |
Expand All @@ -64,6 +66,7 @@ norbix scheduler list
| `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 |
| `norbix ai context` | The project context for an AI agent: your projects, environments, modules, integrations — no secrets |

### Plain-word access to every endpoint

Expand Down Expand Up @@ -131,6 +134,45 @@ create content in a language (`email|sms|push campaign create`,
`scheduler save`, `email template attach`) keep their own `--language` for
that content.

**The repo's Norbix — `.norbix/project.json`.** `norbix ai init` writes the
host, project and (when set) environment this repo belongs to into
`.norbix/project.json` (no secrets — commit it):

```json
{"host": "cloud.example.com", "projectId": "pr_6zbH6AJwyGylTFfs4B9bpC", "env": "TEST"}
```

Every command run in that folder or below uses it **before** the profiles:
`--host` / `--project` / `--env` (and `NORBIX_*`) → `.norbix/project.json` →
the profile → the sign-in. Its host acts like `--host`: a `[default]` profile
for another installation is not used there (its API key is never sent), so a
second installation on the same computer cannot be reached by accident. An
explicit `--profile` for another host wins, and the file is ignored.
`norbix env use <ENV>` inside the repo changes the file's `env`. `whoami`
shows `hostSource: project file`. The home folder is never searched;
`NORBIX_PROJECT_CONFIG=0` turns the lookup off.

**Environments: a change never lands in PROD by default.** Every call
works in an environment: `--env` / `NORBIX_ENV`, else the repo's
`.norbix/project.json`, the profile's `env`, the sign-in's (`norbix env use`).
With none, no `norbix-env` header is sent and the server uses PROD. Reads keep
that default. A **change** (every command with `--dry-run`, and every
non-read `hub` / `api` / `raw` call) follows the same rule as the MCP tools
(`AiToolEnvPolicy`):

| environment of the change | result |
| --- | --- |
| named with `--env` / `NORBIX_ENV` (PROD included) | sent |
| a saved default that is not PROD (repo file, profile, `env use`) | sent |
| none at all — it would be PROD | refused: exit 2 `ENV_REQUIRED` |
| PROD, but only from a saved default | refused: exit 2 `PROD_NOT_EXPLICIT` |

At a terminal the CLI asks instead of refusing; `--yes` does not answer it —
PROD is chosen per call with `--env PROD`. A `--dry-run` is never refused.
Account- and project-wide settings (`account`, `project`, `module`, `env`,
AI service users) are not inside an environment and are not affected.
Scripts that deploy to PROD set `NORBIX_ENV=PROD`.

**Profiles (AWS-style)** — one INI file at `~/.norbix/config`:

```ini
Expand Down Expand Up @@ -323,6 +365,21 @@ norbix ai init # writes .claude/skills/norbix/SKILL.md + a CLAUD
norbix ai init --target all # also AGENTS.md (Codex, OpenCode) and a Cursor rule
```

The agent rebuilds what it knows about your projects from one summary —
every project you may work on, with its environments, regions, enabled
modules, integrations (id, name, kind, default per environment), CORS origins
and languages; scoped to your roles and never holding a secret:

```sh
norbix ai context # Markdown, for a prompt
norbix ai context --json # the structured summary, with a "version"
norbix ai context --write # also saves .norbix-ai/context.md (+ context.json, git-ignored)
```

In a folder set up with `norbix ai init`, `norbix login` writes that file and a
token renewal refreshes it when the summary changed; the agent re-reads it
after its own context was compressed.

The agent signs in by itself, in two steps (its shell commands time out
after ~2 minutes; the code lives 10 minutes):

Expand All @@ -344,6 +401,15 @@ or ask for keys or tokens. In CI, give the agent `NORBIX_HOST` +
The recipe the agent follows is in [docs/AGENTS.md](docs/AGENTS.md); the
guarantees it relies on are in [docs/agent-contract.md](docs/agent-contract.md).

## Updates

When a newer `@norbix.ai/cli` is on npm, the CLI says so in one line on
**stderr** (never in `--json` stdout): at most once a day, and on every
`login`, `ai init` and `whoami`. It never waits for the network: the latest
version is read from `~/.norbix/update-check.json`, which a background process
refreshes at most once a day (3 s timeout, failures ignored). Turn it off with
`NORBIX_NO_UPDATE_CHECK=1` (`NO_UPDATE_NOTIFIER` and `CI` turn it off too).

## Versioning

The major version is frozen at **v1** until the public launch.
Expand All @@ -365,18 +431,23 @@ Commands 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
## Files: which integration

File commands need a files integration ID. Set it once:
File commands use the project's **default files integration** of the current
environment (`--env`), like the database commands use the default database
integration — one read of the Hub's integration list (its default is per
environment). With no files integration at all they exit 5
(`NO_FILES_INTEGRATION`); with integrations but no default in this
environment they exit 2 and list them (the list is project-wide, so none is
picked for you). To pick another one, pass `--integration <id>` (env var:
`NORBIX_FILES_INTEGRATION_ID`) or save it:

```sh
norbix config set files_integration_id <id>
norbix files upload ./invoice.pdf invoices/2026/invoice.pdf
norbix files download invoices/2026/invoice.pdf
```

Or pass `--integration <id>` (env var: `NORBIX_FILES_INTEGRATION_ID`).

### The file commands

| Command | What it does |
Expand Down
8 changes: 8 additions & 0 deletions docs/agent-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ command that makes several calls (`files upload`) shows the first.
`enable`, `invite`, `unblock`, `upload`, `publish`, `unpublish`,
`config set / unset`, `env use`, `logout`, `raw`.
- **Read-only**: everything else; no `--yes`, no `--dry-run`.
- **The environment of a change.** A destructive or mutating call inside an
environment needs one named: `--env` / `NORBIX_ENV` (PROD included), or a
saved non-PROD default (`.norbix/project.json`, the profile, `env use`).
With none — the server would use PROD — it exits 2 `ENV_REQUIRED`; with
PROD only from a saved default, exit 2 `PROD_NOT_EXPLICIT`. Nothing is
sent. `--yes` does not answer it; `--dry-run` is never refused. Reads,
`account`, `project`, `module`, `env` and AI service users are not
affected. Same rule as the MCP tools' `AiToolEnvPolicy`.

## 6. Shared code

Expand Down
60 changes: 36 additions & 24 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ refactoringV2 (2026-10) has only the GET routes, so this CLI needs a current gat
| `norbix db update <collection> --all --update <json> [--array-filters <json-array>] [--yes]` | update every record | `PUT /collections/{collectionName}/many` (`allRecords: true`) |
| `norbix db replace <collection> --id <id> --doc <json>` | replace one record fully | `PUT /collections/{collectionName}/{id}/replace` |
| `norbix db delete <collection> --id <id> [--yes]` | delete one | `DELETE /collections/{collectionName}/{id}` |
| `norbix db delete <collection> --filter <json> [--yes]` | delete the ONE record the filter matches: reads up to 2 matches first; 0 → exit 5, 2+ → exit 2 (add `--many`) | `GET /collections/{collectionName}`, then `DELETE /collections/{collectionName}/{id}` |
| `norbix db delete <collection> --many --filter <json> [--yes]` | delete many | `DELETE /collections/{collectionName}/many` |
| `norbix db delete <collection> --all [--yes]` | delete every record | `DELETE /collections/{collectionName}/many` (`allRecords: true`) |
| `norbix db aggregate <collection> --pipeline <json>` | run an inline aggregation | `POST /collections/{collectionName}/aggregate` |
Expand Down Expand Up @@ -165,16 +166,20 @@ counts, the new ids, and any errors.

`--file` is the data schema (the JSON Schema of one record); `--ui-file` is the
UI schema the dashboard form uses. A change is a draft first: records keep the
published version until `schema publish`. `publish` asks first and sends the
server's `confirmed: true`, so it also publishes a change the server calls
breaking — read `schema draft` (or `schema diff` after a publish) before you
answer yes.
published version until `schema publish`. `publish` asks only when data is at
risk, and lets the Hub decide that: it first sends `confirmed: false`, so a
first publish or one that only adds or changes fields goes through without
`--yes`. When the new version removes fields the published one has, the Hub
answers `CM-ERRORS-SCHEMA-020`; the CLI names the fields and asks (without a
terminal: exit 3, nothing published), and on yes — or with `--yes` up front —
publishes with `confirmed: true`. Read `schema draft` (or `schema diff` after a
publish) before you answer yes.

```bash
norbix db schema create --name orders --file orders.schema.json --ui-file orders.ui.json
norbix db schema update 66b2f0a1c3d4e5f6a7b8c9d0 --file orders.schema.json
norbix db schema draft 66b2f0a1c3d4e5f6a7b8c9d0
norbix db schema publish 66b2f0a1c3d4e5f6a7b8c9d0 --yes
norbix db schema publish 66b2f0a1c3d4e5f6a7b8c9d0
norbix db schema diff 66b2f0a1c3d4e5f6a7b8c9d0 --from 1 --to 2
```

Expand Down Expand Up @@ -239,19 +244,24 @@ module alike.
| command | what it does | endpoint |
|---|---|---|
| `norbix db taxonomies` | list taxonomies | `GET /taxonomies` |
| `norbix db taxonomy <id>` | show one taxonomy | `GET /taxonomies/{id}` |
| `norbix db terms <taxonomyName> [--filter <json>] [--page-size <n>] [--after <cursor>] [--desc]` | list the terms of a taxonomy | `GET /taxonomies/{taxonomyName}/terms` |
| `norbix db terms <taxonomyName> --parent <termId>` | only the children of one term | `GET /taxonomies/{taxonomyName}/terms/{parentId}/children` |
| `norbix db term <taxonomyId> <id>` | show one term | `GET /taxonomies/{TaxonomyId}/terms/{Id}` |
| `norbix db term tree <taxonomyName> [--root <termId>] [--depth <n>]` | the terms as a tree | `GET /taxonomies/{TaxonomyName}/terms/tree` |
| `norbix db term tree <taxonomyName> --merged` | one tree across the parent taxonomies | `GET /taxonomies/{TaxonomyName}/merged-tree` |
| `norbix db term create <taxonomyId> --doc <json>` | add a term | `POST /taxonomies/{TaxonomyId}/terms` |
| `norbix db term update <taxonomyId> <id> --set <json>` | change some fields of a term | `PUT /taxonomies/{TaxonomyId}/terms/{Id}` |
| `norbix db term delete <taxonomyId> <id> [--yes]` | delete a term | `DELETE /taxonomies/{TaxonomyId}/terms/{Id}` |

Note the two kinds of key: listing and trees take the taxonomy **name**; a single
term is addressed by the taxonomy **id** and the term id (that is how the
gateway routes them). `--set` is applied with `$set`: only the fields you give
| `norbix db taxonomy <taxonomy>` | show one taxonomy | `GET /taxonomies/{id}` |
| `norbix db taxonomy create <name> [--description <text>] [--terms-meta-schema <file>] [--terms-ui-schema <file>] [--parent <taxonomyId>] [--dependency <taxonomyId>…]` | create a taxonomy | `POST /taxonomies` |
| `norbix db terms <taxonomy> [--filter <json>] [--page-size <n>] [--after <cursor>] [--desc]` | list the terms of a taxonomy | `GET /taxonomies/{taxonomyName}/terms` |
| `norbix db terms <taxonomy> --parent <termId>` | only the children of one term | `GET /taxonomies/{taxonomyName}/terms/{parentId}/children` |
| `norbix db term <taxonomy> <id>` | show one term | `GET /taxonomies/{TaxonomyId}/terms/{Id}` |
| `norbix db term tree <taxonomy> [--root <termId>] [--depth <n>]` | the terms as a tree | `GET /taxonomies/{TaxonomyName}/terms/tree` |
| `norbix db term tree <taxonomy> --merged` | one tree across the parent taxonomies | `GET /taxonomies/{TaxonomyName}/merged-tree` |
| `norbix db term create <taxonomy> --doc <json>` | add a term | `POST /taxonomies/{TaxonomyId}/terms` |
| `norbix db term update <taxonomy> <id> --set <json>` | change some fields of a term | `PUT /taxonomies/{TaxonomyId}/terms/{Id}` |
| `norbix db term delete <taxonomy> <id> [--yes]` | delete a term | `DELETE /taxonomies/{TaxonomyId}/terms/{Id}` |

`<taxonomy>` is the taxonomy **id** (`txn_…`) or its **name** (the title or
its slug, any case) in every command. The gateway routes them with two kinds
of key — listing and trees by name, a single term by the taxonomy id — so the
CLI looks up the other one when needed (one extra read; a `--dry-run` sends
nothing and shows `<id of taxonomy "…">` instead). The id is the stable key: a
title can be renamed. An unknown name exits 5 (`TAXONOMY_NOT_FOUND`) with the
taxonomies there are. `--set` is applied with `$set`: only the fields you give
change — `name` (a string or a `{lang: value}` map), `description`, `order`
(lower shows first), `parentId`, `multiParents`.

Expand All @@ -268,8 +278,8 @@ merged tree checks every parent taxonomy too. Errors you may meet:
(a name longer than 40 characters).

```bash
norbix db term create 66b2f0a1c3d4e5f6a7b8c9d0 --doc '{"name":"Lithuania","order":1}'
norbix db term update 66b2f0a1c3d4e5f6a7b8c9d0 66b2f0a1c3d4e5f6a7b8c9d1 --set '{"order":2}'
norbix db term create countries --doc '{"name":"Lithuania","order":1}'
norbix db term update txn_5BBS25C3IKHRfzVABMjMiM 66b2f0a1c3d4e5f6a7b8c9d1 --set '{"order":2}'
norbix db term tree countries --depth 2
```

Expand All @@ -282,9 +292,11 @@ norbix db term tree countries --depth 2
checked: `norbix hub database schema bundle apply --bundleJson '<IF json>'
--dry-run` asks the Hub's read-only `account.validateSchema` and exits 6
with the issues when the bundle is invalid.
- `delete`, `discard`, `publish`, `seed`, `trigger disable`, `trigger delete`,
`term delete` and the `--many` / `--all` record writes ask first; `--yes` skips the
question, and without a terminal they exit 3 unless `--yes` is given.
- `delete`, `discard`, `seed`, `trigger disable`, `trigger delete`,
`term delete` and the `--many` / `--all` record writes (and `delete --filter`)
ask first; `--yes` skips the question, and without a terminal they exit 3
unless `--yes` is given. `publish` asks only for a breaking change (see
above); `norbix schema` still lists it as destructive because it can be.
- Exit codes are in the README ("Errors and exit codes").

## No command on purpose
Expand All @@ -295,7 +307,7 @@ state. `norbix hub database <words…>` reaches every one of them.

| endpoint | why there is no command |
|---|---|
| `POST /taxonomies`, `DELETE /taxonomies/{Id}`, `GET /taxonomies/tree`, `DELETE /taxonomies/{TaxonomyId}/terms/many` | Taxonomy structure (dependencies, translatable names) is edited in the dashboard; terms — the data — have commands. |
| `POST /taxonomies` with a `viewId` (replace), `DELETE /taxonomies/{Id}`, `GET /taxonomies/tree`, `DELETE /taxonomies/{TaxonomyId}/terms/many` | Changing a taxonomy's structure (dependencies, translatable names) is done in the dashboard; creating one is `db taxonomy create`, and terms — the data — have commands. |
| `POST /schemas/apply-bundle` | Applies a whole generated bundle of schemas; it is the AI schema builder's step, not a hand-run one. |
| `PUT /schemas/{Id}/rename`, `/embed`, `/list-settings`, `/settings` | Dashboard settings of one schema (title, embedding, list columns, soft delete / owner). |
| `POST /integrations`, `DELETE /integrations/{Id}`, `PUT /integrations/{Id}/enable` / `disable`, `GET /integrations/flex-tiers`, `GET /integrations/{Id}/connection-string` | Creating or removing a database, and revealing its connection string, are account-level decisions taken in the dashboard; a connection string should not land in a shell history. |
Expand Down
5 changes: 3 additions & 2 deletions docs/email.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ first two, for scripts that treat every module alike.

`--provider` is one of `Fake`, `Smtp`, `SendGrid`, `MailGun`, `AwsSes`. **Fake**
needs nothing else: it accepts every send and contacts no mail service, so
nothing reaches a real inbox — use it while you develop. A project can hold one
Fake and it has no update path: delete it and add it again. The real providers
nothing reaches a real inbox — use it while you develop. Each environment of a
project can hold one Fake (PROD and TEST may each have their own; `module
enable email` adds PROD's) and it has no update path: delete it and add it again. The real providers
need a sender (`--from`) and their credentials: put the provider-specific
fields in `--config` as a JSON object (inline, `@file.json`, or `-` for stdin)
so a key does not land in your shell history.
Expand Down
3 changes: 2 additions & 1 deletion docs/sms.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ first two, for scripts that treat every module alike.
`--provider` is one of `Fake`, `Twilio`, `Vonage`, `Plivo`, `Telnyx`, `Bird`,
`Telesign`, `Sinch`. **Fake** needs nothing else: it accepts every send and
contacts no SMS service, so nothing reaches a real phone — use it while you
develop. A project can hold one Fake and it has no update path: delete it and
develop. Each environment of a project can hold one Fake (PROD and TEST may each
have their own) and it has no update path: delete it and
add it again. The real providers need their credentials: put the
provider-specific fields in `--config` as a JSON object (inline, `@file.json`,
or `-` for stdin).
Expand Down
Loading
Loading