Lite LLM Install – Script
These are the commands to run the command
chmod +x install-claude-code-routing.sh
./install-claude-code-routing.sh
This is the script to install everything from the guide
#!/usr/bin/env bash
#
# install-claude-code-routing.sh
#
# Interactive installer for "Claude Code with local/cloud model swapping".
# Automates the validated stack from the install guide:
# rootless Docker -> mise/Node -> Claude Code -> secrets -> LiteLLM gateway
# -> routed + local launchers -> smoke tests.
#
# HOW TO USE
# 1. Copy this file to the Debian host.
# 2. Run it AS YOUR SERVICE USER (e.g. `claude`), over a real SSH login.
# chmod +x install-claude-code-routing.sh
# ./install-claude-code-routing.sh
#
# WHAT IT DOES NOT DO (on purpose)
# - It does not create the Linux user or set up SSH keys. Run it after you
# are already logged in as your unprivileged service user.
# - It does not configure LM Studio (that lives on another machine) or the
# Azure / Anthropic consoles. It will prompt you for the values from those.
# - It does not install claude-code-router (CCR). That step is not yet
# validated; it will be added once it is proven, to keep this script safe.
#
# It re-prompts for every value on each run (it does not remember answers) and
# is safe to re-run: existing config files are backed up before being replaced.
set -uo pipefail
# --------------------------------------------------------------------------
# styling helpers
# --------------------------------------------------------------------------
if [ -t 1 ]; then
BOLD="$(printf '\033[1m')"; DIM="$(printf '\033[2m')"; RED="$(printf '\033[31m')"
GRN="$(printf '\033[32m')"; YEL="$(printf '\033[33m')"; BLU="$(printf '\033[36m')"
RST="$(printf '\033[0m')"
else
BOLD=""; DIM=""; RED=""; GRN=""; YEL=""; BLU=""; RST=""
fi
hr() { printf '%s\n' "${DIM}----------------------------------------------------------------------${RST}"; }
info() { printf '%s\n' "${BLU}${BOLD}==>${RST} ${BOLD}$*${RST}"; }
note() { printf '%s\n' " $*"; }
ok() { printf '%s\n' "${GRN} [ok]${RST} $*"; }
warn() { printf '%s\n' "${YEL} [warn]${RST} $*"; }
err() { printf '%s\n' "${RED} [error]${RST} $*" >&2; }
die() { err "$*"; exit 1; }
# Prompt for a value. Args: VARNAME "prompt" "example" ["default"]
ask() {
local __var="$1" prompt="$2" example="$3" default="${4:-}" input
printf '%s\n' "${BOLD}${prompt}${RST}" >&2
printf '%s\n' " ${DIM}example: ${example}${RST}" >&2
if [ -n "$default" ]; then
printf ' [%s]: ' "$default" >&2
else
printf ' > ' >&2
fi
IFS= read -r input || input=""
[ -z "$input" ] && input="$default"
printf -v "$__var" '%s' "$input"
}
# Prompt for a secret (input hidden). Args: VARNAME "prompt" "example"
ask_secret() {
local __var="$1" prompt="$2" example="$3" input
printf '%s\n' "${BOLD}${prompt}${RST}" >&2
printf '%s\n' " ${DIM}example: ${example} (input hidden)${RST}" >&2
printf ' > ' >&2
IFS= read -rs input || input=""
printf '\n' >&2
printf -v "$__var" '%s' "$input"
}
# Yes/No confirmation. Args: "prompt" ["Y"|"N" default]
confirm() {
local prompt="$1" default="${2:-Y}" ans hint="[Y/n]"
[ "$default" = "N" ] && hint="[y/N]"
printf '%s %s ' "${prompt}" "${hint}" >&2
IFS= read -r ans || ans=""
ans="${ans:-$default}"
case "$ans" in [Yy]*) return 0 ;; *) return 1 ;; esac
}
# Idempotently insert a marked block into ~/.bashrc. Args: MARKER CONTENT
bashrc_block() {
local marker="$1" content="$2" rc="$HOME/.bashrc"
local begin="# >>> claude-installer:${marker} >>>"
local end="# <<< claude-installer:${marker} <<<"
touch "$rc"
# remove any existing block with this marker
sed -i "/^${begin}\$/,/^${end}\$/d" "$rc"
{ printf '%s\n' "$begin"; printf '%s\n' "$content"; printf '%s\n' "$end"; } >> "$rc"
}
backup_file() {
local f="$1"
if [ -f "$f" ]; then
local b="${f}.bak.$(date +%Y%m%d-%H%M%S)"
cp -a "$f" "$b"
note "Backed up existing $(basename "$f") -> $(basename "$b")"
fi
}
# --------------------------------------------------------------------------
# preflight
# --------------------------------------------------------------------------
hr
info "Claude Code local/cloud routing installer"
note "This sets up rootless Docker, Claude Code, a LiteLLM gateway, and"
note "routed + local launchers. Run it as your service user (not root)."
hr
[ "$(id -u)" -eq 0 ] && die "Do not run as root. Log in as your service user (e.g. claude) and run again."
command -v sudo >/dev/null 2>&1 || die "sudo is required but not found."
if [ -z "${XDG_RUNTIME_DIR:-}" ]; then
err "No user session detected (XDG_RUNTIME_DIR is unset)."
err "You are probably in a 'su' shell. Disconnect and SSH in directly as this user, then re-run."
exit 1
fi
confirm "Ready to begin?" Y || { note "Aborted."; exit 0; }
# --------------------------------------------------------------------------
# step 1: base packages (needed early so curl/jq exist for later prompts)
# --------------------------------------------------------------------------
phase_base() {
sudo apt update
sudo apt -y install \
ca-certificates curl wget gnupg git \
build-essential ripgrep jq htop tmux \
unattended-upgrades python3 python3-venv pipx
ok "Base packages installed."
}
hr; info "STEP 1: Base system packages"
if confirm "Install base packages now?" Y; then phase_base; else warn "Skipped base packages (later steps may fail without curl/jq)."; fi
# --------------------------------------------------------------------------
# gather all answers (re-prompted every run)
# --------------------------------------------------------------------------
hr; info "Configuration questions"
note "Answer the prompts below. Defaults shown in [brackets]; press Enter to accept."
hr
DETECTED_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
# --- cloud backend choice ---
printf '%s\n' "${BOLD}Which cloud backend should handle complex/foreground work?${RST}"
printf '%s\n' " 1) Claude API (Anthropic direct) ${DIM}- simplest, no Foundry quirks${RST}"
printf '%s\n' " 2) Azure AI Foundry"
printf '%s\n' " 3) Both"
printf ' [1]: '
IFS= read -r BACKEND_CHOICE || BACKEND_CHOICE=""
BACKEND_CHOICE="${BACKEND_CHOICE:-1}"
USE_ANTHROPIC=0; USE_FOUNDRY=0
case "$BACKEND_CHOICE" in
1) USE_ANTHROPIC=1 ;;
2) USE_FOUNDRY=1 ;;
3) USE_ANTHROPIC=1; USE_FOUNDRY=1 ;;
*) USE_ANTHROPIC=1; warn "Unrecognized choice; defaulting to Claude API." ;;
esac
# --- LiteLLM master key (auto-suggest, overridable) ---
SUGGESTED_KEY="sk-local-$(head -c 12 /dev/urandom | od -An -tx1 | tr -d ' \n')"
ask LITELLM_MASTER_KEY "LiteLLM gateway master key (Claude Code uses this to reach the gateway)" \
"sk-local-abc123..." "$SUGGESTED_KEY"
# --- Anthropic specifics ---
if [ "$USE_ANTHROPIC" -eq 1 ]; then
ask_secret ANTHROPIC_DIRECT_KEY "Claude API key (stored as ANTHROPIC_DIRECT_KEY, never ANTHROPIC_API_KEY)" \
"sk-ant-api03-...."
ask ANTH_SONNET "Claude API Sonnet model id" "claude-sonnet-4-6" "claude-sonnet-4-6"
ask ANTH_OPUS "Claude API Opus model id" "claude-opus-4-8" "claude-opus-4-8"
ask ANTH_HAIKU "Claude API Haiku model id" "claude-haiku-4-5" "claude-haiku-4-5"
fi
# --- Foundry specifics ---
if [ "$USE_FOUNDRY" -eq 1 ]; then
ask_secret AZURE_API_KEY "Azure Foundry API key (stored as AZURE_API_KEY)" "abc123def456..."
ask FOUNDRY_RESOURCE "Foundry resource name (the subdomain only, not the ARM resource id)" \
"myfoundry" ""
ask FND_SONNET "Foundry Sonnet DEPLOYMENT name" "claude-sonnet-4-6" "claude-sonnet-4-6"
ask FND_OPUS "Foundry Opus DEPLOYMENT name" "claude-opus-4-8" "claude-opus-4-8"
ask FND_HAIKU "Foundry Haiku DEPLOYMENT name" "claude-haiku-4-5" "claude-haiku-4-5"
fi
# --- choose primary foreground backend if both ---
PRIMARY="anthropic"
if [ "$USE_ANTHROPIC" -eq 1 ] && [ "$USE_FOUNDRY" -eq 1 ]; then
printf '%s\n' "${BOLD}Which backend should be the default foreground model?${RST}"
printf '%s\n' " 1) Claude API"
printf '%s\n' " 2) Azure Foundry"
printf ' [1]: '
IFS= read -r PRI || PRI=""
[ "${PRI:-1}" = "2" ] && PRIMARY="foundry"
elif [ "$USE_FOUNDRY" -eq 1 ]; then
PRIMARY="foundry"
fi
if [ "$PRIMARY" = "foundry" ]; then
FG_MODEL="foundry-sonnet"; FALLBACK_HAIKU="foundry-haiku"
else
FG_MODEL="claude-sonnet"; FALLBACK_HAIKU="claude-haiku"
fi
# --- LM Studio ---
ask LMSTUDIO_IP "LM Studio host LAN IP" "192.168.1.20" ""
ask LMSTUDIO_PORT "LM Studio server port" "1234" "1234"
LOCAL_MODEL_ID=""
if command -v curl >/dev/null 2>&1 && [ -n "$LMSTUDIO_IP" ]; then
note "Querying LM Studio for available models..."
mapfile -t LM_MODELS < <(curl -fsS "http://${LMSTUDIO_IP}:${LMSTUDIO_PORT}/v1/models" 2>/dev/null \
| jq -r '.data[].id' 2>/dev/null | grep -vi 'embed' || true)
if [ "${#LM_MODELS[@]}" -gt 0 ]; then
ok "LM Studio reachable. Models found:"
i=1; for m in "${LM_MODELS[@]}"; do printf ' %d) %s\n' "$i" "$m"; i=$((i+1)); done
printf ' Pick a number, or type a model id: '
IFS= read -r PICK || PICK=""
if [[ "$PICK" =~ ^[0-9]+$ ]] && [ "$PICK" -ge 1 ] && [ "$PICK" -le "${#LM_MODELS[@]}" ]; then
LOCAL_MODEL_ID="${LM_MODELS[$((PICK-1))]}"
else
LOCAL_MODEL_ID="$PICK"
fi
else
warn "Could not reach LM Studio or no models loaded. Make sure its server is on and 'Serve on Local Network' is enabled."
fi
fi
if [ -z "$LOCAL_MODEL_ID" ]; then
ask LOCAL_MODEL_ID "LM Studio model id (from /v1/models)" "qwen/qwen3.6-35b-a3b" "qwen/qwen3.6-35b-a3b"
fi
LMSTUDIO_KEY="lm-studio"
if confirm "Does your LM Studio require an auth token?" N; then
ask_secret LMSTUDIO_KEY "LM Studio auth token" "lm-..."
fi
# review
hr; info "Review"
note "Cloud backend: $([ "$USE_ANTHROPIC" -eq 1 ] && printf 'Claude-API ')$([ "$USE_FOUNDRY" -eq 1 ] && printf 'Foundry')"
note "Foreground model: ${FG_MODEL}"
note "Local model: ${LOCAL_MODEL_ID} (at ${LMSTUDIO_IP}:${LMSTUDIO_PORT})"
note "Failover target: ${FALLBACK_HAIKU}"
hr
confirm "Proceed with these settings?" Y || { note "Aborted before making changes."; exit 0; }
# --------------------------------------------------------------------------
# step 2: optional host hardening
# --------------------------------------------------------------------------
hr; info "STEP 2: Optional host hardening (fail2ban, nftables)"
note "Not required for Claude Code. fail2ban blunts SSH brute-force; nftables is the firewall."
if confirm "Install fail2ban and nftables?" N; then
sudo apt -y install fail2ban nftables && ok "Hardening tools installed."
else
note "Skipped hardening."
fi
# --------------------------------------------------------------------------
# step 3: rootless Docker
# --------------------------------------------------------------------------
phase_docker() {
if ! command -v docker >/dev/null 2>&1; then
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
uidmap dbus-user-session slirp4netns fuse-overlayfs
sudo systemctl disable --now docker.service docker.socket 2>/dev/null || true
else
note "Docker engine already present; ensuring rootless mode."
fi
sudo loginctl enable-linger "$USER"
if ! systemctl --user is-active --quiet docker 2>/dev/null; then
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
fi
grep -q 'DOCKER_HOST=unix:///run/user' "$HOME/.bashrc" 2>/dev/null || \
bashrc_block "docker" 'export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock'
export DOCKER_HOST="unix:///run/user/$(id -u)/docker.sock"
if docker run --rm hello-world >/dev/null 2>&1; then
ok "Rootless Docker working."
else
warn "Docker test did not succeed. Check 'systemctl --user status docker'."
fi
}
hr; info "STEP 3: Rootless Docker"
if confirm "Install/configure rootless Docker?" Y; then phase_docker; else warn "Skipped Docker (LiteLLM step will fail without it)."; fi
export DOCKER_HOST="unix:///run/user/$(id -u)/docker.sock"
# --------------------------------------------------------------------------
# step 4: toolchain (mise, Node, Python)
# --------------------------------------------------------------------------
phase_toolchain() {
if [ ! -x "$HOME/.local/bin/mise" ] && ! command -v mise >/dev/null 2>&1; then
curl -fsSL https://mise.run | sh
fi
bashrc_block "mise" 'eval "$(~/.local/bin/mise activate bash)"'
eval "$("$HOME/.local/bin/mise" activate bash)" 2>/dev/null || true
"$HOME/.local/bin/mise" use -g node@22 python@3.12
ok "Toolchain (Node 22, Python 3.12) ready via mise."
}
hr; info "STEP 4: Toolchain (mise / Node / Python)"
if confirm "Install mise + Node 22 + Python 3.12?" Y; then phase_toolchain; else warn "Skipped toolchain."; fi
# --------------------------------------------------------------------------
# step 5: Claude Code
# --------------------------------------------------------------------------
phase_claude() {
if command -v claude >/dev/null 2>&1 || [ -x "$HOME/.local/bin/claude" ]; then
note "Claude Code already installed."
else
curl -fsSL https://claude.ai/install.sh | bash
fi
export PATH="$HOME/.local/bin:$PATH"
"$HOME/.local/bin/claude" --version 2>/dev/null && ok "Claude Code installed." || warn "Claude Code install unverified."
}
hr; info "STEP 5: Claude Code"
if confirm "Install Claude Code?" Y; then phase_claude; else warn "Skipped Claude Code."; fi
# --------------------------------------------------------------------------
# step 6: secrets file
# --------------------------------------------------------------------------
phase_secrets() {
mkdir -p "$HOME/.config"
local sf="$HOME/.config/claude-secrets.env"
backup_file "$sf"
umask 077
{
printf '# Generated by install-claude-code-routing.sh on %s\n' "$(date)"
printf 'LITELLM_MASTER_KEY=%s\n' "$LITELLM_MASTER_KEY"
[ "$USE_FOUNDRY" -eq 1 ] && printf 'AZURE_API_KEY=%s\n' "$AZURE_API_KEY"
[ "$USE_ANTHROPIC" -eq 1 ] && printf 'ANTHROPIC_DIRECT_KEY=%s\n' "$ANTHROPIC_DIRECT_KEY"
} > "$sf"
chmod 600 "$sf"
bashrc_block "secrets" '[ -f ~/.config/claude-secrets.env ] && set -a && . ~/.config/claude-secrets.env && set +a'
ok "Secrets written to ~/.config/claude-secrets.env (mode 600) and auto-load added to ~/.bashrc."
note "Note: the Claude key is named ANTHROPIC_DIRECT_KEY on purpose (never ANTHROPIC_API_KEY)."
}
hr; info "STEP 6: Secrets file"
if confirm "Write the secrets file?" Y; then phase_secrets; else warn "Skipped secrets (gateway will not authenticate)."; fi
# --------------------------------------------------------------------------
# step 7: LiteLLM config.yaml
# --------------------------------------------------------------------------
phase_config() {
mkdir -p "$HOME/litellm"
local cf="$HOME/litellm/config.yaml"
backup_file "$cf"
{
printf 'model_list:\n'
printf ' # ---- Local (background/simple): LM Studio ----\n'
printf ' - model_name: local\n'
printf ' litellm_params:\n'
printf ' model: lm_studio/%s\n' "$LOCAL_MODEL_ID"
printf ' api_base: http://%s:%s/v1\n' "$LMSTUDIO_IP" "$LMSTUDIO_PORT"
printf ' api_key: "%s"\n' "$LMSTUDIO_KEY"
if [ "$USE_ANTHROPIC" -eq 1 ]; then
printf '\n # ---- Claude API (Anthropic direct) ----\n'
printf ' - model_name: claude-sonnet\n litellm_params:\n model: anthropic/%s\n api_key: os.environ/ANTHROPIC_DIRECT_KEY\n' "$ANTH_SONNET"
printf ' - model_name: claude-opus\n litellm_params:\n model: anthropic/%s\n api_key: os.environ/ANTHROPIC_DIRECT_KEY\n' "$ANTH_OPUS"
printf ' - model_name: claude-haiku\n litellm_params:\n model: anthropic/%s\n api_key: os.environ/ANTHROPIC_DIRECT_KEY\n' "$ANTH_HAIKU"
fi
if [ "$USE_FOUNDRY" -eq 1 ]; then
local fb="https://${FOUNDRY_RESOURCE}.services.ai.azure.com/anthropic"
printf '\n # ---- Azure Foundry Claude ----\n'
printf ' - model_name: foundry-sonnet\n litellm_params:\n model: azure_ai/%s\n api_base: %s\n api_key: os.environ/AZURE_API_KEY\n' "$FND_SONNET" "$fb"
printf ' - model_name: foundry-opus\n litellm_params:\n model: azure_ai/%s\n api_base: %s\n api_key: os.environ/AZURE_API_KEY\n' "$FND_OPUS" "$fb"
printf ' - model_name: foundry-haiku\n litellm_params:\n model: azure_ai/%s\n api_base: %s\n api_key: os.environ/AZURE_API_KEY\n' "$FND_HAIKU" "$fb"
fi
printf '\nlitellm_settings:\n'
printf ' drop_params: true\n'
printf ' modify_params: true\n'
printf ' num_retries: 2\n'
printf ' request_timeout: 120\n'
printf ' fallbacks: [{"local": ["%s"]}]\n' "$FALLBACK_HAIKU"
} > "$cf"
ok "Wrote ~/litellm/config.yaml (includes the drop_params/modify_params fix)."
}
hr; info "STEP 7: LiteLLM config.yaml"
if confirm "Generate the LiteLLM config (backs up any existing one)?" Y; then phase_config; else warn "Skipped config generation."; fi
# --------------------------------------------------------------------------
# step 8: run the LiteLLM container
# --------------------------------------------------------------------------
phase_run() {
# source secrets so the keys are present for the docker run -e flags
[ -f "$HOME/.config/claude-secrets.env" ] && set -a && . "$HOME/.config/claude-secrets.env" && set +a
docker rm -f litellm >/dev/null 2>&1 || true
local args=(run -d --name litellm --restart unless-stopped
-p 127.0.0.1:4000:4000
-v "$HOME/litellm/config.yaml:/app/config.yaml"
-e "LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY}")
[ "$USE_FOUNDRY" -eq 1 ] && args+=(-e "AZURE_API_KEY=${AZURE_API_KEY}")
[ "$USE_ANTHROPIC" -eq 1 ] && args+=(-e "ANTHROPIC_DIRECT_KEY=${ANTHROPIC_DIRECT_KEY}")
args+=(docker.litellm.ai/berriai/litellm:main-stable --config /app/config.yaml --port 4000)
docker "${args[@]}"
note "Waiting for the gateway to come up..."
local up=0 i
for i in 1 2 3 4 5 6 7 8 9 10; do
sleep 2
if curl -fsS "http://127.0.0.1:4000/v1/models" -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" >/dev/null 2>&1; then
up=1; break
fi
done
if [ "$up" -eq 1 ]; then ok "LiteLLM gateway is up on 127.0.0.1:4000."
else warn "Gateway not responding yet. Check 'docker logs litellm'."; fi
}
hr; info "STEP 8: Start the LiteLLM gateway"
if confirm "Start (or restart) the LiteLLM container now?" Y; then phase_run; else warn "Skipped starting the gateway."; fi
# --------------------------------------------------------------------------
# step 9: smoke tests
# --------------------------------------------------------------------------
phase_smoke() {
[ -f "$HOME/.config/claude-secrets.env" ] && set -a && . "$HOME/.config/claude-secrets.env" && set +a
local body
note "Testing local model path..."
body='{"model":"local","max_tokens":30,"messages":[{"role":"user","content":"say hi"}]}'
if curl -fsS "http://127.0.0.1:4000/v1/messages" -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
-H "Content-Type: application/json" -d "$body" | grep -qi 'content\|text'; then
ok "Local (LM Studio) path responded."
else
warn "Local path did not respond. Is the model loaded in LM Studio at >=32K context?"
fi
note "Testing cloud foreground path (${FG_MODEL})..."
body="{\"model\":\"${FG_MODEL}\",\"max_tokens\":30,\"messages\":[{\"role\":\"user\",\"content\":\"say hi\"}]}"
if curl -fsS "http://127.0.0.1:4000/v1/messages" -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
-H "Content-Type: application/json" -d "$body" | grep -qi 'content\|text'; then
ok "Cloud (${FG_MODEL}) path responded."
else
warn "Cloud path did not respond. Check the key and (for Foundry) the resource/deployment names."
fi
}
hr; info "STEP 9: Smoke-test both backends"
if confirm "Run smoke tests?" Y; then phase_smoke; else warn "Skipped smoke tests."; fi
# --------------------------------------------------------------------------
# step 10: launchers
# --------------------------------------------------------------------------
phase_launchers() {
bashrc_block "launchers" "$(cat <<EOF
claude-routed() {
[ -f ~/.config/claude-secrets.env ] && set -a && . ~/.config/claude-secrets.env && set +a
CLAUDE_CONFIG_DIR="\$HOME/.claude-routed" \\
ANTHROPIC_BASE_URL="http://127.0.0.1:4000" \\
ANTHROPIC_AUTH_TOKEN="\$LITELLM_MASTER_KEY" \\
ANTHROPIC_MODEL="${FG_MODEL}" \\
ANTHROPIC_DEFAULT_HAIKU_MODEL="local" \\
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY="1" \\
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS="1" \\
claude "\$@"
}
claude-local() {
[ -f ~/.config/claude-secrets.env ] && set -a && . ~/.config/claude-secrets.env && set +a
CLAUDE_CONFIG_DIR="\$HOME/.claude-routed" \\
ANTHROPIC_BASE_URL="http://127.0.0.1:4000" \\
ANTHROPIC_AUTH_TOKEN="\$LITELLM_MASTER_KEY" \\
ANTHROPIC_MODEL="local" \\
ANTHROPIC_DEFAULT_HAIKU_MODEL="local" \\
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY="1" \\
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS="1" \\
claude "\$@"
}
EOF
)"
mkdir -p "$HOME/.claude-routed"
if [ ! -f "$HOME/.claude-routed/settings.json" ]; then
printf '{\n "effortLevel": "high",\n "theme": "auto"\n}\n' > "$HOME/.claude-routed/settings.json"
fi
ok "Added claude-routed() and claude-local() to ~/.bashrc and created routed config dir."
}
hr; info "STEP 10: Routed + local launchers"
if confirm "Install the claude-routed and claude-local launchers?" Y; then phase_launchers; else warn "Skipped launchers."; fi
# --------------------------------------------------------------------------
# done
# --------------------------------------------------------------------------
hr
info "Done."
note "Foreground model: ${FG_MODEL} | Local model: ${LOCAL_MODEL_ID}"
note ""
note "Next steps:"
note " 1) Load your environment in this shell: ${BOLD}source ~/.bashrc${RST}"
note " 2) Routed mode (cloud + local background): ${BOLD}claude-routed${RST}"
note " 3) Full local session on LM Studio: ${BOLD}claude-local${RST}"
note " 4) Inside Claude Code, check routing: ${BOLD}/status${RST}"
note ""
note "Gateway controls:"
note " Reload after editing config: docker restart litellm"
note " Watch routing: docker logs -f litellm"
note ""
note "Not installed yet: claude-code-router (CCR) for explicit difficulty/type"
note "routing. That will be added once validated."
hr