npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vectros-ai/cli

v0.21.0

Published

The Vectros provisioning CLI — `vectros bootstrap` mints a least-privilege MCP credential (ssk_* + AccessProfile + optional blueprint) without the root key. Owns the scope gate (the trust boundary).

Readme

@vectros-ai/cli

npm license

The Vectros provisioning CLI — the vectros binary:

  • vectros bootstrap — provision a least-privilege MCP credential (+ optional blueprint).
  • vectros blueprint — author + inspect blueprints (init / validate / plan / list), creds-free.
  • vectros blueprint-test — apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.
  • vectros context — create, list, and destroy app contexts.
  • vectros issuers — register, update, delete, and inspect third-party IdP issuers (token-exchange trust anchors) for a context.
  • vectros namespace — register a new scope namespace, and list/get existing ones.
  • vectros identity — manage tenant-wide principals (users, orgs, clients, and namespaces you register).
  • vectros role — define reusable, context-scoped access rules.
  • vectros scripts — push, list, inspect, and delete the versioned script source a trigger fires, or your own app executes directly.
  • vectros assume — switch identity namespace values on your own token, per a role's assumable grant.
  • vectros access — bind principals to contexts, and inspect what a binding grants.
  • vectros key — issue, list, revoke, and rotate scoped API keys (ssk_*).
  • vectros keyring — hold several identities on one machine, pick which is active, and let other tools resolve keys from one place.
  • vectros join — enroll a principal in a context and keep the resulting credential in the keyring, in one step.
npx -y @vectros-ai/cli bootstrap          # provision a credential
npx -y @vectros-ai/cli blueprint --help   # the authoring commands
npx -y @vectros-ai/cli keyring list       # the identities on this machine

Every command has --help. The authoring loop is documented in AUTHORING.md.

vectros bootstrap

Provisions a least-privilege Vectros credential for the @vectros-ai/mcp-server in one command — without your root key and without manual admin-app steps. It:

  • mints a scoped key (ssk_*) for this machine (independently rotatable),
  • creates the matching AccessProfile — data-plane only,
  • optionally scaffolds a use-case blueprint (e.g. task-management: a schema + seed data), and
  • safe-merges the vectros server entry into your MCP client config (claude_desktop_config.json — Claude Desktop, Cursor, Cline), backing up the original first. Pass --client code to target Claude Code instead: it merges the project .mcp.json and prints the equivalent claude mcp add command.
# Interactive — pick a blueprint (or a blank read-only credential), sign in once.
npx -y @vectros-ai/cli bootstrap

# Scripted / agent — token in the env, no prompts.
VECTROS_BOOTSTRAP_TOKEN=… npx -y @vectros-ai/cli bootstrap \
  --blueprint task-management --yes

# Login once, then bootstrap unattended — no token needed; --yes reuses your
# stored `vectros login` session.
npx -y @vectros-ai/cli login
npx -y @vectros-ai/cli bootstrap --blueprint task-management --yes

# Provision a PRODUCTION context: the blueprint's schemas + key, but NONE of its
# illustrative sample records.
npx -y @vectros-ai/cli bootstrap --blueprint task-management --no-seed --yes

# See every flag.
npx -y @vectros-ai/cli bootstrap --help

Re-running is idempotent; --rotate revokes + re-mints just this machine's key. On a machine with no local record of a prior mint (e.g. an ephemeral CI container re-running --yes for the first time) against a blueprint whose service principal already exists in the tenant, --yes alone isn't enough — add --confirm-existing-principal to make that expected case explicit and non-interactive; see AUTHORING.md's "idempotency contract" for why.

A bundled blueprint ships with sample seed records so the credential works the moment it's minted. For a production context that's synthetic clutter: --no-seed provisions everything else — the schemas, the AccessProfile and the scoped key — but creates no sample records, leaving the context clean for real data. (Interactively, a blueprint with seeds asks before adding them.) This differs from --blank, which is credential-only and drops the schemas too.

