linux/claude-mode was 3,035 lines. It is now 177: the paths and flags, a loader, and the command dispatch. Everything else moved, verbatim, into twelve files under linux/lib/, one per concern - output, core, preflight, sessions, switch, catalogue, commands, doctor, presets, menu, setup, repair. The move was done by line range with a check that every original line landed in exactly one file; the only thing that changed place is the switch's running-sessions question, which now sits with session detection. The CLI finds lib/, cm-json.py and cm-vault.sh beside itself, following the ~/.local/bin symlink, so a checkout runs its own code rather than the installed version's (it used to mix the two). The key helper path written into settings.json is still the installed one. linux/install.sh ships bin/lib/, clearing old modules first so a removed one cannot linger. The package build copies linux/ recursively, which a flat copy would not. tests/cli/test_install.sh runs the real installer into a sandbox home: every file lands, the symlink runs, a reinstall keeps edited presets and drops a stale module. docs/architecture.md lists the modules. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
176 lines
7.4 KiB
Bash
176 lines
7.4 KiB
Bash
# shellcheck shell=bash
|
|
# linux/lib/commands.sh - status, presets, models and set-key.
|
|
#
|
|
# Part of the claude-mode CLI: sourced by linux/claude-mode, which sets the
|
|
# CM_* paths, PY and JSON used here. Not meant to run on its own.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Commands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_status() {
|
|
local mode preset
|
|
mode="$(state_mode)"; preset="$(state_preset)"
|
|
head_ "claude-mode: $mode"
|
|
|
|
if [ "$mode" = "anthropic" ]; then
|
|
say 'native Anthropic login/subscription; no gateway env, no apiKeyHelper'
|
|
else
|
|
local pf; pf="$(preset_path "$preset")"
|
|
say "preset: $preset"
|
|
if [ -f "$pf" ]; then
|
|
say "baseUrl: $(jget "$pf" baseUrl)"
|
|
local t v
|
|
for t in "${TIERS[@]}"; do
|
|
v="$(jget "$pf" "models.$t")"; [ -n "$v" ] && printf ' %-10s%s\n' "$t:" "$v"
|
|
done
|
|
v="$(jget "$pf" subagentModel)"; [ -n "$v" ] && printf ' %-10s%s\n' "subagent:" "$v"
|
|
v="$(jget "$pf" contextTokens)"; [ -n "$v" ] && printf ' %-10s%s tokens\n' "context:" "$v"
|
|
local am; am="$(jget "$pf" auth.mode)"; [ -z "$am" ] && am=vault
|
|
if [ "$am" = "vault" ]; then
|
|
local ref; ref="$(jget "$pf" auth.keyRef)"; [ -z "$ref" ] && ref=openrouter
|
|
printf ' %-10s%s -> %s [%s]\n' "key:" "$ref" "$(cm_vault_mask "$(cm_vault_get "$ref" 2>/dev/null || true)")" "$(cm_vault_backend_label)"
|
|
else
|
|
printf ' %-10s%s (inline, not a secret)\n' "token:" "$(jget "$pf" auth.token)"
|
|
fi
|
|
else
|
|
err "preset '$preset' not found"
|
|
fi
|
|
fi
|
|
|
|
printf '\n settings.json managed keys:\n'
|
|
local any=0 line
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] || continue
|
|
printf ' %s\n' "$line"; any=1
|
|
done < <("$PY" "$JSON" settings-env "$CM_SETTINGS" "$CM_STATE" 2>/dev/null)
|
|
[ "$any" -eq 0 ] && printf ' (none - clean)\n'
|
|
|
|
printf '\n'
|
|
check_stray_env "$mode" || true
|
|
|
|
# Keep the machine-readable mirror current for readers that poll it (the
|
|
# Omarchy bar widget among them) rather than leaving it as stale as the
|
|
# last switch.
|
|
write_health "$mode" "$preset"
|
|
}
|
|
|
|
cmd_presets() {
|
|
head_ 'presets'
|
|
local active_mode active_preset name provider desc mark
|
|
active_mode="$(state_mode)"; active_preset="$(state_preset)"
|
|
while IFS=$'\t' read -r name provider desc; do
|
|
mark=' '
|
|
[ "$name" = "$active_preset" ] && [ "$active_mode" != "anthropic" ] && mark='*'
|
|
printf ' %s %-18s [%-10s] %s\n' "$mark" "$name" "$provider" "$desc"
|
|
done < <("$PY" "$JSON" presets "$CM_PRESETS")
|
|
}
|
|
|
|
cmd_models() {
|
|
local filter='' pname='' refresh=0 as_json=0 a pf='' provider base='' tsv rc
|
|
while [ $# -gt 0 ]; do
|
|
a="$1"; shift
|
|
case "$a" in
|
|
--preset)
|
|
[ $# -gt 0 ] || { err '--preset needs a preset name'; return 1; }
|
|
pname="$1"; shift ;;
|
|
--refresh) refresh=1 ;;
|
|
--json) as_json=1 ;;
|
|
-*) err "unknown option '$a'"; return 1 ;;
|
|
*) filter="$a" ;;
|
|
esac
|
|
done
|
|
|
|
# The active preset by default. The panel's editor names one instead,
|
|
# because the preset being edited is often not the one in use - and two
|
|
# LM Studio presets can point at two different servers.
|
|
if [ -z "$pname" ] && [ "$(state_mode)" != "anthropic" ]; then
|
|
pname="$(state_preset)"
|
|
fi
|
|
if [ -n "$pname" ]; then
|
|
pf="$(preset_path "$pname")"
|
|
[ -f "$pf" ] || { err "preset '$pname' not found"; return 1; }
|
|
provider="$(jget "$pf" provider)"; [ -z "$provider" ] && provider=openrouter
|
|
base="$(jget "$pf" baseUrl)"
|
|
else
|
|
provider=openrouter # on anthropic, the catalogue worth browsing
|
|
fi
|
|
|
|
local quiet=$(( refresh || as_json )) kind title
|
|
kind="$(prov_field "$provider" 10)"; title="$(prov_field "$provider" 3)"
|
|
if [ "$quiet" -eq 0 ]; then
|
|
case "$kind" in
|
|
static) head_ "$title models (from its docs - no public catalogue endpoint)" ;;
|
|
openrouter) head_ 'fetching https://openrouter.ai/api/v1/models ...' ;;
|
|
*) head_ "models on the $title server at $base" ;;
|
|
esac
|
|
fi
|
|
tsv="$(provider_catalogue "$provider" "$base" "$(cm_preset_token "$pf")")"; rc=$?
|
|
|
|
if [ "$as_json" -eq 1 ]; then
|
|
jget "$CM_MODELS_CACHE" "providers.$provider"
|
|
return "$rc"
|
|
fi
|
|
|
|
if [ "$refresh" -eq 1 ]; then
|
|
if [ "$rc" -ne 0 ]; then
|
|
err "could not fetch the $provider catalogue${base:+ from $base}; the cached list, if any, is kept"
|
|
# Not necessarily a fault: plenty of proxies serve Messages and
|
|
# nothing else.
|
|
[ "$(prov_field "$provider" 8)" = lenient ] && \
|
|
say 'some endpoints serve no model list at all - model ids can still be typed by hand'
|
|
return 1
|
|
fi
|
|
ok "cached $(printf '%s\n' "$tsv" | grep -c .) $provider model(s)"
|
|
return 0
|
|
fi
|
|
|
|
[ "$rc" -eq 0 ] || { err "could not fetch the $provider catalogue${base:+ from $base}"; return 1; }
|
|
local id c2 c3 c4
|
|
while IFS=$'\t' read -r id c2 c3 c4; do
|
|
[ -n "$id" ] || continue
|
|
[ -z "$filter" ] || case "$id" in *"$filter"*) ;; *) continue ;; esac
|
|
case "$kind" in
|
|
lmstudio) printf ' %-58s %-11s %s\n' "$id" "$c2" "$c3" ;;
|
|
static) say "$(printf '%-8s - %s' "$id" "$c2")" ;;
|
|
ollama) printf ' %-44s %-8s %s\n' "$id" "$c2" "$c3" ;;
|
|
openrouter) printf ' %-52s %10s $%-8s $%s\n' "$id" "$c2" "$c3" "$c4" ;;
|
|
*) printf ' %s\n' "$id" ;;
|
|
esac
|
|
done <<<"$tsv"
|
|
}
|
|
|
|
cmd_set_key() {
|
|
local ref="${1:-openrouter}" inline="${2:-}" secret unit
|
|
init_root
|
|
printf ' storage backend: %s\n' "$(cm_vault_backend_label)"
|
|
if [ "$(cm_vault_backend)" = "file" ]; then
|
|
warn 'no keyring available - the key will be stored in a 0600 file, NOT encrypted.'
|
|
warn 'install libsecret-tools (secret-tool) or pass for encrypted storage.'
|
|
fi
|
|
if [ -n "$inline" ]; then
|
|
secret="$inline"
|
|
warn 'the key was given on the command line, so it is in this shell history - the hidden prompt leaves no trace'
|
|
else
|
|
printf ' paste the API key for ref '\''%s'\'' (input hidden): ' "$ref"
|
|
IFS= read -rs secret; printf '\n'
|
|
fi
|
|
[ -n "$secret" ] || { err 'empty key, aborted'; return 1; }
|
|
|
|
# A hidden prompt will happily swallow a mis-paste. Guard the two shapes that
|
|
# are never a real key - the failure is otherwise invisible until the
|
|
# provider answers 401 and the UI just spins.
|
|
case "$secret" in
|
|
*[[:space:]]*) err 'that value contains whitespace, so it is not an API key (a pasted command line?). Nothing was stored.'; return 1 ;;
|
|
claude-mode*) err 'that value is a claude-mode command, not an API key. Nothing was stored.'; return 1 ;;
|
|
esac
|
|
if [ "${#secret}" -lt 16 ]; then
|
|
unit=characters; [ "${#secret}" -eq 1 ] && unit=character
|
|
warn "that key is only ${#secret} $unit - unusually short. Storing anyway."
|
|
fi
|
|
if [ "$ref" = "openrouter" ] && [ "${secret#sk-or-}" = "$secret" ]; then
|
|
warn "key does not start with 'sk-or-' - storing anyway"
|
|
fi
|
|
printf '%s' "$secret" | cm_vault_set "$ref" && ok "stored key '$ref' via $(cm_vault_backend_label)"
|
|
}
|