@vectros-ai/cli 0.15.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,14 @@ The Vectros provisioning CLI — the `vectros` binary:
8
8
  - **`vectros bootstrap`** — provision a least-privilege MCP credential (+ optional blueprint).
9
9
  - **`vectros blueprint`** — author + inspect blueprints (`init` / `validate` / `plan` / `list`), creds-free.
10
10
  - **`vectros blueprint-test`** — apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.
11
+ - **`vectros context`** — create, list, and destroy app contexts.
12
+ - **`vectros issuers`** — inspect registered third-party IdP issuers (token-exchange trust anchors) for a context.
13
+ - **`vectros identity`** — manage tenant-wide principals (users, orgs, clients, and namespaces you register).
14
+ - **`vectros role`** — define reusable, context-scoped access rules.
15
+ - **`vectros access`** — bind principals to contexts, and inspect what a binding grants.
16
+ - **`vectros key`** — issue, list, revoke, and rotate scoped API keys (`ssk_*`).
11
17
  - **`vectros keyring`** — hold several identities on one machine, pick which is active, and let other tools resolve keys from one place.
18
+ - **`vectros join`** — enroll a principal in a context and keep the resulting credential in the keyring, in one step.
12
19
 
13
20
  ```bash
14
21
  npx -y @vectros-ai/cli bootstrap # provision a credential
@@ -58,6 +65,11 @@ npx -y @vectros-ai/cli bootstrap --help
58
65
  ```
59
66
 
60
67
  Re-running is idempotent; `--rotate` revokes + re-mints just this machine's key.
68
+ On a machine with no local record of a prior mint (e.g. an ephemeral CI
69
+ container re-running `--yes` for the first time) against a blueprint whose
70
+ service principal already exists in the tenant, `--yes` alone isn't enough —
71
+ add `--confirm-existing-principal` to make that expected case explicit and
72
+ non-interactive; see AUTHORING.md's "idempotency contract" for why.
61
73
 
62
74
  A bundled blueprint ships with **sample seed records** so the credential works
63
75
  the moment it's minted. For a **production** context that's synthetic clutter:
@@ -110,6 +122,214 @@ Exit `0` = applied and every assertion passed; `1` = an assertion failed or appl
110
122
  errored; `2` = no credential, or the blueprint requested control-plane scope.
