# Crush Config Manage Models

> Use when user wants to inspect or plan Crush model configuration, split provider definitions in crushrc from model definitions in crush.json, retrieve model catalogs from Catwalk or LiteLLM, or compare configured and available models before editing config through the built-in crush-config skill.

- Skill: `detro/crush-config-manage-models` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add detro/crush-config-manage-models`
- Raw SKILL.md: https://api.skillmd.com/api/skills/detro/crush-config-manage-models/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: Apache-2.0
- Author: detro (https://skillmd.com/u/detro)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/detro/crush-config-manage-models

---


# 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:

1. Which providers and slots are declared in selected `crushrc`?
2. Which models are declared under `providers.<id>.models[]` in `crush.json`?
3. Which models does downloaded Catwalk provider catalog expose?
4. Which models do one or more downloaded LiteLLM `/models` responses 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:

- `crushrc` is 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.json` is validated against Crush model schema. Full `Model` object
  supports fields that `model add` has no flag for, most notably
  `reasoning_levels` (array of selectable effort levels) and
  `default_reasoning_effort`, plus per-model `options`. Models declared with
  `model add` in `crushrc` can 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):

```bash
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):

```json
{
  "$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.json` must match `provider add <id>` exactly.
  Merge is by key and case-sensitive; mismatch silently creates second,
  credential-less provider.
- Keep `crush.json` provider objects minimal: `models` only. Anything also
  present in `crushrc` is redundant, and `crushrc` wins 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 `Model` fields are `id`, `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 false` on provider when
  catalog must be exactly what `crush.json` declares.
- `model large` / `model small` slot selection is orthogonal to the split.
  Slots may stay in `crushrc`; only model *definitions* move to JSON.

## When to Use

- Split existing provider/model configuration across `crushrc` and `crush.json`.
- Add models that need `reasoning_levels`, `default_reasoning_effort`, or
  per-model `options`.
- 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 `/models` responses.
- Compare configured and available model datasets.
- Gather model data before asking `crush-config` to modify Crush configuration.

## When Not to Use

- Editing `crushrc` or `crush.json` without loading built-in `crush-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:

```bash
SKILL_DIR=~/.agents/skills/crush-config-manage-models
```

## Scripts

### `crushrc-models.sh`

```bash
"$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:

```json
[
  {"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`

```bash
"$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`:

```json
[
  {
    "provider": "LiteLLM",
    "id": "claude-sonnet-4",
    "name": "Claude Sonnet 4",
    "can_reason": true,
    "reasoning_levels": ["low", "medium", "high"]
  }
]
```

- `--provider NAME` filters by exact, case-sensitive provider key.
- `--strict` fails when any model omits a schema-required field, and names the
  offending `provider/id` plus missing fields. Run it after writing model
  definitions, because Crush rejects incomplete model objects at load.
- Multiple files merge by `provider/id` with 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`

```bash
"$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:

```text
https://raw.githubusercontent.com/charmbracelet/catwalk/main/internal/providers/configs/<provider>.json
```

### `litellm-models.sh`

```bash
"$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:

```bash
"$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 `/models` response 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:

```bash
"$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:

```bash
"$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:

1. Retrieve configured and available datasets with this skill.
2. Decide target file by concern: provider settings to `crushrc`, model
   definitions to `crush.json`.
3. Load built-in `crush-config` skill.
4. Follow its config discovery, Bash/JSON syntax, provider-before-model rule,
   supported flags, merge semantics, and validation instructions.
5. Present model diff before editing.
6. Validate with `crushjson-models.sh --strict`, then with `crush-config`
   procedure, 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 in `crush.json`.
- [ ] Provider keys match exactly between the two files.
- [ ] No secrets in `crush.json`.
- [ ] `crushjson-models.sh --strict` exits `0`.
- [ ] 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-config` is 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. |

