Files
ai-setup/claude_setup.sh
vitaly 46b7cfa798 Fix proxy handling and improve error reporting
- Add wrapper proxy for fixing effort level in claude-code-proxy
- Improve proxy cleanup trap to include wrapper
- Add credential backup and restore for ChatGPT to prevent Anthropic keys deletion on /logout
- Enhance error messages for 400 and other HTTP errors in ChatGPT and Gemini handlers
2026-05-31 12:29:51 +07:00

1060 lines
43 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 — Anthropic / GPT-5.5 / DeepSeek / Kimi / Gemini
# Запуск: bash claude_setup.sh
# ============================================================
BASHRC="$HOME/.bashrc"
CONFIG_DIR="$HOME/.config/claude-launcher"
DEEPSEEK_KEY_FILE="$CONFIG_DIR/deepseek_key"
NPM_GLOBAL="$HOME/.npm-global"
PROXY_BIN="$HOME/.local/bin/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
# ── 1. npm prefix в домашнюю папку ──────────────────────────
info "Настраиваю npm prefix..."
mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL"
success "npm prefix -> $NPM_GLOBAL"
# Добавляем npm-global в PATH прямо сейчас (для этого запуска скрипта)
export PATH="$NPM_GLOBAL/bin:$HOME/.local/bin:$PATH"
# Прописываем в .bashrc если ещё нет
if ! grep -q 'NPM_GLOBAL' "$BASHRC" 2>/dev/null; then
cat >> "$BASHRC" << 'PATHEOF'
# npm global без sudo
export NPM_GLOBAL="$HOME/.npm-global"
export PATH="$NPM_GLOBAL/bin:$HOME/.local/bin:$PATH"
PATHEOF
success "npm PATH добавлен в $BASHRC"
else
success "npm PATH уже есть в $BASHRC"
fi
# ── 2. Node.js ───────────────────────────────────────────────
info "Проверяю Node.js..."
if ! command -v node &>/dev/null; then
info "Устанавливаю Node.js (нужен sudo)..."
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
fi
success "Node.js $(node --version)"
# ── 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
# Fallback: npm в наш prefix (без sudo)
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 "$HOME/.local/bin"
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 (патч xhigh→max для claude-code-proxy) ─────────
EFFORT_PROXY_BIN="$HOME/.local/bin/claude-gpt-effort-proxy.py"
cat > "$EFFORT_PROXY_BIN" << 'PYEOF'
#!/usr/bin/env python3
"""Reverse proxy: rewrites "xhigh" effort → "max" for claude-code-proxy (bug in ≤0.0.13)."""
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'"xhigh"', b'"max"')
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 ─────────────────────────────────────
info "Прописываю функции запуска в $BASHRC..."
MARKER="# === CLAUDE LAUNCHER ==="
if grep -q "$MARKER" "$BASHRC" 2>/dev/null; then
warn "Блок уже есть в $BASHRC — обновляю..."
python3 - "$BASHRC" <<'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
fi
cat >> "$BASHRC" << 'BASHEOF'
# === CLAUDE LAUNCHER ===
# ── Shared auth validation helpers ──────────────────────────
# _claude_test_api: Send 1-token test to an Anthropic-compatible endpoint
# Usage: _claude_test_api <url> <auth_header> [model]
# auth_header format: "Authorization: Bearer TOKEN" or "x-api-key: KEY"
# Side effects: Sets globals _CLAUDE_TEST_CODE, _CLAUDE_TEST_BODY
_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: Extract error.message from Anthropic-style error JSON
_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: Ask user if they want to re-authenticate
# Returns 0 for yes, 1 for no
_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
}
# ── claude_anthropic ──────────────────────────────────────────
claude_anthropic() {
unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN
unset CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
local _token="" _method=""
# Извлекаем OAuth-токен из credentials
_token=$(python3 -c "
import json, os
try:
with open(os.path.expanduser('~/.claude/.credentials.json')) as f:
d = json.load(f)
print(d.get('claudeAiOauth', {}).get('accessToken', ''))
except: pass
" 2>/dev/null)
if [ -n "$_token" ]; then
_method="oauth"
elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then
_token="$ANTHROPIC_API_KEY"
_method="apikey"
fi
# ── Pre-launch auth validation ──
if [ "$_method" = "oauth" ]; then
# OAuth-токен предназначен для внутренней авторизации Claude Code, а не для прямых API-вызовов.
# Claude Code сам обрабатывает OAuth — прямой тест API с Bearer-токеном некорректен и даёт ложные 429.
: # пропускаем проверку, Claude Code обработает авторизацию самостоятельно
elif [ -n "$_token" ]; then
echo -n "Проверка авторизации Anthropic... "
_claude_test_api "https://api.anthropic.com/v1/messages" "x-api-key: $_token"
local _emsg
case "$_CLAUDE_TEST_CODE" in
200)
echo -e "\033[0;32mOK\033[0m"
;;
401)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo -e "\033[0;31mОШИБКА: Недействительная авторизация (HTTP 401)\033[0m"
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "API-ключ недействителен."
echo " export ANTHROPIC_API_KEY=sk-ant-..."
echo ""
read -r -p "Ввести новый ключ сейчас? [y/N] " _ans
case "${_ans:-N}" in
[Yy]*)
read -r -p "Введите ключ: " _token
[ -z "$_token" ] && { echo "Ключ не введён."; return 1; }
echo -n "Проверяю новый ключ... "
_claude_test_api "https://api.anthropic.com/v1/messages" "x-api-key: $_token"
case "$_CLAUDE_TEST_CODE" in
200) echo -e "\033[0;32mOK\033[0m"; ANTHROPIC_API_KEY="$_token" ;;
*) echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"; return 1 ;;
esac
;;
*) return 1 ;;
esac
;;
403)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;33m[ПРЕДУПРЕЖДЕНИЕ]\033[0m Доступ запрещён (HTTP 403) — возможно, подписка не позволяет."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo -n "Продолжить всё равно? (запросы могут не работать) [y/N] "
local _ans; read -r _ans
case "${_ans:-N}" in [Yy]*) ;; *) return 1 ;; esac
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;33m[ЛИМИТ]\033[0m Лимит запросов исчерпан или пустой баланс (HTTP 429)."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "Варианты:"
echo " [C] Продолжить всё равно (может не работать)"
echo " [Q] Выйти"
echo -n "Выберите [C/q]: "
local _ans; read -r _ans
case "${_ans:-C}" in
[Cc]) ;; # продолжаем
*) return 1 ;;
esac
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить авторизацию (нет сети?). Продолжаю..."
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m API вернул HTTP $_CLAUDE_TEST_CODE"
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo -n "Продолжить всё равно? [y/N] "
local _ans; read -r _ans
case "${_ans:-N}" in [Yy]*) ;; *) return 1 ;; esac
;;
esac
else
# Нет ни OAuth, ни API-ключа — предлагаем выбор
echo ""
echo -e "\033[1;33mАнтропная авторизация не найдена.\033[0m"
echo ""
echo "Варианты:"
echo " [L] Залогиниться через браузер (OAuth)"
echo " [K] Ввести API-ключ вручную"
echo " [Q] Выйти"
echo -n "Выберите [L/k/q]: "
local _ans; read -r _ans
case "${_ans:-L}" in
[Ll])
echo ""
echo "Запускаю Claude Code — войдите в аккаунт Anthropic в интерфейсе..."
# claude auth login + claude "$@" вызывали двойной auth flow (v2.x запускает
# полный TUI внутри auth login). Запускаем claude напрямую: он сам откроет
# браузер и попросит выбрать тип аккаунта в одном взаимодействии.
ANTHROPIC_MODEL=claude-sonnet-4-6 \
ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-8 \
ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-6 \
ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-haiku-4-5-20251001 \
CLAUDE_CODE_SUBAGENT_MODEL=claude-haiku-4-5-20251001 \
claude "$@"
return "$?"
;;
[Kk])
echo ""
read -r -p "Введите Anthropic API ключ (sk-ant-...): " _token
[ -z "$_token" ] && { echo "Ключ не введён."; return 1; }
_method="apikey"
echo -n "Проверяю ключ... "
_claude_test_api "https://api.anthropic.com/v1/messages" "x-api-key: $_token"
case "$_CLAUDE_TEST_CODE" in
200) echo -e "\033[0;32mOK\033[0m"; export ANTHROPIC_API_KEY="$_token" ;;
*) echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"; return 1 ;;
esac
;;
*) echo "Отменено."; return 1 ;;
esac
fi
# Явно задаём модели Anthropic, чтобы не подхватился deepseek-chat из settings.json
ANTHROPIC_MODEL=claude-sonnet-4-6 \
ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-8 \
ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-6 \
ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-haiku-4-5-20251001 \
CLAUDE_CODE_SUBAGENT_MODEL=claude-haiku-4-5-20251001 \
claude "$@"
}
# ── claude_gpt ────────────────────────────────────────────────
claude_gpt() {
local proxy_bin="$HOME/.local/bin/claude-code-proxy"
if [ ! -f "$proxy_bin" ]; then
echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh"
return 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 ""
echo "Если браузер не открылся, попробуйте device flow:"
echo " claude-code-proxy codex auth device"
return 1
fi
fi
# Запускаем прокси в фоне (если ещё не запущен)
local proxy_pid="" wrapper_pid=""
local effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py"
# Если прокси занял порт 18765 (старый формат без wrapper) — мигрируем
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
# Реальный прокси на 18766
if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then
PORT=18766 "$proxy_bin" serve &>/tmp/claude-code-proxy.log &
proxy_pid=$!
local _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
# Wrapper (xhigh→max патч) на 18765
if ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then
python3 "$effort_proxy" 18766 18765 &>/tmp/claude-gpt-effort-proxy.log &
wrapper_pid=$!
local _i=0
while [ $_i -lt 10 ]; do
sleep 1
curl -sf --max-time 1 http://localhost:18765/ &>/dev/null; local _ce=$?
[ "$_ce" -ne 7 ] && break # exit 7 = connection refused; любой другой = wrapper слушает
_i=$((_i + 1))
done
fi
# Убиваем прокси и wrapper при любом выходе из функции
trap '[ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null; [ -n "$wrapper_pid" ] && kill "$wrapper_pid" 2>/dev/null' RETURN
# ── Pre-launch API validation through proxy ──
echo -n "Проверка авторизации ChatGPT... "
_claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini"
local _emsg
case "$_CLAUDE_TEST_CODE" in
200)
echo -e "\033[0;32mOK\033[0m"
;;
401|403)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Авторизация ChatGPT недействительна (HTTP $_CLAUDE_TEST_CODE)."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "Очищаю недействительную авторизацию..."
"$proxy_bin" codex auth logout 2>/dev/null
if _claude_offer_reauth "ChatGPT"; then
echo "Запускаю повторную авторизацию..."
if ! "$proxy_bin" codex auth login 2>&1; then
echo ""
echo "Если браузер не открылся, попробуйте device flow:"
echo " claude-code-proxy codex auth device"
return 1
fi
echo -n "Проверяю авторизацию после входа... "
_claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini"
if [ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ]; then
echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"
return 1
fi
echo -e "\033[0;32mOK\033[0m"
else
return 1
fi
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Лимит ChatGPT исчерпан."
[ -n "$_emsg" ] && echo " $_emsg"
return 1
;;
400)
# Прокси работает — 400 означает только, что тест-запрос не содержит обязательное поле «instructions»
# Авторизация уже проверена через codex auth status; запускаем Claude
echo -e "\033[0;32mOK\033[0m"
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ChatGPT прокси (нет сети?). Продолжаю..."
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси вернул неожиданный HTTP $_CLAUDE_TEST_CODE."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "Попробуйте:"
echo " • Перезапустить claude_gpt"
echo " • Переавторизоваться: claude-code-proxy codex auth logout && claude-code-proxy codex auth login"
return 1
;;
esac
# Сохраняем Anthropic credentials перед запуском — команда /logout внутри Claude Code
# удаляет их, хотя в режиме GPT они не нужны, но потом сломают claude_anthropic
local _creds_file="$HOME/.claude/.credentials.json"
local _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 "$@"
# Восстанавливаем credentials если /logout их удалил
if [ -n "$_creds_backup" ] && [ ! -f "$_creds_file" ]; then
mkdir -p "$(dirname "$_creds_file")"
echo "$_creds_backup" > "$_creds_file"
echo ""
echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены после /logout внутри сессии."
fi
if [ -n "$proxy_pid" ]; then
kill "$proxy_pid" 2>/dev/null
wait "$proxy_pid" 2>/dev/null
fi
}
# ── claude_deepseek ───────────────────────────────────────────
claude_deepseek() {
local key_file="$HOME/.config/claude-launcher/deepseek_key"
local api_key="" reauth=0
local _emsg
# Read stored key
if [ -f "$key_file" ]; then
api_key=$(cat "$key_file")
fi
# Validate stored key if present
if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого DeepSeek ключа... "
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash"
case "$_CLAUDE_TEST_CODE" in
200)
echo -e "\033[0;32mOK\033[0m"
;;
401|403)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Сохранённый ключ недействителен (HTTP $_CLAUDE_TEST_CODE)."
[ -n "$_emsg" ] && echo " $_emsg"
echo "Удаляю недействительный ключ..."
rm -f "$key_file"
api_key=""
reauth=1
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Баланс DeepSeek пуст или превышен лимит."
[ -n "$_emsg" ] && echo " $_emsg"
echo " Пополните баланс: https://platform.deepseek.com/top_up"
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
local _ans; read -r _ans
case "${_ans:-N}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ключ (нет сети?). Продолжаю..."
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m API вернул HTTP $_CLAUDE_TEST_CODE"
[ -n "$_emsg" ] && echo " $_emsg"
return 1
;;
esac
fi
# Re-prompt if key was invalidated
if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then
echo ""
echo -n "Хотите ввести новый DeepSeek ключ? [Y/n] "
local _ans; read -r _ans
case "${_ans:-Y}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac
fi
# Prompt for key if missing
if [ -z "$api_key" ]; then
echo ""
echo "DeepSeek API ключ не найден."
echo "Получить ключ: https://platform.deepseek.com/api_keys"
echo ""
read -r -p "Введите ваш DeepSeek API ключ: " api_key
echo ""
if [ -z "$api_key" ]; then
echo "Ключ не введён. Выход."
return 1
fi
echo "Проверяю ключ и баланс..."
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash"
case "$_CLAUDE_TEST_CODE" in
200)
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
echo -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m"
;;
000)
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
[ -n "$_emsg" ] && echo " Причина: $_emsg"
echo " Пополните баланс: https://platform.deepseek.com/top_up"
echo " Ключ НЕ сохранён — сначала пополните счёт."
return 1
;;
401|403)
echo -e "\033[0;31mКлюч недействителен (HTTP $_CLAUDE_TEST_CODE).\033[0m Ключ не сохранён."
return 1
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
[ -z "$_emsg" ] && _emsg="HTTP $_CLAUDE_TEST_CODE"
echo "Ошибка API: $_emsg"
echo "Ключ не сохранён."
return 1
;;
esac
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 "$@"
}
# ── claude_kimi ─────────────────────────────────────────────
claude_kimi() {
local key_file="$HOME/.config/claude-launcher/kimi_key"
local api_key="" reauth=0
local _emsg
# Read stored key
if [ -f "$key_file" ]; then
api_key=$(cat "$key_file")
fi
# Validate stored key if present
if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого Kimi ключа... "
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6"
case "$_CLAUDE_TEST_CODE" in
200)
echo -e "\033[0;32mOK\033[0m"
;;
401|403)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Сохранённый ключ недействителен (HTTP $_CLAUDE_TEST_CODE)."
[ -n "$_emsg" ] && echo " $_emsg"
echo "Удаляю недействительный ключ..."
rm -f "$key_file"
api_key=""
reauth=1
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Баланс Kimi пуст или превышен лимит."
[ -n "$_emsg" ] && echo " $_emsg"
echo " Пополните баланс: https://platform.moonshot.ai/console/billing"
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
local _ans; read -r _ans
case "${_ans:-N}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ключ (нет сети?). Продолжаю..."
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m API вернул HTTP $_CLAUDE_TEST_CODE"
[ -n "$_emsg" ] && echo " $_emsg"
return 1
;;
esac
fi
# Re-prompt if key was invalidated
if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then
echo ""
echo -n "Хотите ввести новый Kimi ключ? [Y/n] "
local _ans; read -r _ans
case "${_ans:-Y}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac
fi
# Prompt for key if missing
if [ -z "$api_key" ]; then
echo ""
echo "Kimi (Moonshot AI) API ключ не найден."
echo "Получить ключ: https://platform.moonshot.ai/console/api-keys"
echo ""
read -r -p "Введите ваш Kimi API ключ (начинается с 'sk-'): " api_key
echo ""
if [ -z "$api_key" ]; then
echo "Ключ не введён. Выход."
return 1
fi
echo "Проверяю ключ и баланс..."
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6"
case "$_CLAUDE_TEST_CODE" in
200)
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
echo -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m"
;;
000)
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
[ -n "$_emsg" ] && echo " Причина: $_emsg"
echo " Пополните баланс: https://platform.moonshot.ai/console/billing"
echo " Ключ НЕ сохранён — сначала пополните счёт."
return 1
;;
401|403)
echo -e "\033[0;31mКлюч недействителен (HTTP $_CLAUDE_TEST_CODE).\033[0m Ключ не сохранён."
return 1
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
[ -z "$_emsg" ] && _emsg="HTTP $_CLAUDE_TEST_CODE"
echo "Ошибка API: $_emsg"
echo "Ключ не сохранён."
return 1
;;
esac
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 "$@"
}
# ── claude_gemini ─────────────────────────────────────────────
claude_gemini() {
local acc_cmd=""
command -v antigravity-claude-proxy &>/dev/null && acc_cmd="antigravity-claude-proxy"
command -v acc &>/dev/null && acc_cmd="acc"
if [ -z "$acc_cmd" ]; then
echo "Ошибка: antigravity-claude-proxy не найден. Перезапустите claude_setup.sh"
return 1
fi
# Запускаем прокси в фоне если не запущен
local proxy_pid=""
if ! curl -sf http://localhost:8080/health &>/dev/null; then
"$acc_cmd" start &>/tmp/antigravity-proxy.log &
proxy_pid=$!
echo "Запускаю Gemini прокси..."
local i=0
while [ $i -lt 15 ]; do
sleep 1
curl -sf http://localhost:8080/health &>/dev/null && break
i=$((i+1))
done
fi
# ── Проверка аккаунтов ──
local has_auth total_count invalid_count
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
try:
d = json.load(sys.stdin)
print(len(d.get('accounts', [])))
except:
print(0)
" 2>/dev/null || echo "0")
invalid_count=$(echo "$has_auth" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
accounts = d.get('accounts', [])
invalid = [a for a in accounts if a.get('isInvalid')]
print(len(invalid))
except:
print(0)
" 2>/dev/null || echo "0")
fi
if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then
echo ""
echo "Google-аккаунт не найден."
echo ""
echo -e "\033[1;33m⚠ ВНИМАНИЕ: Используйте ОТДЕЛЬНЫЙ Google-аккаунт!\033[0m"
echo " Google может заблокировать аккаунты, использующие этот прокси."
echo ""
echo "Открываю http://localhost:8080 в браузере..."
echo "Перейдите: Accounts → Add Account → войдите через Google."
echo ""
xdg-open "http://localhost:8080" 2>/dev/null || \
sensible-browser "http://localhost:8080" 2>/dev/null || \
echo "Откройте вручную: http://localhost:8080"
echo "Нажмите Enter после завершения авторизации в браузере..."
read -r
elif [ "$invalid_count" -gt 0 ]; then
echo ""
echo -e "\033[0;33m[ПРЕДУПРЕЖДЕНИЕ]\033[0m Обнаружены проблемные аккаунты ($invalid_count из $total_count)."
echo "Проверьте статус: http://localhost:8080"
fi
# ── Pre-launch API test through proxy ──
echo -n "Проверка доступа к Gemini API... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent"
local _emsg
case "$_CLAUDE_TEST_CODE" in
200)
echo -e "\033[0;32mOK\033[0m"
;;
401|403)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Gemini аккаунты не авторизованы (HTTP $_CLAUDE_TEST_CODE)."
[ -n "$_emsg" ] && echo " $_emsg"
echo "Попробуйте переавторизоваться через http://localhost:8080"
if _claude_offer_reauth "Gemini"; then
xdg-open "http://localhost:8080" 2>/dev/null || \
sensible-browser "http://localhost:8080" 2>/dev/null || \
echo "Откройте http://localhost:8080"
echo "Нажмите Enter после авторизации..."
read -r
echo -n "Проверяю авторизацию Gemini... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent"
if [ "$_CLAUDE_TEST_CODE" != "200" ]; then
echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"
return 1
fi
echo -e "\033[0;32mOK\033[0m"
else
return 1
fi
;;
429)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Все Gemini аккаунты исчерпали лимит."
[ -n "$_emsg" ] && echo " $_emsg"
echo "Подождите сброса лимитов или добавьте новый аккаунт."
if _claude_offer_reauth "Gemini (добавить аккаунт)"; then
xdg-open "http://localhost:8080" 2>/dev/null || \
sensible-browser "http://localhost:8080" 2>/dev/null || \
echo "Откройте http://localhost:8080"
echo "Нажмите Enter после добавления..."
read -r
echo -n "Проверяю авторизацию Gemini... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent"
if [ "$_CLAUDE_TEST_CODE" != "200" ]; then
echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"
return 1
fi
echo -e "\033[0;32mOK\033[0m"
else
return 1
fi
;;
400)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
if echo "$_emsg" | grep -q "RESOURCE_EXHAUSTED"; then
local _reset
_reset=$(echo "$_emsg" | sed -n 's/.*reset after \([^.]*\).*/\1/p' 2>/dev/null || true)
echo -e "\033[0;33m[КВОТА ИСЧЕРПАНА]\033[0m Все Gemini аккаунты исчерпали лимит запросов."
[ -n "$_reset" ] && echo " Квота обновится через: $_reset"
echo ""
echo "Что делать:"
echo " • Подождите сброса квоты и повторите попытку"
echo " • Или добавьте новый аккаунт Google через http://localhost:8080"
if _claude_offer_reauth "Gemini (добавить аккаунт)"; then
xdg-open "http://localhost:8080" 2>/dev/null || \
sensible-browser "http://localhost:8080" 2>/dev/null || \
echo "Откройте http://localhost:8080"
echo "Нажмите Enter после добавления..."
read -r
echo -n "Проверяю авторизацию Gemini... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent"
if [ "$_CLAUDE_TEST_CODE" != "200" ]; then
echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"
return 1
fi
echo -e "\033[0;32mOK\033[0m"
else
return 1
fi
else
echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси Gemini вернул HTTP 400."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "Попробуйте:"
echo " • Перезапустить прокси: перезапустите claude_gemini"
echo " • Проверить статус аккаунтов: http://localhost:8080"
return 1
fi
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить Gemini прокси (нет сети?). Продолжаю..."
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси вернул неожиданный HTTP $_CLAUDE_TEST_CODE."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "Попробуйте:"
echo " • Перезапустить claude_gemini"
echo " • Проверить статус аккаунтов: http://localhost:8080"
return 1
;;
esac
echo "Запускаю Claude Code с Gemini..."
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-3-flash-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 "$@"
if [ -n "$proxy_pid" ]; then
kill "$proxy_pid" 2>/dev/null
wait "$proxy_pid" 2>/dev/null
fi
}
# === END CLAUDE LAUNCHER ===
BASHEOF
success "Функции добавлены в $BASHRC"
# ── 8. Итог ──────────────────────────────────────────────────
echo ""
echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Установка завершена!${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo ""
echo "Доступные команды:"
echo -e " ${CYAN}claude_anthropic${NC} — оригинальный Claude (Anthropic API)"
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 "Все команды принимают стандартные флаги:"
echo -e " ${CYAN}claude_gpt --dangerously-skip-permissions${NC}"
echo ""
echo -e "${YELLOW}⚠️ Для Gemini используйте отдельный Google-аккаунт!${NC}"
echo ""
echo "Управление:"
echo -e " DeepSeek ключ: ${CYAN}rm $DEEPSEEK_KEY_FILE${NC} (сбросить)"
echo -e " Kimi ключ: ${CYAN}rm $CONFIG_DIR/kimi_key${NC} (сбросить)"
echo -e " GPT статус: ${CYAN}claude-code-proxy codex auth status${NC}"
echo -e " GPT выйти: ${CYAN}claude-code-proxy codex auth logout${NC}"
echo -e " Gemini WebUI: ${CYAN}http://localhost:8080${NC} (когда прокси запущен)"
echo ""
if [ "$0" != "${BASH_SOURCE[0]}" ]; then
return 0 2>/dev/null || exit 0
fi
echo -e "${CYAN}Перезапускаю shell для применения функций...${NC}"
exec bash
echo ""