Knowledge Base Sections ▾

Navigation

▸ Start here By roles

Categories

Tools 52
Glossary 12

Tools

Crush + JoinGonka Gateway: Charm agent on Gonka network models

Crush is a terminal coding agent from Charm, the team whose libraries for console interfaces power tens of thousands of programs. It reads and edits project files, runs commands, retrieves context from language servers (LSP), connects external tools via MCP, and can switch models mid-session without losing context. It works in macOS, Linux, and Windows terminals, as well as on Android and BSD; the license is FSL-1.1-MIT. The project has a notable pedigree: the archive repository opencode-ai/opencode directly refers to Crush — the project was continued by its original author and the Charm team.

Crush accepts two types of third-party providers — those with OpenAI- and Anthropic-compatible APIs. JoinGonka Gateway connects as openai-compat: with a single installer command or a dozen lines of config. After that, the agent runs on models of the decentralized Gonka network — DeepSeek V4 Flash, GLM-5.3 Flash, and MiniMax M2.7 — at a flat rate: $0.0069 per million input tokens.

Two features of Crush are worth knowing before your first session: it distributes models into two slots, large and small, and the model chosen in the interface is stored in a separate state file that takes precedence over the config. A separate section is dedicated to this. The commands and messages below have been verified with a live run of Crush 0.96.1 via the gateway on September 23, 2026. After confirming the address, 3M free tokens will be credited to your account — enough to repeat all this yourself.

Quick start: installation and a single command

Step 1: install Crush. Official methods from the project README:

# Homebrew
brew install charmbracelet/tap/crush

# npm
npm install -g @charmland/crush

# Arch Linux
yay -S crush-bin

# Windows
winget install charmbracelet.crush

# Go
go install github.com/charmbracelet/crush@latest

The same README covers package repositories for Debian, Ubuntu, Fedora and RHEL, Nix and Scoop, and ready-made binaries are available on the releases page. To verify, run crush --version and you'll get a line like crush version v0.96.1.

Step 2: get a key. Sign up at gate.joingonka.ai/register, confirm your address, and create a key with the jg- prefix in the "API Keys" section. A single key and a single balance work for all models on the network.

Step 3: run the installer.

npx @joingonka/setup --tool crush

The installer will ask for your key — it isn't passed as a command-line argument so it doesn't end up in your shell history — and will do four things:

  • write the joingonka provider into ~/.config/crush/crush.json: type openai-compat, the gateway address, the key as a literal, and three network models with context windows, response caps and prices per million tokens, which it pulls live from the gateway at install time — Crush uses these to calculate session cost. The file will be set to permissions 600;
  • assign models: DeepSeek V4 Flash to the large slot, MiniMax M2.7 to small, and GLM-5.3 Flash will stay in the list for manual selection. It only does this if the large slot is empty or points to one of our models that has left the network; someone else's choice stays put, and the output will include a hint on how to switch;
  • check that the choice takes effect: the model from the Crush interface is stored in a state file, and that file overrides the config — see the section on large and small;
  • make a backup, leave the other providers and settings untouched, and finish with a live request to the gateway to immediately verify the key, address and model.

To set a different model, use the --model flag with the shorthand deepseek, glm or minimax; an explicitly specified model is always written. The installer handles non-standard directories the same way Crush itself does: CRUSH_GLOBAL_CONFIG for the config, CRUSH_GLOBAL_DATA for the state file, plus XDG_CONFIG_HOME and XDG_DATA_HOME. For servers and scripts there's a non-interactive mode, where the key is taken from an environment variable:

JOINGONKA_API_KEY=jg-your-key npx @joingonka/setup --tool crush --model glm --non-interactive

Crush's primary format is now crushrc, but the installer writes crush.json: all versions understand it. If you already have a crushrc, Crush will merge both files, crushrc wins on key conflicts, and the installer itself leaves it alone.

Manual configuration: crush.json or crushrc

Everything the installer does can be written by hand. Here's a working ~/.config/crush/crush.json — this is the file we ran through the gateway:

