Manage Crush Model Catalogs
Authored and maintained by Ivan De Marino.
Scope: Crush only. This skill retrieves and normalizes model data, and owns the provider/model file-split convention. It does not own Crush configuration syntax, discovery, or editing procedure.
Use four small Bash and jq scripts to answer four questions:
- Which providers and slots are declared in selected
crushrc? - Which models are declared under
providers.<id>.models[]incrush.json? - Which models does downloaded Catwalk provider catalog expose?
- Which models do one or more downloaded LiteLLM
/modelsresponses expose?
For any configuration creation, migration, update, or removal, load and follow
built-in crush-config skill. Separation keeps retrieval scripts stable while
crush-config remains authority for current config paths, syntax, merge
semantics, supported flags, validation, and safe editing.
Configuration Split Convention
Providers belong in crushrc. Model definitions belong in crush.json.
Apply this split always, not only when a model needs advanced fields.
Why
Crush discovers crushrc and crush.json in same directory and deep-merges
them, so single logical provider can be assembled from both files. The split
exploits that merge to put each half of configuration in file format that can
actually express it:
crushrcis Bash. Providers need secrets, environment lookups,${VAR:?message}guards, conditionals, and headers, all of which are natural in Bash and awkward or unsafe in static JSON.crush.jsonis validated against Crush model schema. FullModelobject supports fields thatmodel addhas no flag for, most notablyreasoning_levels(array of selectable effort levels) anddefault_reasoning_effort, plus per-modeloptions. Models declared withmodel addincrushrccan only carry flags that command exposes, so reasoning levels declared that way are simply unrepresentable.
Mixing the two halves is what breaks: a model added in crushrc cannot later
gain reasoning_levels, and a provider defined in crush.json loses Bash
secret handling. Splitting by capability removes both dead ends and keeps
single obvious home for each concern.
Shape
crushrc (providers, plus LSP, MCP, options, permissions):
provider add LiteLLM \
--type anthropic \
--base-url "${LITELLM_URL:?set LITELLM_URL}" \
--extra-header Authorization "Bearer ${LITELLM_TOKEN:?set LITELLM_TOKEN}"
crush.json (model definitions only, keyed by same provider id):
{
"$schema": "https://charm.land/crush.json",
"providers": {
"LiteLLM": {
"models": [
{
"id": "claude-sonnet-4",
"name": "Claude Sonnet 4",
"context_window": 1000000,
"default_max_tokens": 64000,
"can_reason": true,
"reasoning_levels": ["low", "medium", "high"],
"default_reasoning_effort": "medium",
"supports_attachments": true,
"cost_per_1m_in": 3,
"cost_per_1m_out": 15,
"cost_per_1m_in_cached": 3.75,
"cost_per_1m_out_cached": 0.3
}
]
}
}
}
Rules that follow from the split
- Provider key in
crush.jsonmust matchprovider add <id>exactly. Merge is by key and case-sensitive; mismatch silently creates second, credential-less provider. - Keep
crush.jsonprovider objects minimal:modelsonly. Anything also present incrushrcis redundant, andcrushrcwins on conflict. - Never put secrets in
crush.json. Only selected string fields expand there, and the file is far more likely to be shared or committed. - Crush logs a warning when both files exist in one directory. Under this convention that warning is expected and not a defect.
- Required
Modelfields areid,name,cost_per_1m_in,cost_per_1m_out,cost_per_1m_in_cached,cost_per_1m_out_cached,context_window,default_max_tokens,can_reason,supports_attachments. Partial objects fail schema validation, so copy metadata from Catwalk or LiteLLM data rather than inventing it. - If provider auto-discovers models, discovered entries merge with declared
ones and declared entries win. Set
--discover-models falseon provider when catalog must be exactly whatcrush.jsondeclares. model large/model smallslot selection is orthogonal to the split. Slots may stay incrushrc; only model definitions move to JSON.
When to Use
- Split existing provider/model configuration across
crushrcandcrush.json. - Add models that need
reasoning_levels,default_reasoning_effort, or per-modeloptions. - List declared providers, slots, and model definitions per source file.
- Read available models from downloaded Catwalk provider JSON.
- Read and combine available models from downloaded LiteLLM
/modelsresponses. - Compare configured and available model datasets.
- Gather model data before asking
crush-configto modify Crush configuration.
When Not to Use
- Editing
crushrcorcrush.jsonwithout loading built-incrush-config. - Treating static parsing of either file as effective runtime state.
- Editing Crush-owned JSON under data directories
(
~/.local/share/crush,%LOCALAPPDATA%\crush). - Fetching provider catalogs inside helper scripts. Agent native network tools handle network access; scripts consume saved local responses.
Dependencies
All scripts require Bash and jq only. They use set -euo pipefail, validate
input, print JSON on stdout, and report failures on stderr. They never execute
crushrc, access network, or print secrets.
Resolve scripts relative to installed skill:
SKILL_DIR=~/.agents/skills/crush-config-manage-models
Scripts
crushrc-models.sh
"$SKILL_DIR/scripts/crushrc-models.sh" <crushrc> [provider]
Statically reads explicit model add <provider>/<id> declarations. Optional
provider filter is exact and case-sensitive. Output:
[
{"provider":"Gemini","id":"gemini-3-flash"},
{"provider":"LiteLLM","id":"claude-sonnet-4"}
]
Supported input forms include multiline commands ending in backslash and
single- or double-quoted model references. First / separates provider from
model, so model IDs may contain /. Results are unique and sorted.
Under the split convention this script normally returns [], because model
definitions live in crush.json. Non-empty output is therefore a useful
migration signal: those models predate the split and should be moved.
Static parsing cannot resolve conditionals, variables, command substitutions, sourced files, discovered models, or merged runtime state. State this limitation whenever user asks for active/effective models.
crushjson-models.sh
"$SKILL_DIR/scripts/crushjson-models.sh" [--strict] [--provider NAME] <crush.json> [crush.json ...]
Reads model definitions from .providers[<id>].models[] and prints sorted
records shaped as {provider, id, ...model fields}, preserving every declared
field including reasoning_levels:
[
{
"provider": "LiteLLM",
"id": "claude-sonnet-4",
"name": "Claude Sonnet 4",
"can_reason": true,
"reasoning_levels": ["low", "medium", "high"]
}
]
--provider NAMEfilters by exact, case-sensitive provider key.--strictfails when any model omits a schema-required field, and names the offendingprovider/idplus missing fields. Run it after writing model definitions, because Crush rejects incomplete model objects at load.- Multiple files merge by
provider/idwith later file winning. Pass files from lowest to highest precedence so result mirrors Crush merge order.
Reads only files given as arguments, so never point it at machine-owned data directory JSON.
catwalk-models.sh
"$SKILL_DIR/scripts/catwalk-models.sh" <provider.json>
Reads Catwalk JSON already downloaded by agent. Accepts provider object with
.models array or model array. Validates every model has non-empty string id
and prints model array unchanged, preserving metadata needed to author
crush.json model definitions.
Discover valid Catwalk provider filename instead of guessing, then download
with native fetch or download tool. Known layout:
https://raw.githubusercontent.com/charmbracelet/catwalk/main/internal/providers/configs/<provider>.json
litellm-models.sh
"$SKILL_DIR/scripts/litellm-models.sh" [response.json ...]
Reads one or more saved LiteLLM /models responses, or stdin when no files are
provided. Accepts top-level array, .data array, or .models array. String
entries become { "id": "..." }; object entries retain metadata. Duplicate
IDs are combined with later response winning. Output is sorted JSON model array.
Use native authenticated network tool to fetch every required LiteLLM endpoint.
Base URL and token may come from environment, but never execute crushrc to
recover secrets. Never print token or response headers. Multiple files support
multiple LiteLLM endpoints:
"$SKILL_DIR/scripts/litellm-models.sh" \
/tmp/litellm-primary.json \
/tmp/litellm-secondary.json > /tmp/litellm-models.json
LiteLLM /models often returns IDs with little metadata. Fill remaining
required Model fields from Catwalk data or from user-provided values; do not
guess pricing or context windows.
Workflow
Step 1: Locate Inputs
For configuration path discovery and precedence, load built-in crush-config
and follow its current rules. Read both crushrc and crush.json of selected
directory, since split convention spreads one provider across two files. Never
read machine-owned data JSON as user configuration.
Determine required availability source:
- Catwalk provider catalog for Crush-supported provider metadata.
- Live LiteLLM
/modelsresponse for user-specific proxy deployments.
Step 2: Fetch External Data
Use native agent network tools and save responses to temporary files. Scripts must not perform network requests because high-level tools provide clearer permissions, errors, and credential handling.
Step 3: Normalize Datasets
Run one or more scripts. Keep JSON output for reasoning and transformations:
"$SKILL_DIR/scripts/crushrc-models.sh" ~/.config/crush/crushrc \
> /tmp/legacy-crushrc-models.json
"$SKILL_DIR/scripts/crushjson-models.sh" ~/.config/crush/crush.json \
> /tmp/configured.json
"$SKILL_DIR/scripts/catwalk-models.sh" /tmp/gemini.json \
> /tmp/gemini-available.json
"$SKILL_DIR/scripts/litellm-models.sh" /tmp/litellm-response.json \
> /tmp/litellm-available.json
Configured set is union of both configured sources. Report crushrc entries as
pending migration rather than merging them silently, so user sees drift from
convention.
Use jq for selection, joins, counts, and reshaping. Never use --slurpfile;
some jq builds reject it. Load local JSON with --argjson.
Step 4: Report or Configure
For reporting, render Markdown directly from normalized JSON using examples below as layouts, not fixed scripts. Adapt columns to requested providers and always cite source and static-parsing limitation.
For any config change, load built-in crush-config and let it determine format,
commands, supported model flags, insertion/removal behavior, and validation.
This skill supplies model data and target-file decision only. Do not recreate
managed-block or rendering logic here.
Step 5: Verify Split
After any edit, confirm convention still holds:
"$SKILL_DIR/scripts/crushrc-models.sh" ~/.config/crush/crushrc
"$SKILL_DIR/scripts/crushjson-models.sh" --strict ~/.config/crush/crush.json > /dev/null
First command should print []. Second should exit 0. Then confirm Crush
itself resolves merged result, for example through its runtime state tool, since
static parsing proves file contents, not effective configuration.
Visualization Examples
These are output shapes, not shell utilities. Build requested table from JSON with jq or agent reasoning.
Example 1: Configured vs Available for One Provider
| Model | Configured | Available | Reasoning levels |
|---|---|---|---|
gemini-3-flash |
✓ | ✓ | low, medium, high |
gemini-3-pro |
✓ | ||
gemini-2-retired |
✓ | low, high |
Rows are union of configured and available IDs for provider. Configured comes
from crushjson-models.sh; Available comes from Catwalk or LiteLLM script.
Include counts in prose or headers when useful. Name exact availability source.
Example 2: Model Definitions by Provider
| Model | Gemini | LiteLLM | VertexAI |
|---|---|---|---|
gemini-3-flash |
✓ | ✓ | ✓ |
claude-sonnet-4 |
✓ | ✓ | |
gpt-5 |
✓ |
Rows are union of IDs from configured sources; provider columns come from their unique provider values. Mark explicit declaration only. Do not imply model availability or runtime activation.
Example 3: Split Compliance
| Provider | Defined in crushrc |
Models in crush.json |
Models still in crushrc |
|---|---|---|---|
LiteLLM |
✓ | 20 | 0 |
Gemini |
✓ | 0 | 3 |
Use this when auditing or migrating. Any non-zero last column is work to do.
Configuration Handoff
When user requests add, update, remove, migrate, or refresh:
- Retrieve configured and available datasets with this skill.
- Decide target file by concern: provider settings to
crushrc, model definitions tocrush.json. - Load built-in
crush-configskill. - Follow its config discovery, Bash/JSON syntax, provider-before-model rule, supported flags, merge semantics, and validation instructions.
- Present model diff before editing.
- Validate with
crushjson-models.sh --strict, then withcrush-configprocedure, and report changed model IDs.
Catwalk and LiteLLM metadata map closely onto crush.json Model fields, so
prefer copying them into JSON over translating them into model add flags.
Validation Checklist
- Version is
3.0.0. - Providers are declared in
crushrc, model definitions incrush.json. - Provider keys match exactly between the two files.
- No secrets in
crush.json. -
crushjson-models.sh --strictexits0. - Four scripts exist: crushrc, crush.json, Catwalk, LiteLLM retrieval.
- Scripts use Bash and jq only.
- Network access happens through native agent tools.
- Script stdout is valid JSON model data.
- Static parsing limitation is disclosed.
- Availability source is named.
- Built-in
crush-configis loaded before any config edit. - Visualization is generated from JSON, not encoded in helper scripts.
Common Pitfalls
| Pitfall | Resolution |
|---|---|
Adding a model with model add then needing reasoning_levels |
Define model in crush.json; model add has no flag for it. |
| Provider key case mismatch across files | Match provider add <id> exactly; merge is case-sensitive. |
Duplicating provider settings in crush.json |
Keep JSON provider objects to models only. |
| Treating both-files warning as an error | Expected under the split; two files are merged by design. |
| Incomplete model objects in JSON | Run crushjson-models.sh --strict and fill required fields from Catwalk. |
| Unexpected extra models appearing | Provider auto-discovery is on by default; set --discover-models false. |
| Treating parser output as runtime state | Call it explicit static declarations and state limitations. |
| Guessing Catwalk URL or provider filename | Discover provider catalog first, then download known URL. |
Executing crushrc to find secrets or models |
Read declarations statically; use existing environment for API credentials. |
| Adding networking to scripts | Fetch with native agent tools and pass local files. |
| Editing config with this skill's scripts | Hand off all edits to built-in crush-config. |
Using jq --slurpfile |
Use --argjson with local file content. |