Root cause of "/model picker shows only 5 models": Anthropic removed
the CLAUDE_CUSTOM_MODELS env var sometime around v2.1.114. The picker
now reads exclusively from settings.json `availableModels` (a Zod-typed
allowlist field). We were:
- Writing CLAUDE_CUSTOM_MODELS into env.* (no longer read)
- Calling data.pop("availableModels", None) — actively REMOVING the
field that the picker now needs
So the picker fell back to built-in models + the two ANTHROPIC_DEFAULT_*
models, giving exactly 4 entries. Confirmed via strings on the v2.1.119
SEA binary: zero hits for "CLAUDE_CUSTOM_MODELS", but Zod schema
defines availableModels as the model allowlist.
Fix:
- patch_user() writes config['models'] into data['availableModels']
(still also sets the env var for backward-compat with side-by-side
older binaries)
- Verification block in install.sh + updater.py now reports both:
availableModels: N models (env.CLAUDE_CUSTOM_MODELS legacy: N)
Tested locally on v2.1.119 SEA install → settings.json now contains
availableModels with all 19 models. Restart claude → /model should show
the full list.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
243 lines
9.2 KiB
Bash
Executable File
243 lines
9.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# UClaude — one-line installer with full auto-install chain
|
|
# Usage: curl -fsSL -H "Authorization: token TOKEN" https://git.sensey24.ru/aibot777/unlimitedcoding/raw/branch/master/claude/uclaude_install.sh -o /tmp/uclaude.sh && sudo bash /tmp/uclaude.sh
|
|
set -uo pipefail
|
|
|
|
# Error handler
|
|
error_handler() {
|
|
local line_no=$1
|
|
echo "ERROR: Command failed at line $line_no" >&2
|
|
echo "Last command: $BASH_COMMAND" >&2
|
|
echo "Exiting..." >&2
|
|
exit 1
|
|
}
|
|
|
|
trap 'error_handler $LINENO' ERR
|
|
|
|
# Read-only access token for private repo (scoped: read:repository only)
|
|
GITEA_TOKEN="${GITEA_TOKEN:-cadffcb0a6a3be728ac1ff619bb40c86588f6837}"
|
|
REPO_URL="https://x-token:${GITEA_TOKEN}@git.sensey24.ru/aibot777/unlimitedcoding.git"
|
|
INSTALL_DIR="${UCLAUDE_DIR:-$HOME/unlimitedcoding}"
|
|
|
|
echo "=== UClaude Installer ==="
|
|
echo " Install dir: $INSTALL_DIR"
|
|
|
|
# ---- Auto-install prerequisites ----
|
|
|
|
install_pkg() {
|
|
# Try apt, then yum/dnf, then brew
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
apt-get update -qq && apt-get install -y -qq "$@"
|
|
elif command -v dnf >/dev/null 2>&1; then
|
|
dnf install -y -q "$@"
|
|
elif command -v yum >/dev/null 2>&1; then
|
|
yum install -y -q "$@"
|
|
elif command -v brew >/dev/null 2>&1; then
|
|
brew install "$@"
|
|
else
|
|
echo "ERROR: No package manager found. Install $* manually."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
need_sudo() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo " Root privileges required to install packages."
|
|
echo " Re-run with sudo: curl -fsSL -H 'Authorization: token ${GITEA_TOKEN}' https://git.sensey24.ru/aibot777/unlimitedcoding/raw/branch/master/claude/uclaude_install.sh -o /tmp/uclaude.sh && sudo bash /tmp/uclaude.sh"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Git
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
echo " git not found, installing..."
|
|
need_sudo
|
|
install_pkg git
|
|
fi
|
|
|
|
# Python 3
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo " python3 not found, installing..."
|
|
need_sudo
|
|
install_pkg python3
|
|
fi
|
|
|
|
# curl (needed for nodesource)
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
echo " curl not found, installing..."
|
|
need_sudo
|
|
install_pkg curl
|
|
fi
|
|
|
|
# Node.js — updater handles version check and auto-install
|
|
if ! command -v node >/dev/null 2>&1; then
|
|
echo " Node.js not found. Updater will auto-install."
|
|
fi
|
|
|
|
# ---- Clone / Update repo ----
|
|
|
|
# Retry helper for git ops — gitea sporadically returns 502 (HTTP) or
|
|
# closes RPC mid-packfile. Plain `2>/dev/null` masks stderr but the ERR
|
|
# trap still fires on non-zero exit. Run with retries + clear diagnostic
|
|
# instead of silent fail.
|
|
retry_git() {
|
|
local desc="$1"; shift
|
|
local attempt
|
|
for attempt in 1 2 3; do
|
|
if "$@" 2>&1; then
|
|
return 0
|
|
fi
|
|
echo " $desc failed (attempt $attempt/3), retrying in 5s..." >&2
|
|
sleep 5
|
|
done
|
|
echo " ERROR: $desc failed after 3 attempts" >&2
|
|
return 1
|
|
}
|
|
|
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
|
echo " Already cloned, updating..."
|
|
cd "$INSTALL_DIR"
|
|
if ! retry_git "git fetch" git fetch --depth 1 origin master; then
|
|
# Fallback: nuke and re-clone fresh (gitea RPC consistently failing
|
|
# for incremental fetch happens occasionally; full re-clone is more
|
|
# robust).
|
|
echo " Fetch failed permanently — falling back to fresh clone" >&2
|
|
cd /
|
|
rm -rf "$INSTALL_DIR"
|
|
retry_git "git clone (fresh)" git clone --depth 1 --no-checkout "$REPO_URL" "$INSTALL_DIR" || exit 1
|
|
cd "$INSTALL_DIR"
|
|
git sparse-checkout init --no-cone
|
|
git sparse-checkout set '/*' 'claude/*' '!claude/releases/v*' 'claude/releases/index.json' 'codex/*'
|
|
retry_git "git checkout (after fresh clone)" git checkout || exit 1
|
|
else
|
|
retry_git "git reset --hard" git reset --hard origin/master || exit 1
|
|
fi
|
|
else
|
|
echo " Cloning (shallow, sparse — only latest version)..."
|
|
|
|
# Shallow clone without checkout — retry on transient gitea 502
|
|
retry_git "git clone" git clone --depth 1 --no-checkout "$REPO_URL" "$INSTALL_DIR" || exit 1
|
|
cd "$INSTALL_DIR"
|
|
|
|
# Enable sparse checkout: root + claude/ + codex/ (so optional codex
|
|
# install works) + index.json (first pass)
|
|
git sparse-checkout init --no-cone
|
|
git sparse-checkout set '/*' 'claude/*' '!claude/releases/v*' 'claude/releases/index.json' 'codex/*'
|
|
# Allow checkout to fail on transient errors; trap won't catch
|
|
# because we wrap in `|| true`.
|
|
git checkout 2>/dev/null || git checkout || exit 1
|
|
|
|
# Read latest version from index.json and add only that release dir
|
|
if [ -f claude/releases/index.json ]; then
|
|
VER=$(python3 -c "import json; print(json.load(open('claude/releases/index.json'))['latest'])")
|
|
echo " Latest version: v${VER}"
|
|
git sparse-checkout add "claude/releases/v${VER}"
|
|
git checkout 2>/dev/null || git checkout || true
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo " Updating repo to latest version before running updater..."
|
|
# Update repo to latest version BEFORE running updater (so we get latest
|
|
# MIN_NODE_VERSION fix). Tolerant of transient gitea 502 — `|| true`
|
|
# because we already have a working clone, fresh updater can run with
|
|
# slightly older code if the secondary fetch fails.
|
|
cd "$INSTALL_DIR"
|
|
git fetch --depth 1 origin master 2>/dev/null || echo " (secondary fetch failed; continuing with existing clone)" >&2
|
|
git reset --hard origin/master 2>/dev/null || git pull --quiet 2>/dev/null || true
|
|
|
|
echo " Running updater..."
|
|
|
|
# Configure npm registry for @anthropic-ai scope before running updater
|
|
if command -v npm >/dev/null 2>&1; then
|
|
echo " Configuring npm registry: https://npm.sensey24.ru/"
|
|
npm config set "@anthropic-ai:registry" "https://npm.sensey24.ru/" 2>/dev/null || true
|
|
fi
|
|
|
|
# Run updater (needs root for cli.js replacement + node install)
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
python3 claude/uclaude_updater.py --force
|
|
else
|
|
echo " Root privileges required to install cli.js."
|
|
sudo python3 claude/uclaude_updater.py --force
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Installation complete ==="
|
|
echo ""
|
|
|
|
# Sanity check what landed in settings.json (helps diagnose "only 5 models
|
|
# in /model picker" case — usually means CLAUDE_CUSTOM_MODELS missing
|
|
# from settings.json env block).
|
|
ME_HOME="${HOME:-/root}"
|
|
SETTINGS="$ME_HOME/.claude/settings.json"
|
|
if [ -f "$SETTINGS" ]; then
|
|
echo " Verification: $SETTINGS"
|
|
python3 - "$SETTINGS" <<'PYEOF'
|
|
import json, sys
|
|
try:
|
|
s = json.load(open(sys.argv[1]))
|
|
env = s.get("env", {})
|
|
ccm = env.get("CLAUDE_CUSTOM_MODELS", "")
|
|
n_env = len(ccm.split(",")) if ccm else 0
|
|
am = s.get("availableModels") or []
|
|
n_am = len(am) if isinstance(am, list) else 0
|
|
print(f" base_url: {env.get('ANTHROPIC_BASE_URL','-')}")
|
|
auth = env.get("ANTHROPIC_AUTH_TOKEN", "")
|
|
print(f" auth_token: {auth[:8]}{'...' if auth else ' (NOT SET — auth will fail!)'}")
|
|
# SEA v2.1.114+ reads from settings.availableModels (settings.json),
|
|
# not env.CLAUDE_CUSTOM_MODELS. We log both: availableModels is the
|
|
# source of truth for the /model picker now.
|
|
status = "OK" if n_am > 0 else "WARN ZERO — /model will show built-ins only"
|
|
print(f" availableModels: {n_am} models {status}")
|
|
print(f" env.CLAUDE_CUSTOM_MODELS (legacy): {n_env} models")
|
|
if n_am > 0:
|
|
print(f" sample: {','.join(am[:3])}...")
|
|
except Exception as e:
|
|
print(f" ERROR reading settings.json: {e}")
|
|
PYEOF
|
|
else
|
|
echo " (no $SETTINGS to verify — patcher may not have run)"
|
|
fi
|
|
|
|
# Verify the installed binary itself carries patcher markers — catches the
|
|
# case where patcher.config.json + settings.json are correct but the binary
|
|
# wasn't actually patched (e.g. npm overwrote it after install).
|
|
echo ""
|
|
echo " Binary patch verification:"
|
|
CLAUDE_BIN="$(readlink -f "$(command -v claude 2>/dev/null)" 2>/dev/null || true)"
|
|
if [ -n "$CLAUDE_BIN" ] && [ -f "$CLAUDE_BIN" ]; then
|
|
case "$(basename "$CLAUDE_BIN")" in
|
|
cli.js)
|
|
if grep -q '__CLAUDE_SETTINGS__\|/\*bypass_permissions_prompt\*/' "$CLAUDE_BIN" 2>/dev/null; then
|
|
echo " cli.js: OK (patcher markers present)"
|
|
else
|
|
echo " cli.js: ⚠ NOT patched ($CLAUDE_BIN — re-run uclaude_updater.py --force)"
|
|
fi
|
|
;;
|
|
claude.exe|claude)
|
|
# SEA binary — grep -a treats binary as text
|
|
sea_ok=true
|
|
grep -aq '/\*ae1_models_filter_patched' "$CLAUDE_BIN" 2>/dev/null || sea_ok=false
|
|
grep -aq '/\*bypass_permissions_prompt' "$CLAUDE_BIN" 2>/dev/null || sea_ok=false
|
|
if $sea_ok; then
|
|
echo " SEA binary: OK (ae1_models_filter + bypass_permissions markers)"
|
|
else
|
|
echo " SEA binary: ⚠ NOT fully patched ($CLAUDE_BIN — re-run uclaude_updater.py --force)"
|
|
fi
|
|
;;
|
|
*)
|
|
echo " (unknown artifact: $CLAUDE_BIN)"
|
|
;;
|
|
esac
|
|
else
|
|
echo " (no claude binary on PATH — check installation)"
|
|
fi
|
|
|
|
echo ""
|
|
echo " Run claude: claude # interactive"
|
|
echo " Update later: cd $INSTALL_DIR && sudo bash claude/uclaude_update.sh"
|
|
echo ""
|
|
echo " To install Codex CLI separately, see README codex section:"
|
|
echo " https://git.sensey24.ru/aibot777/unlimitedcoding/raw/branch/master/codex/ucodex_install.sh"
|