en un clic
crush-config
// Use when the user needs help configuring Crush — working with crush.json, setting up providers, configuring LSPs, adding MCP servers, managing skills or permissions, or changing Crush behavior.
// Use when the user needs help configuring Crush — working with crush.json, setting up providers, configuring LSPs, adding MCP servers, managing skills or permissions, or changing Crush behavior.
Use when creating a new shell builtin command for Crush (internal/shell/), editing an existing one, or when the user needs to understand how commands are intercepted in Crush's embedded shell.
Use when creating a new builtin skill for Crush, editing an existing builtin skill (internal/skills/builtin/), or when the user needs to understand how the embedded skill system works.
Use when the user wants to add, write, debug, or configure a Crush hook — gating or blocking tool calls, approving or rewriting tool input before execution, injecting context into tool results, or troubleshooting hook behavior in crush.json.
Use when the user needs to query, filter, reshape, extract, create, or construct JSON data — including API responses, config files, log output, or any structured data — or when helping the user write or debug JSON transformations.
| name | crush-config |
| description | Use when the user needs help configuring Crush — working with crush.json, setting up providers, configuring LSPs, adding MCP servers, managing skills or permissions, or changing Crush behavior. |
Crush uses JSON configuration files with the following priority (highest to lowest):
.crush.json (project-local, hidden)crush.json (project-local)$XDG_CONFIG_HOME/crush/crush.json or $HOME/.config/crush/crush.json (global){
"$schema": "https://charm.land/crush.json",
"models": {},
"providers": {},
"mcp": {},
"lsp": {},
"hooks": {},
"options": {},
"permissions": {},
"tools": {}
}
The $schema property enables IDE autocomplete but is optional.
Crush runs selected string fields through an embedded bash-compatible shell at load time, so values can pull from env vars, files, or helper commands.
Supported constructs (match the bash tool):
$VAR and ${VAR}${VAR:-default}, ${VAR:+alt}, ${VAR:?message}$(command) with full quoting and nestingDefault semantics match bash: an unset variable expands to an empty
string, no error. A failing $(command) is always a hard error. For
required credentials, use ${VAR:?message} so a missing variable
fails loudly at load time with your message.
{ "api_key": "${CODEBERG_TOKEN:?set CODEBERG_TOKEN}" }
| Surface | Expansion |
|---|---|
Provider api_key, base_url, api_endpoint | yes |
Provider extra_headers | yes |
Provider extra_body | no |
MCP command, args, env, headers, url | yes |
LSP command, args, env | yes |
Hook command | runs via sh -c, not the resolver |
extra_body is a JSON passthrough. If you need env-driven values in
a request body, put them in extra_headers, api_key, or
base_url instead.
When a header value resolves to the empty string (unset variable,
$(echo), or literal ""), the header is omitted from the
outgoing request. This keeps optional env-gated headers like
"OpenAI-Organization": "$OPENAI_ORG_ID" working cleanly when the
var isn't set. Applies to MCP headers and provider extra_headers.
crush.json is trusted code. Any $(...) in it runs at load time
with the invoking user's shell privileges, before the UI appears.
Don't launch Crush in a directory whose crush.json you haven't
reviewed.
providers with type, base_url, api_key, and models.options.disabled_skills.mcp with type and either command (stdio) or url (http/sse).{
"models": {
"large": {
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"max_tokens": 16384
},
"small": {
"model": "claude-haiku-4-20250514",
"provider": "anthropic"
}
}
}
large is the primary coding model; small is for summarization.model and provider are required.reasoning_effort, think, max_tokens, temperature, top_p, top_k, frequency_penalty, presence_penalty, provider_options.{
"providers": {
"deepseek": {
"type": "openai-compat",
"base_url": "https://api.deepseek.com/v1",
"api_key": "$DEEPSEEK_API_KEY",
"models": [
{
"id": "deepseek-chat",
"name": "Deepseek V3",
"context_window": 64000
}
]
}
}
}
type (required): openai, openai-compat, or anthropicapi_key, base_url, api_endpoint, and extra_headers are shell-expanded (see Shell Expansion).extra_body is a JSON passthrough and is not expanded.disable, system_prompt_prefix, extra_headers, extra_body, provider_options.{
"lsp": {
"go": {
"command": "gopls",
"env": { "GOPATH": "$HOME/go" }
},
"typescript": {
"command": "typescript-language-server",
"args": ["--stdio"]
}
}
}
command (required), args, env cover most setups.command, args, and env values are shell-expanded (see Shell Expansion).disabled, filetypes, root_markers, init_options, options, timeout.{
"mcp": {
"filesystem": {
"type": "stdio",
"command": "node",
"args": ["/path/to/mcp-server.js"]
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer $GH_PAT"
}
}
}
}
type (required): stdio, sse, or httpcommand, args, env, headers, and url are shell-expanded (see Shell Expansion).env, disabled, disabled_tools, timeout.{
"options": {
"skills_paths": ["./skills"],
"disabled_tools": ["bash", "sourcegraph"],
"disabled_skills": ["crush-config"],
"tui": {
"compact_mode": false,
"diff_mode": "unified",
"transparent": false
},
"auto_lsp": true,
"debug": false,
"debug_lsp": false,
"attribution": {
"trailer_style": "assisted-by",
"generated_with": true
}
}
}
[!IMPORTANT] The following skill paths are loaded by default and DO NOT NEED to be added to
skills_paths:.agents/skills,.crush/skills,.claude/skills,.cursor/skills
Other options: context_paths, progress, disable_notifications, disable_auto_summarize, disable_metrics, disable_provider_auto_update, disable_default_providers, data_directory, initialize_as.
Skills can be made invocable as commands from the commands palette. Add user-invocable: true to the skill's YAML frontmatter:
---
name: my-skill
description: A skill that can be invoked as a command.
user-invocable: true
---
User-invocable skills appear in the commands palette with a prefix:
user:skill-nameproject:skill-nameWhen invoked, the skill's instructions are loaded into the conversation context.
To prevent the model from auto-triggering a skill (while still allowing user invocation), add disable-model-invocation: true:
---
name: my-skill
description: Only invocable by users, not the model.
user-invocable: true
disable-model-invocation: true
---
Skills with disable-model-invocation won't appear in the model's available skills list but can still be invoked manually by users.
Hooks are user-defined shell commands that fire on agent events. Currently only PreToolUse is supported, which runs before a tool is executed.
{
"hooks": {
"PreToolUse": [
{
"matcher": "^(edit|write|multiedit)$",
"command": ".crush/hooks/protect-files.sh"
},
{
"matcher": "^bash$",
"command": ".crush/hooks/no-haskell.sh"
}
]
}
}
command (required): Shell command to execute. Runs via sh -c.matcher (optional): Regex pattern tested against the tool name. Empty or absent means match all tools.timeout (optional): Timeout in seconds. Defaults to 30.Event names are case-insensitive and accept snake_case variants: PreToolUse, pretooluse, pre_tool_use, and PRE_TOOL_USE all work.
PreToolUse hooks with a matching matcher (or no matcher) run in parallel.A JSON payload is piped to the hook command:
{
"event": "PreToolUse",
"session_id": "abc-123",
"cwd": "/path/to/project",
"tool_name": "bash",
"tool_input": {"command": "ls -la"}
}
| Variable | Description |
|---|---|
CRUSH_EVENT | Event name (e.g. PreToolUse) |
CRUSH_TOOL_NAME | Name of the tool being called |
CRUSH_SESSION_ID | Current session ID |
CRUSH_CWD | Current working directory |
CRUSH_PROJECT_DIR | Project root directory |
CRUSH_TOOL_INPUT_COMMAND | Value of command from tool input (if present) |
CRUSH_TOOL_INPUT_FILE_PATH | Value of file_path from tool input (if present) |
Exit code 0 — the hook succeeded. Stdout is parsed as JSON:
{"decision": "allow", "context": "optional context appended to tool result"}
decision: allow to explicitly allow, deny to block, none (or omit) for no opinion.reason: Explanation text (used when denying).context: Extra context appended to the tool result.updated_input: Replacement JSON for the tool input. Last non-empty value wins.Exit code 2 — the tool call is blocked. Stderr is used as the deny reason.
echo "No Haskell allowed" >&2
exit 2
Any other exit code — non-blocking error. The tool call proceeds as normal.
Crush also supports the Claude Code hook output format:
{
"hookSpecificOutput": {
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-approved",
"updatedInput": {"command": "echo rewritten"}
}
}
Existing Claude Code hooks should work without modification.
When multiple hooks match, their decisions are aggregated:
updated_input, the last non-empty value wins.{
"permissions": {
"allowed_tools": ["view", "ls", "grep", "edit"]
}
}
CRUSH_GLOBAL_CONFIG - Override global config locationCRUSH_GLOBAL_DATA - Override data directory locationCRUSH_SKILLS_DIR - Override default skills directory