vectros blueprint — authoring (creds-free)

Author and inspect blueprints without an account. You'll run these repeatedly, so install once and drop the npx prefix (npm i -g @vectros-ai/cli, or prefix each command with npx -y @vectros-ai/cli). The inner loop:

vectros blueprint init my-thing                      # scaffold a commented YAML starter
#   …edit ./my-thing.blueprint.yaml…
vectros blueprint validate ./my-thing.blueprint.yaml # structural + scope-gate + lint
vectros blueprint plan     ./my-thing.blueprint.yaml # preview what bootstrap would provision
vectros bootstrap --blueprint ./my-thing.blueprint.yaml   # apply it
  • init — write a starter (--from <bundled> copies an exemplar; --out, --force).
  • validate — structural parse → scope gate → linter. Accepts a file or a bundled name.
  • plan — validate, then a terraform-style preview; mints nothing.
  • list — the bundled library.

Full guide: AUTHORING.md. Format reference: @vectros-ai/blueprints.

vectros blueprint-test — verify it actually provisions (creds-required)

validate/plan check a blueprint statically; blueprint-test proves it end-to-end against a live environment. It applies the blueprint, asserts the result (the app-context + each schema are readable back, and the freshly minted ssk_* authenticates as a scoped key with the requested actions), then tears down what it created — schemas, the AccessProfile, seed records, and the key. Teardown is created-only: anything that already existed before the run is left untouched, so it's safe to point at a shared staging tenant.

# Apply → assert → teardown against staging (sign in first, or pass --token).
vectros blueprint-test ./my-thing.blueprint.yaml --env staging

# Leave the provisioned graph in place to inspect it.
vectros blueprint-test task-management --keep

