#!/bin/bash # ============================================================= # Claude на чужих мозгах — настройщик (Мак и Windows, один файл) # # Запуск (Мак, Терминал): # curl -fsSL {URL}/setup.sh | bash -s ollama только Ollama # curl -fsSL {URL}/setup.sh | bash -s zai только Z.ai # curl -fsSL {URL}/setup.sh | bash -s both обе конфигурации # curl -fsSL {URL}/setup.sh | bash спросит, что ставить # curl -fsSL {URL}/setup.sh | bash -s restore вернуть родного Claude # # Что делает: находит УЖЕ УСТАНОВЛЕННЫЕ приложения, кладёт конфиги # в приложение Claude и честно его перезапускает. # НИЧЕГО НЕ СКАЧИВАЕТ И НЕ УСТАНАВЛИВАЕТ: Claude и Ollama ставятся # руками по ссылкам из гайда. Трогает только приложение Claude, # терминальный Claude Code не затрагивается. # # Флаг --no-restart: не перезапускать приложение. # ============================================================= set -euo pipefail # ---------- ОС и пути ---------- case "$(uname -s)" in Darwin) OS=mac ;; MINGW*|MSYS*|CYGWIN*) OS=win ;; *) echo "Эта система не поддерживается (нужен Мак или Windows с Git Bash)."; exit 1 ;; esac # ---------- Обнаружение приложения (Windows) ---------- # Сборок две, и ведут они себя по-разному. # · Классическая (Squirrel): %LOCALAPPDATA%\AnthropicClaude\app-<версия>\claude.exe, # родные данные в %APPDATA%\Claude. # · MSIX (Microsoft Store и установщик с claude.com): C:\Program Files\WindowsApps\ # Claude_<версия>_x64__pzs8sxrjxfjjc\app\Claude.exe. Папка закрыта системой, exe # оттуда напрямую не запускается, приложение поднимают по идентификатору пакета, # а родные данные Windows подменяет на # %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude. # Сторонний режим у ОБЕИХ сборок живёт в %LOCALAPPDATA%\Claude-3p: так прописано # в коде приложения, а в манифесте MSIX эта папка исключена из подмены. WIN_KIND=""; WIN_AUMID=""; WIN_PFN=""; WIN_EXE=""; WIN_VER="" WIN_PROBE=' $ErrorActionPreference = "SilentlyContinue" $exe = @("$env:LOCALAPPDATA\AnthropicClaude", "$env:LOCALAPPDATA\Programs") | Where-Object { Test-Path $_ } | ForEach-Object { Get-ChildItem $_ -Recurse -Filter "claude.exe" -ErrorAction SilentlyContinue } | Select-Object -First 1 $pkg = $null try { if (-not (Get-Command Get-AppxPackage -ErrorAction SilentlyContinue) -and $PSVersionTable.PSVersion.Major -ge 7) { Import-Module Appx -UseWindowsPowerShell -ErrorAction SilentlyContinue } $all = @(Get-AppxPackage -Name "*Claude*" | Where-Object { $_.Name -eq "Claude" -or $_.Name -like "*ClaudeDesktop*" -or $_.Publisher -like "*Anthropic*" }) $pkg = $all | Where-Object { $_.Name -eq "Claude" } | Select-Object -First 1 if (-not $pkg) { $pkg = $all | Select-Object -First 1 } } catch { } $pfn = ""; $aumid = ""; $ver = "" if ($pkg) { $pfn = "$($pkg.PackageFamilyName)"; $ver = "$($pkg.Version)"; $id = "Claude" try { $ids = @((Get-AppxPackageManifest $pkg).Package.Applications.Application.Id); if ($ids.Count -gt 0 -and $ids[0]) { $id = "$($ids[0])" } } catch { } $aumid = "$pfn!$id" } else { $d = Get-ChildItem (Join-Path $env:LOCALAPPDATA "Packages") -Directory -Filter "Claude_*" | Select-Object -First 1 if ($d) { $pfn = $d.Name; $aumid = "$pfn!Claude" } } $kind = "" if ($pfn) { $launched = Test-Path (Join-Path $env:LOCALAPPDATA "Packages\$pfn\LocalCache\Roaming\Claude"); if ($launched -or -not $exe) { $kind = "msix" } } if (-not $kind -and $exe) { $kind = "classic" } $exePath = ""; if ($exe) { $exePath = $exe.FullName } Write-Output "$kind|$aumid|$pfn|$exePath|$ver" ' win_probe() { local out out="$(powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$WIN_PROBE" 2>/dev/null | tr -d '\r' | tail -n 1)" || out="" IFS='|' read -r WIN_KIND WIN_AUMID WIN_PFN WIN_EXE WIN_VER </dev/null 2>&1; then PY=python3 elif command -v python >/dev/null 2>&1; then PY=python fi have_ps() { command -v powershell.exe >/dev/null 2>&1; } [ -n "$PY" ] || have_ps || die "Нужен python3 (Мак) или powershell (Windows) для работы с JSON." # ---------- Флаг разработчика ---------- # ВАЖНО. Приложение читает developer_settings.json из ТЕКУЩЕЙ папки данных, # а она переключается на -3p только когда сторонний режим уже взлетел. # Конфиги при этом всегда читаются из -3p. Замкнутый круг: если положить # флаг только в -3p, до переключения приложение его не увидит и пункта # Developer не будет. Поэтому пишем в ОБЕ папки. write_dev_flag() { local d for d in "${P1_DIRS[@]}" "$C3P"; do mkdir -p "$d" printf '{"allowDevTools": true}\n' > "$d/developer_settings.json" done ok "Режим разработчика включён во всех папках приложения." } # ---------- deploymentMode ---------- # Ключ "deploymentMode": "1p" намертво запрещает сторонний режим. clear_deployment_pin() { local f="$C3P/claude_desktop_config.json" [ -f "$f" ] || return 0 [ -n "$PY" ] || return 0 "$PY" - "$f" <<'PY' import json, sys p = sys.argv[1] try: d = json.load(open(p, encoding="utf-8")) except Exception: sys.exit(0) if isinstance(d, dict) and d.get("deploymentMode") == "1p": d.pop("deploymentMode", None) json.dump(d, open(p, "w", encoding="utf-8"), indent=2, ensure_ascii=False) print("снят") PY } # ---------- Чистка BOM ---------- # Приложение падает на BOM: в логе видно # [managedConfig] Failed to read config library meta: Unexpected token # и тогда ВСЯ библиотека конфигов не читается. BOM приезжает из Блокнота на # Windows (он по умолчанию сохраняет UTF-8 именно так) и из старых скриптов. repair_json_bom() { [ -n "$PY" ] || return 0 "$PY" - "$LIB" "${P1_DIRS[@]}" "$C3P" <<'PY' import os, sys lib = sys.argv[1] dirs = sys.argv[2:] targets = [] if os.path.isdir(lib): targets += [os.path.join(lib, f) for f in os.listdir(lib) if f.endswith(".json")] for d in dirs: for n in ("developer_settings.json", "claude_desktop_config.json"): p = os.path.join(d, n) if os.path.isfile(p): targets.append(p) fixed = 0 for p in targets: try: b = open(p, "rb").read() if b.startswith(b"\xef\xbb\xbf"): open(p, "wb").write(b[3:]); fixed += 1 except Exception: pass if fixed: print(fixed) PY } # merge_meta merge_meta() { local applied="$1" routes="$2" if [ -n "$PY" ]; then "$PY" - "$LIB" "$ID_OLLAMA" "$ID_ZAI" "$ID_DEFAULT" "$applied" "$routes" <<'PY' import json, os, sys lib, id_o, id_z, id_d, applied, routes = sys.argv[1:7] p = os.path.join(lib, "_meta.json") try: meta = json.load(open(p, encoding="utf-8")) if not isinstance(meta, dict): meta = {} except Exception: meta = {} ent = meta.get("entries") or [] 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}) # Default обязан существовать как пустой конфиг: это точка возврата. dflt = next((x for x in ent if x.get("name") == "Default"), None) if dflt is None: open(os.path.join(lib, id_d + ".json"), "w", encoding="utf-8").write("{}") ensure(id_d, "Default") dflt_id = id_d else: dflt_id = dflt["id"] # Старые записи с нашими именами, но чужими id (от ручной настройки или прошлых # версий) переименовываем, иначе в панели будут два одинаковых пункта. ours = {id_o: "ollama-cloud", id_z: "zai-glm"} for x in ent: if x.get("name") in ours.values() and x.get("id") not in ours: x["name"] = x["name"] + " (старый)" if routes in ("ollama", "both"): ensure(id_o, "ollama-cloud") if routes in ("zai", "both"): ensure(id_z, "zai-glm") # Выкидываем записи, у которых файла на диске нет: они ломают выбор в панели. ent = [x for x in ent if os.path.exists(os.path.join(lib, x.get("id", "") + ".json"))] meta["entries"] = ent meta["appliedId"] = {"ollama": id_o, "zai": id_z}.get(applied, dflt_id) json.dump(meta, open(p, "w", encoding="utf-8"), indent=2, ensure_ascii=False) PY else MERGE_LIB="$(cygpath -w "$LIB")" MERGE_APPLIED="$applied" MERGE_ROUTES="$routes" \ 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" } if($env:MERGE_ROUTES -eq "ollama" -or $env:MERGE_ROUTES -eq "both"){ Ensure $env:MERGE_IDO "ollama-cloud" } if($env:MERGE_ROUTES -eq "zai" -or $env:MERGE_ROUTES -eq "both"){ Ensure $env:MERGE_IDZ "zai-glm" } $meta.entries = @($meta.entries | Where-Object { Test-Path (Join-Path $lib ($_.id+".json")) }) $def=($meta.entries | Where-Object {$_.name -eq "Default"} | Select-Object -First 1).id switch($env:MERGE_APPLIED){ "ollama" { $meta.appliedId=$env:MERGE_IDO } "zai" { $meta.appliedId=$env:MERGE_IDZ } default { $meta.appliedId=$def } } $meta | ConvertTo-Json -Depth 5 | Set-Content $p -Encoding UTF8 ' fi } # Проверяем, что применённый конфиг действительно включает сторонний режим. check_applied() { [ -n "$PY" ] || return 0 "$PY" - "$LIB" <<'PY' import json, os, sys lib = sys.argv[1] try: meta = json.load(open(os.path.join(lib, "_meta.json"), encoding="utf-8")) applied = meta.get("appliedId", "") cfg = json.load(open(os.path.join(lib, applied + ".json"), encoding="utf-8")) except Exception as e: print("НЕТ: не смог прочитать конфиг (%s)" % e); sys.exit(0) name = next((x.get("name") for x in meta.get("entries", []) if x.get("id") == applied), applied) print("ДА: %s" % name if cfg.get("inferenceProvider") else "НЕТ: применён пустой конфиг «%s»" % name) PY } # ---------- Чистка кэшей ---------- # Повторяет пункт меню Developer -> Clear Cache and Restart. В коде приложения он # чистит ТОЛЬКО кэши: HTTP, скомпилированный JS, шейдеры, service workers, DNS. # Ни cookies, ни Local Storage там нет, поэтому вход в аккаунт, чаты, MCP, # скиллы и наши конфиги остаются на месте. Делать это можно лишь при закрытом # приложении, иначе файлы заняты. # ВАЖНО: не путать с соседним пунктом Reset App Data, тот сносит папку целиком. clear_caches() { local d for d in "${P1_DIRS[@]}" "$C3P"; do [ -d "$d" ] || continue rm -rf "$d/Cache" "$d/Code Cache" "$d/GPUCache" \ "$d/DawnGraphiteCache" "$d/DawnWebGPUCache" "$d/Service Worker" 2>/dev/null || true done ok "Кэши очищены (чаты, входы и конфиги не тронуты)." } # ---------- Обнаружение приложения (Мак) ---------- # pgrep тут ненадёжен: под песочницей он не видит процесс, хотя тот живой. # Единственный путь приложения, который нам нужен, это главный бинарь в # /Applications. Рядом крутится встроенный агент по похожему пути внутри # папки данных, и его трогать нельзя, поэтому сравниваем строго целиком. CLAUDE_BIN="/Applications/Claude.app/Contents/MacOS/Claude" claude_pids() { ps -Ao pid=,comm= | awk -v b="$CLAUDE_BIN" '$2==b {print $1}'; } claude_running() { case "$(osascript -e 'application "Claude" is running' 2>/dev/null)" in true) return 0 ;; esac [ -n "$(claude_pids)" ] } # ---------- Честный перезапуск ---------- # open -a на живом процессе только активирует окно: приложение не завершается # при закрытии окна и настройки перечитывает лишь при настоящем старте. restart_claude() { if [ "$NO_RESTART" = "true" ]; then warn "Перезапуск пропущен (--no-restart). Настройки применятся после Cmd+Q и запуска."; return 0; fi say "Перезапускаю приложение Claude..." if [ "$OS" = "mac" ]; then if claude_running; then osascript -e 'tell application "Claude" to quit' >/dev/null 2>&1 || true i=0; while claude_running && [ "$i" -lt 12 ]; do sleep 1; i=$((i+1)); done if claude_running; then for pid in $(claude_pids); do kill "$pid" 2>/dev/null || true; done sleep 3 fi if claude_running; then warn "Claude не закрылся сам (обычно это запрет на автоматизацию:" echo " Системные настройки -> Конфиденциальность и безопасность -> Автоматизация)." echo " Конфиги уже на месте. Закрой Claude вручную через Cmd+Q и открой заново." return 0 fi fi clear_caches open -a "Claude" >/dev/null 2>&1 || { warn "Не смог открыть Claude, открой вручную."; return 0; } # Проверяем, что процесс реально поднялся: рапортовать успех вслепую нельзя. j=0; while ! claude_running && [ "$j" -lt 10 ]; do sleep 1; j=$((j+1)); done if ! claude_running; then warn "Claude не поднялся автоматически. Конфиги на месте, открой приложение вручную." return 0 fi else powershell.exe -NoProfile -Command ' Get-Process -Name "Claude*" -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep 3' 2>/dev/null || true clear_caches if [ "$WIN_KIND" = "msix" ]; then # Пакетное приложение запускают по идентификатору, а не по пути к exe. AK_AUMID="$WIN_AUMID" powershell.exe -NoProfile -Command ' $t = "shell:AppsFolder\" + $env:AK_AUMID try { Start-Process $t -ErrorAction Stop; exit 0 } catch { } try { Start-Process explorer.exe $t -ErrorAction Stop; exit 0 } catch { } $a = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\Claude.exe" if (Test-Path $a) { try { Start-Process $a -ErrorAction Stop; exit 0 } catch { } } try { Start-Process "claude://" -ErrorAction Stop; exit 0 } catch { } exit 1 ' || { warn "Не смог запустить Claude, открой его вручную из меню Пуск."; return 0; } elif [ -n "$WIN_EXE" ]; then AK_EXE="$WIN_EXE" powershell.exe -NoProfile -Command 'Start-Process $env:AK_EXE' \ || { warn "Не смог перезапустить Claude, закрой и открой его вручную."; return 0; } else warn "Не нашёл приложение Claude, открой его вручную."; return 0 fi # Проверяем, что процесс реально поднялся: рапортовать успех вслепую нельзя. j=0 while [ "$j" -lt 12 ]; do if powershell.exe -NoProfile -Command 'if (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }' 2>/dev/null; then break; fi sleep 1; j=$((j+1)) done if [ "$j" -ge 12 ]; then warn "Claude не поднялся автоматически. Конфиги на месте, открой приложение вручную." return 0 fi fi ok "Claude перезапущен." } # ---------- TLS-фикс песочницы (только Мак, для вкладки Code) ---------- apply_tls_fix() { [ "$OS" = "mac" ] || return 0 [ -n "$PY" ] || return 0 local sj="$HOME/.claude/settings.json" mkdir -p "$HOME/.claude" [ -f "$sj" ] || echo '{}' > "$sj" "$PY" - "$sj" <<'PY' import json, sys p = sys.argv[1] try: d = json.load(open(p, encoding="utf-8")) 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", encoding="utf-8"), indent=2, ensure_ascii=False) print("добавлен") else: print("уже был") PY } # ---------- ОТКАТ ---------- if [ "$MODE" = "restore" ]; then say "Откат на родного Claude..." if [ ! -f "$LIB/_meta.json" ]; then ok "Конфигов нет, откатывать нечего."; exit 0; fi merge_meta default none repair_json_bom >/dev/null 2>&1 || true ok "Применён пустой профиль Default." echo " Если приложение всё равно открывается на чужом мозге: меню аккаунта внизу слева -> Sign out," echo " затем на экране входа выбери обычный вход по почте или Google." write_dev_flag restart_claude echo echo "Если что-то осталось сломанным, полный сброс (сотрёт чаты стороннего режима):" echo " rm -rf \"$C3P\"" exit 0 fi # ---------- 1. Приложение Claude ---------- say "[1/4] Ищу приложение Claude..." CLAUDE_OK=false if [ "$OS" = "mac" ] && [ -d "/Applications/Claude.app" ]; then CLAUDE_OK=true; fi if [ "$OS" = "win" ] && [ -n "$WIN_KIND" ]; then CLAUDE_OK=true; fi if [ "$CLAUDE_OK" = "true" ]; then case "$WIN_KIND" in msix) ok "Claude на месте: сборка MSIX (Microsoft Store или установщик с claude.com)${WIN_VER:+, версия $WIN_VER}." ;; classic) ok "Claude на месте: классическая сборка." ;; *) ok "Claude на месте." ;; esac ok "Конфиги стороннего режима: $C3P" else warn "Приложение Claude не найдено, а без него настраивать нечего." [ "$OS" = "win" ] && echo " Смотрел классическую сборку в AppData\\Local и пакеты Microsoft Store." echo echo " Поставь его руками, это одна минута:" echo " 1. Открой https://claude.com/download и скачай версию для своей системы." echo " 2. Установи и запусти приложение хотя бы один раз." echo " 3. Вернись сюда и запусти эту же команду снова." echo echo " Логиниться в аккаунт Anthropic не обязательно." exit 1 fi # ---------- 2. Выбор мозга ---------- if [ -z "$BRAIN" ]; then say "[2/4] Какой мозг подключаем?" echo " 1) Ollama бесплатный старт, программа Ollama должна быть установлена и запущена" echo " 2) Z.ai по подписке от \$18, локально ничего не нужно, только ключ" echo " 3) оба положу обе конфигурации, переключаться будешь в приложении" printf " Твой выбор [1/2/3, по умолчанию 3]: " choice="" [ -r /dev/tty ] && { read -r choice < /dev/tty || choice=""; } case "${choice:-3}" in 1) BRAIN=ollama ;; 2) BRAIN=zai ;; *) BRAIN=both ;; esac else say "[2/4] Режим: $BRAIN" fi case "$BRAIN" in ollama) ok "Ставлю только Ollama." ;; zai) ok "Ставлю только Z.ai." ;; both) ok "Ставлю обе конфигурации." ;; *) warn "Непонятный режим «$BRAIN», беру оба."; BRAIN=both ;; esac # ---------- 3. Ollama и псевдонимы ---------- HAVE_OLLAMA=false if [ "$BRAIN" = "ollama" ] || [ "$BRAIN" = "both" ]; then say "[3/4] Проверяю программу 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 [ -z "$OLLAMA_BIN" ]; then warn "Ollama не найдена." echo echo " Поставь её руками:" echo " 1. Открой https://ollama.com/download и скачай версию для своей системы." echo " 2. Установи, запусти, войди в аккаунт (иконка ламы -> Sign in)." echo " 3. Вернись сюда и запусти команду снова." echo if [ "$BRAIN" = "ollama" ]; then echo " Или подключи Z.ai, ему Ollama не нужна: та же команда со словом zai вместо ollama." exit 1 fi warn "Пропускаю Ollama, кладу только конфиг Z.ai." BRAIN=zai else ok "Ollama на месте." [ "$OS" = "mac" ] && { open -a "Ollama" >/dev/null 2>&1 || true; } HAVE_OLLAMA=true say " Создаю псевдонимы облачных моделей (нужен вход в аккаунт Ollama)..." # Приложение пускает только точные идентификаторы моделей Claude, # поэтому облачная модель получает второе, «правильное» имя. make_alias() { "$OLLAMA_BIN" pull "$1" >/dev/null 2>&1 || { if [ "${3:-}" = "optional" ]; then warn "$1 недоступна на твоём тарифе Ollama, пропускаю (нужен Pro или Max)." return 0 fi warn "Не получилось скачать $1. Обычно это нет входа в аккаунт Ollama." echo " Открой окно Ollama (иконка ламы) -> 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" make_alias "qwen3.5:397b-cloud" "claude-sonnet-4-5" optional make_alias "deepseek-v4-pro:0813-cloud" "claude-opus-4-1" optional make_alias "gpt-oss:120b-cloud" "claude-haiku-3-5" optional make_alias "minimax-m3:cloud" "claude-3-7-sonnet" optional make_alias "nemotron-3-super:cloud" "claude-3-5-haiku" optional fi else say "[3/4] Маршрут Z.ai, Ollama не нужна." fi # ---------- 4. Конфиги ---------- say "[4/4] Кладу конфиги в приложение Claude..." mkdir -p "$LIB" write_dev_flag pin="$(clear_deployment_pin || true)" [ -n "$pin" ] && ok "Снят запрет на сторонний режим (deploymentMode)." if [ "$BRAIN" = "ollama" ] || [ "$BRAIN" = "both" ]; then cat > "$LIB/$ID_OLLAMA.json" <<'JSON' { "inferenceProvider": "gateway", "inferenceGatewayBaseUrl": "http://127.0.0.1:11434", "inferenceGatewayApiKey": "ollama", "inferenceGatewayAuthScheme": "bearer", "inferenceCredentialKind": "static", "modelDiscoveryEnabled": false, "inferenceModels": [ { "name": "claude-sonnet-4-6", "labelOverride": "GLM-5.3 (Ollama)", "anthropicFamilyTier": "sonnet", "isFamilyDefault": true }, { "name": "claude-opus-4-6", "labelOverride": "Kimi K3 (Ollama)", "anthropicFamilyTier": "opus", "isFamilyDefault": true }, { "name": "claude-haiku-4-5", "labelOverride": "GLM-5.3 Flash (Ollama)", "anthropicFamilyTier": "haiku", "isFamilyDefault": true }, { "name": "claude-sonnet-4-5", "labelOverride": "Qwen3.5 397B (Ollama)", "anthropicFamilyTier": "sonnet", "isFamilyDefault": false }, { "name": "claude-opus-4-1", "labelOverride": "DeepSeek V4 Pro (Ollama)", "anthropicFamilyTier": "opus", "isFamilyDefault": false }, { "name": "claude-haiku-3-5", "labelOverride": "GPT-OSS 120B (Ollama)", "anthropicFamilyTier": "haiku", "isFamilyDefault": false }, { "name": "claude-3-7-sonnet", "labelOverride": "MiniMax M3 (Ollama)", "anthropicFamilyTier": "sonnet", "isFamilyDefault": false }, { "name": "claude-3-5-haiku", "labelOverride": "Nemotron 3 Super (Ollama)", "anthropicFamilyTier": "haiku", "isFamilyDefault": false } ] } JSON ok "Конфиг ollama-cloud на месте." fi if [ "$BRAIN" = "zai" ] || [ "$BRAIN" = "both" ]; then if [ -f "$LIB/$ID_ZAI.json" ] && ! grep -q "$ZAI_PLACEHOLDER" "$LIB/$ID_ZAI.json"; then ok "Конфиг zai-glm уже настроен, ключ не трогаю." else cat > "$LIB/$ID_ZAI.json" < Configure Third-Party Inference и выбери конфигурацию вручную." ;; esac restart_claude # ---------- Что дальше ---------- echo echo "Что дальше:" if [ "$HAVE_OLLAMA" = "true" ]; then echo " · Открой новый чат и кликни по имени модели рядом с полем ввода:" echo " там GLM-5.3 и GLM-5.3 Flash, а на платном тарифе Ollama ещё и Kimi K3." echo " · Программа Ollama должна быть запущена: закрыл её, мост отвалился." fi if [ "$BRAIN" = "zai" ] || [ "$BRAIN" = "both" ]; then echo " · Z.ai: возьми ключ на z.ai/manage-apikey/apikey-list, затем в приложении" echo " Developer -> Configure Third-Party Inference -> конфигурация zai-glm ->" echo " вставь ключ в Gateway API key -> Apply Changes -> перезапусти Claude." fi echo " · Нет пункта Developer? Полностью выйди из Claude (Cmd+Q) и открой заново." echo " · Вернуть родного Claude: та же команда со словом restore."