claux
A terminal-based AI coding assistant written in Rust. Streams responses, executes tools, manages sessions, and stays out of your way.
Features
- Streaming chat with tool execution (Read, Write, Edit, Glob, Grep, Bash, WebFetch, Agent)
- Image input — attach PNG, JPEG, GIF, or WebP files in the TUI/REPL or one-shot mode
- Interactive permissions — prompts before writes,
y/n/a; type a message at the prompt instead to deny the tool and steer the model with it - Mid-turn steering — type while claux is running tools and press Enter; the running tool is cancelled, remaining queued tools are skipped, and your message reaches the model immediately
- Interrupt anywhere — Ctrl+C during a turn cancels it cleanly (in-flight tool calls are paired with interrupted results, so the conversation stays valid); press Ctrl+C twice within 2s to quit the app (Ctrl+D still exits immediately)
- Session persistence — SQLite-backed with search; full transcripts including tool calls and results, so
/resumeand--resumerestore exactly what the model saw. Histories from older versions are repaired on load - Safe turn checkpoints —
/diffshows exactly what the last turn changed;/undo-turnrestores it only when no file has been edited since, so later human work is never overwritten - Observable compaction — live context utilization, provider-anchored counts, and before/after compaction reports
- Model selection — search configured models by provider, profile, or model when starting a TUI session;
/model <profile>safely switches providers while preserving the chat - Sub-agents — Agent tool spawns scoped sub-conversations. Sub-agents inherit the parent session's permission mode, so a sub-agent can't act with more authority than you granted the session. Because sub-agents run non-interactively, any tool the mode would prompt for is denied rather than auto-run (Plan denies all writes; Bypass allows all)
- Auto-compact — triggers when conversation gets large
- Task-preserving compaction — summarizes the intact history into an objective, constraints, decisions, progress, and remaining work while retaining the original request verbatim. Later corrections take precedence. Compaction uses a model request; failed, empty, or non-shrinking summaries leave history unchanged.
- Cost tracking — per-model token usage and USD estimates
- Prompt caching — automatic Anthropic cache breakpoints on the system prompt and conversation, cutting input cost and latency on long sessions
- Context assembly — git status, CLAUDE.md, environment info in system prompt. Checked-in CLAUDE.md is loaded only for trusted projects (the user's
~/.claude/CLAUDE.mdalways is), and each file is size-capped - TUI mode — full-screen ratatui interface with
--tui - Multi-provider — Anthropic, OpenAI, Ollama, or any OpenAI-compatible endpoint
- Native system prompt — claux speaks as claux; the full prompt is readable in
src/context.rs, and what you read is what the model gets - Markdown rendering — code blocks, bold, headers in the TUI
Screenshots
The TUI in action. These are generated by tuishot
from claux's own code — cargo test fails if they drift, so they can't go
stale.
Install
# From crates.io
# From source
Requires Rust 1.88+. A shell.nix is included.
First run
Create a starter configuration for Anthropic, OpenAI, OpenRouter, OpenCode Go, Vercel AI Gateway, or Ollama, then verify authentication, required executables, configured hooks/MCP servers, project trust, and provider connectivity:
# Other examples
The generated file contains environment-variable names, never API keys, and is
created with private permissions. Run config init again with another provider
to add it without replacing existing settings or comments. --force explicitly
starts over.
The TUI opens on the session browser. Starting a session lets you choose one named model profile; opening an existing session restores its exact provider, endpoint, protocol, and model:
= "sonnet"
[]
= "anthropic"
= "ANTHROPIC_API_KEY"
[]
= "openai"
= "https://openrouter.ai/api/v1"
= "openrouter"
= "chat_completions"
= "OPENROUTER_API_KEY"
= true
# Vercel AI Gateway is also a generic OpenAI-compatible provider.
[]
= "openai"
= "https://ai-gateway.vercel.sh/v1"
= "vercel"
= "chat_completions"
= "AI_GATEWAY_API_KEY"
[]
= "anthropic"
= "claude-sonnet-5"
= "Sonnet"
[]
= "openrouter"
= "openai/gpt-5.6"
= "GPT via OpenRouter"
[]
= "vercel"
= "zai/glm-5.3-flash"
= "GLM-5.3 Flash via Vercel AI Gateway"
Saved sessions never contain API keys. They retain a credential-free transport snapshot and resolve credentials from the current matching provider config or saved environment-variable name when reopened. If that credential is no longer available, the TUI returns to the session browser with a recovery message.
Compatible OpenAI-style providers can opt into prompt-prefix caching with
prompt_caching = true. OpenRouter configuration created by claux config init enables it by default and sends an ephemeral top-level cache control on
each request. Provider-reported cache reads and writes appear in Claux usage
output when the selected model supports them.
Auth
OpenRouter users can authorize Claux without manually copying an API key:
The command uses OAuth PKCE, opens OpenRouter in the browser, and receives the
result through a temporary localhost callback. On a remote or headless host,
use claux auth login openrouter --headless; OpenRouter will display a
single-use code to paste into the terminal. The resulting user-controlled API
key is stored outside config.toml in Claux's platform configuration directory
(~/.config/claux/credentials/openrouter on Linux) with user-only permissions. Use
claux auth status openrouter or claux auth logout openrouter to inspect or
remove it.
For local integrations that must supply the saved key to another process,
claux auth token openrouter prints it to standard output. Treat that output
as a secret. Replaybook can use this command on the host while continuing to
keep the real credential out of VM snapshots and benchmark artifacts.
To compare the native tokenization behavior of two or more OpenRouter models:
The command sends a fixed multilingual, source-code, whitespace, emoji, and
Unicode probe corpus with one-token completions. It compares prompt-token
deltas rather than absolute counts to reduce chat-template overhead. Matching
fingerprints are evidence of shared tokenization behavior, not proof that two
model identifiers serve the same model. Pass --format json for structured
data or --format markdown for a readable report with the complete probe
vectors. The existing --json shorthand remains supported.
Use --output FILE to write the completed report atomically without relying
on shell redirection. Claux checkpoints each completed model in its platform
cache directory. If a provider becomes unavailable after some models finish,
rerun the same ordered model list with --resume; completed fingerprints are
reused and can also be rendered into another format without new inference:
# If the provider interrupts the run, repeat it with:
Before the first inference request, Claux validates every supplied model ID against OpenRouter's catalog. Typos and accidentally passed shell operators therefore fail without spending credits.
To check provider-reported spending and key limits without making an inference request:
OpenRouter reports key usage, an optional spending limit, remaining allowance, and daily/weekly/monthly counters through its read-only key endpoint. Account- wide credits require a management key and are not queried. Providers without a configured usage endpoint are reported as unavailable; Claux never renders an unknown balance as zero.
OpenCode Go can be queried explicitly with claux usage status opencode-go when
OPENCODE_GO_API_KEY (or OPENCODE_API_KEY) is set. It reports the account-wide
rolling 5-hour, weekly, and monthly usage windows and reset times. OpenCode Go's
credit-wallet balance is separate and is not exposed by that endpoint.
OpenCode Go and Vercel AI Gateway use API keys rather than Claux's OpenRouter OAuth flow. Save either key securely with an interactive prompt:
Use claux auth status, claux auth token, or claux auth logout with the
same provider name to inspect, export, or remove a saved key. The keys are
stored separately with user-only permissions and are used as a fallback after
the configured api_key, api_key_cmd, and environment variable.
For each named provider, claux resolves authentication in order:
api_keyin its provider tableapi_key_cmd(shell command that returns a key)- The provider's
api_key_envenvironment variable - A credential saved by
claux auth loginfor that provider
Claude Free, Pro, and Max subscription credentials are not supported. Use an Anthropic API key or an OpenAI-compatible endpoint such as OpenRouter.
Legacy single-provider configuration
Existing flat configuration remains supported:
= "llama3"
= "http://localhost:11434/v1"
= "ollama"
API keys via command also remain supported (works with 1Password, Vault, etc.).
The command runs through sh -c (cmd /C on Windows) with no stdin and is
killed after 30 seconds, so a secret manager waiting for an interactive unlock
fails with a warning and claux falls back to the environment variable:
= "gpt-4o"
= "https://api.openai.com/v1"
= "op read 'op://vault/OpenAI/key'"
= "openai"
Usage
# Interactive REPL (default)
# Full-screen TUI
# One-shot
# One-shot with one or more images
# Machine-readable one-shot result with usage and cost
# Also retain the conversation and every tool input/result for evaluation
# Resume a session
JSON output has a versioned contract suitable for CI, agent evaluations, and other automation:
cost_usd uses provider-reported cost when available, otherwise configured or
built-in model pricing. It is null when neither source is available.
The JSON document is written on failure too, so a supervisor never has to
parse stderr. result is then null and outcome classifies what happened:
failure.kind is one of context_exceeded, output_limit_exceeded,
malformed_tool_arguments, rate_limited, unavailable, authentication,
model_not_found, policy_rejection, protocol_error, network,
cancelled, or other. The same object appears under outcome.failure in
the transcript. Both additions are backwards compatible with the existing
schema versions.
Rate limits, provider 5xx responses, and transport failures are retried up to
three times with exponential backoff before the turn fails, honoring a capped
Retry-After when the provider sends one. Retries never reissue a request
after a tool call has been surfaced, and waiting stops immediately on SIGINT
or SIGTERM. Each retry is recorded as a model round with status: "retry"
and its failure kind in the transcript.
The one-shot exit status encodes the failure kind so a caller that does not read JSON can still tell a rate limit from a bad credential:
| Exit code | Meaning |
|---|---|
0 |
completed |
1 |
unclassified error, including configuration errors before the engine starts |
2 |
usage error (invalid arguments) |
10 |
cancelled by SIGINT or SIGTERM |
11 |
provider rate limited or unavailable after retries |
12 |
authentication or billing rejected |
13 |
context window exceeded after compaction attempts |
14 |
provider policy rejection |
15 |
protocol error, including malformed tool arguments and empty completions |
16 |
model or endpoint not found |
17 |
network failure after retries |
18 |
output token limit reached at the maximum budget |
Codes 10 through 18 never collide with SSH's 255 or the shell's 126,
127, and 128+signal codes, so a supervisor running claux over SSH can
attribute a failure correctly.
--transcript FILE writes a separate, versioned JSON artifact containing the
final conversation state, outcome, usage, and a complete ordered tool trace.
While a turn is running, Claux atomically checkpoints the artifact after model
rounds and tool batches with outcome.status set to running. Normal
completion replaces that checkpoint with the final completed or error outcome.
The tool trace is retained independently of context compaction, so earlier
tool calls are not lost when the model's active history is summarized.
Transcript schema version 2 also records total turn duration, each streamed
provider round with its usage and duration, and each tool call's monotonic
start offset, duration, and read-only classification. These monotonic timings
measure elapsed execution without depending on the host wall clock. Failed
turns write the partial transcript before returning the error. Failures before
the engine starts, such as invalid configuration, cannot produce a transcript.
Claux creates transcript files with private permissions on Unix.
In one-shot mode, SIGINT and SIGTERM cancel the active provider request or
tool, pair interrupted tool calls with results, and write the partial
transcript before the process exits. This lets supervisors enforce a deadline
without discarding the investigation that occurred before it.
Transcripts contain raw tool inputs and the exact results returned to the model. They may therefore include source code, command output, or credentials the agent explicitly read. Capture is opt-in; store and share these artifacts accordingly.
Commands
| Command | Description |
|---|---|
/help |
Show available commands |
/cost |
Token usage and estimated cost |
/compact |
Summarize conversation to free context |
/context |
Show context utilization, threshold, and remaining headroom |
/diff |
Show file changes made by the last turn |
/undo-turn |
Safely undo the last turn's file changes |
/image <path> |
Attach an image to the next prompt (repeatable) |
/model [profile] |
Show configured models or safely switch provider/model profile |
/resume [id] |
List or resume past sessions |
/clear |
Clear screen |
/exit |
Exit |
Turn checkpoints cover Git-tracked files and non-ignored untracked files.
Ignored files and paths outside the repository are deliberately excluded.
/undo-turn first verifies that every affected file still matches the end of
the turn; if anything changed afterward, it refuses the entire undo.
Config
Global: ~/.config/claux/config.toml
= "sonnet"
= "default" # default | accept-edits | bypass | plan
= "workspace_only" # workspace_only | unrestricted
= "auto" # auto | workspace_write | unrestricted
# Project-local .claux.toml files may tighten permission and filesystem
# policies without trust, but cannot loosen them unless their directory is
# listed here or --trust-project is passed for the invocation.
# Project-local .mcp.json uses the same boundary.
# CLAUDE.md instructions checked into a project (in the working directory and
# its ancestors) are also only loaded for trusted projects; the user's own
# ~/.claude/CLAUDE.md is always loaded. Each CLAUDE.md is capped at 40k chars.
= ["/absolute/path/to/a/trusted/project"]
[]
= "anthropic"
= "ANTHROPIC_API_KEY"
[]
= "openai"
= "https://api.openai.com/v1"
= "openai"
= "responses"
= "OPENAI_API_KEY"
# Compatibility escape hatch for endpoints that close a complete SSE stream
# without a finish_reason or [DONE]. Keep false unless the endpoint requires it.
= false
[]
= "anthropic"
= "claude-sonnet-5"
= "Sonnet"
[]
= "openai"
= "gpt-5.6-sol"
= "OpenAI Coder"
= "medium"
# `reasoning_effort` also works with compatible Chat Completions providers
# such as OpenRouter. Returned reasoning state is preserved across tool rounds
# but is not rendered as assistant text.
# Optional per-profile metadata overrides. OpenRouter context windows are
# discovered automatically and cached locally; an explicit value takes
# precedence. Other compatible providers use built-in knowledge or this
# override because their model-catalog APIs are not standardized.
# context_window = 1050000
# [model_profiles.openai-coder.pricing]
# input = 5.0
# output = 30.0
# cache_read = 0.5
# cache_write = 6.25
# Optional pricing overrides, in USD per million tokens. Built-in prices are
# used for known models; unknown models display "Cost: unavailable". This
# model-ID keyed form remains supported for shared or legacy configuration.
# [model_pricing."gpt-5.6-sol"]
# input = 5.0
# output = 30.0
# cache_read = 0.5
# cache_write = 6.25
Per-project: .claux.toml in the project root (overrides global).
Native tool filesystem policy
native_tool_filesystem_policy controls Claux's built-in Read, Write, Edit,
Glob, and Grep tools. The default, workspace_only, resolves paths and symlinks
before allowing access and rejects paths outside the directory where Claux was
started. It also rejects search patterns which traverse to a parent directory.
Set the global value to unrestricted when native tools must work across the
filesystem. A project-local .claux.toml may tighten this policy without trust,
but may only loosen it for a trusted project.
This is application-level containment for native file tools. MCP tools are not contained by this setting and continue to use Claux's project-trust boundary.
Bash filesystem policy
bash_filesystem_policy controls operating-system containment for commands
spawned by the Bash tool:
auto(the default) uses Linux Landlock workspace-write containment when fully available. Otherwise it emits a warning and preserves unrestricted behavior.workspace_writerequires Linux Landlock and fails closed when the kernel cannot completely enforce the policy.unrestrictedruns Bash with the user's normal filesystem access.
Workspace-write commands can read the filesystem but may only write beneath the
directory where Claux started, system temporary directories, /dev/null, and
the repository's Git metadata. Allowing temporary paths keeps compilers and
other development tools functional. Symlink traversal does not grant access to
targets outside these writable roots.
Run claux doctor to see the effective Bash policy on the current system.
The Bash permission prompt and permission_mode remain separate from
containment: approving a command authorizes it to run, but does not disable the
sandbox. Claux never retries a sandbox-denied command unrestricted. A trusted
project may explicitly select unrestricted; an untrusted project may only
tighten the global policy.
Child process environment
Bash commands, hooks, and MCP servers start with a copy of claux's environment
that has provider credentials removed: every api_key_env named in the loaded
configuration, plus any variable ending in _API_KEY or starting with
ANTHROPIC_, OPENAI_, OPENROUTER_, AI_GATEWAY_, or OPENCODE_. Keys
resolved with api_key_cmd never enter the environment at all.
By default SSH_AUTH_SOCK and DOCKER_HOST are removed as well, so a command
run by the model cannot use your SSH agent or reach a remote Docker daemon.
This also means git push over an agent-held key and rootless or remote
Docker do not work inside the Bash tool. Set strip_agent_sockets = false in
the global config to keep those handles; a project .claux.toml may only
turn stripping on unless the project is trusted.
Hooks
Run a command on a lifecycle event. Each [[plugins]] entry runs its
command when the matching trigger fires. Nothing about the command lives
in claux - it's your config, so a hook can drive anything: a status light, a
notification, a logger, a metrics counter.
[[]]
= "lamp-idle"
= "glow-hook"
= ["purple"]
= "on_session_start"
[[]]
= "lamp-working"
= "glow-hook"
= ["blue"]
= "on_tool_start"
[[]]
= "lamp-your-turn"
= "glow-hook"
= ["green"]
= "on_turn_end"
[[]]
= "lamp-needs-you"
= "glow-hook"
= ["orange"]
= "on_permission_request"
| Trigger | Fires when |
|---|---|
on_context_build |
building the system prompt (stdout is injected as context) |
on_session_start |
a session starts |
on_tool_start |
a tool call begins |
on_tool_complete |
a tool call finishes |
on_turn_end |
the agent finishes a turn and control returns to you |
on_permission_request |
the agent blocks on a permission prompt (it needs you) |
on_permission_check |
before every tool call; stdout can allow, deny, or ask (see below) |
The example above is a glow status lamp: your
keyboard's RGB tracks what claux is doing. on_context_build is special - its
stdout is added to the system prompt; the rest are bounded side effects. Hook
commands run concurrently with a 10-second timeout and bounded captured output.
Permission check hooks
An on_permission_check hook runs before every tool call, after the mode and
[permissions] rules have produced a proposed decision. It receives:
| Variable | Value |
|---|---|
CLAUX_TOOL_NAME |
the tool, e.g. Bash |
CLAUX_TOOL_INPUT |
the raw tool input as JSON |
CLAUX_TOOL_READ_ONLY |
true or false |
CLAUX_PERMISSION_MODE |
the active mode |
CLAUX_PERMISSION_DECISION |
the proposed decision: allow, ask, or deny |
Print nothing to accept the proposal, or print one JSON object:
The most restrictive answer wins across hooks: any deny denies, any ask
turns an allow into a prompt, and allow clears a prompt. A deny from a rule
or from plan mode is final; no hook can lift it. Hooks run with the same
10-second timeout and credential-free environment as other hooks, and a hook
that fails or prints something other than the JSON object is ignored with a
warning.
Permission Modes
| Mode | Reads | File edits | Bash |
|---|---|---|---|
default |
auto | prompt | prompt |
accept-edits |
auto | auto | prompt |
bypass |
auto | auto | auto |
plan |
auto | denied | denied |
In accept-edits mode, Agent, MCP, and other non-read-only tools still require
explicit approval. Sub-agents inherit the parent permission mode; because they
are non-interactive, operations that would require another prompt are denied.
Permission rules
Rules refine a mode per tool and per call. Each entry is a tool name, or a
tool name with a glob matched against the call's subject: the command for
Bash, the path for Read, Write, and Edit, the path or pattern for Glob and
Grep, the URL for WebFetch, the prompt for Agent. * matches every tool.
[]
= ["Bash(cargo *)", "Bash(git status)", "Edit(src/**)"]
= ["Bash(sudo *)", "Bash(rm -rf *)", "Write(.git/**)"]
= ["Edit(Cargo.toml)", "Read(.env*)"]
Evaluation order for each call:
denyrules always win, even inbypassmode and over a session "always allow".- A session "always allow" from an earlier prompt.
askrules force a prompt where the mode would auto-allow.allowrules skip the prompt the mode would show. They never overrideplanmode's write denial.- Otherwise the mode decides.
A project .claux.toml may add deny and ask rules without trust. It may
only add allow rules when the project is trusted. A malformed rule is a
configuration error, so a typo in a deny rule cannot be silently skipped.
Sub-agents inherit the parent's rules.
Agent evaluations
Claux has fixture-driven behavioral evaluations for complete multi-round turns, including real tool execution in isolated workspaces, permissions, steering, file outcomes, recovery, and provider stream failures:
They require no credentials or network and run as a dedicated CI check.
An ignored paid-provider smoke test and manual workflow are also available;
see evals/README.md.
License
MIT