Exit 0 = applied and every assertion passed; 1 = an assertion failed or apply errored; 2 = no credential, or the blueprint requested control-plane scope. The app-context is intentionally not deleted (it's idempotent and reused).

vectros context — app contexts

A context is the namespace all data (records, documents, schemas, roles, access profiles) lives in — the unit bootstrap/blueprint-test provision into. vectros context manages contexts directly, outside of a blueprint.

vectros context create my-app --name "My App"   # idempotent — re-running converges
vectros context list                            # every context in the tenant
vectros context list --mine                     # only contexts you hold an access profile in
vectros context get my-app
vectros context destroy my-app                  # irreversible — asks you to re-type the id

| Subcommand | Flags | Notes | |---|---|---| | create <contextId> | --name <name> (defaults to the id), --tenant test\|live | Idempotent by contextId; re-running converges. | | list | --mine, --tenant test\|live | Default lists every context in the tenant; --mine narrows to contexts you hold an active access profile in. | | get <contextId> | --tenant test\|live | | | 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. |

All four accept --json for machine-readable output, plus the common auth flags (--base-url, --token).

vectros issuers — registered third-party IdP issuers

Manage the third-party identity-provider registrations a context trusts for RFC 8693 token exchange — the issuer/jwksUri/audience a blueprint's issuers[] entries register, plus any self-signup policy attached to one. Everything a blueprint's issuers: block can do is available standalone here too — adding a second IdP, or editing one issuer's selfSignupPolicies after initial setup, no longer requires hand-authoring a blueprint and re-running a full apply.

vectros issuers register --context my-app --issuer-id auth0-prod \
  --issuer https://your-tenant.us.auth0.com/ \
  --jwks-uri https://your-tenant.us.auth0.com/.well-known/jwks.json \
  --audience https://api.your-app.example.com \
  --captured-claim name --captured-claim phone_number
vectros issuers list --context my-app
vectros issuers get primary --context my-app
vectros issuers update primary --context my-app --status suspended
vectros issuers delete primary --context my-app   # re-types the id to confirm

| Subcommand | Flags | Notes | |---|---|---| | register | --context, --issuer-id, --issuer, --jwks-uri, --audience (all required); --sub-claim, --email-claim, --userinfo-uri, --self-signup-policy <type:roleId> (repeatable), --captured-claim <name> (repeatable), --tenant test\|live | Idempotent by --issuer-id within the tenant — a repeat returns the existing registration unchanged, not an error. --captured-claim names additional OIDC claims to capture beyond the email claim on every successful token exchange, stored as your tenant's golden identity-provider-asserted copy for that user; omit it to capture nothing beyond email. At most 20 names of at most 64 characters, no duplicates. | | update <issuerId> | --context <contextId> (required); --sub-claim, --email-claim, --userinfo-uri, --status active\|suspended, --self-signup-policy <type:roleId> (repeatable), --clear-self-signup-policies, --captured-claim <name> (repeatable), --clear-captured-claims, --tenant test\|live | Omitting --captured-claim leaves the stored list UNCHANGED; --clear-captured-claims sends an explicit empty list (capture nothing beyond email). The two are mutually exclusive, and the same absent-vs-clear split applies to --self-signup-policy. Only ever sends the SAFE, mutable fields — issuer/jwks-uri/audience/contextId are trust-anchor fields, immutable via this route on the platform side; rotating one is delete + register. | | delete <issuerId> | --context <contextId> (required), --force, --tenant test\|live | Re-types the issuer id to confirm (mirrors context destroy); --force skips the prompt and is required when stdin isn't a TTY. Refused server-side while any user was ever created or matched through this issuer. | | 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. | | get <issuerId> | --context <contextId> (required), --tenant test\|live | One issuer's full registration, including its self-signup policies and captured claims. |

All five accept --json, plus the common auth flags (--base-url, --token).

vectros namespace — scope-namespace registrations

Register and inspect the namespaces a scope:<namespace> value can belong to — whether values in that namespace must resolve to a real identity entity (entityBacked, the same field identity create --type <namespace> relies on) or are free-form strings. Registering a brand-new namespace (e.g. a practice/team entity dimension) was blueprint-apply-only before this command existed.

vectros namespace register practice --context my-app --specificity-rank 1500 --entity-backed
vectros namespace list                        # tenant-wide registrations
vectros namespace list --context my-app       # just my-app's own
vectros namespace get practice --context my-app

| Subcommand | Flags | Notes | |---|---|---| | register <name> | --context, --specificity-rank <n> (both required); --entity-backed, --default-schema-id <id> | NOT idempotent — a repeat name 400s (unlike issuers register). --context has no tenant-wide option here — a confined credential may only register against its own bound context. | | list | --context <contextId> (optional) | Omit for every tenant-wide registration; supply for one context's own. | | get <name> | --context <contextId> (optional) | Same two-mode read as list. |

No update/delete — both routes require a genuine root API key on the platform side, which this CLI never holds, so neither could ever succeed here. All three subcommands accept --json.

vectros identity — tenant-wide principals

Manage the principals a context's access profiles and roles reference: users (the one fixed principal surface, --type user) and namespaced identity entities (org, client, or any namespace you registered, via --type <namespace>). Identities are tenant-wide and orthogonal to context — a blueprint or role references one by id, it never creates one.

vectros identity create --type user --external-id [email protected] --email [email protected]
vectros identity create --type org --external-id acme-inc --name "Acme Inc"
vectros identity list --type user
vectros identity get --type org --id <vectrosId>
vectros identity delete --type org --id <vectrosId>
vectros identity invite --context my-app --role editor --email [email protected] \
  --accept-url https://my-app.example.com/accept

| Subcommand | Flags | Notes | |---|---|---| | create | --type <user\|namespace> (required), --external-id <id> (required — your stable idempotency key) | Idempotent, upsert-by-externalId. | | | --type user only: --email <email>, --service (mint a SERVICE principal; default HUMAN) | --name/--scope are rejected for --type user. | | | entity namespaces only: --name <name>, --scope <ns:value> (repeatable, up to 2 parent ownership edges) | --email/--service are rejected for an entity namespace. | | | both: --metadata <json> | Free-form JSON object. | | list | --type <user\|namespace> (required), --external-id <id> (filter), --limit <n> | Scoped to your own context — see list-all below for a user created but never granted access anywhere. 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. | | list-all | --external-id <id> (filter), --tenant test\|live, --limit <n> | Users only, and requires an account owner login. Lists every user in the tenant, regardless of app-context access — for recovering a user identity list can never show you: one created but not (yet) granted access anywhere, so no context-scoped view can ever reach it, and a create retry with the same --external-id just collides with the same invisible row. Find it here by --external-id, then grant it access or identity delete it with an ordinary credential. | | get | --type <user\|namespace> (required), --id <vectrosId> (required) | --id is the Vectros-assigned id from create/list, not the --external-id. | | delete | --type <user\|namespace> (required), --id <vectrosId> (required) | | | 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. |

create/list/list-all/get/invite accept --json; delete does not.

vectros role — reusable, context-scoped access rules

A Role is a reusable, identity-agnostic scope rule bound to a principal later via vectros access grant --role <roleId> — the same rule can be shared across many principals. role create builds a single-clause role from a flat --actions list; author a multi-clause role (several rules unioned together) in a blueprint's roles: block instead.

vectros role create --context my-app --role-id editor --name Editor \
  --actions records:cru,search:r
vectros role create --context my-app --role-id own-org --name "Own org" \
  --actions records:r --data-scope '{"scope:org":["<orgId>", null]}'
vectros role create --context my-app --role-id hr-admin --name "HR Admin" \
  --actions records:cru --assumable '{"scope:org":["org_engineering", "org_sales"]}'
vectros role create --context my-app --role-id delegator --name Delegator \
  --actions profiles:c --assignable-role editor --assignable-role own-org
vectros role list --context my-app
vectros role get --context my-app --role-id editor
vectros role delete --context my-app --role-id editor

| Subcommand | Flags | Notes | |---|---|---| | create | --context <c>, --role-id <id>, --name <n>, --actions <csv> (all required); --data-scope <json>, --assumable <json>, --assignable-role <roleId> (repeatable), --description <text>, --tenant test\|live | Idempotent by roleId. --assignable-role restricts WHICH named roles this clause may compose into a delegated AccessProfile — orthogonal to --data-scope, which bounds data reach rather than role composition. Omit it for no restriction; the platform rejects an empty list, so there is no way to spell "compose nothing". Once a clause carries the restriction it can compose only the roles it names, so name every roleId it still needs before adding the flag. access explain reports the restriction for clauses that carry one. --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). --assumable is a SEPARATE, role-level grant — a JSON map of scope:<namespace> → allowed values naming which values a holder of this role may switch into via vectros assume; holding broad --data-scope reach in a namespace does not by itself grant assuming any value in it. | | list | --context <c> (required); --tenant test\|live | Add --tenant test to read a context that lives in the test tenant — a blueprint applied there is otherwise unreadable. | | get | --context <c>, --role-id <id> (both required); --tenant test\|live | Also shows the role's assumable grant, if any is set. | | delete | --context <c>, --role-id <id> (both required); --tenant test\|live | No cascade — any access profile still referencing the role fails to mint (not silently downgraded) until reassigned. |

