Files
ai-setup/claude_setup.sh
vitaly 3647eba494 fix: fallback на raw body при пустом _emsg в проверке RESOURCE_EXHAUSTED
Если python3 недоступен или JSON не парсится, _emsg пустой.
Теперь grep проверяет сырое тело ответа как запасной вариант.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 15:12:15 +07:00

668 lines
25 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================
# Claude Code Setup — GPT-5.5 / DeepSeek / Kimi / Gemini
# Запуск: bash claude_setup.sh
# ============================================================
CONFIG_DIR="$HOME/.config/claude-launcher"
BIN_DIR="$HOME/.local/bin"
NPM_GLOBAL="$HOME/.npm-global"
PROXY_BIN="$BIN_DIR/claude-code-proxy"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERR]${NC} $*"; exit 1; }
# Запрет запуска от root
if [ "$EUID" -eq 0 ]; then
echo -e "${RED}Не запускайте этот скрипт через sudo!${NC}"
echo "Запустите просто: bash claude_setup.sh"
exit 1
fi
info "Проверяю зависимости (python3)..."
if ! command -v python3 &>/dev/null; then
err "Python 3 не установлен, но он требуется для работы скрипта."
fi
success "Python 3 найден"
# ── 1. npm prefix в домашнюю папку ──────────────────────────
info "Настраиваю npm prefix..."
mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL"
success "npm prefix -> $NPM_GLOBAL"
export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH"
# ── 2. Node.js ───────────────────────────────────────────────
info "Проверяю Node.js..."
if ! command -v node &>/dev/null; then
info "Попытка установки Node.js (нужен sudo)..."
if command -v apt-get &>/dev/null; then
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
elif command -v dnf &>/dev/null; then
sudo dnf install -y nodejs
else
warn "Не удалось определить пакетный менеджер. Установите Node.js вручную."
fi
fi
if command -v node &>/dev/null; then
success "Node.js $(node --version)"
else
warn "Node.js не найден. Некоторые функции могут не работать."
fi
# ── 3. Claude Code ───────────────────────────────────────────
info "Проверяю Claude Code..."
if ! command -v claude &>/dev/null; then
info "Устанавливаю Claude Code..."
if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
success "Claude Code установлен (официальный инсталлер)"
else
npm install -g @anthropic-ai/claude-code
success "Claude Code установлен (npm)"
fi
else
success "Claude Code уже установлен: $(claude --version 2>/dev/null | head -1)"
fi
# ── 4. claude-code-proxy (GPT) ───────────────────────────────
mkdir -p "$BIN_DIR"
install_proxy() {
info "Устанавливаю claude-code-proxy..."
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH_TAG="amd64" ;;
aarch64) ARCH_TAG="arm64" ;;
*) err "Неизвестная архитектура: $ARCH" ;;
esac
LATEST=$(curl -fsSL "https://api.github.com/repos/raine/claude-code-proxy/releases/latest" \
| grep '"tag_name"' | sed 's/.*"tag_name": *"\(.*\)".*/\1/')
[ -z "$LATEST" ] && err "Не удалось получить версию claude-code-proxy с GitHub"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
URL="https://github.com/raine/claude-code-proxy/releases/download/${LATEST}/claude-code-proxy-linux-${ARCH_TAG}.tar.gz"
info "Скачиваю $URL"
curl -fsSL "$URL" -o "$TMP/proxy.tar.gz" || err "Не удалось скачать claude-code-proxy"
tar -xzf "$TMP/proxy.tar.gz" -C "$TMP"
BINARY=$(find "$TMP" -name "claude-code-proxy" -type f | head -1)
[ -z "$BINARY" ] && err "Бинарник не найден в архиве"
cp "$BINARY" "$PROXY_BIN"
chmod +x "$PROXY_BIN"
success "claude-code-proxy $LATEST -> $PROXY_BIN"
}
if [ -f "$PROXY_BIN" ]; then
CURRENT_VER=$("$PROXY_BIN" --version 2>/dev/null | head -1 || echo "unknown")
success "claude-code-proxy уже установлен ($CURRENT_VER)"
else
install_proxy
fi
# ── 4b. effort-proxy wrapper (маппинг effort для GPT: max->xhigh) ─────────
EFFORT_PROXY_BIN="$BIN_DIR/claude-gpt-effort-proxy.py"
cat > "$EFFORT_PROXY_BIN" << 'PYEOF'
#!/usr/bin/env python3
"""Effort mapping proxy for GPT backend.
GPT-5.5 natively supports: low, medium, high, xhigh (no "max").
Claude Code may send "max" effort — we map it to "xhigh" (highest GPT level).
See EFFORT_MAPPING.md for the full mapping table across all providers.
"""
import http.client, http.server, sys
UPSTREAM_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 18766
LISTEN_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 18765
class _Proxy(http.server.BaseHTTPRequestHandler):
def proxy_request(self):
body = b""
if cl := self.headers.get("Content-Length"):
body = self.rfile.read(int(cl))
body = body.replace(b'"max"', b'"xhigh"')
try:
conn = http.client.HTTPConnection("localhost", UPSTREAM_PORT, timeout=300)
hdrs = dict(self.headers)
hdrs.pop("Host", None)
hdrs["Content-Length"] = str(len(body))
conn.request(self.command, self.path, body=body or None, headers=hdrs)
resp = conn.getresponse()
self.send_response(resp.status, resp.reason)
chunked = False
for k, v in resp.getheaders():
if k.lower() == "transfer-encoding" and "chunked" in v.lower():
chunked = True
self.send_header(k, v)
self.end_headers()
while chunk := resp.read(4096):
try:
if chunked:
self.wfile.write(f"{len(chunk):X}\r\n".encode() + chunk + b"\r\n")
else:
self.wfile.write(chunk)
self.wfile.flush()
except (BrokenPipeError, ConnectionResetError):
break
if chunked:
try: self.wfile.write(b"0\r\n\r\n")
except: pass
conn.close()
except Exception as e:
try: self.send_error(502, str(e))
except: pass
do_GET = do_POST = do_PUT = do_DELETE = do_HEAD = proxy_request
def log_message(self, *args): pass
http.server.HTTPServer(("localhost", LISTEN_PORT), _Proxy).serve_forever()
PYEOF
chmod +x "$EFFORT_PROXY_BIN"
success "claude-gpt-effort-proxy -> $EFFORT_PROXY_BIN"
# ── 5. antigravity-claude-proxy (Gemini) ────────────────────
info "Проверяю antigravity-claude-proxy..."
if command -v antigravity-claude-proxy &>/dev/null || command -v acc &>/dev/null; then
success "antigravity-claude-proxy уже установлен"
else
info "Устанавливаю antigravity-claude-proxy (npm, без sudo)..."
npm install -g antigravity-claude-proxy@latest
success "antigravity-claude-proxy установлен"
fi
# ── 6. Папка для конфигов ────────────────────────────────────
mkdir -p "$CONFIG_DIR"
# ── 7. Очистка старых функций из .bashrc / .zshrc ───────────
clean_rc() {
local rc_file="$1"
if [ -f "$rc_file" ] && grep -q "# === CLAUDE LAUNCHER ===" "$rc_file"; then
info "Очищаю старые функции из $rc_file..."
python3 - "$rc_file" <<'PYEOF'
import sys
path = sys.argv[1]
with open(path, 'r') as f:
content = f.read()
marker = '# === CLAUDE LAUNCHER ==='
end_marker = '# === END CLAUDE LAUNCHER ==='
start = content.find(marker)
end = content.find(end_marker)
if start != -1 and end != -1:
new_content = content[:start] + content[end + len(end_marker):].lstrip('\n')
with open(path, 'w') as f:
f.write(new_content)
PYEOF
success "Старые функции удалены из $rc_file"
fi
}
clean_rc "$HOME/.bashrc"
clean_rc "$HOME/.zshrc"
add_path_to_rc() {
local rc_file="$1"
if [ -f "$rc_file" ]; then
if ! grep -q 'NPM_GLOBAL' "$rc_file" 2>/dev/null; then
cat >> "$rc_file" << 'PATHEOF'
# Claude Code Launcher PATH
export NPM_GLOBAL="$HOME/.npm-global"
export PATH="$NPM_GLOBAL/bin:$HOME/.local/bin:$PATH"
PATHEOF
success "PATH добавлен в $rc_file"
fi
fi
}
add_path_to_rc "$HOME/.bashrc"
[ -f "$HOME/.zshrc" ] && add_path_to_rc "$HOME/.zshrc"
# ── 8. Генерация Standalone скриптов ────────────────────────
info "Генерирую standalone скрипты в $BIN_DIR..."
HELPERS_FILE="$BIN_DIR/claude_api_helpers.sh"
cat > "$HELPERS_FILE" << 'HELPEREOF'
#!/usr/bin/env bash
# _claude_test_api: Send 1-token test to an Anthropic-compatible endpoint
_claude_test_api() {
local url="$1" auth_header="$2" model="${3:-claude-sonnet-4-6}"
local response
response=$(curl -s -w "\n%{http_code}" --max-time 15 "$url" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d "{\"model\":\"$model\",\"max_tokens\":1,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}" \
2>/dev/null || echo "000")
_CLAUDE_TEST_CODE=$(echo "$response" | tail -1)
_CLAUDE_TEST_BODY=$(echo "$response" | sed '$d')
}
_claude_extract_error() {
local body="$1"
echo "$body" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
e = d.get('error', {})
if isinstance(e, str):
print(e)
else:
msg = e.get('message', '')
if msg:
print(msg)
except:
pass
" 2>/dev/null
}
_claude_offer_reauth() {
local provider="$1"
echo ""
echo -n "Хотите выполнить повторную авторизацию $provider? [Y/n] "
local answer
read -r answer
case "${answer:-Y}" in
[Yy]|[Yy][Ee][Ss]) return 0 ;;
*) echo "Отменено."; return 1 ;;
esac
}
_handle_api_response() {
local provider="$1"
local code="$2"
local body="$3"
local topup_url="$4"
local _emsg
case "$code" in
200)
echo -e "\033[0;32mOK\033[0m"
return 0
;;
401|403)
_emsg=$(_claude_extract_error "$body")
echo ""
echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Авторизация $provider недействительна (HTTP $code)."
[ -n "$_emsg" ] && echo " $_emsg"
return 401
;;
429)
_emsg=$(_claude_extract_error "$body")
echo ""
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Баланс/лимит $provider исчерпан."
[ -n "$_emsg" ] && echo " $_emsg"
[ -n "$topup_url" ] && echo " $topup_url"
return 429
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ключ (нет сети?). Продолжаю..."
return 0
;;
400)
_emsg=$(_claude_extract_error "$body")
if echo "${_emsg:-$body}" | grep -qi "RESOURCE_EXHAUSTED"; then
echo ""
echo -e "\033[0;33m[КВОТА ИСЧЕРПАНА]\033[0m Лимит запросов исчерпан."
[ -n "$topup_url" ] && echo " $topup_url"
return 429
fi
# 400 = auth is valid, but max_tokens=1 is too small for thinking models
echo -e "\033[0;32mOK\033[0m"
return 0
;;
*)
_emsg=$(_claude_extract_error "$body")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m API $provider вернул HTTP $code."
[ -n "$_emsg" ] && echo " $_emsg"
return 1
;;
esac
}
_open_browser() {
local url="$1"
if command -v xdg-open &>/dev/null; then xdg-open "$url" 2>/dev/null
elif command -v open &>/dev/null; then open "$url" 2>/dev/null
elif command -v sensible-browser &>/dev/null; then sensible-browser "$url" 2>/dev/null
else echo "Откройте вручную: $url"; fi
}
HELPEREOF
chmod +x "$HELPERS_FILE"
# === claude_gpt ===
cat > "$BIN_DIR/claude_gpt" << 'GPTEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
proxy_bin="$HOME/.local/bin/claude-code-proxy"
if [ ! -f "$proxy_bin" ]; then
echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh"
exit 1
fi
if ! "$proxy_bin" codex auth status &>/dev/null; then
echo ""
echo "Авторизация ChatGPT не найдена."
echo "Сейчас появится ссылка или откроется браузер..."
echo ""
if ! "$proxy_bin" codex auth login 2>&1; then
echo "Если браузер не открылся, попробуйте device flow: claude-code-proxy codex auth device"
exit 1
fi
fi
proxy_pid=""
wrapper_pid=""
effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py"
cleanup() {
[ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null
[ -n "$wrapper_pid" ] && kill "$wrapper_pid" 2>/dev/null
}
trap cleanup EXIT INT TERM
if pgrep -f "claude-code-proxy serve" &>/dev/null && ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then
pkill -f "claude-code-proxy serve" 2>/dev/null
sleep 0.5
fi
if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then
PORT=18766 "$proxy_bin" serve &>/tmp/claude-code-proxy.log &
proxy_pid=$!
_j=0; while [ $_j -lt 10 ]; do sleep 1; curl -sf --max-time 1 http://localhost:18766/ &>/dev/null; [ "$?" -ne 7 ] && break; _j=$((_j + 1)); done
fi
if ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then
python3 "$effort_proxy" 18766 18765 &>/tmp/claude-gpt-effort-proxy.log &
wrapper_pid=$!
_i=0; while [ $_i -lt 10 ]; do sleep 1; curl -sf --max-time 1 http://localhost:18765/ &>/dev/null; [ "$?" -ne 7 ] && break; # exit 7 = connection refused
_i=$((_i + 1)); done
fi
echo -n "Проверка авторизации ChatGPT... "
_claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini"
if [ "$_CLAUDE_TEST_CODE" != "400" ]; then
_handle_api_response "ChatGPT" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" ""
ret=$?
if [ $ret -eq 401 ]; then
"$proxy_bin" codex auth logout 2>/dev/null
if _claude_offer_reauth "ChatGPT"; then
"$proxy_bin" codex auth login || exit 1
_claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini"
[ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; }
echo -e "\033[0;32mOK\033[0m"
else
exit 1
fi
elif [ $ret -ne 0 ]; then
exit 1
fi
else
echo -e "\033[0;32mOK\033[0m"
fi
_creds_file="$HOME/.claude/.credentials.json"
_creds_backup=""
[ -f "$_creds_file" ] && _creds_backup=$(cat "$_creds_file")
echo -e "\033[0;33m[ИНФО]\033[0m Режим ChatGPT. Для выхода: Ctrl+C или /exit"
echo -e " Команда \033[1m/logout\033[0m выйдет из Anthropic, а не из ChatGPT."
echo ""
ANTHROPIC_BASE_URL=http://localhost:18765 \
ANTHROPIC_AUTH_TOKEN=dummy \
ANTHROPIC_MODEL=gpt-5.5 \
ANTHROPIC_DEFAULT_OPUS_MODEL=gpt-5.5 \
ANTHROPIC_DEFAULT_SONNET_MODEL=gpt-5.5 \
ANTHROPIC_DEFAULT_HAIKU_MODEL=gpt-5.4-mini \
CLAUDE_CODE_SUBAGENT_MODEL=gpt-5.4-mini \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
if [ -n "$_creds_backup" ] && [ ! -f "$_creds_file" ]; then
mkdir -p "$(dirname "$_creds_file")"
echo "$_creds_backup" > "$_creds_file"
echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены."
fi
GPTEOF
chmod +x "$BIN_DIR/claude_gpt"
# === claude_deepseek ===
cat > "$BIN_DIR/claude_deepseek" << 'DEEPSEEKEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
key_file="$HOME/.config/claude-launcher/deepseek_key"
api_key=""
reauth=0
[ -f "$key_file" ] && api_key=$(cat "$key_file")
if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого DeepSeek ключа... "
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "x-api-key: $api_key" "deepseek-v4-flash"
_handle_api_response "DeepSeek" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.deepseek.com/top_up"
ret=$?
if [ $ret -eq 401 ]; then
rm -f "$key_file"
api_key=""
reauth=1
elif [ $ret -eq 429 ]; then
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
elif [ $ret -ne 0 ]; then
exit 1
fi
fi
if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then
echo -n "Хотите ввести новый DeepSeek ключ? [Y/n] "
read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac
fi
if [ -z "$api_key" ]; then
echo "Получить ключ: https://platform.deepseek.com/api_keys"
read -r -p "Введите ваш DeepSeek API ключ: " api_key
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
echo -n "Проверяю ключ и баланс... "
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "x-api-key: $api_key" "deepseek-v4-flash"
_handle_api_response "DeepSeek" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.deepseek.com/top_up"
ret=$?
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
echo "Ключ сохранён."
if [ $ret -eq 429 ]; then
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
fi
else
echo "Ключ НЕ сохранён."
exit 1
fi
fi
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_MODEL=deepseek-v4-pro \
ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-v4-pro \
ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro \
ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash \
CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
DEEPSEEKEOF
chmod +x "$BIN_DIR/claude_deepseek"
# === claude_kimi ===
cat > "$BIN_DIR/claude_kimi" << 'KIMIEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
key_file="$HOME/.config/claude-launcher/kimi_key"
api_key=""
reauth=0
[ -f "$key_file" ] && api_key=$(cat "$key_file")
if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого Kimi ключа... "
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "x-api-key: $api_key" "kimi-k2.6"
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.moonshot.ai/console/billing"
ret=$?
if [ $ret -eq 401 ]; then
rm -f "$key_file"
api_key=""
reauth=1
elif [ $ret -eq 429 ]; then
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
elif [ $ret -ne 0 ]; then
exit 1
fi
fi
if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then
echo -n "Хотите ввести новый Kimi ключ? [Y/n] "
read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac
fi
if [ -z "$api_key" ]; then
echo "Получить ключ: https://platform.moonshot.ai/console/api-keys"
read -r -p "Введите ваш Kimi API ключ: " api_key
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
echo -n "Проверяю ключ и баланс... "
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "x-api-key: $api_key" "kimi-k2.6"
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.moonshot.ai/console/billing"
ret=$?
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
echo "Ключ сохранён."
if [ $ret -eq 429 ]; then
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
fi
else
echo "Ключ НЕ сохранён."
exit 1
fi
fi
ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \
ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_MODEL=kimi-k2.6 \
ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \
ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \
ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
KIMIEOF
chmod +x "$BIN_DIR/claude_kimi"
# === claude_gemini ===
cat > "$BIN_DIR/claude_gemini" << 'GEMINIEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
acc_cmd=""
command -v antigravity-claude-proxy &>/dev/null && acc_cmd="antigravity-claude-proxy"
command -v acc &>/dev/null && acc_cmd="acc"
[ -z "$acc_cmd" ] && { echo "Ошибка: antigravity-claude-proxy не найден."; exit 1; }
proxy_pid=""
cleanup() { [ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null; }
trap cleanup EXIT INT TERM
if ! curl -sf http://localhost:8080/health &>/dev/null; then
"$acc_cmd" start &>/tmp/antigravity-proxy.log &
proxy_pid=$!
echo "Запускаю Gemini прокси..."
i=0; while [ $i -lt 15 ]; do sleep 1; curl -sf http://localhost:8080/health &>/dev/null && break; i=$((i+1)); done
fi
has_auth=$(curl -sf "http://localhost:8080/account-limits" 2>/dev/null || echo "")
if [ -n "$has_auth" ]; then
total_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len(json.load(sys.stdin).get('accounts', [])))" 2>/dev/null || echo "0")
invalid_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len([a for a in json.load(sys.stdin).get('accounts', []) if a.get('isInvalid')]))" 2>/dev/null || echo "0")
fi
if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then
echo -e "\033[1;33m⚠ ВНИМАНИЕ: Используйте ОТДЕЛЬНЫЙ Google-аккаунт!\033[0m"
_open_browser "http://localhost:8080"
echo "Нажмите Enter после завершения авторизации в браузере..."
read -r
elif [ "$invalid_count" -gt 0 ]; then
echo -e "\033[0;33m[ПРЕДУПРЕЖДЕНИЕ]\033[0m Обнаружены проблемные аккаунты ($invalid_count из $total_count)."
fi
echo -n "Проверка доступа к Gemini API... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-pro-agent"
if [ "$_CLAUDE_TEST_CODE" != "400" ]; then
_handle_api_response "Gemini" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" ""
ret=$?
if [ $ret -ne 0 ]; then
if _claude_offer_reauth "Gemini"; then
_open_browser "http://localhost:8080"
echo "Нажмите Enter после авторизации/добавления..."
read -r
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-pro-agent"
[ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; }
echo -e "\033[0;32mOK\033[0m"
else
exit 1
fi
fi
else
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
if echo "$_emsg" | grep -q "RESOURCE_EXHAUSTED"; then
echo -e "\033[0;33m[КВОТА ИСЧЕРПАНА]\033[0m Все Gemini аккаунты исчерпали лимит запросов."
exit 1
fi
echo -e "\033[0;32mOK\033[0m"
fi
ANTHROPIC_BASE_URL=http://localhost:8080 \
ANTHROPIC_AUTH_TOKEN=dummy \
ANTHROPIC_MODEL=gemini-pro-agent \
ANTHROPIC_DEFAULT_OPUS_MODEL=gemini-pro-agent \
ANTHROPIC_DEFAULT_SONNET_MODEL=gemini-pro-agent \
ANTHROPIC_DEFAULT_HAIKU_MODEL=gemini-3.5-flash-low \
CLAUDE_CODE_SUBAGENT_MODEL=gemini-3.5-flash-low \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
GEMINIEOF
chmod +x "$BIN_DIR/claude_gemini"
success "Скрипты сгенерированы."
# ── 9. Итог ──────────────────────────────────────────────────
echo ""
echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Установка завершена!${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo ""
echo "Доступные команды (теперь это независимые скрипты в ~/.local/bin):"
echo -e " ${CYAN}claude_gpt${NC} — GPT-5.5 (ChatGPT Plus/Pro, браузерная авторизация)"
echo -e " ${CYAN}claude_deepseek${NC} — DeepSeek (API ключ сохраняется)"
echo -e " ${CYAN}claude_kimi${NC} — Kimi K2.6 (Moonshot AI, API ключ сохраняется)"
echo -e " ${CYAN}claude_gemini${NC} — Gemini (Google OAuth через браузер)"
echo ""
echo -e "${YELLOW}⚠️ Для Gemini используйте отдельный Google-аккаунт!${NC}"
echo ""