# ============================================================= # Claude на чужих мозгах — настройщик (Windows, PowerShell) # # Запуск: # $env:AK='ollama'; irm {URL}/setup.ps1 | iex только Ollama # $env:AK='zai'; irm {URL}/setup.ps1 | iex только Z.ai # $env:AK='both'; irm {URL}/setup.ps1 | iex обе конфигурации # irm {URL}/setup.ps1 | iex спросит, что ставить # $env:AK='restore'; irm {URL}/setup.ps1 | iex вернуть родного Claude # # Что делает: находит УЖЕ УСТАНОВЛЕННЫЕ приложения, кладёт конфиги # в приложение Claude и перезапускает его. Ничего не скачивает # и не устанавливает. Терминальный Claude Code не затрагивается. # # Понимает обе сборки приложения Claude для Windows: # · классическую: %LOCALAPPDATA%\AnthropicClaude\app-<версия>\claude.exe; # · MSIX (Microsoft Store и установщик с claude.com): # C:\Program Files\WindowsApps\Claude_<версия>_x64__pzs8sxrjxfjjc\app\Claude.exe. # Сторонний режим у обеих живёт в %LOCALAPPDATA%\Claude-3p (так в коде приложения), # родная папка данных зависит от сборки, см. Get-ClaudeInstall. # # ВНИМАНИЕ при правке: сохранять в UTF-8 БЕЗ BOM. Gitea отдаёт файл # с заголовком charset=utf-8, irm декодирует кириллицу правильно, # а BOM в конвейере irm | iex становится лишней командой и роняет разбор. # ============================================================= # Никакого param(): скрипт запускается через `irm ... | iex`, а там param # обязан быть самым первым выражением и всё равно не принимает аргументы. # Режим передаётся переменной окружения AK. $Mode = "" if ($env:AK) { $Mode = $env:AK; $env:AK = $null } $ErrorActionPreference = "Stop" $ID_OLLAMA = "a11a0000-0000-4000-8000-0000000000aa" $ID_ZAI = "a11a0000-0000-4000-8000-0000000000bb" $ID_DEFAULT = "a11a0000-0000-4000-8000-0000000000dd" $ZAI_PLACEHOLDER = "ВСТАВЬ_СЮДА_СВОЙ_КЛЮЧ_ZAI" function Say($t) { Write-Host "`n$t" -ForegroundColor White } function Ok($t) { Write-Host " [ok] $t" -ForegroundColor Green } function Warn($t) { Write-Host " [!] $t" -ForegroundColor Yellow } # JSON приложение читает строго: BOM его ломает, поэтому пишем UTF-8 без BOM. function Write-Utf8($path, $text) { $enc = New-Object System.Text.UTF8Encoding($false) [IO.File]::WriteAllText($path, $text, $enc) } function Read-Json($path) { if (-not (Test-Path $path)) { return $null } try { Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json } catch { $null } } # Windows PowerShell 5.1 при $ErrorActionPreference = "Stop" превращает любую # строку stderr внешней программы в ошибку, если поток перенаправлен в $null. # Ollama пишет прогресс "pulling manifest" именно в stderr, и первая же строка # роняла скрипт. Поэтому внешние команды зовём через эту обёртку: внутри неё # своя, мягкая политика ошибок, наружу уходит только код возврата. function Invoke-Quiet { $ErrorActionPreference = "SilentlyContinue" $exe = $args[0] $rest = @() if ($args.Count -gt 1) { $rest = @($args[1..($args.Count - 1)]) } & $exe @rest 2>&1 | Out-Null return $LASTEXITCODE } # ---------- Где приложение и где его данные ---------- # Сборок две, и ведут они себя по-разному. # · Классическая (Squirrel): %LOCALAPPDATA%\AnthropicClaude\app-<версия>\claude.exe, # родные данные в %APPDATA%\Claude, логи в %LOCALAPPDATA%\Claude\logs. # · MSIX (Microsoft Store и установщик с claude.com): # C:\Program Files\WindowsApps\Claude_<версия>_x64__pzs8sxrjxfjjc\app\Claude.exe. # Папка WindowsApps закрыта системой, exe оттуда напрямую не запускается, а всё, # что приложение пишет в %APPDATA%, Windows подменяет на # %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude. # Сторонний режим у ОБЕИХ сборок живёт в %LOCALAPPDATA%\Claude-3p: так прописано # в коде приложения, а в манифесте MSIX эта папка исключена из подмены. function Find-ClaudeExe { @("$env:LOCALAPPDATA\AnthropicClaude", "$env:LOCALAPPDATA\Programs") | Where-Object { Test-Path $_ } | ForEach-Object { Get-ChildItem $_ -Recurse -Filter "claude.exe" -ErrorAction SilentlyContinue } | Select-Object -First 1 } function Find-ClaudeMsix { $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*" -ErrorAction SilentlyContinue | 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 { $pkg = $null } if ($pkg) { $appId = "Claude" try { $ids = @((Get-AppxPackageManifest $pkg).Package.Applications.Application.Id) if ($ids.Count -gt 0 -and $ids[0]) { $appId = "$($ids[0])" } } catch { } return [pscustomobject]@{ Family = "$($pkg.PackageFamilyName)"; Aumid = "$($pkg.PackageFamilyName)!$appId"; Version = "$($pkg.Version)" } } # Запасной путь: Get-AppxPackage бывает недоступен (PowerShell 7 без модуля Appx), # но папка данных пакета остаётся на месте после первого запуска приложения. $d = Get-ChildItem (Join-Path $env:LOCALAPPDATA "Packages") -Directory -Filter "Claude_*" -ErrorAction SilentlyContinue | Select-Object -First 1 if ($d) { return [pscustomobject]@{ Family = $d.Name; Aumid = "$($d.Name)!Claude"; Version = "" } } return $null } function Get-ClaudeInstall { $msix = Find-ClaudeMsix $exe = Find-ClaudeExe if ($msix) { $home1p = Join-Path $env:LOCALAPPDATA "Packages\$($msix.Family)\LocalCache\Roaming\Claude" $launched = Test-Path $home1p # Если рядом осталась и классическая сборка, верим той, которой пользовались. if ($launched -or -not $exe) { $label = "сборка MSIX (Microsoft Store или установщик с claude.com)" if ($msix.Version) { $label += ", версия $($msix.Version)" } return [pscustomobject]@{ Kind = "msix"; Label = $label; Aumid = $msix.Aumid; Exe = $null; Home = $home1p; Launched = $launched } } } if ($exe) { $home1p = Join-Path $env:APPDATA "Claude" return [pscustomobject]@{ Kind = "classic"; Label = "классическая сборка"; Aumid = $null; Exe = $exe.FullName; Home = $home1p; Launched = (Test-Path $home1p) } } return $null } $INST = Get-ClaudeInstall # Сторонний режим: конфиги, библиотека, флаг разработчика для стороннего режима. $C3P = Join-Path $env:LOCALAPPDATA "Claude-3p" $LIB = Join-Path $C3P "configLibrary" # Родные папки: сюда кладём только флаг разработчика и чистим кэши. Их несколько, # потому что настоящая зависит от сборки, а лишний флаг ничему не вредит. $P1_DIRS = @() if ($INST) { $P1_DIRS += $INST.Home } foreach ($d in @((Join-Path $env:APPDATA "Claude"), (Join-Path $env:LOCALAPPDATA "Claude"), (Join-Path $env:LOCALAPPDATA "Claude-Data"))) { if ($P1_DIRS -notcontains $d -and ((Test-Path $d) -or $P1_DIRS.Count -eq 0)) { $P1_DIRS += $d } } if ($P1_DIRS.Count -eq 0) { $P1_DIRS += (Join-Path $env:APPDATA "Claude") } $ALL_DIRS = @($P1_DIRS) + @($C3P) # Пакетное приложение запускают по идентификатору, а не по пути к exe. function Start-Claude { if ($INST.Kind -eq "msix") { $target = "shell:AppsFolder\$($INST.Aumid)" try { Start-Process $target -ErrorAction Stop; return } catch { } try { Start-Process explorer.exe $target -ErrorAction Stop; return } catch { } $alias = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\Claude.exe" if (Test-Path $alias) { try { Start-Process $alias -ErrorAction Stop; return } catch { } } try { Start-Process "claude://" -ErrorAction Stop } catch { } return } if ($INST.Exe) { Start-Process $INST.Exe } } # ВАЖНО. Приложение читает developer_settings.json из ТЕКУЩЕЙ папки данных, # а она переключается на -3p только когда сторонний режим уже взлетел. # Кладём флаг и в родные папки, и в -3p, иначе пункт Developer не появится. function Write-DevFlag { foreach ($d in $ALL_DIRS) { New-Item -ItemType Directory -Force -Path $d | Out-Null Write-Utf8 (Join-Path $d "developer_settings.json") '{"allowDevTools": true}' } Ok "Режим разработчика включён во всех папках приложения." } # "deploymentMode": "1p" намертво запрещает сторонний режим. function Clear-DeploymentPin { $f = Join-Path $C3P "claude_desktop_config.json" $cfg = Read-Json $f if ($cfg -and $cfg.PSObject.Properties.Name -contains "deploymentMode" -and $cfg.deploymentMode -eq "1p") { $cfg.PSObject.Properties.Remove("deploymentMode") Write-Utf8 $f ($cfg | ConvertTo-Json -Depth 5) Ok "Снят запрет на сторонний режим (deploymentMode)." } } # Приложение падает на BOM: в логе видно # [managedConfig] Failed to read config library meta: Unexpected token 'п»ї' # и тогда ВСЯ библиотека конфигов не читается. BOM берётся из старых версий # скрипта и из Блокнота, который по умолчанию сохраняет UTF-8 именно так. # Поэтому чистим BOM у всех json приложения, а не только у своих. function Repair-JsonBom { $files = @() if (Test-Path $LIB) { $files += Get-ChildItem $LIB -Filter *.json -ErrorAction SilentlyContinue } foreach ($d in $ALL_DIRS) { foreach ($n in @("developer_settings.json", "claude_desktop_config.json")) { $f = Join-Path $d $n if (Test-Path $f) { $files += Get-Item $f } } } $fixed = 0 foreach ($f in $files) { try { $b = [IO.File]::ReadAllBytes($f.FullName) if ($b.Length -ge 3 -and $b[0] -eq 239 -and $b[1] -eq 187 -and $b[2] -eq 191) { [IO.File]::WriteAllBytes($f.FullName, $b[3..($b.Length - 1)]) $fixed++ } } catch { } } if ($fixed -gt 0) { Ok "Убран BOM у $fixed файлов конфигурации (приложение их не читало)." } } function Merge-Meta($applied, $routes) { $p = Join-Path $LIB "_meta.json" $old = Read-Json $p # Собираем список записей заново: мутировать объект из ConvertFrom-Json # ненадёжно, свойства может не быть вовсе. $entries = [System.Collections.ArrayList]::new() if ($old -and $old.entries) { foreach ($e in @($old.entries)) { if ($e.id) { [void]$entries.Add([pscustomobject]@{ id = "$($e.id)"; name = "$($e.name)" }) } } } function Set-Entry($list, $id, $name) { $e = $list | Where-Object { $_.id -eq $id } | Select-Object -First 1 if ($e) { $e.name = $name } else { [void]$list.Add([pscustomobject]@{ id = $id; name = $name }) } } if (-not ($entries | Where-Object { $_.name -eq "Default" })) { Write-Utf8 (Join-Path $LIB "$ID_DEFAULT.json") "{}" Set-Entry $entries $ID_DEFAULT "Default" } # Старые записи с нашими именами, но чужими id переименовываем, иначе в панели два одинаковых пункта. foreach ($e in $entries) { if (($e.name -eq "ollama-cloud" -or $e.name -eq "zai-glm") -and $e.id -ne $ID_OLLAMA -and $e.id -ne $ID_ZAI) { $e.name = $e.name + " (старый)" } } if ($routes -eq "ollama" -or $routes -eq "both") { Set-Entry $entries $ID_OLLAMA "ollama-cloud" } if ($routes -eq "zai" -or $routes -eq "both") { Set-Entry $entries $ID_ZAI "zai-glm" } # Записи без файла на диске ломают выбор в панели. $kept = @($entries | Where-Object { Test-Path (Join-Path $LIB ("$($_.id).json")) }) $def = ($kept | Where-Object { $_.name -eq "Default" } | Select-Object -First 1).id $appliedId = switch ($applied) { "ollama" { $ID_OLLAMA } "zai" { $ID_ZAI } default { $def } } $meta = [pscustomobject]@{ appliedId = $appliedId; entries = $kept } Write-Utf8 $p ($meta | ConvertTo-Json -Depth 5) } function Check-Applied { $meta = Read-Json (Join-Path $LIB "_meta.json") if (-not $meta) { return "НЕТ: нет _meta.json" } $cfg = Read-Json (Join-Path $LIB "$($meta.appliedId).json") $name = ($meta.entries | Where-Object { $_.id -eq $meta.appliedId } | Select-Object -First 1).name if (-not $name) { $name = $meta.appliedId } if ($cfg -and $cfg.inferenceProvider) { return "ДА: $name" } return "НЕТ: применён пустой конфиг «$name»" } # Повторяет пункт меню Developer -> Clear Cache and Restart. В коде приложения он # чистит ТОЛЬКО кэши: HTTP, скомпилированный JS, шейдеры, service workers, DNS. # Ни cookies, ни Local Storage там нет, поэтому вход в аккаунт, чаты, MCP, # скиллы и наши конфиги остаются на месте. Делать только при закрытом приложении. # ВАЖНО: не путать с соседним пунктом Reset App Data, тот сносит папку целиком. function Clear-Caches { foreach ($d in $ALL_DIRS) { if (-not (Test-Path $d)) { continue } foreach ($c in @("Cache", "Code Cache", "GPUCache", "DawnGraphiteCache", "DawnWebGPUCache", "Service Worker")) { Remove-Item (Join-Path $d $c) -Recurse -Force -ErrorAction SilentlyContinue } } Ok "Кэши очищены (чаты, входы и конфиги не тронуты)." } function Restart-Claude { Say "Перезапускаю приложение Claude..." Get-Process -Name "Claude*" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue $i = 0 while ((Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) -and $i -lt 12) { Start-Sleep 1; $i++ } Clear-Caches if (-not $INST) { Warn "Не нашёл приложение Claude, открой его вручную."; return } Start-Claude # Проверяем, что процесс реально поднялся: рапортовать успех вслепую нельзя. # Пакетной сборке на старт нужно больше времени. $j = 0 while (-not (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) -and $j -lt 12) { Start-Sleep 1; $j++ } if (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) { Ok "Claude перезапущен." } else { Warn "Claude не поднялся автоматически. Конфиги на месте, открой приложение вручную." } } # ---------- ОТКАТ ---------- if ($Mode -eq "restore") { Say "Откат на родного Claude..." if (-not (Test-Path (Join-Path $LIB "_meta.json"))) { Ok "Конфигов нет, откатывать нечего."; return } Merge-Meta "default" "none" Repair-JsonBom Ok "Применён пустой профиль Default." Write-Host " Если приложение всё равно открывается на чужом мозге: меню аккаунта внизу слева -> Sign out," Write-Host " затем на экране входа выбери обычный вход по почте или Google." Write-DevFlag Restart-Claude Write-Host "`nЕсли что-то осталось сломанным, полный сброс (сотрёт чаты стороннего режима):" Write-Host " удали папку $C3P" return } # ---------- 1. Приложение Claude ---------- Say "[1/4] Ищу приложение Claude..." if ($INST) { Ok "Claude на месте: $($INST.Label)." Ok "Конфиги стороннего режима: $C3P" if (-not $INST.Launched) { Warn "Похоже, приложение ещё ни разу не запускалось. Это не страшно, но если после" Write-Host " запуска пункта Developer не будет, запусти эту же команду ещё раз." } } else { Warn "Приложение Claude не найдено, а без него настраивать нечего." Write-Host " Смотрел классическую сборку в AppData\Local и пакеты Microsoft Store." Write-Host "" Write-Host " Поставь его руками, это одна минута:" Write-Host " 1. Открой https://claude.com/download и скачай версию для Windows." Write-Host " 2. Установи и запусти приложение хотя бы один раз." Write-Host " 3. Вернись сюда и запусти эту же команду снова." Write-Host "" Write-Host " Логиниться в аккаунт Anthropic не обязательно." return } # ---------- 2. Выбор мозга ---------- $brain = $Mode if ($brain -notin @("ollama", "zai", "both")) { Say "[2/4] Какой мозг подключаем?" Write-Host " 1) Ollama бесплатный старт, программа Ollama должна быть установлена и запущена" Write-Host " 2) Z.ai по подписке от `$18, локально ничего не нужно, только ключ" Write-Host " 3) оба положу обе конфигурации, переключаться будешь в приложении" $choice = Read-Host " Твой выбор [1/2/3, по умолчанию 3]" switch ($choice) { "1" { $brain = "ollama" } "2" { $brain = "zai" } default { $brain = "both" } } } else { Say "[2/4] Режим: $brain" } switch ($brain) { "ollama" { Ok "Ставлю только Ollama." } "zai" { Ok "Ставлю только Z.ai." } "both" { Ok "Ставлю обе конфигурации." } } # ---------- 3. Ollama и псевдонимы ---------- $haveOllama = $false if ($brain -eq "ollama" -or $brain -eq "both") { Say "[3/4] Проверяю программу Ollama..." if (Get-Command ollama -ErrorAction SilentlyContinue) { Ok "Ollama на месте." $haveOllama = $true Say " Создаю псевдонимы облачных моделей (нужен вход в аккаунт Ollama)..." # Приложение пускает только точные идентификаторы моделей Claude, # поэтому облачная модель получает второе, «правильное» имя. # Третий элемент "optional": модель платная, её отсутствие не валит установку. $pairs = @( @("glm-5.3:cloud", "claude-sonnet-4-6", ""), @("kimi-k3:cloud", "claude-opus-4-6", ""), @("glm-5.3-flash:cloud", "claude-haiku-4-5", ""), @("qwen3.5:397b-cloud", "claude-sonnet-4-5", "optional"), @("deepseek-v4-pro:0813-cloud", "claude-opus-4-1", "optional"), @("gpt-oss:120b-cloud", "claude-haiku-3-5", "optional"), @("minimax-m3:cloud", "claude-3-7-sonnet", "optional"), @("nemotron-3-super:cloud", "claude-3-5-haiku", "optional") ) foreach ($p in $pairs) { $rc = Invoke-Quiet ollama pull $p[0] if ($rc -ne 0) { if ($p[2] -eq "optional") { Warn "$($p[0]) недоступна на твоём тарифе Ollama, пропускаю (нужен Pro или Max)." continue } Warn "Не получилось скачать $($p[0]). Обычно это нет входа в аккаунт Ollama." Write-Host " Открой окно Ollama (иконка ламы) -> Sign in, затем запусти команду снова." return } $rc = Invoke-Quiet ollama cp $p[0] $p[1] if ($rc -ne 0) { Warn "Не смог создать псевдоним $($p[1]) для $($p[0])."; continue } Ok "$($p[1]) -> $($p[0])" } } else { Warn "Ollama не найдена." Write-Host "" Write-Host " Поставь её руками:" Write-Host " 1. Открой https://ollama.com/download и скачай версию для Windows." Write-Host " 2. Установи, запусти, войди в аккаунт (иконка ламы -> Sign in)." Write-Host " 3. Вернись сюда и запусти команду снова." Write-Host "" if ($brain -eq "ollama") { Write-Host " Или подключи Z.ai, ему Ollama не нужна: `$env:AK='zai' вместо 'ollama'." return } Warn "Пропускаю Ollama, кладу только конфиг Z.ai." $brain = "zai" } } else { Say "[3/4] Маршрут Z.ai, Ollama не нужна." } # ---------- 4. Конфиги ---------- Say "[4/4] Кладу конфиги в приложение Claude..." New-Item -ItemType Directory -Force -Path $LIB | Out-Null Write-DevFlag Clear-DeploymentPin if ($brain -eq "ollama" -or $brain -eq "both") { Write-Utf8 (Join-Path $LIB "$ID_OLLAMA.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 } ] } '@ Ok "Конфиг ollama-cloud на месте." } if ($brain -eq "zai" -or $brain -eq "both") { $zaiPath = Join-Path $LIB "$ID_ZAI.json" $zaiHasKey = (Test-Path $zaiPath) -and -not ((Get-Content $zaiPath -Raw -Encoding UTF8) -match $ZAI_PLACEHOLDER) if ($zaiHasKey) { Ok "Конфиг zai-glm уже настроен, ключ не трогаю." } else { Write-Utf8 $zaiPath @" { "inferenceProvider": "gateway", "inferenceGatewayBaseUrl": "https://api.z.ai/api/anthropic", "inferenceGatewayApiKey": "$ZAI_PLACEHOLDER", "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)", "anthropicFamilyTier": "haiku", "isFamilyDefault": true } ] } "@ Ok "Конфиг zai-glm на месте, ждёт твой ключ." } } if ($haveOllama) { Merge-Meta "ollama" $brain } else { Merge-Meta "zai" $brain } Repair-JsonBom # ---------- Самопроверка ---------- Say "Проверяю, что получилось..." $res = Check-Applied if ($res.StartsWith("ДА:")) { Ok "Сторонний режим включится: $($res.Substring(4))" } else { Warn $res.Substring(5) Write-Host " Открой Developer -> Configure Third-Party Inference и выбери конфигурацию вручную." } Restart-Claude # ---------- Что дальше ---------- Write-Host "" Write-Host "Что дальше:" if ($haveOllama) { Write-Host " · Открой новый чат и кликни по имени модели рядом с полем ввода:" Write-Host " там GLM-5.3 и GLM-5.3 Flash, а на платном тарифе Ollama ещё и Kimi K3." Write-Host " · Программа Ollama должна быть запущена: закрыл её, мост отвалился." } if ($brain -eq "zai" -or $brain -eq "both") { Write-Host " · Z.ai: возьми ключ на z.ai/manage-apikey/apikey-list, затем в приложении" Write-Host " Developer -> Configure Third-Party Inference -> конфигурация zai-glm ->" Write-Host " вставь ключ в Gateway API key -> Apply Changes -> перезапусти Claude." } Write-Host " · Нет пункта Developer? Полностью закрой Claude и открой заново." Write-Host " · Вернуть родного Claude: `$env:AK='restore' и та же команда."