Docs /cli

izri — Command-line interface

Single-binary CLI for Izri. Talks to the Izri API over tRPC and is the foundation every other integration (GitHub Action, VS Code, JetBrains) shells out to.

Install

# One-shot run (no install)
npx @izri/cli@latest --version

# Global install
npm install -g @izri/cli
izri --version

The CLI requires Node.js 18 or newer.

Configure

The CLI reads two environment variables:

Variable Required Example
IZRI_API_URL yes https://api.izri.example.com
IZRI_API_TOKEN yes izri_xxxxxxxxxxxxxxxx
IZRI_PROJECT_ID no 01HK0... (ULID)

Generate the token from the dashboard. The CLI rejects any token that doesn't start with izri_ — the API short-circuits to bearer-token validation only for that prefix, so anything else would just produce an opaque 401 at request time.

IZRI_PROJECT_ID is optional: when unset and you're running inside a git checkout with a GitHub origin remote, the CLI auto-resolves the project by matching owner/repo against listAccessibleProjects (same comparison the GitHub-App webhook uses). Pass --project <id> to override.

Commands

izri run

Trigger a manual test run for the current repo and (optionally) wait for it to finish.

izri run                              # fire-and-forget
izri run --watch                      # poll until terminal
izri run --project 01HK...            # explicit project

Resolution order: --project <id>IZRI_PROJECT_ID → auto-match by origin remote. The current HEAD SHA and branch are sent along automatically.

--watch polls every 3 seconds until the run reaches one of PASSED/FAILED/ERROR/CANCELLED, with a 30-minute hard cap. The CLI exits non-zero on any terminal status except PASSED so CI scripts can branch on it.

izri status

Print the latest delta report for the current HEAD.

izri status              # formatted summary
izri status --json       # raw JSON for piping

The output renders the umbrella verdict (izri/quality) and each per-signal child (izri/scope, izri/tests, izri/hallucination, izri/visual), including hard-findings count and confidence.

Exits non-zero when no report exists yet for the current HEAD — usually because the runner hasn't finished. Re-run after izri run --watch.

izri check-scope

Run the scope analyzer on the local diff against a base ref.

izri check-scope                      # vs origin/main, deterministic only
izri check-scope --base origin/dev    # different base
izri check-scope --semantic           # opt into the LLM layer (tier-gated)
izri check-scope --json               # raw JSON

The CLI builds the files[] payload from git diff --name-status -M + git diff --numstat -M, merges on the post-rename filename, and sends it to analysis.checkScope. The procedure caps files[] at 500; the CLI pre-flights that and tells you to narrow --base instead of returning a Zod error.

Exits non-zero only when there's at least one error-severity finding. Warnings and info-level nudges stay at exit 0.

Exit codes

All commands honour the same convention:

Exit Meaning
0 Success (run passed, scope clean, status fetched).
1 Hard failure: missing env, run not passed, error findings, no report.

This lets you gate any pipeline with izri run --watch && deploy.sh or izri check-scope || exit 1 without parsing output.

Use in CI

GitHub Actions example:

- name: Izri scope check
  env:
    IZRI_API_URL:   ${{ vars.IZRI_API_URL }}
    IZRI_API_TOKEN: ${{ secrets.IZRI_API_TOKEN }}
  run: |
    npx @izri/cli@latest check-scope --base origin/${{ github.base_ref }}

- name: Izri test run
  env:
    IZRI_API_URL:   ${{ vars.IZRI_API_URL }}
    IZRI_API_TOKEN: ${{ secrets.IZRI_API_TOKEN }}
  run: |
    npx @izri/cli@latest run --watch

The GitHub Action (legendify-dev/izri-action@v1, issue #239) wraps these calls so you don't have to manage the npx boilerplate yourself.

Troubleshooting

IZRI_API_URL is required or IZRI_API_TOKEN is required Set both env vars before invoking the CLI. Most shells pick them up from .env files automatically when paired with direnv or dotenvx.

IZRI_API_TOKEN must start with izri_`` You're passing a token from a different system (e.g. a GitHub PAT). Generate an Izri-native token from the dashboard.

No accessible project matches "owner/repo" Either connect the repo via the dashboard first, or pass --project <id>. Run izri --help to confirm the binary version, and verify your token's organization has the project linked.

Multiple projects match "owner/repo" Two or more projects in your org point at the same repository. The CLI lists their IDs; pass --project <id> to disambiguate. (The GitHub-App webhook silently picks oldest in this case; the CLI is interactive so it asks.)

Diff against origin/main contains 1234 files — the analyzer caps at 500. Choose a closer base with --base origin/<branch> or --base HEAD~50. The 500-file cap matches the server's hard limit.

git is not installed or not on PATH Install git. The CLI shells out for diff + ref resolution; there is no pure-JS fallback.

izri run against a workspace-heavy local repo fails with SUT exited with code 1 before ready The runner clones the repo into a fresh tmpdir per run. That tmpdir has no node_modules, no built packages/*/lib/ outputs, and no .env.shared (which is .gitignore-d). If the project's .izri/izri.yml → runtime.start calls pnpm dev:*, pnpm db:migrate, or any step that depends on a built workspace, it will exit immediately before the ready probe fires.

This is the pre-warm contract (#314): bring-up work like pnpm install, pnpm build:packages, and pnpm env:generate must happen before the runner spawns the SUT. Three options, in increasing order of effort:

  1. Hosted runner — the runner image bakes in pre-built dependencies and a pre-warmed .env.shared. No work on the project side; the default deploy path. This is what staging / prod do today.

  2. Local laptop runner — run pnpm install && pnpm build:packages && pnpm env:generate once in the repo before izri run. The runner reuses the repo's existing node_modules and packages/*/lib/ (only the .env.shared rewrite + db:migrate happen inside the tmpdir clone). See deploy/README.md → "Staging runner: runs on the laptop".

  3. izri check-scope only — for the "I just want to see what changed" use case, izri check-scope runs the deterministic scope analyzer against the local diff without any SUT bring-up. No pre-warm required.

If .izri/izri.yml does declare a non-trivial start:, document the pre-warm steps in the repo's README so first-time contributors aren't stuck on the cryptic exit-code-1.

Where the CLI fits

The CLI is the foundation of the integration ladder (issue #238). The GitHub Action (#239), VS Code extension (#248), and JetBrains plugin (#249) all shell out to izri. The MCP server (@izri/mcp) is a sibling — same auth shape, same IZRI_API_* env vars, but speaks Model Context Protocol instead of taking shell arguments.

Source

apps/cli/ in the legendify-dev/izri monorepo. Bug reports and contributions welcome on GitHub.

Reading this with an agent? /docs/cli.md serves the raw markdown.

All docs