{
  "$schema": "https://charm.land/crush.json",
  "providers": {
    "joingonka": {
      "name": "JoinGonka (Gonka)",
      "type": "openai-compat",
      "base_url": "https://gate.joingonka.ai/v1",
      "api_key": "jg-your-key",
      "models": [
        { "id": "deepseek-ai/DeepSeek-V4-Flash-0731", "name": "DeepSeek V4 Flash (Gonka)",
          "context_window": 380000, "default_max_tokens": 32768,
          "can_reason": true, "supports_attachments": false },
        { "id": "MiniMaxAI/MiniMax-M2.7", "name": "MiniMax M2.7 (Gonka)",
          "context_window": 200000, "default_max_tokens": 8192,
          "can_reason": false, "supports_attachments": false },
        { "id": "zai-org/GLM-5.3-Flash", "name": "GLM-5.3 Flash (Gonka)",
          "context_window": 390000, "default_max_tokens": 8192,
          "can_reason": true, "supports_attachments": false }
      ]
    }
  },
  "models": {
    "large": { "provider": "joingonka", "model": "deepseek-ai/DeepSeek-V4-Flash-0731" },
    "small": { "provider": "joingonka", "model": "MiniMaxAI/MiniMax-M2.7" }
  }
}
FieldValueWhy it matters
typeopenai-compatThe type for third-party services with an OpenAI-compatible API. Crush's docs reserve the openai type for requests that go through OpenAI itself
base_urlhttps://gate.joingonka.ai/v1With /v1 at the end: Crush appends the /chat/completions path itself
api_keyyour jg-… keyCrush runs the value through shell substitution, so instead of the key you can write $JOINGONKA_API_KEY — but then the variable must be exported in the environment Crush is launched from
context_windowthe model's windowCrush uses it to show how full the context is and to decide when to compress the history
default_max_tokensthe response ceilingSent with every request as max_tokens. For reasoning models, the reasoning also counts against this budget

Crush accepts only strict JSON: a comment or a trailing comma and it won't start. The pricing fields (cost_per_1m_in, cost_per_1m_out, and the two cache fields) are only needed for the cost counter: the installer fills in the gateway's live price there, and without them Crush still works and shows zero — only an editor with the schema from $schema will flag them as required.

The same provider in crushrc format — that's plain Bash with Crush's built-in commands. The file ~/.config/crush/crushrc:

provider add joingonka \
  --name "JoinGonka (Gonka)" \
  --type openai-compat \
  --base-url "https://gate.joingonka.ai/v1" \
  --api-key "${JOINGONKA_API_KEY:?set JOINGONKA_API_KEY}"

model add joingonka/deepseek-ai/DeepSeek-V4-Flash-0731 \
  --name "DeepSeek V4 Flash (Gonka)" \
  --context-window 380000 --default-max-tokens 32768 --can-reason true

model add joingonka/MiniMaxAI/MiniMax-M2.7 \
  --name "MiniMax M2.7 (Gonka)" \
  --context-window 200000 --default-max-tokens 8192

model add joingonka/zai-org/GLM-5.3-Flash \
  --name "GLM-5.3 Flash (Gonka)" \
  --context-window 390000 --default-max-tokens 8192 --can-reason true

model large joingonka/deepseek-ai/DeepSeek-V4-Flash-0731
model small joingonka/MiniMaxAI/MiniMax-M2.7

Here a model is named in the provider/model-id form: the provider name comes before the first slash, and after it comes the network identifier exactly as is. The ${JOINGONKA_API_KEY:?…} form keeps the key out of the file, but without an exported variable Crush won't start — better an error at startup than requests with an empty key.

Large and small models and the state file

In Crush, you don't choose a single model for everything; you choose for two slots:

SlotFunctionInstaller DefaultHow to change
largeMain agent: handles all turns involving file reading, editing, and command execution.DeepSeek V4 Flash: 380K context window and the largest response limit in the network, 32768.ctrl+l in the interface, -m for crush run, models.large in the config.
smallAuxiliary tasks: session naming and a sub-agent that searches the web and reads web pages. If the small model fails to generate a title, Crush repeats the request with the large model.MiniMax M2.7: has the largest capacity in the network.--small-model for crush run, models.small in the config, model small in crushrc.