create/list/get accept --json; delete does not.

vectros scripts — the script source a trigger fires, or your app runs directly

A stored script has two consumers, and this is how the source gets there for both. A trigger rule names it in scriptRef and runs it under the trigger's own grant; that scriptRef is validated when the rule is DECLARED rather than when it fires, so a script has to exist before any rule can name it. Your own app runs a stored version directly through POST /v1/scripts/execute, under its own credential, committing everything the script wrote as a single transaction.

Direct execution is separately granted: the calling role needs scripts:x, and without it the call is a 403 that cannot say the missing grant is the reason. The platform accepts it bare (every script in the context) or qualified to one name (scripts:x:create-case). A blueprint may use only the qualified form — its scope gate refuses a bare scripts:x and there is no wildcard — so a blueprint that ships a script grants exactly the one it ships. See The scope gate below, and AUTHORING.md for where that grant goes in a blueprint.

vectros scripts push notify-assignee --context my-app --source-file ./notify.js
vectros scripts push notify-assignee --context my-app --source-file ./notify.js --if-changed
vectros scripts list --context my-app                        # every script, newest first
vectros scripts list --context my-app --name notify-assignee # every version of one name
vectros scripts get 3b1c9f2e7a5d… --context my-app           # one version, with its source
vectros scripts delete 3b1c9f2e7a5d… --context my-app        # ONE version (prompts)
vectros scripts delete 3b1c9f2e7a5d… --context my-app --force  # ...or skip the prompt, e.g. in CI

