# shellcheck shell=bash # linux/lib/presets.sh - the preset subcommand, and re-applying the preset in use. # # 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. cmd_preset() { local sub="${1:-}" name="${2:-}" if [ -n "$name" ] && ! valid_preset_name "$name"; then err "invalid preset name '$name' - letters, digits, . _ and - only, not starting with a dot" return 1 fi case "$sub" in show) [ -n "$name" ] || { err 'usage: claude-mode preset show '; return 1; } [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } cat "$(preset_path "$name")" ;; new) [ -n "$name" ] || { err 'usage: claude-mode preset new [from] | --provider

[--blank]'; return 1; } [ -f "$(preset_path "$name")" ] && { err "preset '$name' already exists"; return 1; } local from='' provider='' blank=0 a shift 2 while [ $# -gt 0 ]; do a="$1"; shift case "$a" in --provider) [ $# -gt 0 ] || { err "--provider needs one of: $(provider_ids | tr '\n' ' ')"; return 1; } provider="$1"; shift ;; --blank) blank=1 ;; -*) err "unknown option '$a'"; return 1 ;; *) from="$a" ;; esac done if [ -n "$provider" ]; then local want="$provider" provider="$(provider_resolve "$want")" || { err "unknown provider '$want' - one of: $(provider_ids | tr '\n' ' ')"; return 1; } fi if [ "$blank" -eq 1 ]; then [ -n "$provider" ] || { err '--blank needs --provider to know which template'; return 1; } [ -z "$from" ] || { err '--blank starts from nothing; drop the source preset'; return 1; } "$PY" "$JSON" scaffold "$provider" > "$(preset_path "$name")" || { rm -f "$(preset_path "$name")"; return 1; } ok "created blank $provider preset '$name'" say 'every tier is empty: set them with claude-mode preset set, or the bar panel' write_health "$(state_mode)" "$(state_preset)" return 0 fi # A literal `default` is only right for OpenRouter. Given a provider # and no source, start from that provider's own default preset. if [ -z "$from" ]; then if [ -n "$provider" ]; then from="$(resolve_preset "$provider")" || return 1 else from=default; fi fi valid_preset_name "$from" || { err "invalid source preset name '$from'"; return 1; } [ -f "$(preset_path "$from")" ] || { err "source preset '$from' not found"; return 1; } if [ -n "$provider" ]; then local got; got="$(jget "$(preset_path "$from")" provider)"; [ -z "$got" ] && got=openrouter [ "$got" = "$provider" ] || { err "'$from' is for $got, not $provider"; return 1; } fi cp "$(preset_path "$from")" "$(preset_path "$name")" ok "created $(preset_path "$name") from '$from'" write_health "$(state_mode)" "$(state_preset)" ;; rename) local new="${3:-}" out [ -n "$name" ] && [ -n "$new" ] || { err 'usage: claude-mode preset rename '; return 1; } valid_preset_name "$new" || { err "invalid preset name '$new' - letters, digits, . _ and - only, not starting with a dot"; return 1; } [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } [ "$name" = "$new" ] && { ok "already called '$new'"; return 0; } [ -e "$(preset_path "$new")" ] && { err "preset '$new' already exists"; return 1; } out="$("$PY" "$JSON" preset-rename "$CM_PRESETS" "$name" "$new" "$CM_STATE" "$CM_DEFAULTS" 2>&1)" || { err "$out"; return 1; } ok "renamed '$name' -> '$new'" case "$out" in *'"active": true'*) say 'it is the active preset; state.json now names it, and running sessions keep working' ;; esac case "$out" in *'"default": true'*) say 'it was chosen as its provider'"'"'s default, and that follows the new name' ;; esac write_health "$(state_mode)" "$(state_preset)" ;; rm) [ -n "$name" ] || { err 'usage: claude-mode preset rm '; return 1; } [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } if [ "$name" = "$(state_preset)" ] && [ "$(state_mode)" != "anthropic" ]; then err "preset '$name' is active. Switch away first."; return 1 fi local prov; prov="$(jget "$(preset_path "$name")" provider)"; [ -z "$prov" ] && prov=openrouter rm -f "$(preset_path "$name")"; ok "deleted preset '$name'" if [ "$(jget "$CM_DEFAULTS" "$prov")" = "$name" ]; then "$PY" "$JSON" set-default "$CM_DEFAULTS" "$prov" "" >/dev/null say "it was the chosen default for $prov; 'claude-mode $prov' means '$(resolve_preset "$prov" 2>/dev/null || printf 'nothing')' now" fi # Allowed, but not quietly: with no preset left, `claude-mode ` # has nothing to resolve to. if [ -z "$(presets_for_provider "$prov")" ]; then warn "that was the last $prov preset - 'claude-mode $prov' has nothing to switch to now" say "create one with: claude-mode preset new --provider $prov [--blank]" fi write_health "$(state_mode)" "$(state_preset)" ;; default) # Which preset `claude-mode ` means when none is named. local prov="$name" target="${3:-}" p cur got if [ -z "$prov" ]; then head_ 'default preset per provider' for p in $(provider_ids); do cur="$(jget "$CM_DEFAULTS" "$p")" if [ -n "$cur" ] && [ -f "$(preset_path "$cur")" ]; then printf ' %-11s %s (chosen)\n' "$p" "$cur" else # "built-in" only when it really is the built-in name; # with that preset gone, resolve_preset takes the first. local got_p why got_p="$(resolve_preset "$p" 2>/dev/null)" || got_p='' if [ -z "$got_p" ]; then why='no preset left' elif [ "$got_p" = "$(builtin_default_preset "$p")" ]; then why='built-in' else why='first by name'; fi printf ' %-11s %s (%s%s)\n' "$p" "${got_p:--}" "$why" \ "$([ -n "$cur" ] && printf "; chosen '%s' no longer exists" "$cur")" fi done printf '\n %sclaude-mode preset default (or --clear)%s\n' "$C_DIM" "$C_RESET" return 0 fi prov="$(provider_resolve "$name")" || { err "unknown provider '$name' - one of: $(provider_ids | tr '\n' ' ')"; return 1; } if [ -z "$target" ]; then resolve_preset "$prov" && printf '\n' return fi if [ "$target" = "--clear" ]; then "$PY" "$JSON" set-default "$CM_DEFAULTS" "$prov" "" >/dev/null || return 1 ok "$prov : no longer chosen; 'claude-mode $prov' means '$(resolve_preset "$prov" 2>/dev/null || printf 'nothing - no preset left')'" else valid_preset_name "$target" || { err "invalid preset name '$target'"; return 1; } [ -f "$(preset_path "$target")" ] || { err "preset '$target' not found"; return 1; } got="$(jget "$(preset_path "$target")" provider)"; [ -z "$got" ] && got=openrouter [ "$got" = "$prov" ] || { err "'$target' is for $got, not $prov"; return 1; } "$PY" "$JSON" set-default "$CM_DEFAULTS" "$prov" "$target" >/dev/null || return 1 ok "$prov : 'claude-mode $prov' now means '$target'" fi write_health "$(state_mode)" "$(state_preset)" ;; set) local tier="${3:-}" model="${4:-}" [ -n "$name" ] && [ -n "$tier" ] && [ -n "$model" ] || { err 'usage: claude-mode preset set '; return 1; } # set-tier saves whatever it loaded, so a mistyped name would # otherwise quietly become a new, half-empty preset. [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } "$PY" "$JSON" set-tier "$(preset_path "$name")" "$tier" "$model" || return 1 ok "$name : $tier -> $model" reapply_if_active "$name" models ;; all) local model="${3:-}" [ -n "$name" ] && [ -n "$model" ] || { err 'usage: claude-mode preset all '; return 1; } [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } local t for t in "${TIERS[@]}" subagent; do "$PY" "$JSON" set-tier "$(preset_path "$name")" "$t" "$model" || return 1 done ok "$name : all tiers + subagent -> $model" reapply_if_active "$name" models ;; url) local url="${3:-}" [ -n "$name" ] && [ -n "$url" ] || { err 'usage: claude-mode preset url '; return 1; } [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } "$PY" "$JSON" set-url "$(preset_path "$name")" "$url" >/dev/null || return 1 ok "$name : baseUrl -> $url" reapply_if_active "$name" ;; auth) local amode="${3:-}" ref="${4:-}" [ -n "$name" ] && [ -n "$amode" ] || { err 'usage: claude-mode preset auth none|key [keyRef]'; return 1; } [ -f "$(preset_path "$name")" ] || { err "preset '$name' not found"; return 1; } # The provider's own slot name by default: lmstudio, ollama, custom. [ -n "$ref" ] || ref="$(prov_field "$(jget "$(preset_path "$name")" provider)" 17)" [ -n "$ref" ] || ref=lmstudio "$PY" "$JSON" set-auth "$(preset_path "$name")" "$amode" "$ref" >/dev/null || return 1 if [ "$amode" = "key" ]; then ok "$name : auth -> vault key '$ref'" cm_vault_has "$ref" || warn "no key stored yet for '$ref' - run: claude-mode set-key $ref" else ok "$name : auth -> none (inline placeholder token)" fi reapply_if_active "$name" ;; *) usage ;; esac } # scope `models` marks an edit that changed tier mappings only. What makes a # switch dangerous to running sessions is the endpoint or the key changing under # them, and a tier edit changes neither - so there is nothing to ask them about, # and the bar panel (which cannot answer a prompt) can edit the active preset. reapply_if_active() { local name="$1" scope="${2:-}" mode mode="$(state_mode)" if [ "$mode" != "anthropic" ] && [ "$(state_preset)" = "$name" ]; then say 're-applying active preset...' [ "$scope" = models ] && CM_SAME_ENDPOINT=1 if ! set_mode "$mode" "$name"; then CM_SAME_ENDPOINT=0 # Last on stderr on purpose: it is the line the panel shows, and # the file *was* written, which the failure above does not say. err "saved, but re-applying the active preset failed - run: claude-mode $mode $name" return 1 fi CM_SAME_ENDPOINT=0 fi }