EnfinitOSEnfinitOS
DevelopersOperator & brand
Production-ready scaffold

EnfinitOS CLI

Operator-facing terminal client — rights, offers, challenges, proof packs, consent, drills, audit export, pilots.

@enfinitos/cliSubstrate AllTypeScript
Install

Get the SDK

npm install -g @enfinitos/cli

About this status badge

Typed, tested, documented, and wired to the EnfinitOS platform endpoints that exist today. Vendor-side SDK integrations (Broadsign / VIOOH / DJI / Tizen / Alexa / Twilio / Stripe / etc.) land per-customer at pilot integration time — those bring the renderer/transport/exchange-specific code; the EnfinitOS half is ready.

README

The developer-facing documentation in full

The same README the SDK package ships with — rendered here at build time so what you read matches exactly what you install.

@enfinitos/cli

EnfinitOS operator-facing terminal CLI — rights, offers, challenges, proof packs, consent, drills, audit export, and pilot programmes, all driven from a single binary.

The CLI is the operator-side counterpart to the platform's HTTP API. Where the operator-web package gives a reviewer a point-and-click console, this package gives an SRE, a compliance officer, or a backend pipeline a scriptable terminal command. The two packages share the same Transport + error hierarchy under the hood — the CLI just renders results to stdout/stderr instead of to React state.

Why a CLI

Operators reach for a CLI in three situations the web UI cannot serve as efficiently:

  1. Pipelines. enfinitos rights ls --status ACTIVE --format json | jq plugs straight into shell or CI scripts. No headless browser.
  2. Reproducible incident response. When something is wrong at 3am, you want a flat, copy-pasteable command history. The CLI prints the structured request, the exit code, and the response on every line.
  3. Bulk ops. Issuing 200 rights from a CSV is one shell loop away once you have an idempotent enfinitos rights issue that takes a --scope flag — much harder via a web form.
Auth shape. Today the CLI consumes a bearer JWT (set via enfinitos login --token … or ENFINITOS_API_KEY). The enfinitos login command will grow an OAuth 2.0 Device Authorisation Grant flow in a follow-up release; the shape is documented in src/commands/misc/login.ts so the eventual rollout is a drop-in change for operators.

Install

The CLI is a workspace-internal package today. From a fresh clone:

pnpm install
pnpm --filter @enfinitos/cli build
pnpm --filter @enfinitos/cli exec enfinitos --help

A standalone publish is on the roadmap (npx @enfinitos/cli …, plus Homebrew + apt). Once published, install with:

# globally
npm install -g @enfinitos/cli
enfinitos --help

# or ad-hoc
npx @enfinitos/cli rights ls

Configuration

Persistent settings live in ~/.enfinitos/config.json. The file is created on first enfinitos config set …; it stores at most:

{
  "version": 1,
  "apiUrl": "https://api.enfinitos.example.com",
  "apiKey": "ey…",
  "orgId": "org_…",
  "defaultFormat": "table",
  "refreshToken": "rt_…",
  "tokenExpiresAt": "2026-06-01T12:00:00.000Z"
}

Every value can be overridden by an environment variable for a single invocation (handy in CI):

SettingEnv varFlag
apiUrlENFINITOS_API_URL--api-url <url>
apiKeyENFINITOS_API_KEY(no flag — secrets)
orgIdENFINITOS_ORG--org <id>
defaultFormatENFINITOS_FORMAT--format <fmt>

Three precedence rules apply: command-line flag > env var > config file > built-in default. The CLI never writes secrets to logs. Token-shaped fields are partially masked (abcd…wxyz) by enfinitos config show.

Command surface

