Исправить 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:
562
claude_setup.sh
Normal file → Executable file
562
claude_setup.sh
Normal file → Executable file
@@ -87,7 +87,7 @@ install_proxy() {
|
|||||||
[ -z "$LATEST" ] && err "Не удалось получить версию claude-code-proxy с GitHub"
|
[ -z "$LATEST" ] && err "Не удалось получить версию claude-code-proxy с GitHub"
|
||||||
|
|
||||||
TMP=$(mktemp -d)
|
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"
|
URL="https://github.com/raine/claude-code-proxy/releases/download/${LATEST}/claude-code-proxy-linux-${ARCH_TAG}.tar.gz"
|
||||||
info "Скачиваю $URL"
|
info "Скачиваю $URL"
|
||||||
curl -fsSL "$URL" -o "$TMP/proxy.tar.gz" || err "Не удалось скачать claude-code-proxy"
|
curl -fsSL "$URL" -o "$TMP/proxy.tar.gz" || err "Не удалось скачать claude-code-proxy"
|
||||||
@@ -146,12 +146,204 @@ cat >> "$BASHRC" << 'BASHEOF'
|
|||||||
|
|
||||||
# === CLAUDE LAUNCHER ===
|
# === 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 ──────────────────────────────────────────
|
||||||
claude_anthropic() {
|
claude_anthropic() {
|
||||||
unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN ANTHROPIC_MODEL
|
unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN
|
||||||
unset ANTHROPIC_DEFAULT_OPUS_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL
|
|
||||||
unset ANTHROPIC_DEFAULT_HAIKU_MODEL CLAUDE_CODE_SUBAGENT_MODEL
|
|
||||||
unset CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
|
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 "$@"
|
claude "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,8 +375,72 @@ claude_gpt() {
|
|||||||
if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then
|
if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then
|
||||||
"$proxy_bin" serve &>/tmp/claude-code-proxy.log &
|
"$proxy_bin" serve &>/tmp/claude-code-proxy.log &
|
||||||
proxy_pid=$!
|
proxy_pid=$!
|
||||||
|
local _i=0
|
||||||
|
while [ $_i -lt 10 ]; do
|
||||||
sleep 1
|
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
|
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_BASE_URL=http://localhost:18765 \
|
||||||
ANTHROPIC_AUTH_TOKEN=dummy \
|
ANTHROPIC_AUTH_TOKEN=dummy \
|
||||||
@@ -205,12 +461,66 @@ claude_gpt() {
|
|||||||
# ── claude_deepseek ───────────────────────────────────────────
|
# ── claude_deepseek ───────────────────────────────────────────
|
||||||
claude_deepseek() {
|
claude_deepseek() {
|
||||||
local key_file="$HOME/.config/claude-launcher/deepseek_key"
|
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
|
if [ -f "$key_file" ]; then
|
||||||
api_key=$(cat "$key_file")
|
api_key=$(cat "$key_file")
|
||||||
fi
|
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
|
if [ -z "$api_key" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "DeepSeek API ключ не найден."
|
echo "DeepSeek API ключ не найден."
|
||||||
@@ -225,43 +535,42 @@ claude_deepseek() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Проверяю ключ и баланс..."
|
echo "Проверяю ключ и баланс..."
|
||||||
local http_code http_body err_msg
|
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash"
|
||||||
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')
|
|
||||||
|
|
||||||
if [ "$http_code" = "200" ]; then
|
case "$_CLAUDE_TEST_CODE" in
|
||||||
|
200)
|
||||||
mkdir -p "$(dirname "$key_file")"
|
mkdir -p "$(dirname "$key_file")"
|
||||||
echo "$api_key" > "$key_file"
|
echo "$api_key" > "$key_file"
|
||||||
chmod 600 "$key_file"
|
chmod 600 "$key_file"
|
||||||
echo "Ключ действителен, баланс в порядке. Ключ сохранён."
|
echo -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m"
|
||||||
elif [ "$http_code" = "000" ]; then
|
;;
|
||||||
|
000)
|
||||||
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
|
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
|
||||||
mkdir -p "$(dirname "$key_file")"
|
mkdir -p "$(dirname "$key_file")"
|
||||||
echo "$api_key" > "$key_file"
|
echo "$api_key" > "$key_file"
|
||||||
chmod 600 "$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 "")
|
429)
|
||||||
|
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
|
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
|
||||||
echo " Причина: $err_msg"
|
[ -n "$_emsg" ] && echo " Причина: $_emsg"
|
||||||
echo " Пополните баланс: https://platform.deepseek.com/top_up"
|
echo " Пополните баланс: https://platform.deepseek.com/top_up"
|
||||||
echo " Ключ НЕ сохранён — сначала пополните счёт."
|
echo " Ключ НЕ сохранён — сначала пополните счёт."
|
||||||
return 1
|
return 1
|
||||||
elif [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
|
;;
|
||||||
echo "Ключ недействителен (HTTP $http_code). Ключ не сохранён."
|
401|403)
|
||||||
|
echo -e "\033[0;31mКлюч недействителен (HTTP $_CLAUDE_TEST_CODE).\033[0m Ключ не сохранён."
|
||||||
return 1
|
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"
|
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
|
||||||
|
[ -z "$_emsg" ] && _emsg="HTTP $_CLAUDE_TEST_CODE"
|
||||||
|
echo "Ошибка API: $_emsg"
|
||||||
echo "Ключ не сохранён."
|
echo "Ключ не сохранён."
|
||||||
return 1
|
return 1
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
|
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
|
||||||
@@ -278,12 +587,66 @@ claude_deepseek() {
|
|||||||
# ── claude_kimi ─────────────────────────────────────────────
|
# ── claude_kimi ─────────────────────────────────────────────
|
||||||
claude_kimi() {
|
claude_kimi() {
|
||||||
local key_file="$HOME/.config/claude-launcher/kimi_key"
|
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
|
if [ -f "$key_file" ]; then
|
||||||
api_key=$(cat "$key_file")
|
api_key=$(cat "$key_file")
|
||||||
fi
|
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
|
if [ -z "$api_key" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Kimi (Moonshot AI) API ключ не найден."
|
echo "Kimi (Moonshot AI) API ключ не найден."
|
||||||
@@ -298,43 +661,42 @@ claude_kimi() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Проверяю ключ и баланс..."
|
echo "Проверяю ключ и баланс..."
|
||||||
local http_code http_body err_msg
|
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6"
|
||||||
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')
|
|
||||||
|
|
||||||
if [ "$http_code" = "200" ]; then
|
case "$_CLAUDE_TEST_CODE" in
|
||||||
|
200)
|
||||||
mkdir -p "$(dirname "$key_file")"
|
mkdir -p "$(dirname "$key_file")"
|
||||||
echo "$api_key" > "$key_file"
|
echo "$api_key" > "$key_file"
|
||||||
chmod 600 "$key_file"
|
chmod 600 "$key_file"
|
||||||
echo "Ключ действителен, баланс в порядке. Ключ сохранён."
|
echo -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m"
|
||||||
elif [ "$http_code" = "000" ]; then
|
;;
|
||||||
|
000)
|
||||||
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
|
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..."
|
||||||
mkdir -p "$(dirname "$key_file")"
|
mkdir -p "$(dirname "$key_file")"
|
||||||
echo "$api_key" > "$key_file"
|
echo "$api_key" > "$key_file"
|
||||||
chmod 600 "$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 "")
|
429)
|
||||||
|
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
|
echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован."
|
||||||
echo " Причина: $err_msg"
|
[ -n "$_emsg" ] && echo " Причина: $_emsg"
|
||||||
echo " Пополните баланс: https://platform.moonshot.ai/console/billing"
|
echo " Пополните баланс: https://platform.moonshot.ai/console/billing"
|
||||||
echo " Ключ НЕ сохранён — сначала пополните счёт."
|
echo " Ключ НЕ сохранён — сначала пополните счёт."
|
||||||
return 1
|
return 1
|
||||||
elif [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
|
;;
|
||||||
echo "Ключ недействителен (HTTP $http_code). Ключ не сохранён."
|
401|403)
|
||||||
|
echo -e "\033[0;31mКлюч недействителен (HTTP $_CLAUDE_TEST_CODE).\033[0m Ключ не сохранён."
|
||||||
return 1
|
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"
|
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
|
||||||
|
[ -z "$_emsg" ] && _emsg="HTTP $_CLAUDE_TEST_CODE"
|
||||||
|
echo "Ошибка API: $_emsg"
|
||||||
echo "Ключ не сохранён."
|
echo "Ключ не сохранён."
|
||||||
return 1
|
return 1
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \
|
ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \
|
||||||
@@ -373,15 +735,32 @@ claude_gemini() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Проверяем наличие аккаунта
|
# ── Проверка аккаунтов ──
|
||||||
local acc_count
|
local has_auth total_count invalid_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
|
|
||||||
has_auth=$(curl -sf "http://localhost:8080/account-limits" 2>/dev/null || echo "")
|
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 ""
|
||||||
echo "Google-аккаунт не найден."
|
echo "Google-аккаунт не найден."
|
||||||
echo ""
|
echo ""
|
||||||
@@ -391,15 +770,86 @@ claude_gemini() {
|
|||||||
echo "Открываю http://localhost:8080 в браузере..."
|
echo "Открываю http://localhost:8080 в браузере..."
|
||||||
echo "Перейдите: Accounts → Add Account → войдите через Google."
|
echo "Перейдите: Accounts → Add Account → войдите через Google."
|
||||||
echo ""
|
echo ""
|
||||||
# Открываем браузер
|
|
||||||
xdg-open "http://localhost:8080" 2>/dev/null || \
|
xdg-open "http://localhost:8080" 2>/dev/null || \
|
||||||
sensible-browser "http://localhost:8080" 2>/dev/null || \
|
sensible-browser "http://localhost:8080" 2>/dev/null || \
|
||||||
echo "Откройте вручную: http://localhost:8080"
|
echo "Откройте вручную: http://localhost:8080"
|
||||||
|
|
||||||
echo "Нажмите Enter после завершения авторизации в браузере..."
|
echo "Нажмите Enter после завершения авторизации в браузере..."
|
||||||
read -r
|
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
|
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..."
|
echo "Запускаю Claude Code с Gemini..."
|
||||||
|
|
||||||
ANTHROPIC_BASE_URL=http://localhost:8080 \
|
ANTHROPIC_BASE_URL=http://localhost:8080 \
|
||||||
|
|||||||
304
tests/test_fixes.sh
Executable file
304
tests/test_fixes.sh
Executable file
@@ -0,0 +1,304 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# tests/test_fixes.sh — unit tests for code-review fixes in claude_setup.sh
|
||||||
|
# Run: bash tests/test_fixes.sh
|
||||||
|
# Requires: bash 4+, curl (can be mocked via PATH)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT="$(cd "$(dirname "$0")/.." && pwd)/claude_setup.sh"
|
||||||
|
PASS=0; FAIL=0
|
||||||
|
|
||||||
|
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
|
||||||
|
fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); }
|
||||||
|
|
||||||
|
# ── helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Source only the heredoc functions, not the setup-script body.
|
||||||
|
# The heredoc begins after "cat >> \"$BASHRC\" << 'BASHEOF'" and contains
|
||||||
|
# all the launcher functions; we extract and source that block directly.
|
||||||
|
_source_functions() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
awk '/^# === CLAUDE LAUNCHER ===/,/^# === END CLAUDE LAUNCHER ===/' "$SCRIPT" > "$tmp"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 1: ANTHROPIC_API_KEY exported in manual-key path ────────────────────
|
||||||
|
test_fix1_export_api_key() {
|
||||||
|
# Extract the [Kk] branch from the script and confirm `export` keyword exists
|
||||||
|
local kk_block
|
||||||
|
kk_block=$(awk '/\[Kk\]/,/\[Ll\]/' "$SCRIPT" | grep 'ANTHROPIC_API_KEY')
|
||||||
|
if echo "$kk_block" | grep -q 'export ANTHROPIC_API_KEY'; then
|
||||||
|
ok "Fix1: [K] branch uses 'export ANTHROPIC_API_KEY'"
|
||||||
|
else
|
||||||
|
fail "Fix1: [K] branch missing 'export' for ANTHROPIC_API_KEY"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 2: trap RETURN kills proxy on early exit ─────────────────────────────
|
||||||
|
test_fix2_trap_return() {
|
||||||
|
if grep -q "trap '.*kill.*proxy_pid.*' RETURN" "$SCRIPT"; then
|
||||||
|
ok "Fix2: trap RETURN for proxy cleanup present in claude_gpt"
|
||||||
|
else
|
||||||
|
fail "Fix2: trap RETURN for proxy cleanup missing in claude_gpt"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 3: readiness loop replaces bare sleep 1 ──────────────────────────────
|
||||||
|
test_fix3_readiness_loop() {
|
||||||
|
# The old code had just "sleep 1" after starting proxy; now there's a while loop
|
||||||
|
local gpt_section
|
||||||
|
gpt_section=$(awk '/^claude_gpt\(\)/,/^}/' "$SCRIPT")
|
||||||
|
|
||||||
|
if echo "$gpt_section" | grep -q 'while \[ \$_i -lt'; then
|
||||||
|
ok "Fix3: readiness poll loop present in claude_gpt proxy start"
|
||||||
|
else
|
||||||
|
fail "Fix3: readiness poll loop missing in claude_gpt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirm bare "sleep 1" is gone from the proxy-start section (the loop contains sleep 1 but in context)
|
||||||
|
# The old pattern was: proxy_pid=$!\n sleep 1\n fi
|
||||||
|
if echo "$gpt_section" | grep -qP 'proxy_pid=\$!\n\s+sleep 1\n\s+fi'; then
|
||||||
|
fail "Fix3: bare 'sleep 1' still present right after proxy_pid=\$!"
|
||||||
|
else
|
||||||
|
ok "Fix3: bare 'sleep 1; fi' pattern removed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 3b: curl exit-7 logic correct ────────────────────────────────────────
|
||||||
|
test_fix3b_exit7_logic() {
|
||||||
|
# Verify the comment and condition are as expected
|
||||||
|
if grep -q 'exit 7 = connection refused' "$SCRIPT"; then
|
||||||
|
ok "Fix3b: exit-7 comment present (connection refused check documented)"
|
||||||
|
else
|
||||||
|
fail "Fix3b: exit-7 comment missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q '_ce.*-ne 7' "$SCRIPT"; then
|
||||||
|
ok "Fix3b: [ \$_ce -ne 7 ] break condition present"
|
||||||
|
else
|
||||||
|
fail "Fix3b: exit-7 break condition missing"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 4: re-validate after claude_gpt reauth ───────────────────────────────
|
||||||
|
test_fix4_gpt_revalidate() {
|
||||||
|
local gpt_section
|
||||||
|
gpt_section=$(awk '/^claude_gpt\(\)/,/^}/' "$SCRIPT")
|
||||||
|
|
||||||
|
if echo "$gpt_section" | grep -q 'Проверяю авторизацию после входа'; then
|
||||||
|
ok "Fix4: re-validate after codex auth login present in claude_gpt"
|
||||||
|
else
|
||||||
|
fail "Fix4: re-validate after codex auth login missing in claude_gpt"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 5: re-validate after claude_gemini reauth (both 401 and 429) ─────────
|
||||||
|
test_fix5_gemini_revalidate() {
|
||||||
|
local gemini_section
|
||||||
|
gemini_section=$(awk '/^claude_gemini\(\)/,/^}/' "$SCRIPT")
|
||||||
|
|
||||||
|
local count
|
||||||
|
count=$(echo "$gemini_section" | grep -c 'Проверяю авторизацию Gemini' || true)
|
||||||
|
if [ "$count" -ge 2 ]; then
|
||||||
|
ok "Fix5: re-validate after gemini reauth present in both 401/403 and 429 branches ($count occurrences)"
|
||||||
|
else
|
||||||
|
fail "Fix5: re-validate after gemini reauth missing or only in one branch (found $count)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 6: prompt [C/q] matches default C in 429 handler ─────────────────────
|
||||||
|
test_fix6_prompt_default() {
|
||||||
|
# The prompt should now show [C/q] (capital C = default) matching case "${_ans:-C}"
|
||||||
|
if grep -q '\[C/q\]' "$SCRIPT"; then
|
||||||
|
ok "Fix6: prompt shows [C/q] — capital C signals default=continue"
|
||||||
|
else
|
||||||
|
fail "Fix6: prompt still shows [c/Q] (misleading) or was changed incorrectly"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirm the default in case is still C
|
||||||
|
local anthropic_section
|
||||||
|
anthropic_section=$(awk '/^claude_anthropic\(\)/,/^}/' "$SCRIPT")
|
||||||
|
if echo "$anthropic_section" | grep -q '${_ans:-C}'; then
|
||||||
|
ok "Fix6: default in case is still C (continue on Enter)"
|
||||||
|
else
|
||||||
|
fail "Fix6: default in case changed unexpectedly"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Fix 7: trap quotes $TMP correctly ────────────────────────────────────────
|
||||||
|
test_fix7_trap_tmp() {
|
||||||
|
# Should be single-quoted trap so $TMP expands at execution, not definition
|
||||||
|
if grep -q "trap 'rm -rf \"\$TMP\"' EXIT" "$SCRIPT"; then
|
||||||
|
ok "Fix7: trap uses single quotes with quoted \"\$TMP\""
|
||||||
|
else
|
||||||
|
fail "Fix7: trap still uses double quotes or $TMP still unquoted at execution"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Old bad form should be gone
|
||||||
|
if grep -q 'trap "rm -rf \$TMP" EXIT' "$SCRIPT"; then
|
||||||
|
fail "Fix7: old unquoted trap form still present"
|
||||||
|
else
|
||||||
|
ok "Fix7: old unquoted trap form removed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── _claude_test_api function isolation ──────────────────────────────────────
|
||||||
|
test_globals_set_by_test_api() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
# Mock curl to return a fake 200 response
|
||||||
|
curl() {
|
||||||
|
echo '{"id":"msg_test"}'
|
||||||
|
echo "200"
|
||||||
|
}
|
||||||
|
export -f curl
|
||||||
|
|
||||||
|
_CLAUDE_TEST_CODE=""
|
||||||
|
_CLAUDE_TEST_BODY=""
|
||||||
|
_claude_test_api "http://fake.api/v1/messages" "x-api-key: testkey" "test-model"
|
||||||
|
|
||||||
|
if [ "$_CLAUDE_TEST_CODE" = "200" ]; then
|
||||||
|
ok "test_api: _CLAUDE_TEST_CODE set to 200"
|
||||||
|
else
|
||||||
|
fail "test_api: _CLAUDE_TEST_CODE='$_CLAUDE_TEST_CODE' expected '200'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$_CLAUDE_TEST_BODY" | grep -q '"id"'; then
|
||||||
|
ok "test_api: _CLAUDE_TEST_BODY contains response body"
|
||||||
|
else
|
||||||
|
fail "test_api: _CLAUDE_TEST_BODY='$_CLAUDE_TEST_BODY' missing body"
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset -f curl
|
||||||
|
}
|
||||||
|
|
||||||
|
test_globals_set_on_curl_fail() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
# curl fails → fallback "000"
|
||||||
|
curl() { return 1; }
|
||||||
|
export -f curl
|
||||||
|
|
||||||
|
_CLAUDE_TEST_CODE=""
|
||||||
|
_claude_test_api "http://unreachable/" "x-api-key: k" "m"
|
||||||
|
|
||||||
|
if [ "$_CLAUDE_TEST_CODE" = "000" ]; then
|
||||||
|
ok "test_api: _CLAUDE_TEST_CODE=000 on curl failure"
|
||||||
|
else
|
||||||
|
fail "test_api: expected 000 on curl failure, got '$_CLAUDE_TEST_CODE'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset -f curl
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── _claude_extract_error ────────────────────────────────────────────────────
|
||||||
|
test_extract_error_message() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
local body='{"type":"error","error":{"type":"authentication_error","message":"Invalid API key"}}'
|
||||||
|
local result
|
||||||
|
result=$(_claude_extract_error "$body")
|
||||||
|
|
||||||
|
if [ "$result" = "Invalid API key" ]; then
|
||||||
|
ok "extract_error: extracts error.message from JSON"
|
||||||
|
else
|
||||||
|
fail "extract_error: expected 'Invalid API key', got '$result'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_extract_error_empty_body() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(_claude_extract_error "not json at all")
|
||||||
|
|
||||||
|
if [ -z "$result" ]; then
|
||||||
|
ok "extract_error: returns empty string on non-JSON input"
|
||||||
|
else
|
||||||
|
fail "extract_error: unexpected output '$result' on bad input"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── _claude_offer_reauth ─────────────────────────────────────────────────────
|
||||||
|
test_offer_reauth_yes() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
# Simulate user typing "Y"
|
||||||
|
local result
|
||||||
|
result=$(echo "Y" | (
|
||||||
|
_claude_offer_reauth "TestProvider"
|
||||||
|
echo "retcode:$?"
|
||||||
|
))
|
||||||
|
|
||||||
|
if echo "$result" | grep -q "retcode:0"; then
|
||||||
|
ok "offer_reauth: returns 0 on 'Y'"
|
||||||
|
else
|
||||||
|
fail "offer_reauth: expected retcode 0 on Y, got: $result"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_offer_reauth_no() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(echo "n" | (
|
||||||
|
_claude_offer_reauth "TestProvider"
|
||||||
|
echo "retcode:$?"
|
||||||
|
))
|
||||||
|
|
||||||
|
if echo "$result" | grep -q "retcode:1"; then
|
||||||
|
ok "offer_reauth: returns 1 on 'n'"
|
||||||
|
else
|
||||||
|
fail "offer_reauth: expected retcode 1 on n, got: $result"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_offer_reauth_enter_defaults_yes() {
|
||||||
|
_source_functions
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(echo "" | (
|
||||||
|
_claude_offer_reauth "TestProvider"
|
||||||
|
echo "retcode:$?"
|
||||||
|
))
|
||||||
|
|
||||||
|
if echo "$result" | grep -q "retcode:0"; then
|
||||||
|
ok "offer_reauth: Enter (empty) defaults to Yes (retcode 0)"
|
||||||
|
else
|
||||||
|
fail "offer_reauth: Enter should default to Yes, got: $result"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── bash syntax of the whole script ─────────────────────────────────────────
|
||||||
|
test_script_syntax() {
|
||||||
|
if bash -n "$SCRIPT" 2>&1; then
|
||||||
|
ok "syntax: claude_setup.sh passes 'bash -n'"
|
||||||
|
else
|
||||||
|
fail "syntax: claude_setup.sh has syntax errors"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── run all tests ─────────────────────────────────────────────────────────────
|
||||||
|
test_script_syntax
|
||||||
|
test_fix1_export_api_key
|
||||||
|
test_fix2_trap_return
|
||||||
|
test_fix3_readiness_loop
|
||||||
|
test_fix3b_exit7_logic
|
||||||
|
test_fix4_gpt_revalidate
|
||||||
|
test_fix5_gemini_revalidate
|
||||||
|
test_fix6_prompt_default
|
||||||
|
test_fix7_trap_tmp
|
||||||
|
test_globals_set_by_test_api
|
||||||
|
test_globals_set_on_curl_fail
|
||||||
|
test_extract_error_message
|
||||||
|
test_extract_error_empty_body
|
||||||
|
test_offer_reauth_yes
|
||||||
|
test_offer_reauth_no
|
||||||
|
test_offer_reauth_enter_defaults_yes
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Results: $PASS passed, $FAIL failed"
|
||||||
|
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
||||||
Reference in New Issue
Block a user