fix: dev-флаг в обе папки, честный рестарт, проверка applied, аргументы
This commit is contained in:
@@ -1,17 +1,25 @@
|
||||
#!/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. Скрипт открыт и короток: прочитай перед запуском.
|
||||
# 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 ;;
|
||||
@@ -19,55 +27,115 @@ case "$(uname -s)" in
|
||||
esac
|
||||
|
||||
if [ "$OS" = "mac" ]; then
|
||||
C3P="$HOME/Library/Application Support/Claude-3p"
|
||||
C1P="$HOME/Library/Application Support/Claude"
|
||||
else
|
||||
C3P="$LOCALAPPDATA/Claude-3p"
|
||||
C1P="$LOCALAPPDATA/Claude"
|
||||
fi
|
||||
# Папка стороннего режима это та же папка с суффиксом -3p.
|
||||
C3P="${C1P}-3p"
|
||||
LIB="$C3P/configLibrary"
|
||||
|
||||
ID_OLLAMA="a11a0000-0000-4000-8000-0000000000aa"
|
||||
ID_ZAI="a11a0000-0000-4000-8000-0000000000bb"
|
||||
ID_DEFAULT="a11a0000-0000-4000-8000-0000000000dd"
|
||||
ZAI_PLACEHOLDER="ВСТАВЬ_СЮДА_СВОЙ_КЛЮЧ_ZAI"
|
||||
|
||||
MODE=install
|
||||
BRAIN="${AK:-}"
|
||||
NO_RESTART=false
|
||||
for a in "$@"; do [ "$a" = "--no-restart" ] && NO_RESTART=true; done
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
restore) MODE=restore ;;
|
||||
ollama|zai|both) BRAIN="$a" ;;
|
||||
--no-restart) NO_RESTART=true ;;
|
||||
esac
|
||||
done
|
||||
[ "$BRAIN" = "restore" ] && { MODE=restore; BRAIN=""; }
|
||||
|
||||
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' "$*"; }
|
||||
die() { printf ' \033[31m[стоп]\033[0m %s\n' "$*"; exit 1; }
|
||||
|
||||
# ---------- 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
|
||||
# ---------- JSON-хелпер ----------
|
||||
PY=""
|
||||
if command -v python3 >/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 "$C1P" "$C3P"; do
|
||||
mkdir -p "$d"
|
||||
printf '{"allowDevTools": true}\n' > "$d/developer_settings.json"
|
||||
done
|
||||
ok "Режим разработчика включён в обеих папках приложения."
|
||||
}
|
||||
|
||||
# merge_meta <applied: ollama|default|keep>
|
||||
# ---------- 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
|
||||
}
|
||||
|
||||
# merge_meta <applied: ollama|zai|default> <routes: ollama|zai|both|none>
|
||||
merge_meta() {
|
||||
local applied="$1" tool; tool="$(json_tool)"
|
||||
if [ "$tool" = "python3" ]; then
|
||||
python3 - "$LIB" "$ID_OLLAMA" "$ID_ZAI" "$ID_DEFAULT" "$applied" <<'PY'
|
||||
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 = sys.argv[1:6]
|
||||
lib, id_o, id_z, id_d, applied, routes = sys.argv[1:7]
|
||||
p = os.path.join(lib, "_meta.json")
|
||||
meta = json.load(open(p)) if os.path.exists(p) else {"appliedId": "", "entries": []}
|
||||
ent = meta.get("entries", [])
|
||||
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})
|
||||
if not any(x.get("name") == "Default" for x in ent):
|
||||
open(os.path.join(lib, id_d + ".json"), "w").write("{}")
|
||||
# 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")
|
||||
ensure(id_o, "ollama-cloud"); ensure(id_z, "zai-glm")
|
||||
dflt_id = id_d
|
||||
else:
|
||||
dflt_id = dflt["id"]
|
||||
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
|
||||
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)
|
||||
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
|
||||
elif [ "$tool" = "powershell" ]; then
|
||||
MERGE_LIB="$(cygpath -w "$LIB")" MERGE_APPLIED="$applied" \
|
||||
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"
|
||||
@@ -82,50 +150,82 @@ PY
|
||||
"{}" | 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"
|
||||
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 }
|
||||
"default" { $meta.appliedId=$def }
|
||||
default { if(-not $meta.appliedId){ $meta.appliedId=$def } }
|
||||
"zai" { $meta.appliedId=$env:MERGE_IDZ }
|
||||
default { $meta.appliedId=$def }
|
||||
}
|
||||
$meta | ConvertTo-Json -Depth 5 | Set-Content $p -Encoding UTF8
|
||||
'
|
||||
else
|
||||
warn "Не нашёл ни python3, ни powershell для работы с JSON."; exit 1
|
||||
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
|
||||
}
|
||||
|
||||
# ---------- Честный перезапуск ----------
|
||||
# open -a на живом процессе только активирует окно: приложение не завершается
|
||||
# при закрытии окна и настройки перечитывает лишь при настоящем старте.
|
||||
restart_claude() {
|
||||
if [ "$NO_RESTART" = "true" ]; then warn "Перезапуск Claude пропущен (--no-restart)."; return; fi
|
||||
if [ "$NO_RESTART" = "true" ]; then warn "Перезапуск пропущен (--no-restart). Настройки применятся после Cmd+Q и запуска."; return 0; fi
|
||||
say "Перезапускаю приложение Claude..."
|
||||
if [ "$OS" = "mac" ]; then
|
||||
if pgrep -x "Claude" >/dev/null 2>&1; then
|
||||
osascript -e 'tell application "Claude" to quit' >/dev/null 2>&1 || true
|
||||
sleep 3
|
||||
open -a "Claude"
|
||||
i=0; while pgrep -x "Claude" >/dev/null 2>&1 && [ "$i" -lt 12 ]; do sleep 1; i=$((i+1)); done
|
||||
if pgrep -x "Claude" >/dev/null 2>&1; then
|
||||
pkill -x "Claude" >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
fi
|
||||
if pgrep -x "Claude" >/dev/null 2>&1; then
|
||||
warn "Claude не закрылся сам (обычно это запрет на автоматизацию:"
|
||||
echo " Системные настройки -> Конфиденциальность и безопасность -> Автоматизация)."
|
||||
echo " Конфиги уже на месте. Закрой Claude вручную через Cmd+Q и открой заново."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
open -a "Claude" >/dev/null 2>&1 || { warn "Не смог открыть Claude, открой вручную."; return 0; }
|
||||
else
|
||||
powershell.exe -NoProfile -Command '
|
||||
Get-Process -Name "Claude*" -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
Start-Sleep 3
|
||||
$exe=@("$env:LOCALAPPDATA\AnthropicClaude","$env:LOCALAPPDATA\Programs") | ForEach-Object { Get-ChildItem $_ -Recurse -Filter "claude.exe" -ErrorAction SilentlyContinue } | Select-Object -First 1
|
||||
if($exe){ Start-Process $exe.FullName }
|
||||
' || warn "Открой Claude вручную."
|
||||
if($exe){ Start-Process $exe.FullName } else { exit 1 }
|
||||
' || { warn "Не смог перезапустить Claude, закрой и открой его вручную."; return 0; }
|
||||
fi
|
||||
ok "Готово. Открой выбор модели в чате: там GLM-5.3 и Flash, а на платном тарифе Ollama ещё и Kimi K3."
|
||||
ok "Claude перезапущен."
|
||||
}
|
||||
|
||||
# ---------- TLS-фикс песочницы (только Мак): для вкладки Code ----------
|
||||
# ---------- TLS-фикс песочницы (только Мак, для вкладки Code) ----------
|
||||
apply_tls_fix() {
|
||||
[ "$OS" = "mac" ] || return 0 # на Windows песочницы нет, фикс не нужен
|
||||
[ "$OS" = "mac" ] || return 0
|
||||
[ -n "$PY" ] || return 0
|
||||
local sj="$HOME/.claude/settings.json"
|
||||
mkdir -p "$HOME/.claude"
|
||||
[ -f "$sj" ] || echo '{}' > "$sj"
|
||||
python3 - "$sj" <<'PY'
|
||||
"$PY" - "$sj" <<'PY'
|
||||
import json, sys
|
||||
p = sys.argv[1]
|
||||
try:
|
||||
d = json.load(open(p))
|
||||
d = json.load(open(p, encoding="utf-8"))
|
||||
if not isinstance(d, dict): d = {}
|
||||
except Exception:
|
||||
d = {}
|
||||
@@ -134,93 +234,108 @@ 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")
|
||||
json.dump(d, open(p, "w", encoding="utf-8"), indent=2, ensure_ascii=False)
|
||||
print("добавлен")
|
||||
else:
|
||||
print(" present")
|
||||
print("уже был")
|
||||
PY
|
||||
}
|
||||
|
||||
# ---------- ОТКАТ ----------
|
||||
if [ "${1:-}" = "restore" ]; then
|
||||
say "Откат на родной Claude..."
|
||||
if [ "$MODE" = "restore" ]; then
|
||||
say "Откат на родного Claude..."
|
||||
if [ ! -f "$LIB/_meta.json" ]; then ok "Конфигов нет, откатывать нечего."; exit 0; fi
|
||||
merge_meta default
|
||||
ok "Применён конфиг Default: приложение вернётся к родному входу."
|
||||
merge_meta default none
|
||||
ok "Применён пустой конфиг Default: приложение вернётся к родному входу."
|
||||
write_dev_flag
|
||||
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
|
||||
echo " rm -rf \"$C3P\""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------- 1. Claude ----------
|
||||
say "[1/5] Проверяю приложение Claude..."
|
||||
# ---------- 1. Приложение Claude ----------
|
||||
say "[1/4] Ищу приложение Claude..."
|
||||
CLAUDE_OK=false
|
||||
if [ "$OS" = "mac" ] && [ -d "/Applications/Claude.app" ]; then CLAUDE_OK=true; fi
|
||||
if [ "$OS" = "win" ] && find "$LOCALAPPDATA/AnthropicClaude" "$LOCALAPPDATA/Programs" -maxdepth 3 -iname "claude.exe" 2>/dev/null | grep -q .; then CLAUDE_OK=true; fi
|
||||
if [ "$CLAUDE_OK" = "true" ]; then ok "Claude установлен."
|
||||
if [ "$CLAUDE_OK" = "true" ]; then
|
||||
ok "Claude на месте."
|
||||
else
|
||||
warn "Claude не найден. Качаю установщик..."
|
||||
if [ "$OS" = "mac" ]; then
|
||||
curl -fsSL -o "$HOME/Downloads/Claude.dmg" "https://claude.ai/download/dmg" && open "$HOME/Downloads/Claude.dmg"
|
||||
echo " Перетащи Claude в Applications, запусти, войди в аккаунт, затем запусти скрипт снова."
|
||||
else
|
||||
DL="$(cygpath -w "$HOME/Downloads/ClaudeSetup.exe" 2>/dev/null || echo "$USERPROFILE\\Downloads\\ClaudeSetup.exe")"
|
||||
curl -fsSL -o "$HOME/Downloads/ClaudeSetup.exe" "https://storage.googleapis.com/osprey-downloads-c02f6a0d-347c-492b-a752-3e0651722e97/nest-win-x64/Claude-Setup-x64.exe"
|
||||
powershell.exe -NoProfile -Command "Start-Process '$DL'"
|
||||
echo " Установщик Claude запущен, дождись установки и входа в аккаунт, затем запусти скрипт снова."
|
||||
fi
|
||||
exit 0
|
||||
warn "Приложение Claude не найдено, а без него настраивать нечего."
|
||||
echo
|
||||
echo " Поставь его руками, это одна минута:"
|
||||
echo " 1. Открой https://claude.com/download и скачай версию для своей системы."
|
||||
echo " 2. Установи и запусти приложение хотя бы один раз."
|
||||
echo " 3. Вернись сюда и запусти эту же команду снова."
|
||||
echo
|
||||
echo " Логиниться в аккаунт Anthropic не обязательно."
|
||||
exit 1
|
||||
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
|
||||
# ---------- 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
|
||||
printf " Ollama не найдена. Скачать? Нужна для маршрута Ollama Cloud [Y/n]: "
|
||||
read -r ans < /dev/tty || ans=y
|
||||
if [ "${ans:-y}" != "n" ] && [ "${ans:-y}" != "N" ]; then
|
||||
warn "Качаю установщик Ollama..."
|
||||
if [ "$OS" = "mac" ]; then
|
||||
curl -fsSL -o "$HOME/Downloads/Ollama.dmg" "https://ollama.com/download/Ollama.dmg" && open "$HOME/Downloads/Ollama.dmg"
|
||||
else
|
||||
DLO="$(cygpath -w "$HOME/Downloads/OllamaSetup.exe" 2>/dev/null || echo "$USERPROFILE\\Downloads\\OllamaSetup.exe")"
|
||||
curl -fsSL -o "$HOME/Downloads/OllamaSetup.exe" "https://ollama.com/download/OllamaSetup.exe"
|
||||
powershell.exe -NoProfile -Command "Start-Process '$DLO'"
|
||||
fi
|
||||
echo " Установи Ollama, запусти, войди в аккаунт (Sign in), затем запусти скрипт снова."
|
||||
exit 0
|
||||
else
|
||||
warn "Пропускаю Ollama: поставлю только конфиг Z.ai."
|
||||
fi
|
||||
say "[2/4] Режим: $BRAIN"
|
||||
fi
|
||||
case "$BRAIN" in
|
||||
ollama) ok "Ставлю только Ollama." ;;
|
||||
zai) ok "Ставлю только Z.ai." ;;
|
||||
both) ok "Ставлю обе конфигурации." ;;
|
||||
*) warn "Непонятный режим «$BRAIN», беру оба."; BRAIN=both ;;
|
||||
esac
|
||||
|
||||
# ---------- 3. Псевдонимы ----------
|
||||
# ---------- 3. Ollama и псевдонимы ----------
|
||||
HAVE_OLLAMA=false
|
||||
if [ -n "$OLLAMA_BIN" ]; then
|
||||
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 "[3/5] Создаю псевдонимы облачных моделей (нужен вход в аккаунт Ollama)..."
|
||||
# make_alias <модель> <псевдоним> [optional]
|
||||
# optional: модель платная (Kimi K3) -> не валим установку, просто предупреждаем
|
||||
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 (иконка ламы) -> Settings -> Sign in, затем запусти скрипт снова."
|
||||
warn "Не получилось скачать $1. Обычно это нет входа в аккаунт Ollama."
|
||||
echo " Открой окно Ollama (иконка ламы) -> Sign in, затем запусти команду снова."
|
||||
exit 1
|
||||
}
|
||||
"$OLLAMA_BIN" cp "$1" "$2" >/dev/null
|
||||
@@ -229,15 +344,19 @@ if [ -n "$OLLAMA_BIN" ]; then
|
||||
make_alias "glm-5.3:cloud" "claude-sonnet-4-6"
|
||||
make_alias "glm-5.3-flash:cloud" "claude-haiku-4-5"
|
||||
make_alias "kimi-k3:cloud" "claude-opus-4-6" optional
|
||||
fi
|
||||
else
|
||||
say "[3/5] Ollama нет, псевдонимы пропускаю."
|
||||
say "[3/4] Маршрут Z.ai, Ollama не нужна."
|
||||
fi
|
||||
|
||||
# ---------- 4. Конфиги ----------
|
||||
say "[4/5] Кладу конфиги в приложение Claude..."
|
||||
say "[4/4] Кладу конфиги в приложение Claude..."
|
||||
mkdir -p "$LIB"
|
||||
[ -f "$C3P/developer_settings.json" ] || printf '{"allowDevTools": true}' > "$C3P/developer_settings.json"
|
||||
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",
|
||||
@@ -253,39 +372,61 @@ cat > "$LIB/$ID_OLLAMA.json" <<'JSON'
|
||||
]
|
||||
}
|
||||
JSON
|
||||
ok "Конфиг ollama-cloud на месте."
|
||||
fi
|
||||
|
||||
# Не затираем уже вставленный ключ: перезаписываем только заглушку или отсутствующий файл
|
||||
if [ -f "$LIB/$ID_ZAI.json" ] && ! grep -q "ВСТАВЬ_СЮДА_СВОЙ_КЛЮЧ_ZAI" "$LIB/$ID_ZAI.json"; then
|
||||
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" <<'JSON'
|
||||
else
|
||||
cat > "$LIB/$ID_ZAI.json" <<JSON
|
||||
{
|
||||
"inferenceProvider": "gateway",
|
||||
"inferenceGatewayBaseUrl": "https://api.z.ai/api/anthropic",
|
||||
"inferenceGatewayApiKey": "ВСТАВЬ_СЮДА_СВОЙ_КЛЮЧ_ZAI",
|
||||
"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, vision)", "anthropicFamilyTier": "haiku", "isFamilyDefault": true }
|
||||
{ "name": "claude-haiku-4-5", "labelOverride": "GLM-5.3 Flash (Z.ai)", "anthropicFamilyTier": "haiku", "isFamilyDefault": true }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
ok "Конфиг zai-glm на месте, ждёт твой ключ."
|
||||
fi
|
||||
fi
|
||||
|
||||
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
|
||||
# Активной делаем ту конфигурацию, которая реально готова работать.
|
||||
if [ "$HAVE_OLLAMA" = "true" ]; then merge_meta ollama "$BRAIN"; else merge_meta zai "$BRAIN"; fi
|
||||
|
||||
if [ "$OS" = "mac" ]; then
|
||||
ok "TLS-фикс песочницы для вкладки Code: $(apply_tls_fix)"
|
||||
fi
|
||||
|
||||
# ---------- Самопроверка ----------
|
||||
say "Проверяю, что получилось..."
|
||||
res="$(check_applied || true)"
|
||||
case "$res" in
|
||||
ДА:*) ok "Сторонний режим включится: ${res#ДА: }" ;;
|
||||
НЕТ:*) warn "${res#НЕТ: }"
|
||||
echo " Открой Developer -> Configure Third-Party Inference и выбери конфигурацию вручную." ;;
|
||||
esac
|
||||
|
||||
# ---------- 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"
|
||||
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."
|
||||
|
||||
Reference in New Issue
Block a user