Four changes to the POSIX build, found while getting it working on Omarchy. Colour follows the desktop theme. The sixteen ANSI slots carry no guarantee about relative brightness and monochrome themes exploit that: under Omarchy's Solitude, slot 36 (headings) resolves to #707070 and slot 31 (FAIL) to #565d60, which against #cacccc body text on a #101315 ground is 3.8:1 and 2.8:1 where the body text is 11.6:1. Headings rendered as fine print and errors as the quietest thing on screen. The palette is now derived from the theme's own colors.toml, with each role measured against the background it will actually be drawn on and lifted toward the foreground when it falls short - hue kept where the theme has any, weight substituted where it does not. Headings go 3.8:1 -> 9.4:1 and FAIL 2.8:1 -> 5.2:1. Falls back to the ANSI slots off Omarchy, with the two roles the slots get wrong corrected. health.json was only ever written by a switch, so a fresh install had none at all and any reader had to guess. It is now refreshed by `status` and seeded at install, and `claude-mode health` forces it. The installer also copies VERSION, which cm_version() has always read and nothing ever wrote - every health.json until now reported 0.0.0. Preflight, because a switch that cannot work does not fail loudly: it succeeds, and every session started afterwards breaks in a way that points at Claude Code rather than at here. Keys, the helper, the preset's provider and the server are all checked before the write. LM Studio is the sharp case - its token is an inline placeholder, so nothing about the switch needs the server to exist. Session control, because Claude Code reads settings.json once at startup: a switch leaves running sessions on the old provider until they are restarted, and one mid-request can lose that turn outright. `sessions` lists them, `--stop` and `--restart` act on them behind a confirmation, `--dry-run` shows the plan. Sessions are found through /proc/<pid>/exe rather than by process name, which would sweep up every shell that merely mentions claude - including the one this runs from. Two exclusions: the calling session, and forks of a session. A busy session spawns children off its own binary that inherit the same exe, and without filtering those the count climbed and fell with load - it read 2, 5, 11 and 40 for the same two sessions before the parent check went in. LM Studio is no longer assumed to be on this machine. `preset url` and `preset auth` move it to a LAN box, a tunnel or a proxy and turn authentication on, and the probe distinguishes ok / auth / notfound / refused, because "start the server" and "your key is wrong" are opposite remedies. It is probed wherever it lives - a sleeping LAN box is exactly as absent as an empty loopback port - while remote gateways are not, since those being briefly unreachable is the network's problem and a missing key never fixes itself.
151 lines
5.5 KiB
Bash
Executable File
151 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One-time installer for claude-mode (Linux / macOS).
|
|
#
|
|
# Installs to ~/.claude-mode, symlinks the entry point into ~/.local/bin, and
|
|
# adds a marked block to your shell rc for the `claude` wrapper.
|
|
#
|
|
# Idempotent: safe to re-run to upgrade. Existing presets are kept unless
|
|
# --force is passed. ~/.claude/settings.json is NOT touched here - only by an
|
|
# actual `claude-mode <mode>`.
|
|
|
|
set -euo pipefail
|
|
|
|
FORCE=0
|
|
SKIP_KEY=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--force) FORCE=1 ;;
|
|
--skip-key-prompt) SKIP_KEY=1 ;;
|
|
-h|--help) echo "usage: install.sh [--force] [--skip-key-prompt]"; exit 0 ;;
|
|
esac
|
|
done
|
|
|
|
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT="${CM_ROOT:-$HOME/.claude-mode}"
|
|
BINDIR="$HOME/.local/bin"
|
|
|
|
green() { printf ' \033[32mok \033[0m %s\n' "$*"; }
|
|
warn() { printf ' \033[33mwarn\033[0m %s\n' "$*"; }
|
|
fail() { printf ' \033[31mFAIL\033[0m %s\n' "$*"; }
|
|
|
|
printf '\ninstalling claude-mode -> %s\n' "$ROOT"
|
|
|
|
# --- preflight -------------------------------------------------------------
|
|
PY="${CLAUDE_MODE_PYTHON:-python3}"
|
|
if ! command -v "$PY" >/dev/null 2>&1; then
|
|
fail "python3 not found. claude-mode uses it for JSON handling."
|
|
fail "install it (apt install python3 / dnf install python3 / brew install python) and re-run."
|
|
exit 1
|
|
fi
|
|
green "python3: $(command -v "$PY")"
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
warn 'curl not found - `models` and `doctor` network checks will not work'
|
|
fi
|
|
if ! command -v claude >/dev/null 2>&1; then
|
|
warn 'claude is not on PATH - claude-mode installs anyway, but nothing uses its config yet'
|
|
fi
|
|
|
|
# --- directories -----------------------------------------------------------
|
|
mkdir -p "$ROOT/bin" "$ROOT/presets" "$ROOT/vault" "$ROOT/backups" "$BINDIR"
|
|
chmod 700 "$ROOT" "$ROOT/vault" 2>/dev/null || true
|
|
|
|
# --- payload ---------------------------------------------------------------
|
|
install -m 0755 "$SRC/claude-mode" "$ROOT/bin/claude-mode"
|
|
install -m 0755 "$SRC/claude-key-helper.sh" "$ROOT/bin/claude-key-helper.sh"
|
|
install -m 0644 "$SRC/cm-json.py" "$ROOT/bin/cm-json.py"
|
|
install -m 0644 "$SRC/cm-vault.sh" "$ROOT/bin/cm-vault.sh"
|
|
green 'copied claude-mode + helpers'
|
|
|
|
# cm_version() reads this; without it every health.json reported 0.0.0.
|
|
VERSION_SRC="$SRC/../VERSION"
|
|
[ -f "$VERSION_SRC" ] || VERSION_SRC="$SRC/VERSION"
|
|
if [ -f "$VERSION_SRC" ]; then
|
|
install -m 0644 "$VERSION_SRC" "$ROOT/VERSION"
|
|
green "version $(tr -d '[:space:]' < "$ROOT/VERSION")"
|
|
fi
|
|
|
|
# --- presets (shared with the Windows build) -------------------------------
|
|
PRESET_SRC="$SRC/../presets"
|
|
[ -d "$PRESET_SRC" ] || PRESET_SRC="$SRC/presets"
|
|
if [ -d "$PRESET_SRC" ]; then
|
|
for f in "$PRESET_SRC"/*.json; do
|
|
[ -e "$f" ] || continue
|
|
base="$(basename "$f")"
|
|
if [ -e "$ROOT/presets/$base" ] && [ "$FORCE" -eq 0 ]; then
|
|
printf ' skip preset %s (exists; --force to overwrite)\n' "${base%.json}"
|
|
else
|
|
install -m 0644 "$f" "$ROOT/presets/$base"
|
|
green "preset ${base%.json}"
|
|
fi
|
|
done
|
|
else
|
|
warn 'no presets directory found in the payload'
|
|
fi
|
|
|
|
# --- initial state ---------------------------------------------------------
|
|
if [ ! -f "$ROOT/state.json" ]; then
|
|
printf '{\n "mode": "anthropic",\n "preset": "",\n "writtenEnvKeys": []\n}\n' > "$ROOT/state.json"
|
|
green 'state.json initialised (mode=anthropic)'
|
|
fi
|
|
|
|
# --- PATH entry ------------------------------------------------------------
|
|
ln -sf "$ROOT/bin/claude-mode" "$BINDIR/claude-mode"
|
|
green "linked $BINDIR/claude-mode"
|
|
|
|
case ":$PATH:" in
|
|
*":$BINDIR:"*) ;;
|
|
*) warn "$BINDIR is not on PATH - add it in your shell rc: export PATH=\"\$HOME/.local/bin:\$PATH\"" ;;
|
|
esac
|
|
|
|
# --- shell rc block --------------------------------------------------------
|
|
SNIPPET="$SRC/shell-snippet.sh"
|
|
START='# >>> claude-mode >>>'
|
|
END='# <<< claude-mode <<<'
|
|
|
|
add_block() {
|
|
local rc="$1"
|
|
[ -f "$rc" ] || return 0
|
|
if grep -qF "$START" "$rc" 2>/dev/null; then
|
|
# replace the existing block in place
|
|
"$PY" - "$rc" "$SNIPPET" "$START" "$END" <<'PYEOF'
|
|
import sys, re
|
|
rc, snip, start, end = sys.argv[1:5]
|
|
body = open(rc, encoding='utf-8').read()
|
|
new = open(snip, encoding='utf-8').read().rstrip('\n')
|
|
pattern = re.compile(re.escape(start) + r'.*?' + re.escape(end), re.S)
|
|
open(rc, 'w', encoding='utf-8').write(pattern.sub(lambda _: new, body))
|
|
PYEOF
|
|
green "updated claude-mode block in $rc"
|
|
else
|
|
printf '\n%s\n' "$(cat "$SNIPPET")" >> "$rc"
|
|
green "appended claude-mode block to $rc"
|
|
fi
|
|
}
|
|
|
|
RC_TOUCHED=0
|
|
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
|
|
if [ -f "$rc" ]; then add_block "$rc"; RC_TOUCHED=1; fi
|
|
done
|
|
[ "$RC_TOUCHED" -eq 0 ] && warn 'no ~/.bashrc or ~/.zshrc found - the `claude` wrapper was not installed'
|
|
|
|
# --- key -------------------------------------------------------------------
|
|
if [ "$SKIP_KEY" -eq 0 ]; then
|
|
# shellcheck source=/dev/null
|
|
. "$ROOT/bin/cm-vault.sh"
|
|
if cm_vault_has openrouter && [ "$FORCE" -eq 0 ]; then
|
|
green 'OpenRouter key already stored'
|
|
else
|
|
printf '\n'
|
|
"$ROOT/bin/claude-mode" set-key openrouter || warn 'key not stored - run `claude-mode set-key openrouter` later'
|
|
fi
|
|
fi
|
|
|
|
# --- machine-readable state ------------------------------------------------
|
|
"$ROOT/bin/claude-mode" health >/dev/null 2>&1 && green 'health.json seeded'
|
|
|
|
printf '\ndone. Open a new shell, then:\n'
|
|
printf ' claude-mode\n'
|
|
printf ' claude-mode status\n'
|
|
printf ' claude-mode doctor\n'
|