Refactor architecture to use standalone scripts in ~/.local/bin/ instead of ~/.bashrc

This commit is contained in:
2026-05-31 12:55:54 +07:00
parent 4f25596867
commit 61c21fe296
4 changed files with 435 additions and 857 deletions

View File

@@ -1,14 +1,13 @@
#!/usr/bin/env bash
# ============================================================
# Claude Code Setup — Anthropic / GPT-5.5 / DeepSeek / Kimi / Gemini
# Claude Code Setup — 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"
BIN_DIR="$HOME/.local/bin"
NPM_GLOBAL="$HOME/.npm-global"
PROXY_BIN="$HOME/.local/bin/claude-code-proxy"
PROXY_BIN="$BIN_DIR/claude-code-proxy"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
@@ -23,46 +22,46 @@ if [ "$EUID" -eq 0 ]; then
exit 1
fi
info "Проверяю зависимости (python3)..."
if ! command -v python3 &>/dev/null; then
err "Python 3 не установлен, но он требуется для работы скрипта."
fi
success "Python 3 найден"
# ── 1. npm prefix в домашнюю папку ──────────────────────────
info "Настраиваю npm prefix..."
mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL"
success "npm prefix -> $NPM_GLOBAL"
# Добавляем 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
export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH"
# ── 2. Node.js ───────────────────────────────────────────────
info "Проверяю Node.js..."
if ! command -v node &>/dev/null; then
info "Устанавливаю Node.js (нужен sudo)..."
info "Попытка установки Node.js (нужен sudo)..."
if command -v apt-get &>/dev/null; then
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
elif command -v dnf &>/dev/null; then
sudo dnf install -y nodejs
else
warn "Не удалось определить пакетный менеджер. Установите Node.js вручную."
fi
fi
if command -v node &>/dev/null; then
success "Node.js $(node --version)"
else
warn "Node.js не найден. Некоторые функции могут не работать."
fi
# ── 3. Claude Code ───────────────────────────────────────────
info "Проверяю Claude Code..."
if ! command -v claude &>/dev/null; then
info "Устанавливаю Claude Code..."
# Пробуем официальный инсталлер
if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
success "Claude Code установлен (официальный инсталлер)"
else
# Fallback: npm в наш prefix (без sudo)
npm install -g @anthropic-ai/claude-code
success "Claude Code установлен (npm)"
fi
@@ -71,7 +70,7 @@ else
fi
# ── 4. claude-code-proxy (GPT) ───────────────────────────────
mkdir -p "$HOME/.local/bin"
mkdir -p "$BIN_DIR"
install_proxy() {
info "Устанавливаю claude-code-proxy..."
@@ -106,11 +105,11 @@ else
install_proxy
fi
# ── 4b. effort-proxy wrapper (патч xhighmax для claude-code-proxy) ─────────
EFFORT_PROXY_BIN="$HOME/.local/bin/claude-gpt-effort-proxy.py"
# ── 4b. effort-proxy wrapper (патч xhigh->max для claude-code-proxy) ─────────
EFFORT_PROXY_BIN="$BIN_DIR/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)."""
"""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
@@ -173,14 +172,12 @@ 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'
# ── 7. Очистка старых функций из .bashrc / .zshrc ───────────
clean_rc() {
local rc_file="$1"
if [ -f "$rc_file" ] && grep -q "# === CLAUDE LAUNCHER ===" "$rc_file"; then
info "Очищаю старые функции из $rc_file..."
python3 - "$rc_file" <<'PYEOF'
import sys
path = sys.argv[1]
with open(path, 'r') as f:
@@ -194,18 +191,38 @@ if start != -1 and end != -1:
with open(path, 'w') as f:
f.write(new_content)
PYEOF
success "Старые функции удалены из $rc_file"
fi
}
clean_rc "$HOME/.bashrc"
clean_rc "$HOME/.zshrc"
cat >> "$BASHRC" << 'BASHEOF'
add_path_to_rc() {
local rc_file="$1"
if [ -f "$rc_file" ]; then
if ! grep -q 'NPM_GLOBAL' "$rc_file" 2>/dev/null; then
cat >> "$rc_file" << 'PATHEOF'
# === CLAUDE LAUNCHER ===
# Claude Code Launcher PATH
export NPM_GLOBAL="$HOME/.npm-global"
export PATH="$NPM_GLOBAL/bin:$HOME/.local/bin:$PATH"
PATHEOF
success "PATH добавлен в $rc_file"
fi
fi
}
add_path_to_rc "$HOME/.bashrc"
[ -f "$HOME/.zshrc" ] && add_path_to_rc "$HOME/.zshrc"
# ── Shared auth validation helpers ──────────────────────────
# ── 8. Генерация Standalone скриптов ────────────────────────
info "Генерирую standalone скрипты в $BIN_DIR..."
HELPERS_FILE="$BIN_DIR/claude_api_helpers.sh"
cat > "$HELPERS_FILE" << 'HELPEREOF'
#!/usr/bin/env bash
# _claude_test_api: Send 1-token test to an Anthropic-compatible endpoint
# 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
@@ -219,7 +236,6 @@ _claude_test_api() {
_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 "
@@ -238,8 +254,6 @@ except:
" 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 ""
@@ -252,138 +266,133 @@ _claude_offer_reauth() {
esac
}
# ── claude_gpt ────────────────────────────────────────────────
claude_gpt() {
local proxy_bin="$HOME/.local/bin/claude-code-proxy"
_handle_api_response() {
local provider="$1"
local code="$2"
local body="$3"
local topup_url="$4"
local _emsg
case "$code" in
200)
echo -e "\033[0;32mOK\033[0m"
return 0
;;
401|403)
_emsg=$(_claude_extract_error "$body")
echo ""
echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Авторизация $provider недействительна (HTTP $code)."
[ -n "$_emsg" ] && echo " $_emsg"
return 401
;;
429)
_emsg=$(_claude_extract_error "$body")
echo ""
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Баланс/лимит $provider исчерпан."
[ -n "$_emsg" ] && echo " $_emsg"
[ -n "$topup_url" ] && echo " $topup_url"
return 429
;;
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ключ (нет сети?). Продолжаю..."
return 0
;;
*)
_emsg=$(_claude_extract_error "$body")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m API $provider вернул HTTP $code."
[ -n "$_emsg" ] && echo " $_emsg"
return 1
;;
esac
}
_open_browser() {
local url="$1"
if command -v xdg-open &>/dev/null; then xdg-open "$url" 2>/dev/null
elif command -v open &>/dev/null; then open "$url" 2>/dev/null
elif command -v sensible-browser &>/dev/null; then sensible-browser "$url" 2>/dev/null
else echo "Откройте вручную: $url"; fi
}
HELPEREOF
chmod +x "$HELPERS_FILE"
# === claude_gpt ===
cat > "$BIN_DIR/claude_gpt" << 'GPTEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
proxy_bin="$HOME/.local/bin/claude-code-proxy"
if [ ! -f "$proxy_bin" ]; then
echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh"
return 1
exit 1
fi
# Проверяем авторизацию
if ! "$proxy_bin" codex auth status &>/dev/null; then
echo ""
echo "Авторизация ChatGPT не найдена."
echo "Сейчас появится ссылка или откроется браузер..."
echo ""
if ! "$proxy_bin" codex auth login 2>&1; then
echo ""
echo "Если браузер не открылся, попробуйте device flow:"
echo " claude-code-proxy codex auth device"
return 1
echo "Если браузер не открылся, попробуйте device flow: claude-code-proxy codex auth device"
exit 1
fi
fi
# Запускаем прокси в фоне (если ещё не запущен)
local proxy_pid="" wrapper_pid=""
local effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py"
proxy_pid=""
wrapper_pid=""
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
cleanup() {
[ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null
[ -n "$wrapper_pid" ] && kill "$wrapper_pid" 2>/dev/null
}
trap cleanup EXIT INT TERM
if pgrep -f "claude-code-proxy serve" &>/dev/null && ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then
pkill -f "claude-code-proxy serve" 2>/dev/null
sleep 0.5
fi
# Реальный прокси на 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
_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
_i=0; while [ $_i -lt 10 ]; do sleep 1; curl -sf --max-time 1 http://localhost:18765/ &>/dev/null; [ "$?" -ne 7 ] && break; # exit 7 = connection refused
_i=$((_i + 1)); done
fi
# Убиваем прокси и 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 "Очищаю недействительную авторизацию..."
if [ "$_CLAUDE_TEST_CODE" != "400" ]; then
_handle_api_response "ChatGPT" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" ""
ret=$?
if [ $ret -eq 401 ]; then
"$proxy_bin" codex auth logout 2>/dev/null
if _claude_offer_reauth "ChatGPT"; then
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 "Проверяю авторизацию после входа... "
"$proxy_bin" codex auth login || exit 1
_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
[ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; }
echo -e "\033[0;32mOK\033[0m"
else
return 1
exit 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
elif [ $ret -ne 0 ]; then
exit 1
fi
else
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
fi
# Сохраняем Anthropic credentials перед запуском — команда /logout внутри Claude Code
# удаляет их, хотя в режиме GPT они не нужны, но потом сломают оригинальный claude
local _creds_file="$HOME/.claude/.credentials.json"
local _creds_backup=""
_creds_file="$HOME/.claude/.credentials.json"
_creds_backup=""
[ -f "$_creds_file" ] && _creds_backup=$(cat "$_creds_file")
echo -e "\033[0;33m[ИНФО]\033[0m Режим ChatGPT. Для выхода: Ctrl+C или /exit"
@@ -400,133 +409,66 @@ claude_gpt() {
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 внутри сессии."
echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены."
fi
GPTEOF
chmod +x "$BIN_DIR/claude_gpt"
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
# === claude_deepseek ===
cat > "$BIN_DIR/claude_deepseek" << 'DEEPSEEKEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
# Read stored key
if [ -f "$key_file" ]; then
api_key=$(cat "$key_file")
fi
key_file="$HOME/.config/claude-launcher/deepseek_key"
api_key=""
reauth=0
[ -f "$key_file" ] && api_key=$(cat "$key_file")
# 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 "Удаляю недействительный ключ..."
_handle_api_response "DeepSeek" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.deepseek.com/top_up"
ret=$?
if [ $ret -eq 401 ]; then
rm -f "$key_file"
api_key=""
reauth=1
;;
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"
elif [ $ret -eq 429 ]; then
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
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
elif [ $ret -ne 0 ]; then
exit 1
fi
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
read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 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 ""
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
if [ -z "$api_key" ]; then
echo "Ключ не введён. Выход."
return 1
fi
echo "Проверяю ключ и баланс..."
echo -n "Проверяю ключ и баланс... "
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash"
case "$_CLAUDE_TEST_CODE" in
200)
_handle_api_response "DeepSeek" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.deepseek.com/top_up"
ret=$?
if [ $ret -eq 0 ]; then
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
echo "Ключ сохранён."
else
echo "Ключ НЕ сохранён."
exit 1
fi
fi
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
@@ -538,121 +480,60 @@ claude_deepseek() {
CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
}
DEEPSEEKEOF
chmod +x "$BIN_DIR/claude_deepseek"
# ── claude_kimi ─────────────────────────────────────────────
claude_kimi() {
local key_file="$HOME/.config/claude-launcher/kimi_key"
local api_key="" reauth=0
local _emsg
# === claude_kimi ===
cat > "$BIN_DIR/claude_kimi" << 'KIMIEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
# Read stored key
if [ -f "$key_file" ]; then
api_key=$(cat "$key_file")
fi
key_file="$HOME/.config/claude-launcher/kimi_key"
api_key=""
reauth=0
[ -f "$key_file" ] && api_key=$(cat "$key_file")
# 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 "Удаляю недействительный ключ..."
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.moonshot.ai/console/billing"
ret=$?
if [ $ret -eq 401 ]; then
rm -f "$key_file"
api_key=""
reauth=1
;;
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"
elif [ $ret -eq 429 ]; then
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
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
elif [ $ret -ne 0 ]; then
exit 1
fi
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
read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 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 ""
read -r -p "Введите ваш Kimi API ключ: " api_key
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
if [ -z "$api_key" ]; then
echo "Ключ не введён. Выход."
return 1
fi
echo "Проверяю ключ и баланс..."
echo -n "Проверяю ключ и баланс... "
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6"
case "$_CLAUDE_TEST_CODE" in
200)
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.moonshot.ai/console/billing"
ret=$?
if [ $ret -eq 0 ]; then
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
echo "Ключ сохранён."
else
echo "Ключ НЕ сохранён."
exit 1
fi
fi
ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \
@@ -664,191 +545,72 @@ claude_kimi() {
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
}
KIMIEOF
chmod +x "$BIN_DIR/claude_kimi"
# ── claude_gemini ─────────────────────────────────────────────
claude_gemini() {
local acc_cmd=""
# === claude_gemini ===
cat > "$BIN_DIR/claude_gemini" << 'GEMINIEOF'
#!/usr/bin/env bash
source ~/.local/bin/claude_api_helpers.sh
acc_cmd=""
command -v antigravity-claude-proxy &>/dev/null && acc_cmd="antigravity-claude-proxy"
command -v acc &>/dev/null && acc_cmd="acc"
if [ -z "$acc_cmd" ]; then
echo "Ошибка: antigravity-claude-proxy не найден. Перезапустите claude_setup.sh"
return 1
fi
[ -z "$acc_cmd" ] && { echo "Ошибка: antigravity-claude-proxy не найден."; exit 1; }
proxy_pid=""
cleanup() { [ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null; }
trap cleanup EXIT INT TERM
# Запускаем прокси в фоне если не запущен
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
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")
total_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len(json.load(sys.stdin).get('accounts', [])))" 2>/dev/null || echo "0")
invalid_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len([a for a in json.load(sys.stdin).get('accounts', []) if a.get('isInvalid')]))" 2>/dev/null || echo "0")
fi
if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then
echo ""
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"
_open_browser "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_TEST_CODE" != "400" ]; then
_handle_api_response "Gemini" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" ""
ret=$?
if [ $ret -ne 0 ]; then
if _claude_offer_reauth "Gemini"; then
xdg-open "http://localhost:8080" 2>/dev/null || \
sensible-browser "http://localhost:8080" 2>/dev/null || \
echo "Откройте http://localhost:8080"
echo "Нажмите Enter после авторизации..."
_open_browser "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
[ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; }
echo -e "\033[0;32mOK\033[0m"
else
return 1
exit 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
exit 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 \
@@ -859,47 +621,22 @@ except:
CLAUDE_CODE_SUBAGENT_MODEL=gemini-3.5-flash-low \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@"
GEMINIEOF
chmod +x "$BIN_DIR/claude_gemini"
if [ -n "$proxy_pid" ]; then
kill "$proxy_pid" 2>/dev/null
wait "$proxy_pid" 2>/dev/null
fi
}
success "Скрипты сгенерированы."
# === END CLAUDE LAUNCHER ===
BASHEOF
success "Функции добавлены в $BASHRC"
# ── 8. Итог ──────────────────────────────────────────────────
# ── 9. Итог ──────────────────────────────────────────────────
echo ""
echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Установка завершена!${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo ""
echo "Доступные команды:"
echo "Доступные команды (теперь это независимые скрипты в ~/.local/bin):"
echo -e " ${CYAN}claude_gpt${NC} — GPT-5.5 (ChatGPT Plus/Pro, браузерная авторизация)"
echo -e " ${CYAN}claude_deepseek${NC} — DeepSeek (API ключ сохраняется)"
echo -e " ${CYAN}claude_kimi${NC} — Kimi K2.6 (Moonshot AI, API ключ сохраняется)"
echo -e " ${CYAN}claude_gemini${NC} — Gemini (Google OAuth через браузер)"
echo ""
echo "Все команды принимают стандартные флаги:"
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 ""

View File

@@ -1,135 +0,0 @@
import os
import pty
import time
import subprocess
import signal
def run_test():
# Create pseudo-terminal
master, slave = pty.openpty()
# Start bash in interactive mode
p = subprocess.Popen(
['bash', '-i'],
stdin=slave,
stdout=slave,
stderr=slave,
close_fds=True,
preexec_fn=os.setsid # Create a new session/process group
)
# Close slave end in parent
os.close(slave)
# Set master to non-blocking
os.set_blocking(master, False)
# Helper to write to bash
def write_to_bash(cmd):
os.write(master, cmd.encode())
time.sleep(0.5)
# Define function in the interactive shell
func_def = """
my_func() {
sleep 100 &
local bg_pid=$!
echo "ACTUAL_BG_PID:$bg_pid"
sleep 10
echo "RUNNING CLEANUP"
kill $bg_pid 2>/dev/null
wait $bg_pid 2>/dev/null
echo "CLEANUP DONE"
}
"""
write_to_bash(func_def + "\n")
# Run the function
write_to_bash("my_func\n")
# Read output to wait for "ACTUAL_BG_PID:"
output = b""
start_time = time.time()
bg_pid = None
while time.time() - start_time < 5:
try:
r = os.read(master, 1024)
if r:
output += r
except BlockingIOError:
pass
if b"ACTUAL_BG_PID:" in output:
parts = output.split(b"ACTUAL_BG_PID:")
if len(parts) > 1:
# Get the part after ACTUAL_BG_PID:
rest = parts[-1].strip()
# Split by newline or carriage return or spaces
potential_pid = rest.split(b"\r")[0].split(b"\n")[0].split(b" ")[0].decode().strip()
if potential_pid.isdigit():
bg_pid = int(potential_pid)
break
time.sleep(0.1)
print("Found BG PID:", bg_pid)
print("Output so far:", output.decode('utf-8', errors='ignore'))
if bg_pid is None:
print("Error: Could not find BG PID.")
p.terminate()
return
# Wait a bit to ensure it is sleeping
time.sleep(1)
# In a terminal, Ctrl+C sends SIGINT to the foreground process group.
# The shell sets the foreground process group of the terminal to the active job.
# Let's get the foreground process group of the master terminal.
# We can use os.tcgetpgrp(master) to get the process group currently in the foreground!
try:
fore_pgid = os.tcgetpgrp(master)
print(f"Foreground process group of tty: {fore_pgid}")
# Send SIGINT to the foreground process group
os.killpg(fore_pgid, signal.SIGINT)
except Exception as e:
print("Failed to get foreground pgid via tcgetpgrp or killpg:", e)
# Fallback: kill the bash process group
pgid = os.getpgid(p.pid)
print(f"Fallback: sending SIGINT to bash process group {pgid}")
os.killpg(pgid, signal.SIGINT)
# Give it some time to process
time.sleep(2)
# Let's read the remaining output to see if "RUNNING CLEANUP" was printed
try:
time.sleep(1)
remaining = b""
start_time = time.time()
while time.time() - start_time < 2:
try:
r = os.read(master, 4096)
if r:
remaining += r
except BlockingIOError:
pass
time.sleep(0.1)
print("Remaining output:", remaining.decode('utf-8', errors='ignore'))
except Exception as e:
print("Read error or no more output:", e)
# Check if bg_pid is still running
try:
os.kill(bg_pid, 0)
print(f"VERDICT: Process {bg_pid} is STILL RUNNING! It was orphaned!")
# Clean it up
os.kill(bg_pid, signal.SIGKILL)
except OSError:
print(f"VERDICT: Process {bg_pid} is NOT running. Cleanup worked!")
# Clean up bash
p.terminate()
p.wait()
if __name__ == '__main__':
run_test()

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
test_func() {
echo "Starting background process..."
sleep 100 &
local pid=$!
echo "Background PID: $pid"
echo "Starting foreground sleep (simulate claude)..."
sleep 10
echo "Foreground sleep finished. Cleaning up background process $pid..."
kill "$pid"
wait "$pid" 2>/dev/null
echo "Cleanup done."
}
test_func

View File

@@ -1,7 +1,6 @@
#!/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
@@ -11,56 +10,28 @@ PASS=0; FAIL=0
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); }
# ── helpers ──────────────────────────────────────────────────────────────────
# Extract sections
GPT_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_gpt"/,/^GPTEOF/' "$SCRIPT")
GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_gemini"/,/^GEMINIEOF/' "$SCRIPT")
# 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'"
# ── Fix 2: trap EXIT kills proxy ──────────────────────────────────────────────
test_fix2_trap_exit() {
if echo "$GPT_SECTION" | grep -q "trap .* EXIT"; then
ok "Fix2: trap EXIT for proxy cleanup present in claude_gpt"
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"
fail "Fix2: trap EXIT 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
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
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"
@@ -69,15 +40,14 @@ test_fix3_readiness_loop() {
# ── 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
if echo "$GPT_SECTION" | grep -q 'exit 7 = connection refused'; 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"
if echo "$GPT_SECTION" | grep -q '\[ "\$?" -ne 7 \]'; then
ok "Fix3b: [ \$? -ne 7 ] break condition present"
else
fail "Fix3b: exit-7 break condition missing"
fi
@@ -85,27 +55,51 @@ test_fix3b_exit7_logic() {
# ── 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"
if echo "$GPT_SECTION" | grep -q '_claude_test_api.*http://localhost:18765'; then
ok "Fix4: _claude_test_api called in claude_gpt"
else
fail "Fix4: re-validate after codex auth login missing in claude_gpt"
fail "Fix4: _claude_test_api 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)
count=$(echo "$GEMINI_SECTION" | grep -c '_claude_test_api' || true)
if [ "$count" -ge 2 ]; then
ok "Fix5: re-validate after gemini reauth present in both 401/403 and 429 branches ($count occurrences)"
ok "Fix5: _claude_test_api present in gemini reauth flow ($count occurrences)"
else
fail "Fix5: re-validate after gemini reauth missing or only in one branch (found $count)"
fail "Fix5: _claude_test_api missing or only in one branch (found $count)"
fi
}
# ── Fix 7: trap quotes $TMP correctly ────────────────────────────────────────
test_fix7_trap_tmp() {
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
}
# ── 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_fix2_trap_exit
test_fix3_readiness_loop
test_fix3b_exit7_logic
test_fix4_gpt_revalidate
test_fix5_gemini_revalidate
test_fix7_trap_tmp
echo ""
echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1