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

@corti/cli

v1.3.1

Published

Corti CLI — wire Corti as an OpenAI-compatible provider into terminal AI coding agents (OpenCode, Pi, Crush, ForgeCode, Codex CLI) and more.

Downloads

3,782

Readme

Corti CLI

npm License: MIT

Wire Corti as an OpenAI-compatible provider into your terminal AI coding agent.

npx @corti/cli@latest models init

The command prompts you to paste an .env block from the Corti Console (Developer quickstart → Copy all as .env variables) containing CORTI_TENANT_NAME, CORTI_CLIENT_ID, and CORTI_CLIENT_SECRET. It constructs the Bearer token (base64 of <tenant>:client_credentials:<client_id>:<client_secret>) and writes a corti provider entry into each tool you select. Pass -i, --interactive to enter credentials field-by-field instead.

| Tool | File written | Format | Notes | | ------------- | --------------------------------------------------------- | ------ | ----- | | OpenCode | opencode.json (or ~/.config/opencode/opencode.json) | JSON | Full provider entry — CORTI_BEARER referenced via {env:…} | | Pi | .pi/agent/models.json (or ~/.pi/agent/models.json) | JSON | providers.corti entry, api: "openai-completions", apiKey: "CORTI_BEARER" (bare env name) | | ZCode | ~/.zcode/v2/config.json (global only) | JSON | provider.corti entry with the bearer embedded (ZCode has no env indirection); restart ZCode after, see below | | Crush | .crush.json (or ~/.config/crush/crush.json) | JSON | Full provider entry — $CORTI_BEARER substitution | | ForgeCode | .forge.toml + .forge/.credentials.json | TOML+JSON | [[providers]] entry (selected by default); bearer embedded in sidecar .credentials.json (no env indirection) |

It also writes a managed # >>> corti-cli >>> block to .env with:

  • CORTI_CLIENT_ID
  • CORTI_CLIENT_SECRET
  • CORTI_TENANT
  • CORTI_BEARER (base64 of <tenant>:client_credentials:<client_id>:<client_secret>, used by the tools)
  • CORTI_BASE_URL

Each tool config references CORTI_BEARER rather than embedding the secret in the JSON/TOML file, so the credential stays out of version control. Two exceptions embed the bearer because the tool offers no env indirection: ForgeCode's .credentials.json sidecar and ZCode's ~/.zcode/v2/config.json (both global/home-dir files, never in your repo).

Under global scope it also offers to add a managed block to your shell rc (~/.zshrc, ~/.bashrc / ~/.bash_profile, or fish config, resolved from $SHELL) so new shells load the CORTI_* variables from ~/.env automatically. The block exports only CORTI_* lines — it never sources the whole file and never contains secret values. It shows up as its own row on the plan screen (press s there to skip it), and the file is backed up before its first edit. Project scope never touches your shell rc.

Usage