The third network model, the reasoning-focused GLM-5.3 Flash, is chosen for complex logic, keeping in mind that its response limit is 8192 and part of that is used for reasoning — see details in the model overview. For a single run, the model is specified by its full name: crush run -m joingonka/zai-org/GLM-5.3-Flash "…". All available names can be listed via crush models joingonka.

State file. The model selected in the interface via ctrl+l is not saved by Crush in crush.json, but in a machine-specific state file: ~/.local/share/crush/crush.json, or on Windows — %LOCALAPPDATA%\crush\crush.json. This file takes precedence over both the user config and crushrc: Crush reads them in order: /etc/crush/crush.json → ~/.config/crush/crush.json → ~/.config/crush/crushrc → state file, with each subsequent one overriding the previous. Higher priority is given only to project-specific settings — crush.json or crushrc in the project directory. The command crush dirs will show where the files are located on your machine.

In our run, we opened the model selection menu — the “JoinGonka (Gonka)” provider appears there marked as “✓ Configured” — and selected MiniMax M2.7. Crush replied with “Large model changed to MiniMax M2.7 (Gonka)” and wrote to the state file:

{"models":{"large":{"model":"MiniMaxAI/MiniMax-M2.7","provider":"joingonka","max_tokens":8192}}, …}

Now this model overrides what is written in crush.json. The installer recognizes this situation and behaves differently:

  • without --model it does not touch the state file — that is your working selection — but explicitly warns which model Crush will actually start with: Heads-up: Crush will still start with joingonka/MiniMaxAI/MiniMax-M2.7, not joingonka/deepseek-ai/DeepSeek-V4-Flash-0731. It will do the same if GLM-5.3 Flash or another provider's model is selected in the interface;
  • with an explicit --model it modifies exactly one entry in the state file — models.large — after saving a backup, and reports the change. Otherwise, the flag would have no effect silently. After such a run, our Crush instance opened using the model specified in the flag.

Verification: what should happen

First, make sure Crush can see the provider:

crush models joingonka

You should get three lines back:

joingonka/MiniMaxAI/MiniMax-M2.7
joingonka/deepseek-ai/DeepSeek-V4-Flash-0731
joingonka/zai-org/GLM-5.3-Flash

Next, do a one-off run without the interface. Drop a file with an obvious bug into your project directory and ask it to find the bug:

crush run -q "Read calc.py and tell me in one sentence whether it has a bug."

The agent should call the file-reading tool on its own and give a substantive answer, including the line number. In our run, all three models on the network found the bug, and the task took 20-40 seconds. The -q flag hides the waiting indicator. In crush run mode, all tool calls are approved automatically, so run it in your own project. In the regular interface (the crush command), the active model is visible in the status line: ◇ DeepSeek V4 Flash (Gonka) via JoinGonka (Gonka). Crush stores sessions and the log in the project's .crush directory, isolated from git by its own .gitignore; the log is printed by crush logs. On the gateway side, the request is visible in the dashboard: the "Usage" section, broken down by "By model" and "By key".

If something goes wrong, the diagnosis is usually readable straight from the message:

What you seeWhat it meansWhat to do
unauthorized: Invalid API key.The gateway rejected the keyCheck api_key: the whole key, no spaces. If it references a variable, that variable must be exported in this shell
invalid JSON in config file …/crush.jsonThe file has a comment, a trailing comma, or a typoCrush accepts only strict JSON: fix the file. The installer doesn't strip comments itself, but it does warn about them, and it won't touch a file that fails to parse at all — it will tell you why
failed to load shell config …/crushrc: … exit status 1An error in crushrc, most often an unset variable with a key in the form ${…:?}Export the variable before launching, or fix the line where the script fails
Failed to override models: large model "…" not foundA typo in the model name passed to the -m flagCopy the name from the output of crush models joingonka
too many requests: Model "…" is currently overloaded in the Gonka network (rate limit)The model has run out of free capacity on the network during peak hoursCrush retries the request on its own with growing pauses — about a minute in our run — and only then gives up. Switch to another model with ctrl+l or -m, or wait; the status is visible on the status page
Crush opened on a different model than the installer reportedThe interface selection is stored in a state file and overrides the configPick a model with ctrl+l or rerun the installation with --model
402The balance has run out of fundsTop up your account in the "Billing" section; the key itself is still valid

