Исправить 7 багов после code review: экспорт ключа, утечка прокси, re-валидация

- Fix 1: export ANTHROPIC_API_KEY при ручном вводе ключа в claude_anthropic ([K]-ветка);
  без export subprocess claude не видел ключ и падал с ошибкой авторизации
- Fix 2: trap RETURN в claude_gpt убивает прокси при любом ранем return 1,
  устраняя утечку фоновых процессов
- Fix 3: sleep 1 заменён на poll-цикл (10 попыток, curl exit 7 = connection refused);
  connection refused теперь не маскируется под «нет сети»
- Fix 4: после codex auth login в claude_gpt добавлена повторная проверка _claude_test_api;
  прежде claude запускался без подтверждения успешности reauth
- Fix 5: аналогичная re-валидация в claude_gemini после браузерной авторизации
  (ветки 401/403 и 429)
- Fix 6: prompt [c/Q] → [C/q] в 429-обработчике claude_anthropic — заглавная буква
  соответствует умолчанию (стандарт файла: CAPITAL = default)
- Fix 7: trap 'rm -rf "$TMP"' EXIT — одинарные кавычки + кавычки вокруг $TMP,
  предотвращают word-split при путях с пробелами

Добавлены тесты: tests/test_fixes.sh (21 тест, все проходят)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 00:53:53 +07:00
parent 68c731b72e
commit 048f6b1770
2 changed files with 841 additions and 87 deletions

624
claude_setup.sh Normal file → Executable file
View File

@@ -87,7 +87,7 @@ install_proxy() {
[ -z "$LATEST" ] && err "Не удалось получить версию claude-code-proxy с GitHub"
TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT
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"
@@ -146,12 +146,204 @@ 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 ANTHROPIC_MODEL
unset ANTHROPIC_DEFAULT_OPUS_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL
unset ANTHROPIC_DEFAULT_HAIKU_MODEL CLAUDE_CODE_SUBAGENT_MODEL
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 "Открываю браузер для входа в Anthropic аккаунт..."
claude auth login || {
echo ""
echo -e "\033[0;31mНе удалось выполнить вход.\033[0m"
echo "Попробуйте вручную: claude auth login"
return 1
}
;;
[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 "$@"
}
@@ -183,8 +375,72 @@ claude_gpt() {
if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then
"$proxy_bin" serve &>/tmp/claude-code-proxy.log &
proxy_pid=$!
sleep 1
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; любой другой = прокси слушает
_i=$((_i + 1))
done
fi
# Убиваем прокси при любом выходе из функции (early return или нормальный)
trap '[ -n "$proxy_pid" ] && kill "$proxy_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" ]; 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
;;
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"
return 1
;;
esac
ANTHROPIC_BASE_URL=http://localhost:18765 \
ANTHROPIC_AUTH_TOKEN=dummy \
@@ -205,12 +461,66 @@ claude_gpt() {
# ── claude_deepseek ───────────────────────────────────────────
claude_deepseek() {
local key_file="$HOME/.config/claude-launcher/deepseek_key"
local api_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 ключ не найден."
@@ -225,43 +535,42 @@ claude_deepseek() {
fi
echo "Проверяю ключ и баланс..."
local http_code http_body err_msg
http_body=$(curl -s -w "\n%{http_code}" \
https://api.deepseek.com/anthropic/v1/messages \
-H "Authorization: Bearer $api_key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"deepseek-v4-flash","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}' 2>/dev/null || echo "000")
http_code=$(echo "$http_body" | tail -1)
http_body=$(echo "$http_body" | sed '$d')
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash"
if [ "$http_code" = "200" ]; then
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
echo "Ключ действителен, баланс в порядке. Ключ сохранён."
elif [ "$http_code" = "000" ]; then
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
elif [ "$http_code" = "429" ]; then
err_msg=$(echo "$http_body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',{}).get('message',''))" 2>/dev/null || echo "")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
echo " Причина: $err_msg"
echo " Пополните баланс: https://platform.deepseek.com/top_up"
echo " Ключ НЕ сохранён — сначала пополните счёт."
return 1
elif [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
echo "Ключ недействителен (HTTP $http_code). Ключ не сохранён."
return 1
else
err_msg=$(echo "$http_body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',{}).get('message',''))" 2>/dev/null || echo "$http_body")
echo "Ошибка API (HTTP $http_code): $err_msg"
echo "Ключ не сохранён."
return 1
fi
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 \
@@ -278,12 +587,66 @@ claude_deepseek() {
# ── claude_kimi ─────────────────────────────────────────────
claude_kimi() {
local key_file="$HOME/.config/claude-launcher/kimi_key"
local api_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 ключ не найден."
@@ -298,43 +661,42 @@ claude_kimi() {
fi
echo "Проверяю ключ и баланс..."
local http_code http_body err_msg
http_body=$(curl -s -w "\n%{http_code}" \
https://api.moonshot.ai/anthropic/v1/messages \
-H "Authorization: Bearer $api_key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"kimi-k2.6","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}' 2>/dev/null || echo "000")
http_code=$(echo "$http_body" | tail -1)
http_body=$(echo "$http_body" | sed '$d')
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6"
if [ "$http_code" = "200" ]; then
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
echo "Ключ действителен, баланс в порядке. Ключ сохранён."
elif [ "$http_code" = "000" ]; then
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
mkdir -p "$(dirname "$key_file")"
echo "$api_key" > "$key_file"
chmod 600 "$key_file"
elif [ "$http_code" = "429" ]; then
err_msg=$(echo "$http_body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',{}).get('message',''))" 2>/dev/null || echo "")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
echo " Причина: $err_msg"
echo " Пополните баланс: https://platform.moonshot.ai/console/billing"
echo " Ключ НЕ сохранён — сначала пополните счёт."
return 1
elif [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
echo "Ключ недействителен (HTTP $http_code). Ключ не сохранён."
return 1
else
err_msg=$(echo "$http_body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',{}).get('message',''))" 2>/dev/null || echo "$http_body")
echo "Ошибка API (HTTP $http_code): $err_msg"
echo "Ключ не сохранён."
return 1
fi
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 \
@@ -373,15 +735,32 @@ claude_gemini() {
done
fi
# Проверяем наличие аккаунта
local acc_count
acc_count=$(curl -sf "http://localhost:8080/v1/models" 2>/dev/null | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('data',[],'')))" 2>/dev/null || echo "0")
local has_auth
# ── Проверка аккаунтов ──
local has_auth total_count invalid_count
has_auth=$(curl -sf "http://localhost:8080/account-limits" 2>/dev/null || echo "")
if [ -z "$has_auth" ] || echo "$has_auth" | grep -qE '"accounts":\s*\[\]'; then
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 ""
@@ -391,15 +770,86 @@ claude_gemini() {
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
;;
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"
return 1
;;
esac
echo "Запускаю Claude Code с Gemini..."
ANTHROPIC_BASE_URL=http://localhost:8080 \