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>
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# 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
|
||||
}
|
||||
Reference in New Issue
Block a user