CommandWhat it does
enfinitos login [--token JWT]Establish a session. Device-flow OAuth coming soon.
enfinitos logoutClear local credentials.
enfinitos whoamiPrint the calling identity.
enfinitos config set <key> <value>Persist a config field.
enfinitos config showShow the merged config (secrets masked).
enfinitos version / enfinitos --versionPrint CLI + protocol version (version-info is a legacy alias).
enfinitos doctorDiagnose local env (network, file perms, version skew).
enfinitos rights ls [--status …]List rights with cursor pagination.
enfinitos rights get <rightId>Show a single right with provenance.
enfinitos rights issue --basis … --scope …Issue a root right from a verified basis.
enfinitos rights suspend <rightId> --reason …Suspend a right (reversible).
enfinitos rights revoke <rightId> --reason …Revoke a right (terminal).
enfinitos offers propose --right … --target …Propose an offer to another org.
enfinitos offers list [--status …]List offers (inbound/outbound/all).
enfinitos offers accept <offerId>Accept an offer (derives a new right).
enfinitos offers reject <offerId> --reason …Reject an offer.
enfinitos offers withdraw <offerId>Withdraw a proposed offer.
enfinitos offers counter <offerId> --terms …Counter-propose new terms.
enfinitos challenges open <rightId> --reason …Open a challenge against a right.
enfinitos challenges resolve <id> --outcome …Resolve a challenge (upheld\dismissed).
enfinitos challenges withdraw <id>Withdraw an open challenge.
enfinitos proof get <campaignId>Fetch the proof pack for a campaign.
enfinitos proof verify <pack.json>Locally verify a proof pack (offline).
enfinitos proof export <campaignId> --out …Export a campaign's proof pack to disk.
enfinitos consent issue --holder … --kind …Record a fresh consent grant.
enfinitos consent revoke <consentId>Revoke a consent record.
enfinitos consent check --holder … --kind …Probe whether a consent is currently active.
enfinitos drill run <gateId>Run a regulatory fire-drill scenario.
enfinitos drill listList all available drills.
enfinitos audit export --from … --to …Export the audit ledger over a date range.
enfinitos audit verify <audit.json>Verify a Merkle-included audit bundle.
enfinitos pilot statusShow pilot-programme enrolment.
enfinitos pilot enrol <orgId>Enrol an org in the pilot programme.

Every command supports --format table|json|yaml and --yes for unattended runs.

Output formats

The CLI picks a default format based on whether stdout is a TTY:

  • On a terminal: table (human-readable, ANSI-coloured when the terminal supports it; honours NO_COLOR and FORCE_COLOR).
  • Piped or redirected: json (so enfinitos rights ls | jq … works without flags).

Force a format with --format json, --format yaml, or --format table. Set ENFINITOS_FORMAT=json to make every CLI call in a session emit JSON without sprinkling flags.

The structured-error envelope is the same in all formats — a JSON object with ok: false, code, message, optional hint, and optional reasons. Table mode renders this prose-style; json/yaml modes emit it verbatim.

Exit codes

The CLI uses small, stable exit codes so shell pipelines can branch on them. They are exported from @enfinitos/cli's programmatic entry point as the EXIT constant:

CodeNameMeaning
0OKSuccess.
1GENERICUnclassified error.
2USAGEBad command-line invocation.
3AUTHAuthentication / authorisation failure.
4NETWORKCouldn't reach the platform.
5SERVERThe platform returned a 5xx envelope.
6VERIFICATIONA proof verify or audit verify failed.
7NOT_FOUNDThe target resource doesn't exist.
8CONFLICTThe operation conflicts with current state.

Architecture

+--------------------------+
|  bin/cli.ts (shebang)    |
+-----------+--------------+
            |
            v
+-----------+---------------------------+
|  runner.ts                            |
|    - commander tree (one cmd / file)  |
|    - context builder                  |
|    - error classifier + formatter     |
+-----+----------+-------------------+--+
      |          |                   |
      v          v                   v
+----------+ +----------+ +-----------------+
| commands/| | output.ts| |  transport.ts   |
|  rights  | |  - table | |  - HTTP client  |
|  offers  | |  - json  | |  - retries      |
|  proof   | |  - yaml  | |  - idempotency  |
|  consent | +----------+ |  - envelope     |
|  ...     |              |    parser       |
+----------+              +-----------------+
      |                          |
      v                          v
+---------------+        +-----------------+
| confirm.ts    |        |  config.ts      |
|  (enquirer)   |        |  (~/.enfinitos/ |
+---------------+        |    config.json) |
                         +-----------------+

Module boundaries

  • cli.ts is the bin entry — shebang, process.argv, process.exit. Nothing else.
  • runner.ts is the testable inside: it takes argv + env + streams + a transport factory and runs to completion. Tests exercise it end-to-end without touching the real network.
  • commands/<group>/<verb>.ts is one file per command. Each exports a handle<Group><Verb>(ctx, args) function — the runner is the only caller. The command file owns its input validation, HTTP call (via ctx.transport()), and output formatting.
  • transport.ts is a thin REST client with retry-with-backoff, idempotency-key generation, structured error mapping, and JSON envelope parsing. It does not depend on any single command.
  • config.ts owns ~/.enfinitos/config.json — JSON Schema-style validation, atomic writes, and the env-merge precedence rules.
  • output.ts is the table/json/yaml dispatcher plus the small ANSI-aware table renderer. Lives outside any command file so all groups render the same way.
  • errors.ts is the typed error hierarchy + the classify() function that turns any thrown value into a structured envelope plus an exit code.

Programmatic API

The CLI exports a small programmatic surface from @enfinitos/cli so other packages (notably operator-web) can re-use the same Transport, config loader, and proof-pack verifier without spawning a subprocess:

