AugmentClaude

Crush Config

Configure Crush settings, providers, servers, and permissions via JSON.

Installation

  1. Make sure Claude is on your device and in your terminal.

    Skills load from ~/.claude/skills/ when Claude Code starts up — so you need it on your machine first. If you don't have it yet, install it once with the command below, then run claude in any terminal to verify.

    One-time setup
    npm i -g @anthropic-ai/claude-code

    Already have it? Skip ahead.

  2. Paste into Claude Code or into your terminal.

    This copies the whole skill folder into ~/.claude/skills/crush-config-charmbracelet/ — the SKILL.md plus any scripts, reference docs, or templates the skill ships with. Safe default: works for every skill.

    Faster alternative (instruction-only skills)

    Skips the clone and grabs only the SKILL.md file. Don't use this if the skill ships Python scripts, reference markdowns, or asset templates — they won't be downloaded and the skill will fail when it tries to load them.

    Quick install (SKILL.md only)
    Sign up to copy
  3. Restart Claude Code.

    Quit and reopen Claude Code (or any other agent that loads from ~/.claude/skills/). New skills are picked up on startup.

  4. Just ask Claude.

    Skills auto-activate when your request matches the skill's description — no slash command needed. Trigger phrases live in the skill's own frontmatter; you can read them in the “What this skill does” section above.

Prefer to read the source first? Open on GitHub.

When Claude uses it

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.

What this skill does

Crush Configuration

Crush uses JSON configuration files with the following priority (highest to lowest):

  1. .crush.json (project-local, hidden)
  2. crush.json (project-local)
  3. $XDG_CONFIG_HOME/crush/crush.json or $HOME/.config/crush/crush.json (global)

Basic Structure

{
  "$schema": "https://charm.land/crush.json",
  "models": {},
  "providers": {},
  "mcp": {},
  "lsp": {},
  "hooks": {},
  "options": {},
  "permissions": {},
  "tools": {}
}

The $schema property enables IDE autocomplete but is optional.

Shell Expansion

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 nesting
  • Single- and double-quoted strings, escapes

Default 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}" }

Which fields expand

SurfaceExpansion
Provider api_key, base_url, api_endpointyes
Provider extra_headersyes
Provider extra_bodyno
MCP command, args, env, headers, urlyes
LSP command, args, envyes
Hook commandruns 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.

Empty-resolved headers are dropped

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.

Security note

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.

Common Tasks

  • Add a custom provider: add an entry under providers with type, base_url, api_key, and models.
  • Disable a builtin or local skill: add the skill name to options.disabled_skills.
  • Add an MCP server: add an entry under mcp with type and either command (stdio) or url (http/sse).

Model Selection

{
  "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.
  • Only model and provider are required.
  • Optional tuning: reasoning_effort, think, max_tokens, temperature, top_p, top_k, frequency_penalty, presence_penalty, provider_options.

Custom Providers

{
  "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 anthropic
  • api_key, base_url, api_endpoint, and extra_headers are shell-expanded (see Shell Expansion).
  • extra_body is a JSON passthrough and is not expanded.
  • Additional fields: disable, system_prompt_prefix, extra_headers, extra_body, provider_options.

LSP Configuration

{
  "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).
  • Additional fields: disabled, filetypes, root_markers, init_options, options, timeout.

MCP Servers

{
  "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 http
  • command, args, env, headers, and url are shell-expanded (see Shell Expansion).
  • Additional fields: env, disabled, disabled_tools, timeout.

Options

{
  "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.

User-Invocable Skills

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:

  • Skills from global directories: user:skill-name
  • Skills from project directories: project:skill-name

When 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

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"
      }
    ]
  }
}

Hook Properties

  • 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 Name Normalization

Event names are case-insensitive and accept snake_case variants: PreToolUse, pretooluse, pre_tool_use, and PRE_TOOL_USE all work.

How Hooks Work

  1. When a tool is about to be called, all PreToolUse hooks with a matching matcher (or no matcher) run in parallel.
  2. Duplicate commands are deduplicated — each unique command runs at most once.
  3. The hook receives JSON on stdin and hook-specific environment variables.

Hook Input (stdin)

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"}
}

Hook Environment Variables

VariableDescription
CRUSH_EVENTEvent name (e.g. PreToolUse)
CRUSH_TOOL_NAMEName of the tool being called
CRUSH_SESSION_IDCurrent session ID
CRUSH_CWDCurrent working directory
CRUSH_PROJECT_DIRProject root directory
CRUSH_TOOL_INPUT_COMMANDValue of command from tool input (if present)
CRUSH_TOOL_INPUT_FILE_PATHValue of file_path from tool input (if present)

Hook Output

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.

Claude Code Compatibility

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.

Decision Aggregation

When multiple hooks match, their decisions are aggregated:

  • Deny wins over allow — if any hook denies, the tool call is blocked.
  • Allow wins over none — if no hook denies but at least one allows, the call proceeds.
  • All deny reasons are concatenated (newline-separated).
  • All context strings are concatenated (newline-separated).
  • For updated_input, the last non-empty value wins.

Tool Permissions

{
  "permissions": {
    "allowed_tools": ["view", "ls", "grep", "edit"]
  }
}

Environment Variables

  • CRUSH_GLOBAL_CONFIG - Override global config location
  • CRUSH_GLOBAL_DATA - Override data directory location
  • CRUSH_SKILLS_DIR - Override default skills directory

Related skills