| Subcommand | Flags | Notes | |---|---|---| | push | --context <c>, --source-file <path> (both required); --input-contract <text> or --input-contract-file <path>, --if-changed, --tenant <test\|live> | Always stores a new version by default — pushing is not idempotent, and each push gets the next server-assigned version number, which the output reports. --if-changed reads the current version first and pushes only on a genuine difference; use it where the same push re-runs, e.g. in CI. --source-file is capped at 300,000 UTF-8 bytes and the input contract at 50,000, both checked locally so an oversized push fails immediately instead of sending the body. The input contract is descriptive only — nothing validates a firing against it. Nothing parses the source either, so a syntax error surfaces the first time the version runs. | | list | --context <c> (required); --name <name>, --tenant <test\|live> | With --name, every version of that name, oldest first; without, every script in the context, newest first. The human view shows a byte count instead of the source — a listing would otherwise print the full text of every version. --json includes the source. | | get | --context <c> (required) + the version id; --tenant <test\|live> | Shows one version, including its full source. Version ids are opaque — a deterministic hex string, not a prefixed or random id — so copy them from scripts list. | | delete | --context <c> (required) + the version id; --force, --tenant <test\|live> | Deletes ONE version; other versions of the same name are untouched. Irreversible — the stored source is the only copy — so it asks you to type the version id back before proceeding. --force skips that, and is REQUIRED in a non-interactive shell rather than the prompt being silently waived there. --json emits {id, deleted} on stdout; the warning always goes to stderr, so it stays visible without breaking a pipe. ⚠️ Deleting a name's LATEST version reuses its identity — the next push of that name recomputes the same version number and the same id, so a pinned reference silently resolves to different source. |

There is no scripts update — a stored version is immutable, and the platform's update route always rejects. Supersede a version by pushing a new one.

--context is required on all four. Scripts are context-scoped and a trigger resolves its scriptRef inside its own context, so a defaulted context would push where nobody asked and leave the rule that wanted it still unresolvable. --tenant defaults to live; pass --tenant test to reach the versions a blueprint-test --tenant test run created.

⚠️ delete is not reference-checked. Nothing verifies whether a trigger rule still points at the version being removed, and the two outcomes differ: a rule pinned to that version fails on every subsequent firing, while a rule on version: latest silently resolves to the PREVIOUS version and carries on running older code with nothing to indicate it. This CLI has no verb that lists trigger rules, so it cannot answer that question for you — check against the API before removing a version a rule may still use.

A blueprint can ship a script instead of pushing one — see scripts: in @vectros-ai/blueprints. An apply is convergent (an unchanged source pushes nothing) and never deletes: removing a scripts: entry leaves every version in place, which is why this command group exists to remove one deliberately.

vectros assume — switch identity namespace values on your own token

Re-mints your own verb token with one or more identity.<namespace> values switched — the "org switcher" flow, named (like the platform endpoint it calls) after AWS STS AssumeRole. Only works if a role you currently hold explicitly grants assuming the requested value(s) via that role's own --assumable grant (checked live against your CURRENT roles, never a value frozen into your token at mint time).

