SKILL.md


name: updating-llm-client-model-lists description: >- Synchronizes model configurations across Zed and Crush from the Plexus proxy and Hyper. Use when the user asks to update model lists, sync models, refresh available models, or mentions Plexus or Hyper model availability. user-invocable: true license: LicenseRef-MutuaL-1.2 metadata: author: Amolith amolith@secluded.site

Updates LLM client configs from two sources:

  1. Plexus (self-hosted at harp-willow.snowy-egret.ts.net:4000) β€” feeds both Zed (language_models.openai_compatible.plexus) and Crush (providers.plexus.models).
  2. Charm Hyper (hyper.charm.land) and Hyper Staging (hyper.charmcli.dev) β€” feed Zed only (language_models.openai_compatible.hyper and language_models.openai_compatible.hyper-staging respectively). Crush manages its own Hyper models; do not touch them.

Scripts

Two reusable, typed Python scripts live in scripts/ alongside this skill. They fetch and normalise model metadata into a uniform TSV or JSON format. Run them with --json when piping into a config-apply helper, or --tsv (default) for review.

Script Source Auth Notes
scripts/plexus.py Plexus management + metadata APIs 1Password (op read) Unsets OP_SERVICE_ACCOUNT_TOKEN internally, retrieves admin key, does two-step alias β†’ metadata lookup. Excludes nomic-embed-text-v1.5, diff-apply, fix-json, and any alias without a metadata block. Uses floor(context_length * 0.2) for max_completion_tokens.
scripts/hyper.py Hyper /v1/models None Pass --staging to target Hyper Staging. Uses max_output_tokens verbatim (no 20% rule).

Both scripts share scripts/models.py (the Model dataclass, display-name cleaner, and TSV/JSON emitters). All scripts are executable and use #!/usr/bin/env python3.

Running the scripts

SKILL_DIR="$HOME/.agents/skills/updating-llm-client-model-lists/scripts"

# Plexus β€” TSV for review
"$SKILL_DIR/plexus.py" --tsv | column -t -s$'\t'

# Plexus β€” JSON for config apply
"$SKILL_DIR/plexus.py" --json

# Hyper β€” TSV
"$SKILL_DIR/hyper.py" --tsv | column -t -s$'\t'

# Hyper Staging β€” JSON
"$SKILL_DIR/hyper.py" --staging --json

The TSV/JSON output is the single source of truth for model IDs, display names, context windows, and capability flags. Review the display names in the table, and if they need modification, make the modification while editing the configs.

jq

The jq on PATH inside Crush is a shell function, not the real binary. Always invoke the real one via mise exec jq -- jq ….

JSONC

Zed's settings.json is JSONC (allows trailing commas and comments), so stdlib jq cannot parse it. Use Python with the json5 module instead:

import json, json5, pathlib
src = pathlib.Path("/home/amolith/.config/zed/settings.json")
data = json5.loads(src.read_text())
# …mutate data…
out = json.dumps(data, indent=4, ensure_ascii=False).replace("    ", "\t")
src.write_text(out)

Network reachability

  1. Ping harp-willow.snowy-egret.ts.net once.
  2. If unreachable and Tailscale DNS is disabled, enable it:
    tailscale set --accept-dns=true
    
  3. Test ping again. If still unreachable, STOP and inform the user.

Remember to disable after finishing the whole process if it was previously disabled:

tailscale set --accept-dns=false

max_completion_tokens

Different sources use different rules:

  • Plexus: use floor(context_length * 0.2). Do NOT use the top_provider.max_completion_tokens field β€” some models report their entire context window, which is not a sensible default. scripts/plexus.py applies this automatically.
  • Hyper / Hyper Staging: use the max_output_tokens field verbatim. scripts/hyper.py uses this directly (no 20% rule).

Rules

Exclusions (Plexus)

  • Always exclude nomic-embed-text-v1.5 (embedding model, not supported by any client)
  • Exclude diff-apply and fix-json from all client model lists
  • Exclude models with no context_length field (e.g. voxtral-small, mistral-ocr) unless the user provides values manually
  • Only aliases with a metadata block are fetched; aliases without metadata (like diff-apply, fix-json, memri, smol) are implicitly excluded.

Capabilities

  • Assume every Plexus-listed model supports tools, regardless of whether Plexus reports tools in supported_parameters.
  • For Hyper and Hyper Staging models, every entry supports tools and interleaved reasoning; set tools: true, parallel_tool_calls: true, and interleaved_reasoning: true on every Hyper entry.

Display names

