Files
claude-mode/linux/lib/presets.sh
T
smoidoandClaude Opus 5 441166d003 Split the POSIX CLI into modules
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>
2026-09-15 02:00:10 +03:00

219 lines
12 KiB
Bash

# 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 <name>'; 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 <name> [from] | <name> --provider <p> [--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 <name> <new-name>'; 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 <name>'; 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 <provider>`
# 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 <name> --provider $prov [--blank]"
fi
write_health "$(state_mode)" "$(state_preset)"
;;
default)
# Which preset `claude-mode <provider>` 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 <provider> <name> (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 <name> <tier> <model-id>'; 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 <name> <model-id>'; 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 <name> <base-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 <name> 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
}