Crush Config
Configure Crush settings, providers, servers, and permissions via JSON.
Installation
- 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 runclaudein any terminal to verify.One-time setupnpm i -g @anthropic-ai/claude-codeAlready have it? Skip ahead.
- 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 - Restart Claude Code.
Quit and reopen Claude Code (or any other agent that loads from
~/.claude/skills/). New skills are picked up on startup. - 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):
.crush.json(project-local, hidden)crush.json(project-local)$XDG_CONFIG_HOME/crush/crush.jsonor$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):
$VARand${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
| 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.
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
providerswithtype,base_url,api_key, andmodels. - Disable a builtin or local skill: add the skill name to
options.disabled_skills. - Add an MCP server: add an entry under
mcpwithtypeand eithercommand(stdio) orurl(http/sse).
Model Selection
{
"models": {
"large": {
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"max_tokens": 16384
},
"small": {
"model": "claude-haiku-4-20250514",
"provider": "anthropic"
}
}
}
largeis the primary coding model;smallis for summarization.- Only
modelandproviderare 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, oranthropicapi_key,base_url,api_endpoint, andextra_headersare shell-expanded (see Shell Expansion).extra_bodyis 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,envcover most setups.command,args, andenvvalues 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, orhttpcommand,args,env,headers, andurlare 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 viash -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
- When a tool is about to be called, all
PreToolUsehooks with a matchingmatcher(or no matcher) run in parallel. - Duplicate commands are deduplicated — each unique command runs at most once.
- 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
| 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) |
Hook Output
Exit code 0 — the hook succeeded. Stdout is parsed as JSON:
{"decision": "allow", "context": "optional context appended to tool result"}
decision:allowto explicitly allow,denyto 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 locationCRUSH_GLOBAL_DATA- Override data directory locationCRUSH_SKILLS_DIR- Override default skills directory
Related skills
Generative Code Art
anthropics
Create algorithmic art with p5.js using randomness and interactive parameters.
Poster & Visual Design
anthropics
Create original posters and visual art in PNG and PDF formats.
Claude API Helper
anthropics
Build, debug, and optimize Claude API applications with caching and model migration support.
MCP Server Builder
anthropics
Build protocol servers that connect language models to external APIs and services.