scripts/models.py:clean_name() handles normalisation for both sources:

  • Strip redundant vendor prefixes (DeepSeek: DeepSeek V4 Pro β†’ DeepSeek V4 Pro)
  • Remove parentheses ((fast) β†’ fast)
  • Replace hyphens with spaces
  • Split brand-version compounds (Qwen3.6 β†’ Qwen 3.6)
  • Normalise capitalisation for known brands and acronyms (DeepSeek, GLM, GPT, MiMo, MiniMax, Qwen, OSS, FP8, INT4, etc.)

When a Plexus alias ID ends in -h (indicating a hosted variant), the display name from Plexus already reflects the correct form; no special suffix handling is needed beyond clean_name().

Update Zed config

File: ~/.config/zed/settings.json (JSONC β€” edit via Python json5, see above).

Plexus

Update language_models.openai_compatible.plexus.available_models[]. Each entry:

{
  "name": "<id>",
  "display_name": "<name>",
  "max_tokens": <context_length>,
  "max_completion_tokens": <max_completion_tokens>,
  "capabilities": {
    "chat_completions": true,
    "prompt_cache_key": false,
    "tools": true,
    "parallel_tool_calls": true,
    "images": <supports_image>
  }
}

Hyper

Update language_models.openai_compatible.hyper.available_models[]. Each entry:

{
  "name": "<id>",
  "display_name": "<name>",
  "max_tokens": <context_window>,
  "max_completion_tokens": <max_output_tokens>,
  "capabilities": {
    "chat_completions": false,
    "prompt_cache_key": false,
    "tools": true,
    "parallel_tool_calls": true,
    "interleaved_reasoning": true,
    "images": <supports_attachments>
  }
}

Hyper Staging

Update language_models.openai_compatible.hyper-staging.available_models[] with the same schema as Hyper. The api_url is https://hyper.charmcli.dev/v1.

Example apply helper for all three Zed providers:

import json, json5, pathlib, subprocess

SKILL_DIR = "$HOME/.agents/skills/updating-llm-client-model-lists/scripts"
src = pathlib.Path.home() / ".config/zed/settings.json"
data = json5.loads(src.read_text())
om = data["language_models"]["openai_compatible"]

# Plexus
plexus = json.loads(subprocess.check_output([SKILL_DIR + "/plexus.py", "--json"]))
om["plexus"]["available_models"] = [{
    "name": m["id"], "display_name": m["name"],
    "max_tokens": m["context_length"],
    "max_completion_tokens": m["max_completion_tokens"],
    "capabilities": {"chat_completions": True, "prompt_cache_key": False,
        "tools": True, "parallel_tool_calls": True, "images": m["supports_image"]},
} for m in plexus]

# Hyper
def hyper_entries(staging=False):
    cmd = [SKILL_DIR + "/hyper.py", "--json"]
    if staging: cmd.append("--staging")
    models = json.loads(subprocess.check_output(cmd))
    return [{
        "name": m["id"], "display_name": m["name"],
        "max_tokens": m["context_length"],
        "max_completion_tokens": m["max_completion_tokens"],
        "capabilities": {"chat_completions": False, "prompt_cache_key": False,
            "tools": True, "parallel_tool_calls": True,
            "interleaved_reasoning": True, "images": m["supports_image"]},
    } for m in models]

om["hyper"]["available_models"] = hyper_entries()
om["hyper-staging"] = {
    "api_url": "https://hyper.charmcli.dev/v1",
    "available_models": hyper_entries(staging=True),
}

out = json.dumps(data, indent=4, ensure_ascii=False).replace("    ", "\t")
src.write_text(out)

Update Crush config

File: ~/.local/share/chezmoi/dot_config/crush/crush.json

Update providers.plexus.models[]. Each entry:

{
  "id": "<id>",
  "name": "<name>",
  "context_window": <context_length>,
  "default_max_tokens": <max_completion_tokens>,
  "can_reason": <supports_reasoning>,
  "supports_attachments": <supports_image>
}

This file is strict JSON β€” safe to edit with mise exec jq directly:

"$SKILL_DIR/plexus.py" --json > /tmp/plexus_models.json
mise exec jq -- jq --tab --slurpfile new /tmp/plexus_models.json \
  '.providers.plexus.models = [$new[0][] | {
    "id": .id, "name": .name,
    "context_window": .context_length,
    "default_max_tokens": .max_completion_tokens,
    "can_reason": .supports_reasoning,
    "supports_attachments": .supports_image
  }]' \
  ~/.local/share/chezmoi/dot_config/crush/crush.json > /tmp/crush_new.json
mv /tmp/crush_new.json ~/.local/share/chezmoi/dot_config/crush/crush.json

After editing, run chezmoi apply ~/.config/crush/crush.json. STOP if chezmoi has any output other than success. Do not touch providers.hyper β€” Crush manages its own Hyper models.