111
123
  The app-context is intentionally **not** deleted (it's idempotent and reused).
112
124
 
125
+ ## `vectros context` — app contexts
126
+
127
+ A context is the namespace all data (records, documents, schemas, roles, access
128
+ profiles) lives in — the unit `bootstrap`/`blueprint-test` provision into.
129
+ `vectros context` manages contexts directly, outside of a blueprint.
130
+
131
+ ```bash
132
+ vectros context create my-app --name "My App" # idempotent — re-running converges
133
+ vectros context list # every context in the tenant
134
+ vectros context list --mine # only contexts you hold an access profile in
135
+ vectros context get my-app
136
+ vectros context destroy my-app # irreversible — asks you to re-type the id
137
+ ```
138
+
139
+ | Subcommand | Flags | Notes |
140
+ |---|---|---|
141
+ | `create <contextId>` | `--name <name>` (defaults to the id), `--tenant test\|live` | Idempotent by `contextId`; re-running converges. |
142
+ | `list` | `--mine`, `--tenant test\|live` | Default lists every context in the tenant; `--mine` narrows to contexts you hold an active access profile in. |
143
+ | `get <contextId>` | `--tenant test\|live` | |
144
+ | `destroy <contextId>` | `--force`, `--tenant test\|live` | **Irreversible.** Permanently erases the context and ALL of its records, documents, folders, schemas, roles, and access profiles, and revokes its scoped keys up front. Interactively you must re-type the context id to confirm; `--force` skips the prompt and is **required** when stdin is not a TTY (scripts/CI). Reports `status: purging` — the teardown itself runs asynchronously. |
145
+
146
+ All four accept `--json` for machine-readable output, plus the common auth
147
+ flags (`--base-url`, `--token`).
148
+
149
+ ## `vectros issuers` — registered third-party IdP issuers
150
+
151
+ Read the third-party identity-provider registrations a context trusts for
152
+ [RFC 8693 token exchange](https://www.rfc-editor.org/rfc/rfc8693) — the
153
+ `issuer`/`jwksUri`/`audience` a blueprint's `issuers[]` entries register, plus
154
+ any self-signup policy attached to one. Read-only: registering an issuer is
155
+ done through a blueprint (`bootstrap`/`blueprint-test`), not this command.
156
+
157
+ ```bash
158
+ vectros issuers list --context my-app
159
+ vectros issuers get primary --context my-app
160
+ ```
161
+
162
+ | Subcommand | Flags | Notes |
163
+ |---|---|---|
164
+ | `list` | `--context <contextId>` (required), `--tenant test\|live` | Every issuer registered in that context. `--context` has no default — an issuer read is confined to the context you name; there's no tenant-wide "every issuer in every context" view. |
165
+ | `get <issuerId>` | `--context <contextId>` (required), `--tenant test\|live` | One issuer's full registration, including its self-signup policies. |
166
+
167
+ Both accept `--json`, plus the common auth flags (`--base-url`, `--token`).
168
+
169
+ ## `vectros identity` — tenant-wide principals
170
+
171
+ Manage the principals a context's access profiles and roles reference: users
172
+ (the one fixed principal surface, `--type user`) and namespaced identity
173
+ entities (`org`, `client`, or any namespace you registered, via `--type
174
+ <namespace>`). Identities are **tenant-wide and orthogonal to context** — a
175
+ blueprint or role *references* one by id, it never creates one.
176
+
177
+ ```bash
178
+ vectros identity create --type user --external-id alice@example.com --email alice@example.com
179
+ vectros identity create --type org --external-id acme-inc --name "Acme Inc"
180
+ vectros identity list --type user
181
+ vectros identity get --type org --id <vectrosId>
182
+ vectros identity delete --type org --id <vectrosId>
183
+ vectros identity invite --context my-app --role editor --email bob@example.com \
184
+ --accept-url https://my-app.example.com/accept
185
+ ```
186
+
187
+ | Subcommand | Flags | Notes |
188
+ |---|---|---|
189
+ | `create` | `--type <user\|namespace>` (required), `--external-id <id>` (required — your stable idempotency key) | Idempotent, upsert-by-`externalId`. |
190
+ | | `--type user` only: `--email <email>`, `--service` (mint a SERVICE principal; default HUMAN) | `--name`/`--scope` are rejected for `--type user`. |
191
+ | | entity namespaces only: `--name <name>`, `--scope <ns:value>` (repeatable, up to 2 parent ownership edges) | `--email`/`--service` are rejected for an entity namespace. |
192
+ | | both: `--metadata <json>` | Free-form JSON object. |
193
+ | `list` | `--type <user\|namespace>` (required), `--external-id <id>` (filter), `--limit <n>` | Omit `--limit` and the CLI drains every page for the complete set; pass `--limit` (1–100) and it returns exactly one page of that size instead. |
194
+ | `get` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | `--id` is the Vectros-assigned id from `create`/`list`, not the `--external-id`. |
195
+ | `delete` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | |
196
+ | `invite` | `--context <c>`, `--role <roleId>`, `--email <e>` (all required); `--first-name <n>`, `--accept-url <url>`, `--no-send-email` | Onboards a person into a context by email (`POST /v1/users/invite`) — no root key required, your `vectros login` session already carries enough scope. `--role` is an existing Role's own id (no separate lookup). `--accept-url` is required unless `--no-send-email` is given, in which case the response carries the raw `inviteToken`/`acceptLink` for you to deliver yourself. |
197
+
198
+ `create`/`list`/`get`/`invite` accept `--json`; `delete` does not.
199
+
200
+ ## `vectros role` — reusable, context-scoped access rules
201
+
202
+ A Role is a reusable, identity-agnostic scope rule bound to a principal later
203
+ via `vectros access grant --role <roleId>` — the same rule can be shared
204
+ across many principals. `role create` builds a **single-clause** role from a
205
+ flat `--actions` list; author a multi-clause role (several rules unioned
206
+ together) in a blueprint's `roles:` block instead.
207
+
208
+ ```bash
209
+ vectros role create --context my-app --role-id editor --name Editor \
210
+ --actions records:cru,search:r
211
+ vectros role create --context my-app --role-id own-org --name "Own org" \
212
+ --actions records:r --data-scope '{"scope:org":["<orgId>", null]}'
213
+ vectros role list --context my-app
214
+ vectros role get --context my-app --role-id editor
215
+ vectros role delete --context my-app --role-id editor
216
+ ```
217
+
218
+ | Subcommand | Flags | Notes |
219
+ |---|---|---|
220
+ | `create` | `--context <c>`, `--role-id <id>`, `--name <n>`, `--actions <csv>` (all required); `--data-scope <json>`, `--description <text>` | Idempotent by `roleId`. `--data-scope` confines the clause to owned data — a JSON map of ownership dimension → allowed values, ANDed across keys (e.g. `{"userId":["${{ self.userId }}"]}`, or `{"scope:org":["<orgId>", null]}` — the `null` entry additionally admits scope-less/tenant-shared records). Keys must be `userId` or `scope:<namespace>` (the legacy flat `orgId`/`clientId` keys are rejected server-side). |
221
+ | `list` | `--context <c>` (required) | |
222
+ | `get` | `--context <c>`, `--role-id <id>` (both required) | Also shows the role's `assumable` grant (which values, per namespace, a holder of this role may become via `POST /v1/auth/token/assume`), if any is set. `role create` has no `--assumable` flag today — author it via a blueprint's `roleAssumable:` block or a direct API call. |
223
+ | `delete` | `--context <c>`, `--role-id <id>` (both required) | No cascade — any access profile still referencing the role fails to mint (not silently downgraded) until reassigned. |
224
+
225
+ `create`/`list`/`get` accept `--json`; `delete` does not.
226
+
227
+ ## `vectros access` — bind principals to contexts
228
+
229
+ The binding layer between an identity and a context: `access grant` joins a
230
+ principal to a context via a Role reference (`--role`) **or** inline actions
231
+ (`--actions`) — never both. A later `vectros key issue` mints a credential
232
+ that authenticates *as* the bound principal and can never exceed this
233
+ binding.
234
+
235
+ ```bash
236
+ vectros access grant --principal me --context my-app --role editor
237
+ vectros access grant --principal usr_<id> --context my-app --actions records:r,search:r \
238
+ --scope org:<orgId>
239
+ vectros access list --context my-app # who's in this context
240
+ vectros access list --principal me # which contexts I'm bound to
241
+ vectros access get --principal me --context my-app
242
+ vectros access explain --principal me --context my-app
243
+ vectros access revoke --principal usr_<id> --context my-app
244
+ ```
245
+
246
+ `--principal` accepts `usr_<userId>`, `key_<keyId>`, or the alias `me` (your
247
+ own signed-in user, resolved from your login) — every `access` subcommand
248
+ shares this grammar.
249
+
250
+ | Subcommand | Flags | Notes |
251
+ |---|---|---|
252
+ | `grant` | `--principal <p>`, `--context <c>` (required); exactly one of `--role <roleId>` (repeatable — 2+ compose additively) / `--actions <csv>`; `--scope <ns:value>` (repeatable), `--identity-overrides <json>` | Idempotent **upsert** — re-granting an existing binding updates it to the newly-declared shape (role(s)/actions/overrides), it does not no-op. Repeating `--role` composes: `--role case-handler --role hr-admin` grants each role's own clauses, concatenated in the order given — never merged, so each clause keeps meaning exactly what its own author wrote. `--scope`/`--identity-overrides` extend the principal's stamped identity (e.g. `--scope org:<id>` → `scope:org`) — a credential can only stamp ownership its identity carries, so granting write access to org-scoped data requires that scope on the profile. When both are given, `--identity-overrides` wins on key conflicts. |
253
+ | `revoke` | `--principal <p>`, `--context <c>` (required) | |
254
+ | `list` | Exactly one of `--context <c>` / `--principal <p>` | `--context` lists the context's members; `--principal` lists every context that principal is bound to. |
255
+ | `get` | `--principal <p>`, `--context <c>` (required) | |
256
+ | `explain` | `--principal <p>`, `--context <c>` (required) | Resolves the binding's clauses (following every referenced role and concatenating their clauses, if bound via one or more) and renders a concrete **access matrix** — a canned set of record-ownership archetypes (yours alone, another user's, org-shared, scope-less, …) evaluated against the clauses. A client-side preview for authoring-time sanity, not the platform's own evaluator: it models READ reachability only — a `c`/`u`/`d` action shown as granted is advisory, not a guarantee a specific write will succeed, since the platform's write rule (a clause must explicitly name a dimension to place a value into it) diverges from its read rule (an unmentioned dimension never narrows reads). |
257
+
258
+ `grant`/`list`/`get`/`explain` accept `--json`; `revoke` does not.
259
+
260
+ ## `vectros join` — enroll and keep the credential, in one step
261
+
262
+ `join` is a **composition verb**: everything it does already exists as a
263
+ discrete command (`access grant`, `key issue`, `keyring add`) — `join` runs
264
+ them in order against one context and records the outcome as a keyring
265
+ entry, so `vectros switch <alias>` (and the MCP server / recall hook) can act
266
+ as that principal from then on.
267
+
268
+ ```bash
269
+ vectros join my-app --role editor # enroll "me" via a role
270
+ vectros join my-app --role case-handler --role hr-admin # compose 2+ roles additively
271
+ vectros join my-app --actions records:cru,search:r --scope org:<orgId>
272
+ vectros join my-app --as usr_<id> --role editor --alias teammate
273
+ ```
274
+
275
+ | Flag | Notes |
276
+ |---|---|
277
+ | `--role <roleId>` | Repeatable — 2+ compose additively, same semantics as `access grant --role`. XOR with `--actions`. |
278
+ | `--actions <csv>` | Inline single-clause grant. XOR with `--role`. |
279
+ | `--as me\|usr_<id>` (default `me`) | Which principal to enroll — `me` resolves to your own signed-in user. |
280
+ | `--scope <ns:value>` | Repeatable. Places the member under a scope for this context (an identity override on the profile) so their key can WRITE that scope's data. |
281
+ | `--alias <name>` | Keyring entry name (defaults to the context id). |
282
+ | `--activate` / `--no-activate` | Force (or skip) making the freshly-stored key this machine's active identity. |
283
+
284
+ Re-joining an alias whose secret is still held locally is idempotent: it
285
+ re-grants (a no-op upsert) and re-activates without churning the key. Only
286
+ when the local secret is missing does it rotate to obtain a fresh one.
287
+
288
+ ## `vectros key` — the scoped API key (`ssk_*`) lifecycle
289
+
290
+ Mint, inspect, revoke, and rotate the scoped keys (`ssk_*`) that authenticate
291
+ as a principal already bound to a context (`vectros access grant` first — a
292
+ key can never exceed its profile). The raw secret is disclosed **exactly
293
+ once**, on `issue`/`rotate`; if you lose it, `key rotate` mints a fresh one.
294
+
295
+ ```bash
296
+ vectros key issue --principal usr_<id> --context my-app # print the secret (default)
297
+ vectros key issue --principal usr_<id> --context my-app --to-keyring # store it instead
298
+ vectros key list # keys in the default context
299
+ vectros key list --all-contexts # every key, every context (owner-only)
300
+ vectros key get <keyId>
301
+ vectros key revoke <keyId>
302
+ vectros key rotate --principal usr_<id> --context my-app # revoke + re-mint the same tuple
303
+ ```
304
+
305
+ `--principal` here is `usr_<userId>` **or a bare user id** (the leading `usr_`
306
+ is optional and stripped if present) — narrower than `access`'s grammar: there
307
+ is no `me` alias on any `key` subcommand, and a key never authenticates as a
308
+ `key_*` principal. Look your own id up first with `vectros identity list
309
+ --type user` (or `vectros whoami`), or grant/issue via `vectros join`, which
310
+ does resolve `--as me`.
311
+
312
+ A minted secret has exactly two destinations, and the flags for choosing one
313
+ are shared by `issue` and `rotate`:
314
+
315
+ | Flag | Meaning |
316
+ |---|---|
317
+ | `--format human\|raw\|env\|json` (default `human`) | Print the secret — `raw` = bare secret only, `env` = a `VECTROS_API_KEY=...` line, `json` = the full key object. |
318
+ | `--to-keyring` | Store the secret in this machine's keyring instead of printing it (the desktop/MCP path). Mutually exclusive with an explicit `--format raw\|env\|json`. |
319
+ | `--alias <name>` | Keyring entry name for `--to-keyring` (defaults to the context id). |
320
+ | `--no-activate` | With `--to-keyring`, store the entry without making it the active identity. |
321
+
322
+ | Subcommand | Flags | Notes |
323
+ |---|---|---|
324
+ | `issue` | `--principal <p>`, `--context <c>` (required); `--name <n>` (default `default`), `--label <l>`, `--tenant test\|live`, destination flags above | Idempotent on `(tenant, context, principal, name)` — re-issuing an existing tuple returns the key **without** the secret (never re-disclosed); the CLI detects that and points you at `rotate`. |
325
+ | `list` | `--principal <p>`, `--context <c>`, `--tenant test\|live` **or** `--all-contexts` (owner-only; spans both tenants, not combinable with `--tenant`) | **The default lists only ONE context's keys** (the token's context — `default`, or `--context`), not your whole account — easy to miss since the active-key limit is tenant-wide across all contexts. `--all-contexts` is the true account-wide view. |
326
+ | `get <keyId>` | `--context <c>`, `--tenant test\|live` | `--context` pins the verb token to the key's context — omit it for a key bound to a non-default context (e.g. one `bootstrap` minted) and the lookup 404s. |
327
+ | `revoke <keyId>` | `--context <c>`, `--tenant test\|live` | Soft-delete; ~5 minutes to take effect (authorizer cache). Same `--context` pinning caveat as `get`. |
328
+ | `rotate` | `--principal <p>`, `--context <c>` (required); `--name <n>`, `--force-new`, `--tenant test\|live`, destination flags above | Revokes the matching active key (by the exact `(context, principal, name)` tuple), then mints a fresh one — there is no dedicated rotate endpoint. **Default-name guard:** if no key is currently active under `--name` (default `default`) but the principal/context has *other* active keys, `rotate` refuses rather than silently minting an unrelated second key — pass the right `--name` or `--force-new` to proceed anyway. |
329
+
330
+ `issue`/`rotate` have no `--json` (use `--format json` instead); `list`/`get`
331
+ accept `--json`, `revoke` does not.
332
+
113
333
  ## `vectros keyring` — one home for your keys
