name: c-thru-config
description: |
Unified c-thru configuration: diagnose the active setup, resolve what a
capability alias maps to, switch connectivity modes, remap per-capability
models, validate the config, or reload the running proxy.
Subcommands: diag [--verbose] | resolve | mode [] [--reload] | remap [--tier ] [--reload] | set-cloud-best-model [--tier ] [--reload] | set-local-best-model [--tier ] [--reload] | route [--reload] | backend [--kind ] [--auth-env ] [--reload] | agent list | agent set [--reload] | agent pin [--reload] | agent reset [--reload] | alias list | alias set [--reload] | alias remove [--reload] | override list | override set [--reload] | override remove [--reload] | validate | reload | restart [--force]
color: cyan
/c-thru-config — Unified Config & Diagnostics
Routing
Parse the first word of $ARGUMENTS as SUBCOMMAND. Route to the matching
section below.
NL fallthrough
If $ARGUMENTS is empty or the SUBCOMMAND is not a known subcommand, treat
the full $ARGUMENTS as a natural-language intent. Do NOT print static
usage. Instead, interpret what the user wants and map it to the correct
subcommand below, constructing the arguments as needed. Ask one short
clarifying question only if the intent is genuinely ambiguous.
Intent mapping table — map user phrasing to concrete actions:
| User says (examples) | Action | Notes |
|---|---|---|
| "switch to local", "local mode", "best-local-oss", "disconnect", "switch to offline", "go offline", "offline mode" | mode best-local-oss --reload |
apply immediately |
| "switch to cloud", "cloud mode", "best-cloud", "switch to connected", "go online", "connected mode" | mode best-cloud --reload |
apply immediately |
| "use best opensource cloud", "best-cloud-oss", "open source cloud", "best open source" | mode best-cloud-oss --reload |
|
| "what mode am I in", "show mode", "current mode" | mode (read) |
|
| "use |
remap <cap> <model> [--tier] |
e.g. "use qwen for coding" → remap coder qwen3-coder-next:cloud; default tier is active |
| "set cloud model for |
set-cloud-best-model <cap> <model> [--tier] |
|
| "set local model for |
set-local-best-model <cap> <model> [--tier] |
|
| "route |
route <model> <backend> |
|
| "add backend |
backend <name> <url> [--kind] [--auth-env] |
|
| "list agents", "show agents", "agent table" | agent list |
|
| "pin |
agent pin <agent> <model> --reload |
apply immediately |
| "reset |
agent reset <agent> --reload |
apply immediately |
| "move |
agent set <agent> <cap> --reload |
|
| "show overrides", "list model overrides", "what overrides are set" | override list |
|
| "override |
override set <from> <to> --reload |
unconditional substitution before all routing |
| "remove override for |
override remove <model> --reload |
|
| "show aliases", "list entry aliases", "what aliases are set" | alias list |
|
| "alias sonnet to |
alias set claude-sonnet <cap> --reload |
substring match — covers all sonnet versions |
| "alias opus to |
alias set claude-opus <cap> --reload |
|
| "alias haiku to |
alias set claude-haiku <cap> --reload |
|
| "remove sonnet alias", "disable sonnet alias", "stop redirecting sonnet" | alias remove claude-sonnet --reload |
|
| "remove opus alias", "disable opus alias" | alias remove claude-opus --reload |
|
| "set default model to |
alias set claude-sonnet <cap> --reload |
maps the most common entry-point model |
| "validate", "check config" | validate |
|
| "reload", "refresh proxy" | reload |
|
| "restart proxy", "bounce proxy", "bounce the proxy" | restart |
|
| "restart proxy force", "force restart" | restart --force |
|
| "diagnostics", "diag", "what's happening", "status", "show status", "health" | diag [--verbose] |
|
| "verbose diagnostics", "detailed status" | diag --verbose |
|
| "what does |
resolve <cap_or_agent> |
|
| "disable planner hint", "stop planning hints" | planning off |
|
| "enable planner hint", "start planning hints" | planning on |
|
| "toggle planner hint", "flip planner hint" | toggle → planning (infer current state, then enable/disable) |
Capability/agent reference table — when the user mentions a capability or agent by a natural-language role, resolve to the canonical key:
| User says | Canonical capability/agent |
|---|---|
| "coder", "coding", "code" | coder |
| "fallback coder", "backup coder" | coder-fallback |
| "planner", "planning", "orchestrator" | planner |
| "hard planner", "hardest planning", "complex plan" | planner-hard |
| "explorer", "explore", "discovery", "search" | explore |
| "tester", "test writer", "tests" | tester |
| "docs", "documentation writer" | docs |
| "reviewer", "review", "routine review", "code review" | code-reviewer |
| "security reviewer", "security review", "sec review" | reviewer-security |
| "hypothesis", "debug hypothesis" | debugger-hypothesis |
| "investigator", "debug investigate" | debugger-investigate |
| "hard debugger", "hard debug", "deep debug" | debugger-hard |
| "vision", "image", "screenshot" | vision |
| "pdf", "document reading" | pdf |
| "generalist", "general purpose" | generalist |
| "fast generalist", "fast" | fast-generalist |
| "fast scout", "scout", "quick search" | fast-scout |
| "long context", "large context" | long-context |
| "edge", "small tasks", "minimal" | edge |
| "prose writer", "long-form writing" | writer |
Model name shorthand — when the user mentions a model by a short/partial
name, expand to the full tag registered in model_routes:
| User says | Full model tag |
|---|---|
| "qwen coder", "qwen code" | qwen3-coder-next:cloud |
| "qwen 35b", "qwen coding" | qwen3.6:35b-a3b-coding-nvfp4 or qwen3.6:35b-a3b-coding-mxfp8 (ask if ambiguous) |
| "qwen 27b", "qwen fast" | qwen3.6:27b-coding-nvfp4 |
| "deepseek r1", "r1" | deepseek-r1:32b |
| "deepseek v4", "ds v4" | deepseek-v4-pro:cloud |
| "gemma 4", "gemma" | gemma4:26b |
| "gemma e2b" | gemma4:e2b |
| "sonnet", "claude sonnet" | claude-sonnet-4-6 |
| "opus", "claude opus" | claude-opus-4-7 |
| "haiku", "claude haiku" | claude-haiku-4-5-20251001 |
| "devstral", "devstral small" | devstral-small-2:24b |
| "mistral", "mistral small" | mistral-small3.1:24b |
| "gpt oss" | gpt-oss:20b |
After constructing the subcommand and arguments from the intent table, execute that subcommand's block exactly as if the user had typed it directly.
Subcommand: resolve
Usage: /c-thru-config resolve <capability>
Answers "under the current mode and hardware tier, what concrete model will
<capability> use?" Accepts capability keys (coder, planner, code-reviewer) and agent names.
Extract capability name from $ARGUMENTS. If missing, print usage and stop.
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(cd "$(dirname "$0")/.." && pwd)")
node "$REPO_ROOT/tools/c-thru-config-helpers.js" resolve "<CAPABILITY>"
Exit codes: 0 on resolved, 2 on unknown capability, 1 on config error.
Subcommand: mode
Usage: /c-thru-config mode — show active mode and its source
Usage: /c-thru-config mode <mode> [--reload] — persistently set mode; --reload sends SIGHUP immediately after
Read (no second argument in $ARGUMENTS):
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(cd "$(dirname "$0")/.." && pwd)")
node "$REPO_ROOT/tools/c-thru-config-helpers.js" mode-read
Write — validate <mode> is one of the five valid values, then:
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(cd "$(dirname "$0")/.." && pwd)")
node "$REPO_ROOT/tools/c-thru-config-helpers.js" mode-write "<MODE>" [--reload]
If model-map-edit is not found (install.sh not run), the helper prints an error and exits 1.
Subcommand: remap
Usage: /c-thru-config remap <capability> <model> [--tier <tier>] [--reload]
Rebinds llm_profiles[<tier>][<capability>] in the user's overrides.
Default tier is the active hardware tier unless --tier is given.
--reload sends SIGHUP to the running proxy immediately after a successful edit.
Extract <CAPABILITY>, <MODEL>, and optionally --tier <TIER> and --reload from $ARGUMENTS.
If capability or model is missing, print usage and stop.
Steps: Extract <TIER>, <CAPABILITY>, <MODEL>, and optional --reload from $ARGUMENTS.
Delegate entirely to c-thru-config-helpers.js:
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
remap "<TIER>" "<CAPABILITY>" "<MODEL>" [--reload]
The helper reads the active mode via resolveLlmMode, then sets
llm_profiles[<CAPABILITY>][<activeMode>][<TIER>] = <MODEL> in overrides.
On success the helper prints:
remapped <CAPABILITY> → <MODEL> for mode <MODE> (tier: <TIER>)- If
--reloadabsent: also printsrun '/c-thru-config reload' to apply to running proxy - If
--reloadpresent: callsc-thru reloadimmediately after writing
Subcommand: set-cloud-best-model
Usage: /c-thru-config set-cloud-best-model <capability> <model> [--tier <tier>] [--reload]
Sets llm_profiles[<CAPABILITY>][best-cloud][<TIER>] in overrides — the model used when
CLAUDE_LLM_MODE=best-cloud at the given tier.
Extract <TIER>, <CAPABILITY>, <MODEL>, and optional --reload from $ARGUMENTS.
Delegate entirely to c-thru-config-helpers.js:
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
set-cloud-best "<TIER>" "<CAPABILITY>" "<MODEL>" [--reload]
On success prints set cloud model for <CAPABILITY> → <MODEL> in mode best-cloud (tier: <TIER>).
If --reload present, calls c-thru reload after writing.
Subcommand: set-local-best-model
Usage: /c-thru-config set-local-best-model <capability> <model> [--tier <tier>] [--reload]
Sets llm_profiles[<CAPABILITY>][best-local-oss][<TIER>] in overrides — the model used when
CLAUDE_LLM_MODE=best-local-oss at the given tier.
Extract <TIER>, <CAPABILITY>, <MODEL>, and optional --reload from $ARGUMENTS.
Delegate entirely to c-thru-config-helpers.js:
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
set-local-best "<TIER>" "<CAPABILITY>" "<MODEL>" [--reload]
On success prints set local model for <CAPABILITY> → <MODEL> in mode best-local-oss (tier: <TIER>).
If --reload present, calls c-thru reload after writing.
Subcommand: route
Usage: /c-thru-config route <model> <backend> [--reload]
Binds a specific model name to a named backend in model_routes. Any request using that
exact model string will be forwarded to <backend> regardless of the route graph.
Extract <MODEL>, <BACKEND>, and optional --reload from $ARGUMENTS. Both <MODEL> and <BACKEND> are required.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
SPEC=$(node -e "process.stdout.write(JSON.stringify({model_routes: {[process.argv[1]]: process.argv[2]}}))" -- "<MODEL>" "<BACKEND>")
node "$CLAUDE_DIR/tools/model-map-edit" \
"$CLAUDE_DIR/model-map.system.json" \
"$CLAUDE_DIR/model-map.overrides.json" \
"$CLAUDE_DIR/model-map.json" \
"$SPEC"
On success:
- If
--reloadis absent: printbound <MODEL> → backend '<BACKEND>'andrun '/c-thru-config reload' to apply to running proxy - If
--reloadis present: printbound <MODEL> → backend '<BACKEND>'then run:~/.claude/tools/c-thru reload || echo "proxy not running — config saved, will apply on next spawn"
To remove a binding, delete the key directly from ~/.claude/model-map.overrides.json
(setting it to an empty string will be rejected by model-map-edit — use null in a raw
JSON edit instead).
Subcommand: backend
Usage: /c-thru-config backend <name> <url> [--kind <kind>] [--auth-env <VAR>] [--reload]
Adds or updates a backend entry in backends. Default kind is ollama when omitted.
--auth-env sets the env var name that holds the API key (e.g. OPENROUTER_API_KEY).
Note: this subcommand replaces the entire backend entry — it does not merge with
the existing one. If the backend already has fields (e.g. auth_env) that you want to
keep, you must re-pass them explicitly, or edit ~/.claude/model-map.overrides.json
directly.
Extract <NAME>, <URL>, optional --kind <KIND>, optional --auth-env <VAR>, and
optional --reload from $ARGUMENTS. <NAME> and <URL> are required.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
SPEC=$(node -e "
'use strict';
const name = process.argv[1], url = process.argv[2];
const kind = process.argv[3] || 'ollama';
const authEnv = process.argv[4] || null;
const entry = { url, kind };
if (authEnv) entry.auth_env = authEnv;
process.stdout.write(JSON.stringify({ backends: { [name]: entry } }));
" -- "<NAME>" "<URL>" "<KIND_OR_EMPTY>" "<AUTH_ENV_OR_EMPTY>")
node "$CLAUDE_DIR/tools/model-map-edit" \
"$CLAUDE_DIR/model-map.system.json" \
"$CLAUDE_DIR/model-map.overrides.json" \
"$CLAUDE_DIR/model-map.json" \
"$SPEC"
Substitute <KIND_OR_EMPTY> with the --kind value (or empty string to use default ollama),
and <AUTH_ENV_OR_EMPTY> with the --auth-env value (or empty string to omit).
On success:
- If
--reloadis absent: printbackend '<NAME>' set (url: <URL>, kind: <KIND>)andrun '/c-thru-config reload' to apply to running proxy - If
--reloadis present: printbackend '<NAME>' set (url: <URL>, kind: <KIND>)then run:~/.claude/tools/c-thru reload || echo "proxy not running — config saved, will apply on next spawn"
Subcommand: alias
Usage:
/c-thru-config alias list— show all entry_aliases/c-thru-config alias set <pattern> <capability> [--reload]— add/update a substring alias/c-thru-config alias remove <pattern> [--reload]— delete an alias
Entry aliases are substring-matched against the incoming model field before route graph traversal.
Use them to transparently redirect Claude model names (e.g. claude-sonnet-4-6) to a local capability
(e.g. coder) so Claude Code uses local models without any per-session configuration.
Parse the verb (list / set / remove) from $ARGUMENTS. Delegate entirely to c-thru-config-helpers.js:
Verb: list
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" alias-list
Verb: set
Extract <PATTERN>, <CAPABILITY>, and optional --reload from $ARGUMENTS.
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
alias-set "<PATTERN>" "<CAPABILITY>" ${RELOAD_FLAG}
Substitute ${RELOAD_FLAG} with --reload when present in $ARGUMENTS, otherwise empty.
Example: alias set claude-sonnet coder --reload maps all claude-sonnet-* requests to the coder capability.
Verb: remove
Extract <PATTERN> and optional --reload from $ARGUMENTS.
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
alias-remove "<PATTERN>" ${RELOAD_FLAG}
Subcommand: override
Usage:
/c-thru-config override list— show all model_overrides/c-thru-config override set <from> <to> [--reload]— add/update an unconditional model name substitution/c-thru-config override remove <from> [--reload]— delete an override
Model overrides are applied unconditionally to every request's model field before route graph traversal — before entry_aliases, before resolveBackend(). Use them to redirect a specific concrete model tag to another (e.g. gemma4:26b → gemma4:31b when pulling a newer variant).
Difference from entry_aliases: overrides are exact-match and fire before aliases; aliases are substring-match and fire after overrides.
Parse the verb (list / set / remove) from $ARGUMENTS. Delegate entirely to c-thru-config-helpers.js:
Verb: list
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" override-list
Verb: set
Extract <FROM>, <TO>, and optional --reload from $ARGUMENTS.
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
override-set "<FROM>" "<TO>" ${RELOAD_FLAG}
Verb: remove
Extract <FROM> and optional --reload from $ARGUMENTS.
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" \
override-remove "<FROM>" ${RELOAD_FLAG}
Subcommand: restart
Usage: /c-thru-config restart [--force]
Performs a full proxy restart: SIGTERM + waits for listener to vanish + re-spawns (port inherited from env or auto-assigned).
~/.claude/tools/c-thru restart ${FORCE_FLAG}
Substitute ${FORCE_FLAG} with --force if present in $ARGUMENTS, otherwise leave empty.
Subcommand: validate
Usage: /c-thru-config validate
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
node "$CLAUDE_DIR/tools/model-map-validate" "$CLAUDE_DIR/model-map.json" \
&& echo "model-map.json: valid" \
|| echo "model-map.json: INVALID — see errors above"
Subcommand: reload
Usage: /c-thru-config reload
Sends SIGHUP to the running proxy so it re-reads the config.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
PID_FILE="$CLAUDE_DIR/proxy.pid"
if [ ! -f "$PID_FILE" ]; then
echo "reload: no proxy PID file at $PID_FILE — proxy may not be running"
echo " the next 'c-thru' invocation will auto-spawn a fresh proxy"
exit 0
fi
PID=$(cat "$PID_FILE" 2>/dev/null)
if [ -z "$PID" ]; then
echo "reload: PID file is empty — proxy may not be running"
exit 0
fi
if kill -0 "$PID" 2>/dev/null; then
if kill -HUP "$PID" 2>/dev/null; then
echo "reload: sent SIGHUP to proxy (pid $PID)"
else
echo "reload: failed to signal pid $PID — try: pkill -HUP -f claude-proxy"
fi
else
echo "reload: pid $PID is not running — proxy has stopped"
echo " the next 'c-thru' invocation will auto-spawn a fresh proxy"
fi
Subcommand: diag
Usage: /c-thru-config diag [--verbose]
Run the following steps in sequence and display the combined output.
Step 1 — Mode, tier, capability table, and stale-config notice
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
node -e "
'use strict';
const fs = require('fs'), os = require('os'), path = require('path');
const CLAUDE_DIR = process.env.CLAUDE_PROFILE_DIR || path.join(os.homedir(), '.claude');
const mapPath = CLAUDE_DIR + '/model-map.json';
const ovrPath = CLAUDE_DIR + '/model-map.overrides.json';
let config = {}; try { config = JSON.parse(fs.readFileSync(mapPath,'utf8')); } catch {}
let overrides = {}; try { overrides = JSON.parse(fs.readFileSync(ovrPath,'utf8')); } catch {}
const { resolveActiveTier, resolveLlmMode, resolveProfileModel, LLM_MODE_ENUM } = require(path.join(CLAUDE_DIR, 'tools', 'model-map-resolve.js'));
const envMode = process.env.CLAUDE_LLM_MODE;
let mode, modeSource;
if (envMode && LLM_MODE_ENUM.has(envMode)) {
mode = envMode; modeSource = 'CLAUDE_LLM_MODE env (transient)';
} else if (overrides.llm_mode && LLM_MODE_ENUM.has(overrides.llm_mode)) {
mode = overrides.llm_mode; modeSource = ovrPath;
} else if (config.llm_mode && LLM_MODE_ENUM.has(config.llm_mode)) {
mode = config.llm_mode; modeSource = mapPath + ' (system default)';
} else { mode = 'best-cloud'; modeSource = 'built-in default'; }
const gbOverride = process.env.CLAUDE_LLM_MEMORY_GB;
const gb = gbOverride && parseInt(gbOverride,10) > 0 ? parseInt(gbOverride,10) : Math.ceil(os.totalmem()/(1024**3));
const tier = resolveActiveTier(config);
console.log('mode: ' + mode + ' (source: ' + modeSource + ')');
console.log('hw tier: ' + tier + ' (' + gb + ' GB RAM)');
// Stale-config notice: model-map.json written after proxy.pid mtime
try {
const mapMtime = fs.statSync(mapPath).mtimeMs;
const pidMtime = fs.statSync(CLAUDE_DIR + '/proxy.pid').mtimeMs;
if (mapMtime > pidMtime) {
console.log('\nWARNING: config changed since proxy started — run /c-thru-config reload');
}
} catch {}
// Capability → model table
const CAPS = [
'planner','planner-hard','explore','coder','coder-fallback',
'tester','docs','code-reviewer','reviewer-security',
'debugger-hypothesis','debugger-investigate','debugger-hard',
'vision','pdf','writer','edge','generalist','fast-generalist','fast-scout','long-context',
];
const profiles = config.llm_profiles || {};
const maxLen = CAPS.filter(c => profiles[c] && profiles[c][mode]).reduce((m,c) => Math.max(m,c.length), 0);
console.log('\nCapability → model (mode: ' + mode + ', tier: ' + tier + ')');
for (const cap of CAPS) {
const entry = (profiles[cap] || {})[mode]; if (!entry) continue;
const resolved = resolveProfileModel(entry, tier, mode);
const tag = (typeof entry === 'object' && entry.on_failure === 'hard_fail') ? ' [hard_fail]' : '';
console.log(' ' + cap.padEnd(maxLen + 2) + (resolved || '(unresolved)') + tag);
}
"
Step 2 — Proxy status
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
PID_FILE="$CLAUDE_DIR/proxy.pid"
echo ""
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE" 2>/dev/null)
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
echo "proxy: pid $PID running"
# Find the listening port via lsof
PORT=$(lsof -a -iTCP -sTCP:LISTEN -n -P -p "$PID" 2>/dev/null | awk 'NR>1{print $9}' | grep -oE '[0-9]+$' | head -1)
if [ -n "$PORT" ]; then
PING=$(curl -sf --max-time 2 "http://127.0.0.1:$PORT/ping" 2>/dev/null || true)
if [ -n "$PING" ]; then
echo " http://127.0.0.1:$PORT healthy"
else
echo " http://127.0.0.1:$PORT not responding to /ping"
fi
fi
else
echo "proxy: not running (stale pid file: $PID)"
fi
else
echo "proxy: not running (no pid file at $PID_FILE)"
fi
Step 3 — Backend health (delegate to c-thru --list)
Only show this step if the c-thru tool is available. If --verbose was given,
show the full c-thru --list output; otherwise show a brief summary.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
echo ""
echo "Backends:"
"$CLAUDE_DIR/tools/c-thru" --list 2>&1 | head -30 || echo " (c-thru --list unavailable)"
Step 4 — Ollama drift probe (skip if ollama not installed)
Compares the Ollama models referenced by the active tier's profile against those actually pulled. Prints a one-line summary of any missing models.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
if command -v ollama >/dev/null 2>&1; then
echo ""
ollama list 2>/dev/null | node -e "
'use strict';
const fs = require('fs'), os = require('os'), path = require('path'), readline = require('readline');
const CLAUDE_DIR = process.env.CLAUDE_PROFILE_DIR || path.join(os.homedir(), '.claude');
const { resolveActiveTier } = require(path.join(CLAUDE_DIR, 'tools', 'model-map-resolve.js'));
let config = {};
try { config = JSON.parse(fs.readFileSync(CLAUDE_DIR + '/model-map.json','utf8')); } catch {}
const tier = resolveActiveTier(config);
const profiles = config.llm_profiles || {};
const refs = new Set();
for (const modes of Object.values(profiles)) {
if (!modes || typeof modes !== 'object') continue;
for (const tierMap of Object.values(modes)) {
if (!tierMap || typeof tierMap !== 'object') continue;
const m = tierMap[tier];
if (m && typeof m === 'string' && m.includes(':') &&
!m.includes('claude') && !m.includes('gpt')) refs.add(m);
}
}
const pulled = new Set();
const rl = readline.createInterface({ input: process.stdin, terminal: false });
rl.on('line', l => { const t = l.split(/\s+/)[0]; if (t && t !== 'NAME') pulled.add(t); });
rl.on('close', () => {
const missing = [...refs].filter(m => !pulled.has(m));
if (missing.length > 0)
console.log('ollama drift: ' + missing.join(', ') + ' not pulled — run: ollama pull <model>');
else if (refs.size > 0)
console.log('ollama: all ' + refs.size + ' capability-referenced model(s) present');
});
" 2>/dev/null
fi
Subcommand: agent
Usage: /c-thru-config agent <list|set|pin|reset> [...]
Override which model an agent uses without editing config/model-map.json (system config is read-only). Changes are written to ~/.claude/model-map.overrides.json and merged at runtime.
Two override modes:
- Logical remap (
set): change which capability tier an agent routes through (e.g. pointcoder-fallbackatplannerinstead ofcoder) - Direct pin (
pin): skip the capability tier entirely and route an agent straight to a specific model tag
Verb: list
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd 2>/dev/null)" || REPO_ROOT="${CLAUDE_PROFILE_DIR:-$HOME/.claude}/../.."
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" agent-list
Prints a three-column table: AGENT | CAPABILITY | MODEL. Entries with user overrides are marked with *. Pinned entries show [pinned] in the capability column.
Verb: set
Usage: /c-thru-config agent set <agent> <capability> [--reload]
Map <agent> → <capability> alias (logical tier remap). <capability> must be a valid capability key in llm_profiles[activeTier] — use /c-thru-config diag to list valid values.
Extract <AGENT>, <CAPABILITY>, and optional --reload from $ARGUMENTS. Both <AGENT> and <CAPABILITY> are required.
AGENT="<AGENT>"
CAPABILITY="<CAPABILITY>"
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" agent-set "$AGENT" "$CAPABILITY" ${RELOAD_FLAG}
Substitute ${RELOAD_FLAG} with --reload when present in $ARGUMENTS, otherwise empty.
Verb: pin
Usage: /c-thru-config agent pin <agent> <model> [--reload]
Pin <agent> directly to <model>, bypassing capability tier lookup. Uses model: prefix in agent_to_capability internally. The model is resolved through normal model_routes for backend lookup.
Extract <AGENT>, <MODEL>, and optional --reload from $ARGUMENTS. Both <AGENT> and <MODEL> are required.
AGENT="<AGENT>"
MODEL="<MODEL>"
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" agent-pin "$AGENT" "$MODEL" ${RELOAD_FLAG}
Substitute ${RELOAD_FLAG} with --reload when present in $ARGUMENTS, otherwise empty.
Verb: reset
Usage: /c-thru-config agent reset <agent> [--reload]
Remove the user override for <agent>, restoring the system default. Null-deletes the key from model-map.overrides.json. If the agent has no system-default entry, a warning is printed.
Extract <AGENT> and optional --reload from $ARGUMENTS.
AGENT="<AGENT>"
node "${CLAUDE_PROFILE_DIR:-$HOME/.claude}/tools/c-thru-config-helpers.js" agent-reset "$AGENT" ${RELOAD_FLAG}
Substitute ${RELOAD_FLAG} with --reload when present in $ARGUMENTS, otherwise empty.
Subcommand: planning
Usage: /c-thru-config planning [<anything>]
Manages the EnterPlanMode PreToolUse advisory hook that hints about /c-thru-plan
on every Shift+Tab. The hook fires in all Claude Code sessions on this machine.
Intent inference
Parse $ARGUMENTS (after stripping the leading planning word) as free text.
Infer the user's intent from the meaning of the words — do not require exact keywords.
| Intent signals | Action |
|---|---|
| "off", "disable", "turn off", "remove", "stop", "quiet", "no hint", "opt out", "silence" | → disable |
| "on", "enable", "turn on", "add", "register", "activate", "restore" | → enable |
| "toggle", "flip", "switch", "invert" | → invert (see Action: invert below) |
| "status", "check", "what", "show", "is it", "current", "state" | → status |
| empty arguments | → status |
If the intent is unclear or contradictory (e.g. "on but maybe off?"), ask the user one short clarifying question before acting. Do not guess.
Action: status
Report current state — whether the hook is registered and whether the opt-out override is set.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
SETTINGS="$CLAUDE_DIR/settings.json"
OVERRIDES="$CLAUDE_DIR/model-map.overrides.json"
HOOK_CMD="$CLAUDE_DIR/tools/c-thru-enter-plan-hook"
hook_registered=no
if [ -f "$SETTINGS" ] && command -v jq >/dev/null 2>&1; then
count=$(jq -r --arg cmd "$HOOK_CMD" \
'(.hooks.PreToolUse // []) | [.[].hooks[]?.command // ""] | map(select(. == $cmd)) | length' \
"$SETTINGS" 2>/dev/null || echo 0)
[ "${count:-0}" -gt 0 ] && hook_registered=yes
fi
hint_pref=unset
if [ -f "$OVERRIDES" ] && command -v jq >/dev/null 2>&1; then
hint_pref=$(jq -r '.planner_hint // "unset"' "$OVERRIDES" 2>/dev/null || echo unset)
fi
echo "planning hint hook: $hook_registered"
echo "planner_hint pref: $hint_pref"
if [ "${CLAUDE_ROUTER_PLANNER_HINT:-1}" = "0" ]; then
echo "effective: suppressed (CLAUDE_ROUTER_PLANNER_HINT=0 env — hook runs but exits silently)"
elif [ "$hook_registered" = "yes" ] && [ "$hint_pref" != "false" ]; then
echo "effective: on"
elif [ "$hint_pref" = "false" ]; then
echo "effective: off (opt-out in overrides)"
else
echo "effective: off (hook not registered)"
fi
Action: invert
Read current state, then enable if off or disable if on. Single deterministic block — do not split into two steps.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
SETTINGS="$CLAUDE_DIR/settings.json"
OVERRIDES="$CLAUDE_DIR/model-map.overrides.json"
HOOK_CMD="$CLAUDE_DIR/tools/c-thru-enter-plan-hook"
if ! command -v jq >/dev/null 2>&1; then
echo "planning: jq not found" >&2; exit 1
fi
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"
[ -f "$OVERRIDES" ] || echo '{}' > "$OVERRIDES"
count=$(jq -r --arg cmd "$HOOK_CMD" \
'(.hooks.PreToolUse // []) | [.[].hooks[]?.command // ""] | map(select(. == $cmd)) | length' \
"$SETTINGS" 2>/dev/null || echo 0)
hint_pref=$(jq -r '.planner_hint // "unset"' "$OVERRIDES" 2>/dev/null || echo unset)
if [ "${count:-0}" -gt 0 ] && [ "$hint_pref" != "false" ]; then
# currently on → disable
tmp="${SETTINGS}.tmp.$$"
jq --arg cmd "$HOOK_CMD" '
if .hooks.PreToolUse then
.hooks.PreToolUse |= map(.hooks |= map(select((.command // "") != $cmd)))
| .hooks.PreToolUse |= map(select(.hooks | length > 0))
else . end
' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
tmp="${OVERRIDES}.tmp.$$"
jq '.planner_hint = false' "$OVERRIDES" > "$tmp" && mv "$tmp" "$OVERRIDES"
echo "planning hint: toggled off"
echo " re-enable: /c-thru-config planning on"
else
# currently off → enable
if [ "${count:-0}" -eq 0 ]; then
tmp="${SETTINGS}.tmp.$$"
jq --arg cmd "$HOOK_CMD" '
if .hooks == null then .hooks = {} else . end |
if .hooks.PreToolUse == null then .hooks.PreToolUse = [] else . end |
.hooks.PreToolUse += [{"matcher": "EnterPlanMode", "hooks": [{"type": "command", "command": $cmd, "timeout": 3}]}]
' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
fi
tmp="${OVERRIDES}.tmp.$$"
jq 'del(.planner_hint)' "$OVERRIDES" > "$tmp" && mv "$tmp" "$OVERRIDES"
echo "planning hint: toggled on"
echo " disable: /c-thru-config planning off"
fi
Action: disable
Remove the EnterPlanMode hook and write planner_hint: false to overrides.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
SETTINGS="$CLAUDE_DIR/settings.json"
OVERRIDES="$CLAUDE_DIR/model-map.overrides.json"
HOOK_CMD="$CLAUDE_DIR/tools/c-thru-enter-plan-hook"
if ! command -v jq >/dev/null 2>&1; then
echo "planning: jq not found — cannot modify settings.json" >&2
exit 1
fi
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"
[ -f "$OVERRIDES" ] || echo '{}' > "$OVERRIDES"
tmp="${SETTINGS}.tmp.$$"
jq --arg cmd "$HOOK_CMD" '
if .hooks.PreToolUse then
.hooks.PreToolUse |= map(.hooks |= map(select((.command // "") != $cmd)))
| .hooks.PreToolUse |= map(select(.hooks | length > 0))
else . end
' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
tmp="${OVERRIDES}.tmp.$$"
jq '.planner_hint = false' "$OVERRIDES" > "$tmp" && mv "$tmp" "$OVERRIDES"
echo "planning hint: off"
echo " hook removed from settings.json, planner_hint: false written to overrides"
echo " re-enable: /c-thru-config planning on"
Action: enable
Register the EnterPlanMode hook (idempotent) and clear the opt-out override.
CLAUDE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude}"
SETTINGS="$CLAUDE_DIR/settings.json"
OVERRIDES="$CLAUDE_DIR/model-map.overrides.json"
HOOK_CMD="$CLAUDE_DIR/tools/c-thru-enter-plan-hook"
if ! command -v jq >/dev/null 2>&1; then
echo "planning: jq not found — cannot modify settings.json" >&2
exit 1
fi
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"
[ -f "$OVERRIDES" ] || echo '{}' > "$OVERRIDES"
count=$(jq -r --arg cmd "$HOOK_CMD" \
'(.hooks.PreToolUse // []) | [.[].hooks[]?.command // ""] | map(select(. == $cmd)) | length' \
"$SETTINGS" 2>/dev/null || echo 0)
if [ "${count:-0}" -eq 0 ]; then
tmp="${SETTINGS}.tmp.$$"
jq --arg cmd "$HOOK_CMD" '
if .hooks == null then .hooks = {} else . end |
if .hooks.PreToolUse == null then .hooks.PreToolUse = [] else . end |
.hooks.PreToolUse += [{"matcher": "EnterPlanMode", "hooks": [{"type": "command", "command": $cmd, "timeout": 3}]}]
' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
echo "planning hint: hook registered"
else
echo "planning hint: hook already registered"
fi
tmp="${OVERRIDES}.tmp.$$"
jq 'del(.planner_hint)' "$OVERRIDES" > "$tmp" && mv "$tmp" "$OVERRIDES"
echo "planning hint: on"
echo " Note: fires in all Claude Code sessions on this machine"
echo " Disable: /c-thru-config planning off"