vectros assume --context my-app --scope org:org_sales

| Flags | Notes | |---|---| | --context <c> (required), --scope <ns:value> (repeatable, at least one required) | Each --scope becomes one scope:<namespace> entry in the request. No artificial cap on how many namespaces you name — the platform requires a SINGLE role to jointly grant the whole combination and rejects an unsatisfiable one itself. |

Prints the re-minted access_token/expires_in, plus the resolved identity and allowedActions the platform returns alongside it — what you actually assumed, without decoding the token (accepts --json) — there is no --to-keyring here, since the result is an ephemeral bearer token with the same remaining lifetime as the verb token this command itself mints to make the call, not a durable secret the keyring is built to hold.

vectros access — bind principals to contexts

The binding layer between an identity and a context: access grant joins a principal to a context via a Role reference (--role) or inline actions (--actions) — never both. A later vectros key issue mints a credential that authenticates as the bound principal and can never exceed this binding.

vectros access grant --principal me --context my-app --role editor
vectros access grant --principal usr_<id> --context my-app --actions records:r,search:r \
  --scope org:<orgId>
vectros access list --context my-app        # who's in this context
vectros access list --principal me          # which contexts I'm bound to
vectros access get --principal me --context my-app
vectros access explain --principal me --context my-app
vectros access revoke --principal usr_<id> --context my-app

--principal accepts usr_<userId>, key_<keyId>, or the alias me (your own signed-in user, resolved from your login) — every access subcommand shares this grammar.

| Subcommand | Flags | Notes | |---|---|---| | grant | --principal <p>, --context <c> (required); exactly one of --role <roleId> (repeatable — 2+ compose additively) / --actions <csv>; --scope <ns:value> (repeatable), --identity-overrides <json>, --tenant test\|live | 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. | | revoke | --principal <p>, --context <c> (required); --tenant test\|live | | | list | Exactly one of --context <c> / --principal <p>; --tenant test\|live | --context lists the context's members; --principal lists every context that principal is bound to. | | get | --principal <p>, --context <c> (required); --tenant test\|live | | | explain | --principal <p>, --context <c> (required); --tenant test\|live | 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). |

grant/list/get/explain accept --json; revoke does not.

vectros join — enroll and keep the credential, in one step

join is a composition verb: everything it does already exists as a discrete command (access grant, key issue, keyring add) — join runs them in order against one context and records the outcome as a keyring entry, so vectros switch <alias> (and the MCP server / recall hook) can act as that principal from then on.

vectros join my-app --role editor                       # enroll "me" via a role
vectros join my-app --role case-handler --role hr-admin  # compose 2+ roles additively
vectros join my-app --actions records:cru,search:r --scope org:<orgId>
vectros join my-app --as usr_<id> --role editor --alias teammate

| Flag | Notes | |---|---| | --role <roleId> | Repeatable — 2+ compose additively, same semantics as access grant --role. XOR with --actions. | | --actions <csv> | Inline single-clause grant. XOR with --role. | | --as me\|usr_<id> (default me) | Which principal to enroll — me resolves to your own signed-in user. | | --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. | | --alias <name> | Keyring entry name (defaults to the context id). | | --activate / --no-activate | Force (or skip) making the freshly-stored key this machine's active identity. |

Re-joining an alias whose secret is still held locally is idempotent: it re-grants (a no-op upsert) and re-activates without churning the key. Only when the local secret is missing does it rotate to obtain a fresh one.

vectros key — the scoped API key (ssk_*) lifecycle

Mint, inspect, revoke, and rotate the scoped keys (ssk_*) that authenticate as a principal already bound to a context (vectros access grant first — a key can never exceed its profile). The raw secret is disclosed exactly once, on issue/rotate; if you lose it, key rotate mints a fresh one.