114
334
 
115
335
  A machine can hold several Vectros identities (one per context or principal).
@@ -188,11 +408,42 @@ Blueprints are **untrusted input** (agent- or community-authored) and the
188
408
  CLI runs under an elevated ephemeral bridge token. So the trust boundary is
189
409
  **this binary, not the blueprint**: a hardcoded **scope gate** bounds every
190
410
  mint to a data-plane allowlist (`records` / `schemas` / `search` / `documents`
191
- / `folders` / `inference` / `entities`). Any control-plane scope (keys /
192
- profiles / users / billing / admin / namespaces) or `*` wildcard
411
+ / `folders` / `inference` / `entities`). Most control-plane scope (keys /
412
+ app-contexts / users / billing / admin / namespaces) or a `*` wildcard
193
413
  **hard-rejects** — the CLI mints nothing and exits non-zero. There is
194
414
  intentionally **no override flag**.
195
415
 
416
+ **One narrow, deliberate exception:** `profiles` (which configures who has
417
+ access to what — control-plane by nature) admits its `r`/`u`/`d` operations,
418
+ because the platform enforces an unconditional check that a write to an
419
+ AccessProfile can never grant more scope than the requesting credential
420
+ already holds — those verbs can only disclose or redistribute access, never
421
+ manufacture new reach. `profiles:c` (creating a profile for an arbitrary,
422
+ caller-chosen principal name) still hard-rejects — **except** `profiles:c:self`,
423
+ which the platform resolves from the credential's own identity at request
424
+ time, never from anything the blueprint declares, and **except a bare
425
+ `profiles:c` on a clause that also declares the `member-lifecycle` capability**
426
+ (see Capabilities, below) — that combination is admitted because
427
+ `member-lifecycle` already grants unbounded principal-creation reach, so
428
+ gating bare `profiles:c` behind it adds no further technical bound but keeps
429
+ the same explicit, fail-closed opt-in signal every other admission here
430
+ requires. A literal-qualified `profiles:c:usr_<id>` naming another principal
431
+ still hard-rejects regardless of `member-lifecycle`, same as every other
432
+ control-plane scope.
433
+
434
+ ### Capabilities (`granted_capabilities`) — a separate, equally-gated dimension
435
+
436
+ A blueprint's `accessProfile` and role clauses may also declare
437
+ `capabilities: [...]` — named platform effects (like creating or removing an
438
+ identity in your tenant) that reach across a partition boundary in a way no
439
+ `resource:ops` verb can express. This rides a field entirely separate from
440
+ `allowedActions`, and it has its own allowlist: **only `member-lifecycle` is
441
+ admitted today.** Every other capability name — including ones the platform's
442
+ own registry recognizes — is refused, the same fail-closed, deliberately-widen
443
+ posture as the scope gate above. `vectros blueprint validate`/`plan` and
444
+ `bootstrap` all report a rejected capability the same way they report a
445
+ rejected scope: nothing mints, and the CLI exits non-zero.
446
+
196
447
  ## Rate limits
197
448
 
198
449
  `blueprint-test` provisions against the live API, so a large blueprint can brush the per-account
@@ -1,11 +1,11 @@
1
1
  # Third-party notices
2
2
 
3
3
  `@vectros-ai/cli` compiles the following third-party packages directly into its
4
- published binary (`dist/cli.js`). Their licenses require that these notices travel
5
- with the code, so they are reproduced in full below.
4
+ published bundle. Their licenses require that these notices travel with the code,
5
+ so they are reproduced in full below.
6
6
 
7
- This file is GENERATED by `pipelines/third_party_notices.mjs` from the `noExternal`
8
- lists in `packages/*/tsup.config.ts`. Do not edit it by hand — run
7
+ This file is GENERATED by `pipelines/third_party_notices.mjs` from `packages/cli/tsup.config.ts`'s
8
+ `noExternal` list. Do not edit it by hand — run
9
9
  `node pipelines/third_party_notices.mjs --write`. CI fails if it is stale, because a
10
10
  notices file that silently drifts is worse than none: it asserts a compliance that
11
11
  nobody checked.