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:
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.Local laptop runner — run
pnpm install && pnpm build:packages && pnpm env:generateonce in the repo beforeizri run. The runner reuses the repo's existingnode_modulesandpackages/*/lib/(only the .env.shared rewrite +db:migratehappen inside the tmpdir clone). Seedeploy/README.md → "Staging runner: runs on the laptop".izri check-scopeonly — for the "I just want to see what changed" use case,izri check-scoperuns 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.