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.

~/.config/crush/crushrc
# 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
The base URL keeps the /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.
Use --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.
A 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.
Kunavo has not runtime-tested Crush — not this client and not any other on these pages. What is checked here is Crush's own documented configuration against the endpoint Kunavo publishes; a setup page is not a test result. Run one bounded task before you move a daily driver onto it.

Step by step

  1. Create a key at /app/keys and copy it — it is shown once. Export it as KUNAVO_API_KEY, or read it from a password manager in the config itself.
  2. 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.
  3. Start crush and press ctrl+l to open the model picker. The model large and model small lines above already pin both slots, so the picker is for switching rather than for setup.
  4. If you would rather not hand-register ids: auto-discovery runs when the model list for an openai-compat provider is empty, or when you pass --discover-models true. Kunavo answers GET /v1/models, so the list populates itself, and your own model add fields win on conflicts.
  5. 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.

This is the short version. The full walkthrough — model choice, what a real session costs, and the failure modes — is in Crush vs OpenCode.

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 idKunavo in / outWhere it fits in Crush
claude-sonnet-5$2.00 / $10.00the model large slot — the everyday coding and editing model
claude-haiku-4-5$0.40 / $2.00the model small slot, which Crush drives constantly for titles and summaries
claude-opus-5$2.00 / $10.00swap into model large for a refactor where a wrong plan is expensive
gpt-5-6-terra$0.70 / $4.20a second family on the same key, one more model add away
Billing is per token from a prepaid balance with no monthly fee — see billing. On repeated context — which is most of what an editor or a chat client sends — prompt caching moves the bill more than the model choice does.

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.