vectros key issue --principal usr_<id> --context my-app                  # print the secret (default)
vectros key issue --principal usr_<id> --context my-app --to-keyring     # store it instead
vectros key list                                                         # keys in the default context
vectros key list --all-contexts                                         # every key, every context (owner-only)
vectros key get <keyId>
vectros key revoke <keyId>
vectros key rotate --principal usr_<id> --context my-app                 # revoke + re-mint the same tuple

--principal here is usr_<userId> or a bare user id (the leading usr_ is optional and stripped if present) — narrower than access's grammar: there is no me alias on any key subcommand, and a key never authenticates as a key_* principal. Look your own id up first with vectros identity list --type user (or vectros whoami), or grant/issue via vectros join, which does resolve --as me.

A minted secret has exactly two destinations, and the flags for choosing one are shared by issue and rotate:

| Flag | Meaning | |---|---| | --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. | | --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. | | --alias <name> | Keyring entry name for --to-keyring (defaults to the context id). | | --no-activate | With --to-keyring, store the entry without making it the active identity. |

| Subcommand | Flags | Notes | |---|---|---| | 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. | | 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. | | 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. | | revoke <keyId> | --context <c>, --tenant test\|live | Soft-delete; ~5 minutes to take effect (authorizer cache). Same --context pinning caveat as get. | | 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. |

issue/rotate have no --json (use --format json instead); list/get accept --json, revoke does not.

vectros keyring — one home for your keys

A machine can hold several Vectros identities (one per context or principal). The keyring stores them as named entries plus one active pointer, so you can switch between them without re-issuing keys. vectros join <context> adds an entry and activates it; vectros key issue/key rotate add one when passed --to-keyring (which stores the new secret instead of printing it); vectros switch <alias> flips the active one.

vectros keyring list                 # the entries; * marks the active one
vectros switch <alias>               # make <alias> active  (= keyring use)
vectros keyring show                 # the active entry's fields + its key
vectros keyring show --format raw    # JUST the secret, for scripts (see below)
vectros keyring doctor               # is the keyring healthy? which keys are live?
vectros keyring prune                # drop entries whose stored secret is gone
vectros keyring remove <alias>       # drop one entry + its secret

These are local — no network, no auth. The index itself never holds a secret.

Where your keys are kept. In your OS credential store (Windows Credential Manager, Linux Secret Service) where one is available, so they are protected by your OS login. Where none is — a headless Linux box with no Secret Service, or a platform with no prebuilt for the optional native package — the CLI falls back to an encrypted file under ~/.vectros/secrets rather than failing. Be aware of what that fallback does and doesn't buy you: it protects a single stray .enc file, but its encryption key is derived from material stored alongside it, so anyone with a copy of your whole ~/.vectros directory can decrypt it. Treat that directory as a secret in its own right. vectros keyring doctor tells you which store you are on and why; VECTROS_KEYRING_BACKEND=file forces the fallback (existing keychain keys keep working and move back to the file as you use them).

On macOS the encrypted file is used for now. A Keychain item grants access to the application that created it and prompts for anything else — and the "application" here is the node binary running this CLI, which is not stable across installs. We expect that to put a password prompt in the startup path of any tool resolving a key unattended, which on a headless host nobody can answer. We have not yet verified it on real hardware, and would rather ship the honest fallback than a claim we haven't tested.

Upgrading from an older CLI needs no action: an existing encrypted secret moves into the credential store the first time that key is used.

Resolving a key from another tool. keyring show --format raw prints the bare secret and nothing else, which makes it a credential helper in the shape of git credential / docker-credential-* / aws credential_process:

export VECTROS_API_KEY=$(vectros keyring show --format raw)                      # active
export VECTROS_API_KEY=$(vectros keyring show --format raw --alias support-bot)  # a specific entry

@vectros-ai/mcp-server does this for you: leave VECTROS_API_KEY unset and it resolves the key from this keyring, so you don't paste a copy into its config. Exit codes: 0 secret printed, 2 no such (or no active) entry, 1 the entry exists but its secret can't be read.

