diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..b003a03 --- /dev/null +++ b/setup.sh @@ -0,0 +1,266 @@ +#!/bin/bash +# ============================================================= +# Claude на чужих мозгах — установщик (Мак и Windows, один файл) +# Запуск одной командой (Мак, Терминал): +# curl -fsSL {URL}/setup.sh | bash +# Откат: +# curl -fsSL {URL}/setup.sh | bash -s -- restore +# Что делает: проверяет Claude и Ollama, создаёт псевдонимы +# моделей, кладёт два конфига (Ollama Cloud + Z.ai), перезапускает +# Claude. Скрипт открыт и короток: прочитай перед запуском. +# ============================================================= +set -euo pipefail + +# ---------- ОС ---------- +case "$(uname -s)" in + Darwin) OS=mac ;; + MINGW*|MSYS*|CYGWIN*) OS=win ;; + *) echo "Эта система не поддерживается (нужен Мак или Windows с Git Bash)."; exit 1 ;; +esac + +if [ "$OS" = "mac" ]; then + C3P="$HOME/Library/Application Support/Claude-3p" +else + C3P="$LOCALAPPDATA/Claude-3p" +fi +LIB="$C3P/configLibrary" +ID_OLLAMA="a11a0000-0000-4000-8000-0000000000aa" +ID_ZAI="a11a0000-0000-4000-8000-0000000000bb" +ID_DEFAULT="a11a0000-0000-4000-8000-0000000000dd" + +NO_RESTART=false +for a in "$@"; do [ "$a" = "--no-restart" ] && NO_RESTART=true; done + +say() { printf '\n\033[1m%s\033[0m\n' "$*"; } +ok() { printf ' \033[32m[ok]\033[0m %s\n' "$*"; } +warn() { printf ' \033[33m[!]\033[0m %s\n' "$*"; } + +# ---------- JSON-хелпер: python3 на Маке, powershell на Windows ---------- +json_tool() { + if command -v python3 >/dev/null 2>&1; then echo python3 + elif command -v powershell.exe >/dev/null 2>&1; then echo powershell + else echo none; fi +} + +# merge_meta +merge_meta() { + local applied="$1" tool; tool="$(json_tool)" + if [ "$tool" = "python3" ]; then + python3 - "$LIB" "$ID_OLLAMA" "$ID_ZAI" "$ID_DEFAULT" "$applied" <<'PY' +import json, os, sys +lib, id_o, id_z, id_d, applied = sys.argv[1:6] +p = os.path.join(lib, "_meta.json") +meta = json.load(open(p)) if os.path.exists(p) else {"appliedId": "", "entries": []} +ent = meta.get("entries", []) +def ensure(i, n): + e = next((x for x in ent if x.get("id") == i), None) + if e: e["name"] = n + else: ent.append({"id": i, "name": n}) +if not any(x.get("name") == "Default" for x in ent): + open(os.path.join(lib, id_d + ".json"), "w").write("{}") + ensure(id_d, "Default") +ensure(id_o, "ollama-cloud"); ensure(id_z, "zai-glm") +meta["entries"] = ent +if applied == "ollama": meta["appliedId"] = id_o +elif applied == "default": meta["appliedId"] = next(x["id"] for x in ent if x["name"] == "Default") +elif not meta.get("appliedId"): meta["appliedId"] = next(x["id"] for x in ent if x["name"] == "Default") +json.dump(meta, open(p, "w"), indent=2, ensure_ascii=False) +PY + elif [ "$tool" = "powershell" ]; then + MERGE_LIB="$(cygpath -w "$LIB")" MERGE_APPLIED="$applied" \ + MERGE_IDO="$ID_OLLAMA" MERGE_IDZ="$ID_ZAI" MERGE_IDD="$ID_DEFAULT" \ + powershell.exe -NoProfile -Command ' + $lib=$env:MERGE_LIB; $p=Join-Path $lib "_meta.json" + if (Test-Path $p) { $meta=Get-Content $p -Raw -Encoding UTF8 | ConvertFrom-Json } + else { $meta=[pscustomobject]@{appliedId="";entries=@()} } + function Ensure($id,$name){ + $e=$script:meta.entries | Where-Object { $_.id -eq $id } + if($e){$e.name=$name} + else{$script:meta.entries=@($script:meta.entries)+[pscustomobject]@{id=$id;name=$name}} + } + if(-not ($meta.entries | Where-Object {$_.name -eq "Default"})){ + "{}" | Set-Content (Join-Path $lib ($env:MERGE_IDD+".json")) -Encoding UTF8 + Ensure $env:MERGE_IDD "Default" + } + Ensure $env:MERGE_IDO "ollama-cloud" + Ensure $env:MERGE_IDZ "zai-glm" + $def=($meta.entries | Where-Object {$_.name -eq "Default"} | Select-Object -First 1).id + switch($env:MERGE_APPLIED){ + "ollama" { $meta.appliedId=$env:MERGE_IDO } + "default" { $meta.appliedId=$def } + default { if(-not $meta.appliedId){ $meta.appliedId=$def } } + } + $meta | ConvertTo-Json -Depth 5 | Set-Content $p -Encoding UTF8 + ' + else + warn "Не нашёл ни python3, ни powershell для работы с JSON."; exit 1 + fi +} + +restart_claude() { + if [ "$NO_RESTART" = "true" ]; then warn "Перезапуск Claude пропущен (--no-restart)."; return; fi + say "Перезапускаю приложение Claude..." + if [ "$OS" = "mac" ]; then + osascript -e 'tell application "Claude" to quit' >/dev/null 2>&1 || true + sleep 3 + open -a "Claude" + else + powershell.exe -NoProfile -Command ' + Get-Process -Name "Claude*" -ErrorAction SilentlyContinue | Stop-Process -Force + Start-Sleep 3 + $exe=Get-ChildItem "$env:LOCALAPPDATA\Programs" -Recurse -Filter "Claude.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 + if($exe){ Start-Process $exe.FullName } + ' || warn "Открой Claude вручную." + fi + ok "Готово. Открой выбор модели в чате: там GLM-5.3, Kimi K3 и Flash." +} + +# ---------- TLS-фикс песочницы (только Мак): для вкладки Code ---------- +apply_tls_fix() { + [ "$OS" = "mac" ] || return 0 # на Windows песочницы нет, фикс не нужен + local sj="$HOME/.claude/settings.json" + mkdir -p "$HOME/.claude" + [ -f "$sj" ] || echo '{}' > "$sj" + python3 - "$sj" <<'PY' +import json, sys +p = sys.argv[1] +try: + d = json.load(open(p)) + if not isinstance(d, dict): d = {} +except Exception: + d = {} +sb = d.get("sandbox") +if not isinstance(sb, dict): sb = {} +if sb.get("enableWeakerNetworkIsolation") is not True: + sb["enableWeakerNetworkIsolation"] = True + d["sandbox"] = sb + json.dump(d, open(p, "w"), indent=2, ensure_ascii=False) + print(" added") +else: + print(" present") +PY +} + +# ---------- ОТКАТ ---------- +if [ "${1:-}" = "restore" ]; then + say "Откат на родной Claude..." + if [ ! -f "$LIB/_meta.json" ]; then ok "Конфигов нет, откатывать нечего."; exit 0; fi + merge_meta default + ok "Применён конфиг Default: приложение вернётся к родному входу." + restart_claude + echo + echo "Если что-то осталось сломанным, полный сброс (сотрёт чаты стороннего режима):" + if [ "$OS" = "mac" ]; then + echo " rm -rf \"$HOME/Library/Application Support/Claude-3p\" \"$HOME/Library/Application Support/Claude Nest-3p\"" + else + echo " удали папки Claude-3p и \"Claude Nest-3p\" в %LOCALAPPDATA% (Проводник)" + fi + exit 0 +fi + +# ---------- 1. Claude ---------- +say "[1/5] Проверяю приложение Claude..." +CLAUDE_OK=false +if [ "$OS" = "mac" ] && [ -d "/Applications/Claude.app" ]; then CLAUDE_OK=true; fi +if [ "$OS" = "win" ] && find "$LOCALAPPDATA/Programs" -maxdepth 3 -name "Claude.exe" 2>/dev/null | grep -q .; then CLAUDE_OK=true; fi +if [ "$CLAUDE_OK" = "true" ]; then ok "Claude установлен." +else + warn "Claude не найден. Открываю страницу загрузки..." + if [ "$OS" = "mac" ]; then open "https://claude.com/download"; else powershell.exe -NoProfile -Command 'Start-Process "https://claude.com/download"'; fi + echo " Установи Claude, запусти его один раз, войди в аккаунт, затем запусти скрипт снова." + exit 0 +fi + +# ---------- 2. Ollama ---------- +say "[2/5] Проверяю программу Ollama..." +OLLAMA_BIN="" +if command -v ollama >/dev/null 2>&1; then OLLAMA_BIN="ollama" +elif [ "$OS" = "mac" ] && [ -x "/Applications/Ollama.app/Contents/Resources/ollama" ]; then OLLAMA_BIN="/Applications/Ollama.app/Contents/Resources/ollama" +fi +if [ -n "$OLLAMA_BIN" ]; then + ok "Ollama установлена." + if [ "$OS" = "mac" ]; then open -a "Ollama" >/dev/null 2>&1 || true; fi +else + printf " Ollama не найдена. Скачать? Нужна для маршрута Ollama Cloud [Y/n]: " + read -r ans < /dev/tty || ans=y + if [ "${ans:-y}" != "n" ] && [ "${ans:-y}" != "N" ]; then + if [ "$OS" = "mac" ]; then open "https://ollama.com/download"; else powershell.exe -NoProfile -Command 'Start-Process "https://ollama.com/download"'; fi + echo " Установи Ollama, запусти, войди в аккаунт (Sign in), затем запусти скрипт снова." + exit 0 + else + warn "Пропускаю Ollama: поставлю только конфиг Z.ai." + fi +fi + +# ---------- 3. Псевдонимы ---------- +HAVE_OLLAMA=false +if [ -n "$OLLAMA_BIN" ]; then + HAVE_OLLAMA=true + say "[3/5] Создаю псевдонимы облачных моделей (нужен вход в аккаунт Ollama)..." + make_alias() { + "$OLLAMA_BIN" pull "$1" >/dev/null 2>&1 || { + warn "Не получилось скачать $1. Скорее всего, нет входа в аккаунт." + echo " Открой окно Ollama (иконка ламы) -> Settings -> Sign in, затем запусти скрипт снова." + exit 1 + } + "$OLLAMA_BIN" cp "$1" "$2" >/dev/null + ok "$2 -> $1" + } + make_alias "glm-5.3:cloud" "claude-sonnet-4-6" + make_alias "kimi-k3:cloud" "claude-opus-4-6" + make_alias "glm-5.3-flash:cloud" "claude-haiku-4-5" +else + say "[3/5] Ollama нет, псевдонимы пропускаю." +fi + +# ---------- 4. Конфиги ---------- +say "[4/5] Кладу конфиги в приложение Claude..." +mkdir -p "$LIB" +[ -f "$C3P/developer_settings.json" ] || printf '{"allowDevTools": true}' > "$C3P/developer_settings.json" + +cat > "$LIB/$ID_OLLAMA.json" <<'JSON' +{ + "inferenceProvider": "gateway", + "inferenceGatewayBaseUrl": "http://localhost:11434", + "inferenceGatewayApiKey": "ollama", + "inferenceGatewayAuthScheme": "bearer", + "inferenceCredentialKind": "static", + "modelDiscoveryEnabled": false, + "inferenceModels": [ + { "name": "claude-sonnet-4-6", "labelOverride": "GLM-5.3 (Ollama Cloud)", "anthropicFamilyTier": "sonnet", "isFamilyDefault": true }, + { "name": "claude-opus-4-6", "labelOverride": "Kimi K3 (Ollama Cloud)", "anthropicFamilyTier": "opus", "isFamilyDefault": true }, + { "name": "claude-haiku-4-5", "labelOverride": "GLM-5.3 Flash (Ollama Cloud)", "anthropicFamilyTier": "haiku", "isFamilyDefault": true } + ] +} +JSON + +cat > "$LIB/$ID_ZAI.json" <<'JSON' +{ + "inferenceProvider": "gateway", + "inferenceGatewayBaseUrl": "https://api.z.ai/api/anthropic", + "inferenceGatewayApiKey": "ВСТАВЬ_СЮДА_СВОЙ_КЛЮЧ_ZAI", + "inferenceGatewayAuthScheme": "bearer", + "inferenceCredentialKind": "static", + "modelDiscoveryEnabled": false, + "inferenceModels": [ + { "name": "claude-sonnet-4-6", "labelOverride": "GLM-5.3 (Z.ai Coding Plan)", "anthropicFamilyTier": "sonnet", "isFamilyDefault": true }, + { "name": "claude-haiku-4-5", "labelOverride": "GLM-5.3 Flash (Z.ai, vision)", "anthropicFamilyTier": "haiku", "isFamilyDefault": true } + ] +} +JSON + +if [ "$HAVE_OLLAMA" = "true" ]; then merge_meta ollama; else merge_meta keep; fi +ok "Конфиги на месте: ollama-cloud (применён), zai-glm (ждёт твой ключ)." +ok "Developer-режим включён." +if [ "$OS" = "mac" ]; then say "[4.5/5] TLS-фикс песочницы для вкладки Code..."; res=$(apply_tls_fix); ok "sandbox.enableWeakerNetworkIsolation:$res"; fi + + +# ---------- 5. Финал ---------- +say "[5/5] Финал." +restart_claude +echo +echo "Что дальше:" +echo " · Модель выбирается в чате, рядом с полем ввода." +echo " · Z.ai: панель Developer -> Configure Third-Party Inference, конфигурация zai-glm," +echo " вставь свой ключ в Gateway API key, Apply Changes, перезапуск." +echo " · Вернуть родной Claude: bash setup.sh restore"