import {
  loadConfig,
  effectiveConfig,
  transportFromConfig,
  verifyProofPack,
  EXIT,
} from "@enfinitos/cli";

const config = effectiveConfig(await loadConfig(), process.env);
const transport = transportFromConfig(config);
const res = await transport.request({ method: "GET", path: "/v1/rights/123" });

const verification = verifyProofPack(JSON.parse(await fs.readFile("pack.json")));
if (!verification.ok) process.exit(EXIT.VERIFICATION);

The run() entry point is also exported, so embedding the CLI in a different binary is one function call:

import { run } from "@enfinitos/cli";
const code = await run({ argv: process.argv.slice(2), returnExitCode: true });
process.exit(code);

Getting started

# 1. point the CLI at your platform
enfinitos config set apiUrl https://api.enfinitos.example.com

# 2. authenticate (interactive device flow coming soon)
enfinitos login --token "$ENFINITOS_API_KEY"

# 3. sanity-check the local env
enfinitos doctor

# 4. real work
enfinitos rights ls --status ACTIVE --substrate DOOH
enfinitos rights get rgt_abc123
enfinitos rights issue --basis bas_xyz --scope ./scope.json

# 5. proof export + offline verification
enfinitos proof get cmp_2026q1 --out cmp_2026q1.pack.json
enfinitos proof verify cmp_2026q1.pack.json

Testing

Tests run under Vitest and live in src/__tests__/. The CLI is tested end-to-end at the runner boundary — every test passes argv + env + an injected fake Transport into run() and asserts on captured stdout/stderr.

pnpm --filter @enfinitos/cli test
pnpm --filter @enfinitos/cli typecheck

The fake Transport (__tests__/_fakeTransport.ts) supports scripted request/response sequences with per-call assertions on the outgoing path, method, body, and idempotency key. The capture helpers (__tests__/_capture.ts) snapshot stdout/stderr so each test asserts on the exact bytes the operator would have seen.

Example workflows

Bread-and-butter operator recipes — each one shells out to a single pipeline. All emit --format json so they slot into automated jobs.

Find all open challenges and resolve them

# 1) list every open challenge (cursor-paginated)
enfinitos challenges list --status open --format json \
  | jq -r '.data.items[].id' \
  | while read -r cid; do
      # 2) inspect, then resolve. Substitute upheld/dismissed as
      #    your judgement requires.
      enfinitos challenges resolve "$cid" --outcome dismissed --yes
    done

Export the last 30 days of audit

FROM=$(date -u -d '30 days ago' +%Y-%m-%d)
TO=$(date -u +%Y-%m-%d)
enfinitos audit export --from "$FROM" --to "$TO" --out audit.json
# Optional: verify the bundle locally before archiving.
enfinitos audit verify audit.json

Run all CRITICAL drills and write evidence

enfinitos drill list --format json \
  | jq -r '.data.items[] | select(.priority=="CRITICAL") | .gateId' \
  | while read -r gate; do
      enfinitos drill run "$gate" \
        --evidence-out "evidence/${gate}-$(date -u +%Y%m%dT%H%M%SZ).json" \
        --yes
    done

Bulk issue rights from a CSV

# columns: basis_id,scope_json_path
while IFS=, read -r basis scope; do
  enfinitos rights issue --basis "$basis" --scope "$scope" --format json \
    | jq -r '.data.id' >> issued-rights.txt
done < rights-to-issue.csv

Watch an inbox for new offers

while sleep 60; do
  enfinitos offers list --status open --direction inbound --format json \
    | jq -c '.data.items[] | {id, terms, target}'
done

Roadmap

  • OAuth 2.0 Device Authorisation Grant for enfinitos login (RFC 8628). Today the command accepts a token via --token; the fully-fledged flow lands in the next milestone.
  • Standalone publish. The bin/ entry is wired today; we still need release tooling to ship per-platform binaries (npm, Homebrew, Linux package archives).
  • enfinitos audit verify for differential bundles. Today the command verifies a self-contained bundle; the differential bundle format lands when the audit module's snapshot tooling does.
  • Tab completion. Commander supports completion generation for bash/zsh/fish; we ship the generator + an install hook.
API reference

Hit the HTTP surface directly

The EnfinitOS CLI is a thin client over the same governed HTTP API every other SDK calls. The full OpenAPI 3.1 reference lives on the docs site, published alongside the April 2027 platform launch.

Sandbox

Run this SDK against a real tenant

The browser demo at enfinitos.com/sandbox runs today against a shared synthetic tenant. The dedicated developer sandbox — your own persistent tenant, API keys, full HTTP-contract coverage — opens ahead of the April 2027 platform launch.