@cruxy/cli
v1.17.3
Published
an agentic coding CLI
Readme
@cruxy/cli
An agentic coding CLI that plans, edits, tests, and ships from your terminal — powered by the Cruxy API.
Cruxy is a terminal-first coding agent that works directly in your repository — reading code, editing files, running commands, and verifying its own work, with an approval gate before anything touches disk.
Install
npm install -g @cruxy/cli
cruxy run "explain this codebase" # first run guides you through setupOn your first interactive run with no key, cruxy walks you through getting one,
validates it, and saves it to ~/.cruxy (owner-only) — no env var needed. You
can also run setup on demand:
cruxy login # set or replace your API key
cruxy init # key + a project CRUXY.md + a first run
export CRUXY_API_KEY=cxy_live_... # …or just use an env var (always wins)Features
- Onboarding — a guided first run (
cruxy login/cruxy initon demand): get a key, validate it live, and save it to~/.cruxy/credentials.json(0600) — never toconfig.json, the project, or env files. Keys resolve env → credentials store. Non-interactive runs stay fail-loud (CRUXY_E_AUTH_MISSING_KEY), never blocking on input. - Tools —
read_file,write_file,edit_file,glob,list_files,grep_files,run_command,git_status,apply_patch,search_codebase,list_skills,load_skill,create_pull_request. - Pull requests —
cruxy pr(and thecreate_pull_requesttool) turn the current changes into a PR: feature branch → conventional commit → push → open the PR, with a generated title/body, all behind the approval gate. GitHub ships first behind a swappableForgeProvider; the token is read fromGITHUB_TOKEN/GH_TOKEN/gh auth token(never stored). It never commits or pushes on a protected branch, never force-pushes, and never bypasses the commit/push hooks. - Codebase index — a local, incremental semantic index (
cruxy index) behind thesearch_codebasetool. Embeddings run on-device via fastembed (ONNX, bge-small-en-v1.5), an opt-in runtime installed withcruxy index --install; the store is Node's built-in SQLite at.cruxy/index.db(Node ≥22.13). Only changed files are re-embedded;.gitignore/.cruxyignoreare respected and secrets (.env*, keys) are never indexed. - Skills — reusable, task-specific instructions in a
SKILL.md(frontmatter + markdown), discovered vialist_skillsand pulled on demand withload_skill(progressive disclosure: only name + description are in context until a skill is loaded). Layered project > user > builtin, strictly validated, and never auto-executed. - Agent — streaming output, multi-turn interactive sessions, context
compaction, and awareness of git state and project instructions (
CRUXY.md). - Streaming render — flicker-free live output: a single in-place status line
(spinner while the model thinks / tools run), append-only committed text,
syntax-highlighted code fences, tool-call notes, and diffs drawn by the same
renderer as the approval prompt. Degrades cleanly: piped/CI output is plain
append-only text with zero ANSI (chrome on stderr),
NO_COLORdrops color, andCRUXY_NO_SPINNER=1stills the animation. - Plan mode (opt-in:
cruxy run --plan,/plan, oragent.planMode) — the agent proposes a structured, step-by-step plan; you approve it once, then it executes with live per-step status. Approving consents to the shape of the work — every action still passes the approval gate, and destructive/ungrantable actions always re-confirm even after approval. - Safety — a single approval gate with diff previews that fails closed; read-only tools never prompt; file access is confined to the project root; shell commands run bounded.
Interactive slash commands: /help, /clear, /compact, /reload, /exit.
For unattended runs, pass --yes to approve every action.
Codebase search
cruxy index --install # one-time: install the local embedding runtime
cruxy index # build / refresh the local index (.cruxy/index.db)
cruxy index --status # show what's indexed, and the runtime
cruxy index --force # re-embed every file
cruxy index --uninstall # remove the runtime and the model cacheSemantic search needs a local embedding runtime (fastembed + onnxruntime),
which is not part of the default install, so installing cruxy builds and
downloads no native code. cruxy index --install fetches it into
~/.cruxy/runtime/embedder: about 85 MB from registry.npmjs.org (or your
configured npm registry), every package checked against a pinned sha512, no
install scripts run, and only your platform's binaries kept (about 50–60 MB
on disk). It tells you all that before downloading; a plain cruxy index
offers to install it on a terminal and never installs it unasked. The agent
can't install it: without it, search_codebase says so in one line and the
agent uses grep_files. Supported: macOS (arm64, x64), Linux x64 (glibc),
Windows x64.
The index refreshes itself lazily the first time the agent calls
search_codebase, so it works even without running cruxy index first. The
bge-small embedding model (about 67 MB) downloads once from huggingface.co,
each file checked against a pinned sha256, and is cached in ~/.cruxy/models.
A persistent index needs Node 22.13 or newer; on older Node, search uses an
in-memory index rebuilt each session.
Skills
cruxy skills # list the resolved skill catalog
cruxy skills --status # show source directories and validation errorsAdd a skill by creating <name>/SKILL.md under .cruxy/skills/ (project) or
~/.cruxy/skills/ (user); shipped builtins are the lowest layer. See the
built-in using-skills skill for the authoring rules.
Pull requests
export GITHUB_TOKEN=ghp_... # or GH_TOKEN, or be logged in with `gh`
cruxy pr # generate + open a PR for the current changes
cruxy pr --base develop # target a specific base branch
cruxy pr --title "fix(cli): …" # override the generated title
cruxy pr --draft # open as a draftThe whole plan — branch, commit, and PR title/body — is shown for approval
before anything runs. On a protected branch (main/master/configured via
git.protectedBranches) cruxy branches off first; the base defaults to
git.defaultBase, then the repo's default branch, then main.
Checkpoints & rollback
Before an agent run's first file mutation, cruxy snapshots the working tree
(tracked + untracked non-ignored files; gitignored paths and the secrets
denylist are never captured). cruxy rollback undoes the whole run — creates,
edits, deletes — in one operation:
cruxy checkpoint list # saved checkpoints, newest first
cruxy rollback # restore the most recent checkpoint
cruxy rollback <id> # restore a specific oneRollback is destructive, so it previews exactly what will change (including
anything that changed outside the run — surfaced, never silently clobbered)
and always asks for approval; it cannot be session-granted and refuses to run
non-interactively (CRUXY_E_ROLLBACK_APPROVAL_REQUIRED).
In a git repo, snapshots go into the git object database via a temporary index
— HEAD, your index, the stash, and every ref are untouched, and nothing shows
up in git status. Outside a repo, a content-addressed shadow copy under
.cruxy/checkpoints/ is used. Retention is bounded (checkpoint.retention,
default 10; disable with checkpoint.enabled = false).
Boundary: checkpoints cover working-tree files only. Commits, pushes, and PRs made during a run are never undone — the rollback preview says so.
Terminal & accessibility
Cruxy probes the terminal once per run and reduces it to a handful of capabilities — color, unicode, cursor control, motion, screen-reader mode, size — which every rendered surface resolves a theme from. That is why degradation is consistent: there is one place that decides, not one decision per panel.
Each axis has an environment override, and the axes are independent. Turning
color off does not change which glyphs are used; asking for ASCII glyphs does not
turn color off. A NO_COLOR terminal still gets ✓, a colored CRUXY_ASCII
terminal gets a green [ok].
Color
| Variable | Effect |
| ------------- | ---------------------------------------------------------------- |
| NO_COLOR | No ANSI color anywhere. Not muted color — zero escape bytes. |
| FORCE_COLOR | Keep color even when stdout is a pipe or a file. |
Color is on by default only when the stream is a TTY. TERM=dumb forces it off
regardless.
Glyphs
| Variable | Effect |
| ------------- | ----------------------------------------------------------------------------------------- |
| CRUXY_ASCII | Use the ASCII glyph table: [ok] [x] [ ] -> ... in place of ✓ ✗ ○ → …. |
Also implied by TERM=dumb. Piped output keeps unicode — writing ✓ to a file
is fine, and forcing ASCII there would change long-standing behaviour for
everything that already parses cruxy's output.
Screen readers
| Variable | Effect |
| --------------------- | ---------------------------------------------- |
| CRUXY_SCREEN_READER | Screen-reader mode. |
| ACCESSIBLE | The same thing, under the ecosystem-wide name. |
In this mode:
- status glyphs become words —
✓ read_fileis announced asdone read_file, and◐/○becomeworking/pending, so a status mark is never read out as bare punctuation; - decorative marks collapse to nothing — the
↻cache marker, the text cursor bar, and the cells of a progress bar all render as empty, because a run of block characters announces as noise and the percentage beside it already carries the whole meaning; - animation is off — screen-reader mode implies reduced motion, since a spinner has no live region to animate into.
It is opt-in and never inferred. Cruxy has no way to detect a screen reader, and inferring one from a non-TTY stdout would reword every piped and CI run in the world:
export CRUXY_SCREEN_READER=1Motion
| Variable | Effect |
| ---------------------- | ---------------------------------------------- |
| NO_MOTION | The ecosystem-wide reduced-motion signal. |
| CRUXY_REDUCED_MOTION | The explicit cruxy knob. |
| CRUXY_NO_SPINNER | Kept as an alias; flows through the same axis. |
Any of them disables the frame clock outright — nothing is scheduled and no frame ever ticks, so every animation collapses to its static end-state, drawn once. Reduced motion is also implied by screen-reader mode.
The alternate screen
| Variable | Effect |
| --------------------- | ------------------------------------------------------------------------------ |
| CRUXY_NO_ALT_SCREEN | Keep the full-screen TUI in the normal buffer instead of the alternate screen. |
| CRUXY_NO_MOUSE | Leave the mouse to the terminal — the wheel scrolls the window, not the pane. |
By default the TUI runs on the terminal's alternate screen — the second buffer
less and vim use. Leaving it restores the normal buffer byte for byte, so
the prompt you started cruxy from comes back exactly as it was, with no
frame-shaped hole in your scrollback.
On a clean exit the last dozen lines of the conversation are echoed into the normal buffer, so the session survives in the shell's scrollback rather than being discarded with the alternate screen. The full transcript is the session log; this is the tail.
The opt-out exists because the alternate screen is not universally available or
wanted — multiplexers and emulators can be configured to refuse it, some
capture-and-replay tooling reads only the normal buffer, and you may simply
prefer your shell to keep the frame. It changes nothing else about the TUI, and
it is ignored where the TUI does not run at all (a pipe, a screen reader,
TERM=dumb).
The mouse
While the TUI is active it turns mouse reporting on, so the wheel scrolls the
conversation (or the selected view) the way Page Up and Page Down do, instead
of scrolling the terminal window over a buffer that has no scrollback. The
trade is the one less and vim make: native text selection needs Shift
(Option in Terminal.app) while the TUI is active. If you select text
constantly, set CRUXY_NO_MOUSE=1 — the wheel goes back to the terminal, and
nothing else about the TUI changes. It is implied off wherever the alternate
screen is off, because the two are turned on and released as one.
However cruxy exits — quit, kill -TERM, a hangup when the window closes, an
uncaught error — the terminal is handed back: the frame erased, the cursor
shown, the alternate screen left.
Size
| Variable | Effect |
| --------- | -------------------------------------------- |
| COLUMNS | Terminal width, overriding what it reports. |
| LINES | Terminal height, overriding what it reports. |
These win over the stream's own values, which is what makes
COLUMNS=100 cruxy … work in CI where the terminal reports no size at all. They
fall back to 80 × 24. Unlike the flags above they take a positive integer; a
value that is not one is ignored rather than treated as "set".
The "set" convention
Every variable except COLUMNS / LINES follows the NO_COLOR rule: any
non-empty value counts as set. CRUXY_ASCII=1 and CRUXY_ASCII=false both
enable ASCII glyphs — to turn one off, unset it or set it to the empty string.
Errors & exit codes
Every user-facing error prints a title, the cause (when known), concrete next
steps, and a stable code (e.g. CRUXY_E_GATEWAY_UNREACHABLE). Pass --verbose
(or --log-level debug / DEBUG=1) to see the underlying error and stack;
NO_COLOR disables color. Exit codes are stable per category, so scripts can
branch on them:
| Exit | Category | Example codes |
| ---- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | success | — |
| 1 | internal | CRUXY_E_INTERNAL |
| 2 | usage | CRUXY_E_USAGE, CRUXY_E_CONFIG_KEY_UNKNOWN, CRUXY_E_PROVIDER_UNSUPPORTED, CRUXY_E_GIT_PROTECTED_BRANCH, CRUXY_E_PLAN_INVALID, CRUXY_E_PLAN_REVISION_LIMIT, CRUXY_E_CHECKPOINT_NOT_FOUND |
| 3 | config | CRUXY_E_CONFIG_PARSE, CRUXY_E_CONFIG_INVALID, CRUXY_E_CONFIG_NOT_FOUND |
| 4 | auth | CRUXY_E_AUTH_MISSING_KEY, CRUXY_E_AUTH_INVALID, CRUXY_E_FORGE_AUTH |
| 5 | network | CRUXY_E_GATEWAY_UNREACHABLE, CRUXY_E_GIT_PUSH_FAILED |
| 6 | api | CRUXY_E_API, CRUXY_E_API_REQUEST_REJECTED, CRUXY_E_API_RATE_LIMIT, CRUXY_E_API_OVERLOADED, CRUXY_E_BUDGET_EXHAUSTED, CRUXY_E_FORGE_API |
| 7 | filesystem | CRUXY_E_FILE_NOT_FOUND, CRUXY_E_PERMISSION_DENIED, CRUXY_E_PATH_ESCAPE, CRUXY_E_CHECKPOINT_FAILED |
| 8 | index | CRUXY_E_INDEX_EMBEDDER_UNAVAILABLE, CRUXY_E_INDEX_EMBEDDER_NOT_INSTALLED, CRUXY_E_INDEX_EMBEDDER_INSTALL_FAILED, CRUXY_E_INDEX_EMBEDDER_DOWNLOAD_FAILED, CRUXY_E_INDEX_STORE_UNAVAILABLE, CRUXY_E_INDEX_FAILED |
| 9 | skill | CRUXY_E_SKILL_INVALID, CRUXY_E_SKILL_NOT_FOUND |
| 10 | approval | CRUXY_E_APPROVAL_REQUIRED, CRUXY_E_PLAN_APPROVAL_REQUIRED, CRUXY_E_ROLLBACK_APPROVAL_REQUIRED |
| 22 | paid plan | CRUXY_E_PAID_PLAN_REQUIRED |
22 is a developer API key whose account is on a plan without API access
(the gateway's 403 plan_required). The key itself is fine, so it is not an
auth error: cruxy prints "API keys need a paid Cruxy plan. Upgrade at
https://cruxy.ai/settings/billing, or run cruxy login to use your account's
allowance." A run, cruxy limits, and cruxy login --paste all exit 22 for
it, so a script can tell it apart from a bad key (4) or a network fault
(5).
The LLM client is @cruxy/sdk —
cruxy's client for the cruxy gateway, built over fetch, with no vendor SDKs.
License
MIT
