# 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)" }