Docs
Crush
Crush is Charm's terminal coding agent — not the Rust shell of the same name. Its config is Bash, so pointing it at a different endpoint is one provider add with a type, a base URL and a key.
Crush's config is Bash — one `provider add kunavo --type openai-compat --base-url "https://api.kunavo.com/v1"` in a crushrc puts Charm's terminal agent on Claude, GPT and Gemini.
# A crushrc is Bash, not a settings file. Everything here is executed.
provider add kunavo \
--type openai-compat \
--base-url "https://api.kunavo.com/v1" \
--api-key "${KUNAVO_API_KEY:?set KUNAVO_API_KEY}"
model add kunavo/claude-sonnet-5 \
--name "Claude Sonnet 5" \
--context-window 1000000 \
--default-max-tokens 32000 \
--price-input 2 \
--price-output 10
model add kunavo/claude-haiku-4-5 \
--name "Claude Haiku 4.5" \
--context-window 200000 \
--default-max-tokens 16000 \
--price-input 0.4 \
--price-output 2
model large kunavo/claude-sonnet-5
model small kunavo/claude-haiku-4-5/v1 suffix. Crush's own documented OpenAI-compatible example is --base-url "https://api.deepseek.com/v1", and its Anthropic-compatible one ends the same way — so the suffix is the client's convention, not a guess. Leave it off and the failure arrives as a 404 rather than an authentication error.--type openai-compat, not openai. The README draws the line itself: openai is for proxying or routing requests through OpenAI, openai-compat is for non-OpenAI providers with OpenAI-compatible APIs. Kunavo is the second case.crushrc is Bash with Crush builtins, and Crush's own warning is that it is trusted code — it runs in a full shell. That is also the feature: --api-key "$(op read ...)" keeps the key out of the file. The older crush.json still loads but the README calls it deprecated, so build on the crushrc.Step by step
- Create a key at
/app/keysand copy it — it is shown once. Export it asKUNAVO_API_KEY, or read it from a password manager in the config itself. - Put the block above in
~/.config/crush/crushrc. Crush reads./.crushrc, then./crushrc, then the global one, so a project can override the machine — and a repository you cloned can ship one. - Start
crushand pressctrl+lto open the model picker. Themodel largeandmodel smalllines above already pin both slots, so the picker is for switching rather than for setup. - If you would rather not hand-register ids: auto-discovery runs when the model list for an
openai-compatprovider is empty, or when you pass--discover-models true. Kunavo answersGET /v1/models, so the list populates itself, and your ownmodel addfields win on conflicts. - Run one bounded task, then read the charge your account recorded at
/app/billing. The figure in the terminal is arithmetic on the--price-*numbers you typed; the ledger is the bill.
Checked against Crush's Custom Providers section on September 21, 2026. Third-party settings move; if a field name here no longer matches what you see, that page is the authority, not this one.
Verify before you debug the client
One request settles whether a failure is the endpoint, the key, or the configuration file. If this returns JSON, the same base URL and key work in Crush.
# Settles whether a failure is the endpoint, the key, or the client.
curl -sS https://api.kunavo.com/v1/models \
-H "Authorization: Bearer sk-kn-..."Which model id to put in the field
Every text model is reachable as a model id — the live list is GET /v1/models, and the catalog with prices is on the models page. Rates are USD per 1M tokens, input / output.
| Model id | Kunavo in / out | Where it fits in Crush |
|---|---|---|
claude-sonnet-5 | $2.00 / $10.00 | the model large slot — the everyday coding and editing model |
claude-haiku-4-5 | $0.40 / $2.00 | the model small slot, which Crush drives constantly for titles and summaries |
claude-opus-5 | $2.00 / $10.00 | swap into model large for a refactor where a wrong plan is expensive |
gpt-5-6-terra | $0.70 / $4.20 | a second family on the same key, one more model add away |
FAQ
How do I add a custom API provider to the Crush CLI?
Write it into a crushrc, which is Bash with Crush builtins. One line registers the endpoint — provider add kunavo --type openai-compat --base-url "https://api.kunavo.com/v1" --api-key "$KUNAVO_API_KEY" — and a model add per id registers what you want to call, with the display name, context window and per-million prices Crush uses for its on-screen estimate. Crush reads ./.crushrc, then ./crushrc, then ~/.config/crush/crushrc, so the same block works per project or per machine.
Does the Crush base URL need /v1 at the end?
Yes. Crush's own custom-provider examples write the suffix for both types: https://api.deepseek.com/v1 for the OpenAI-compatible case and https://api.anthropic.com/v1 for the Anthropic-compatible one. For a Kunavo key the value is https://api.kunavo.com/v1. This is the opposite of Claude Code, where ANTHROPIC_BASE_URL takes a bare origin because that client appends the path itself — same gateway, two spellings, and a missing /v1 shows up as a 404 rather than a 401.
Should I use --type openai or --type openai-compat?
openai-compat, for any third-party gateway. Crush's README reserves openai for proxying or routing requests through OpenAI itself and directs non-OpenAI providers with OpenAI-compatible APIs to openai-compat. The type also decides behaviour beyond the wire format: auto-discovery of models runs for an openai-compat provider whose model list is empty. Crush additionally supports --type anthropic for Anthropic-compatible endpoints, which takes --extra-header anthropic-version 2023-06-01.
Is crush.json still the right place for this config?
No. crush.json is the original format and Crush's own documentation now describes it as deprecated and not receiving new features; the current format is a crushrc. Note that both are executed rather than parsed — a crushrc runs in a full shell and any $(...) inside crush.json is expanded at load time — which is why the documentation warns against launching Crush in a directory whose config you have not read, and why pulling a key from a password manager inside the config works at all.
Why does the cost Crush shows differ from what I was charged?
Because they are two different numbers with two different sources. The on-screen estimate for a hand-registered provider is arithmetic on the --price-input and --price-output values you typed into model add, and for built-in providers it comes from Catwalk, Crush's external provider catalog. Neither reads your account. A typo in a --price-* flag produces a wrong readout, not a wrong charge. Reconcile against the ledger at /app/billing instead.
Can Crush use Claude or GPT models through a custom provider?
Yes, and nothing in Crush restricts it. Charm Hyper is the official provider the onboarding steers you to, but a custom provider is a documented first-class path with no plan gate, and the model id is resolved at the endpoint rather than in the client. So a Claude id on an openai-compat provider is the intended combination: the type names the wire protocol, not the vendor.