corti models init [options]

  --client-id <id>          Corti client ID     (else $CORTI_CLIENT_ID,     else prompted)
  --client-secret <secret>  Corti client secret (else $CORTI_CLIENT_SECRET, else prompted)
  --tenant-name <name>      Corti tenant name   (else $CORTI_TENANT,        else prompted; default base)
  --base-url <url>          Corti base URL      (else $CORTI_BASE_URL,      else prompted; default https://ai.eu.corti.app/v1)
  --region <region>         Corti region, e.g. eu (else $CORTI_ENVIRONMENT) — sets the base URL
  --model <id>              Default model       (else $CORTI_MODEL,  else picked from /models, else prompted)
  --tools <list>            Comma-separated subset: opencode,pi,zcode,crush,forgecode (else $CORTI_TOOLS)
  --scope <project|global>  Where to write configs (else $CORTI_SCOPE; default global)
  --shell-rc                Wire your shell rc to load CORTI_* into new shells (headless is opt-in; interactive default)
  --no-shell-rc             Never touch your shell rc file
  -y, --yes                 Non-interactive; fail if required values are missing
  -i, --interactive         Prompt for credentials field-by-field instead of pasting a .env block
  --fresh                   Ignore credential/region CORTI_* env vars (shell-exported or from ~/.env/./.env); use to re-provision to a different region/tenant (a harvested ~/.env block otherwise re-uses the prior region's credentials)
  --dry-run                 Print file changes without writing them
  --no-verify               Skip the /models probe (--yes skips prompts, not verification)
  --json                    Emit exactly one JSON document on stdout; all text to stderr (else $CORTI_JSON)
  --no-color                Disable color output (NO_COLOR and TERM=dumb are also honored)
  --theme <mode>            Palette for light/dark terminals (else $CORTI_THEME; default auto — probes the background)

Resolution order per value: flag → env var → interactive prompt. With --yes, missing required values fail fast (exit 2), the /models probe still runs (--no-verify opts out), and Ink/React are never loaded.

When a newer version is published, the wizard shows a one-line update row at startup; headless runs print the notice on stderr after the command finishes (never in CI, --json runs, or piped output; set NO_UPDATE_NOTIFIER=1 to opt out). Prefer npx @corti/cli@latest — it always runs the newest release.

Exit codes

| Code | Meaning | E_* codes | | ---- | ------- | ----------- | | 0 | Full success (including "everything already up to date" and clean --dry-run) | E_NO_MODELS, E_MODEL_UNKNOWN (warnings; the run continues) | | 1 | Internal error, or plan declined | E_DECLINED, E_INTERNAL, E_DEVICE_DENIED | | 2 | Usage/validation error (bad flag, missing flag under --yes, no TTY without --yes) | E_USAGE | | 3 | Auth failure (HTTP 401/403 from the probe) | E_AUTH | | 4 | Network/API failure (DNS, timeout, TLS, connection refused, 5xx) | E_API, E_DNS, E_TIMEOUT, E_TLS, E_NET, E_DEVICE, E_DEVICE_EXPIRED | | 5 | Run failed with nothing modified (total write failure, backup gate, stale plan) | E_PLAN, E_WRITE, E_BACKUP, E_STALE_PLAN | | 6 | Partial success (some tools configured, some failed; includes env-write failure) | E_PARTIAL, E_ENV_WRITE | | 130 | Interrupted (SIGINT/SIGTERM) | — |

The E_* code is stable, printed with every failure, and carried in --json.

--json

--json puts exactly one versioned JSON document on stdout — on success and failure — with all human-readable progress on stderr. Combine with --yes (writes) or --dry-run (preview):

corti models init --yes --json --tools opencode --model corti-1 \
  --client-id … --client-secret …          # writes, then emits the result document
corti models init --json --dry-run --tools opencode --model corti-1 \
  --client-id … --client-secret …          # previews with would-* actions

The document carries ok, exitCode, resolved (each value with its provenance), probe, per-file results (with diffs — secret-bearing files are diffstat-only, never a diff body), env, backupDir, warnings, and error: {code, message} | null. Secrets never appear in it.

models list

corti models list probes GET {base-url}/models and prints the available model ids:

corti models list --bearer $CORTI_BEARER --base-url https://ai.eu.corti.app/v1
corti-s1  # ctx 524,288 · cost in 2 out 8
corti-s1-mini  # ctx 262,144 · cost in 1 out 4
corti-s1-tiny  # ctx 32,768

Each line is the bare model id first (so awk/cut pipelines stay intact), then # and any metadata the API provides — context window and cost per million tokens, only the keys that are present. Models with no metadata print as a bare id.

With --json the document adds a modelDetails sibling, index-for-index with models, carrying each model's metadata (capabilities, cost, effort, maxInputTokens) verbatim from the API:

corti models list --json --bearer $CORTI_BEARER --base-url https://ai.eu.corti.app/v1
{
  "$schema": "https://corti.app/schemas/cli-list-models.v1.json",
  "ok": true,
  "exitCode": 0,
  "models": ["corti-s1", "corti-s1-mini", "corti-s1-tiny"],
  "count": 3,
  "modelDetails": [
    { "id": "corti-s1", "maxInputTokens": 524288, "capabilities": { "reasoning": true, "image_input": false, "tool_call": true, "temperature": true }, "cost": { "input": 2, "output": 8, "cacheRead": 0.2 }, "effort": { "supported": true, "levels": ["high", "max"] } },
    { "id": "corti-s1-mini", "maxInputTokens": 262144, "capabilities": { "image_input": true } },
    { "id": "corti-s1-tiny" }
  ],
  "probe": { "status": "ok", "httpStatus": 200, "latencyMs": 42 },
  "error": null
}

modelDetails is always present (never omitted, even when empty), so a consumer can distinguish "old deployment, no metadata support" from "new deployment, this tenant's models happen to have none" by the key's presence rather than its contents. The models array stays string[] — adding modelDetails is an additive change, $schema stays v1.

Backups

Before modifying any existing file, the CLI copies it to ~/.local/share/corti/backups/<timestamp>/ (mode 0700, manifest included) and verifies the file on disk still matches what was planned — a mismatch aborts the whole run with nothing written.

What it does

  1. Builds the Bearer token as base64 of <tenant>:client_credentials:<client_id>:<client_secret>.
  2. Probes GET {base-url}/models with that Bearer. The response carries per-model metadata — context window, cost, capabilities (reasoning, image input, tool calling), and reasoning-effort levels — which the CLI writes into each tool's config. A field the API omits is omitted from the config too; the tool falls back to its own default.
    • On success, lets you pick a model.
    • On failure (auth or network), surfaces the status in the side panel and falls back to a typed model id.
  3. Detects which tools are installed (best-effort) and pre-selects them.
  4. Asks before writing. Each writer merges into existing config rather than overwriting unrelated keys.
  5. Writes are atomic (temp file + rename) and idempotent — a second run is a no-op.

After init

If you accepted the shell rc block (global scope), CORTI_* loads automatically in shells you open from now on — the shell you ran init in is not one of them, so open a new terminal. For that shell — or if you skipped the block or used project scope — load .env manually so CORTI_BEARER is available when the coding tool starts:

set -a; . .env; set +a

Then run the tool:

opencode --model corti/<model>     # OpenCode
pi                                 # Pi           (select provider 'corti')
crush                              # Crush        (select provider 'corti')
forge                              # ForgeCode

ZCode is a desktop app: restart it after init (it loads providers at launch), then pick the Corti provider in chat.

Development

pnpm install
pnpm build
pnpm test
node ./dist/cli.js models init --dry-run \
  --client-id test-client-id \
  --client-secret test-client-secret \
  --base-url https://ai.eu.corti.app/v1 \
  --model corti-1 \
  --tools opencode,pi,zcode,crush,forgecode

Releasing

Releases are automated — merging to main runs semantic-release, which derives the version from the conventional commit message and publishes to npm via OIDC. Since PRs are squash-merged, that commit message is the PR title.

  • fix: → patch · feat: → minor · feat!: / BREAKING CHANGE: → major
  • Prerelease: push a suffixed tag — git tag v1.2.0-rc.1 && git push origin v1.2.0-rc.1 (publishes under the rc dist-tag, never latest)
  • Don't hand-push a bare vX.Y.Z tag — CI rejects it; stable goes through main

License

MIT