When something's off, vectros keyring doctor is the first stop — it shows which entry is active, whether each secret is readable, which entries are live keys (the ones that act on real data), and which store is holding them, exiting non-zero when it finds something to fix.

vectros keyring prune drops entries whose secret is gone, but only where "gone" is knowable — that is, on the encrypted-file store. Entries kept in the OS credential store are never pruned. That store can't tell us "no such secret" apart from "couldn't read it right now", so a locked keychain would otherwise look like a keyring full of dead entries; deleting one would throw away the key id you need to revoke a key that may still be live. Use vectros keyring remove <alias> to drop one deliberately.

The scope gate (the trust boundary)

Blueprints are untrusted input (agent- or community-authored) and the CLI runs under an elevated ephemeral bridge token. So the trust boundary is this binary, not the blueprint: a hardcoded scope gate bounds every mint to a data-plane allowlist (records / schemas / search / documents / folders / inference / entities). Most control-plane scope (keys / app-contexts / users / billing / admin / namespaces / triggers) or a * wildcard hard-rejects — the CLI mints nothing and exits non-zero. There is intentionally no override flag.

Two narrow, deliberate exceptions. The first: profiles (which configures who has access to what — control-plane by nature) admits its r/u/d operations, because the platform enforces an unconditional check that a write to an AccessProfile can never grant more scope than the requesting credential already holds — those verbs can only disclose or redistribute access, never manufacture new reach. profiles:c (creating a profile for an arbitrary, caller-chosen principal name) still hard-rejects — except profiles:c:self, which the platform resolves from the credential's own identity at request time, never from anything the blueprint declares, and except a bare profiles:c on a clause that also declares the member-lifecycle capability (see Capabilities, below) — that combination is admitted because member-lifecycle already grants unbounded principal-creation reach, so gating bare profiles:c behind it adds no further technical bound but keeps the same explicit, fail-closed opt-in signal every other admission here requires. A literal-qualified profiles:c:usr_<id> naming another principal still hard-rejects regardless of member-lifecycle, same as every other control-plane scope.

The second exception: scripts:x:<script-name> — execute one named script — is admitted, so a blueprint that ships a script in its scripts: block can also grant permission to run it. Execution consults no data scope at all, and every write the script makes is still checked one by one against the caller's own permissions, so this widens what can be composed, never what data is reachable. Everything else on the resource still hard-rejects: a bare scripts:x, because it would mean "run every script in this context, including ones added later that your reviewer never saw"; scripts:x:*, because there is no wildcard form — the qualifier is compared to the script name exactly, so such a grant would store cleanly and then refuse every execution; and scripts:c, authoring, which stays a design-time admin surface. triggers is unchanged.

Capabilities (granted_capabilities) — a separate, equally-gated dimension

A blueprint's accessProfile and role clauses may also declare capabilities: [...] — named platform effects (like creating or removing an identity in your tenant) that reach across a partition boundary in a way no resource:ops verb can express. This rides a field entirely separate from allowedActions, and it has its own allowlist: member-lifecycle and delegate-principal-stamp are admitted today. Every other capability name — including ones the platform's own registry recognizes — is refused, the same fail-closed, deliberately-widen posture as the scope gate above. vectros blueprint validate/plan and bootstrap all report a rejected capability the same way they report a rejected scope: nothing mints, and the CLI exits non-zero.

Rate limits

blueprint-test provisions against the live API, so a large blueprint can brush the per-account per-minute rate limit. The CLI surfaces the 429 (with its Retry-After hint) directly — rerun after the window resets, or split a large blueprint. See the rate limits guide.

See also

Security & trust

Vectros enforces per-customer, fail-closed isolation and least-privilege scoped keys, with a tamper-evident audit and version history. Customer-facing surfaces are hardened through extensive adversarial security review. For the full trust posture, drawn plainly with its boundaries, see the compliance and trust guide.

License

Apache-2.0. See the LICENSE file.