How much does it cost

An agent spends tokens differently than a chat: for every phrase you send, Crush adds a system prompt and descriptions of its tools, and a task usually takes several turns. In our test run, each request to the model carried about 11,500 input tokens, and the task of "reading a file and finding an error" took two to three requests and 23,000–35,000 tokens, almost entirely input. Therefore, the price per token is the deciding factor here.

Via the JoinGonka Gateway, tokens cost $0.0069 per million on input and $0.021 per million on output—the price is the same for all network models and is fetched on this page from a live source.

ScenarioConsumptionVia Gateway
One-time task: read a file, find an error23-35K tokenshundredths of a cent
A day of active work3-7M tokensa few cents
A month of active development~150M tokensabout a dollar

The estimates in the right column are based on prices as of September 2026. For comparison—how you can pay for models in Crush:

MethodPayment modelLimitations
Hyper — official Charm providersubscription, free tier availableTerms of service on the Charm side
Direct vendor keypay for tokens at vendor priceBill grows with session length; price depends on the model
JoinGonka Gatewaypay-per-token, prepaid balanceConsumption visible in the dashboard; no subscriptions or monthly quotas

The cost counter in the Crush interface calculates using price fields from the config. It captures the gateway price at the moment of installation, and the dollar price on the network fluctuates with the GNK exchange rate, so the counter is an approximation. Precise consumption and balance are available in the dashboard, under the "Usage" and "Billing" sections. Why DeepSeek V4 Flash is set to the large slot by default is explained in detail in the model review.

What to keep in mind

Permissions. By default, the Crush interface asks for permission before calling a tool. The --yolo flag turns off all prompts at once, while individually trusted tools are listed in the config:

# crushrc
permissions allow view ls grep

# crush.json
"permissions": { "allowed_tools": ["view", "ls", "grep"] }

Recall that crush run approves everything on its own. This is a property of Crush and does not depend on the model provider.

Request timeout. Crush aborts a request if no fragment of the response arrives from the model for a long time: in version 0.96.1 that's two minutes of silence (the documentation says 60 seconds, but in the code and in our test it's two minutes). The keep-alive pings the gateway uses to hold the connection do not reset this counter — we verified this on a local test bench. Meanwhile, during peak hours the gateway waits up to 150 seconds for the first token from the network, so the margin is worth raising:

# crushrc
option request-timeout 300

# crush.json
"options": { "request_timeout": 300 }

Commit signatures. Commits and pull requests created by Crush get the line Assisted-by: Crush:<model> and the note "Generated with Crush" by default. If you don't want that:

# crushrc
option attribution-trailer-style none
option attribution-generated-with false

# crush.json
"options": { "attribution": { "trailer_style": "none", "generated_with": false } }

Metrics. Crush sends pseudonymous usage statistics to the developers — metadata only, no prompts or responses. It's disabled with the variable CRUSH_DISABLE_METRICS=1 or DO_NOT_TRACK=1. The gateway, for its part, does not store the contents of prompts or responses — only usage aggregates remain in the statistics.

The config is code. Crush executes both crushrc and crush.json with the privileges of your shell: $(…) in a key field will run on load, and a project-level crushrc will fire as soon as you open Crush in that directory. Don't run the agent in someone else's repository without reading its configs.

Crush connects to JoinGonka Gateway with a single command — npx @joingonka/setup --tool crush — or by adding the providers.joingonka block to ~/.config/crush/crush.json: set type to openai-compat, address to https://gate.joingonka.ai/v1, key to jg-…, and models with honest context_window and default_max_tokens settings. The same can be written via multiple provider add and model add lines in crushrc. The large slot is provided by DeepSeek V4 Flash, small — by MiniMax M2.7, and GLM-5.3 Flash — for complex logic. The main trap is the state file: the model selected via ctrl+l takes precedence over the config, so the installer with an explicit --model flag updates it as well. To verify, run crush models joingonka and crush run; the request timeout should be increased to 300 seconds.

Want to learn more?

Explore other sections or start earning GNK right now.

Get a key and free tokens →