From 61c21fe296f78c68cc890ba5bf5ab3b34f7d72b8 Mon Sep 17 00:00:00 2001 From: vitaly Date: Sun, 31 May 2026 12:55:54 +0700 Subject: [PATCH] Refactor architecture to use standalone scripts in ~/.local/bin/ instead of ~/.bashrc --- claude_setup.sh | 1035 ++++++++++++++++--------------------------- test_interactive.py | 135 ------ test_sigint.sh | 18 - tests/test_fixes.sh | 104 ++--- 4 files changed, 435 insertions(+), 857 deletions(-) delete mode 100644 test_interactive.py delete mode 100644 test_sigint.sh diff --git a/claude_setup.sh b/claude_setup.sh index f5b1ad0..835b0a9 100755 --- a/claude_setup.sh +++ b/claude_setup.sh @@ -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)..." - curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - - sudo apt-get install -y nodejs + 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 -success "Node.js $(node --version)" # ── 3. Claude Code ─────────────────────────────────────────── info "Проверяю Claude Code..." if ! command -v claude &>/dev/null; then info "Устанавливаю Claude Code..." - # Пробуем официальный инсталлер if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then success "Claude Code установлен (официальный инсталлер)" else - # Fallback: npm в наш prefix (без sudo) npm install -g @anthropic-ai/claude-code success "Claude Code установлен (npm)" fi @@ -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 (патч xhigh→max для 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 -fi + 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 [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,654 +266,377 @@ _claude_offer_reauth() { esac } -# ── claude_gpt ──────────────────────────────────────────────── -claude_gpt() { - local proxy_bin="$HOME/.local/bin/claude-code-proxy" - - if [ ! -f "$proxy_bin" ]; then - echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh" - return 1 - fi - - # Проверяем авторизацию - if ! "$proxy_bin" codex auth status &>/dev/null; then - echo "" - echo "Авторизация ChatGPT не найдена." - echo "Сейчас появится ссылка или откроется браузер..." - echo "" - if ! "$proxy_bin" codex auth login 2>&1; then - echo "" - echo "Если браузер не открылся, попробуйте device flow:" - echo " claude-code-proxy codex auth device" - return 1 - fi - fi - - # Запускаем прокси в фоне (если ещё не запущен) - local proxy_pid="" wrapper_pid="" - local effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py" - - # Если прокси занял порт 18765 (старый формат без wrapper) — мигрируем - if pgrep -f "claude-code-proxy serve" &>/dev/null && \ - ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then - pkill -f "claude-code-proxy serve" 2>/dev/null - sleep 0.5 - fi - - # Реальный прокси на 18766 - if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then - PORT=18766 "$proxy_bin" serve &>/tmp/claude-code-proxy.log & - proxy_pid=$! - local _j=0 - while [ $_j -lt 10 ]; do - sleep 1 - curl -sf --max-time 1 http://localhost:18766/ &>/dev/null - [ "$?" -ne 7 ] && break - _j=$((_j + 1)) - done - fi - - # Wrapper (xhigh→max патч) на 18765 - if ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then - python3 "$effort_proxy" 18766 18765 &>/tmp/claude-gpt-effort-proxy.log & - wrapper_pid=$! - local _i=0 - while [ $_i -lt 10 ]; do - sleep 1 - curl -sf --max-time 1 http://localhost:18765/ &>/dev/null; local _ce=$? - [ "$_ce" -ne 7 ] && break # exit 7 = connection refused; любой другой = wrapper слушает - _i=$((_i + 1)) - done - fi - - # Убиваем прокси и wrapper при любом выходе из функции - trap '[ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null; [ -n "$wrapper_pid" ] && kill "$wrapper_pid" 2>/dev/null' RETURN - - # ── Pre-launch API validation through proxy ── - echo -n "Проверка авторизации ChatGPT... " - _claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini" - +_handle_api_response() { + local provider="$1" + local code="$2" + local body="$3" + local topup_url="$4" + local _emsg - case "$_CLAUDE_TEST_CODE" in + case "$code" in 200) echo -e "\033[0;32mOK\033[0m" + return 0 ;; 401|403) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") + _emsg=$(_claude_extract_error "$body") echo "" - echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Авторизация ChatGPT недействительна (HTTP $_CLAUDE_TEST_CODE)." + echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Авторизация $provider недействительна (HTTP $code)." [ -n "$_emsg" ] && echo " $_emsg" - echo "" - echo "Очищаю недействительную авторизацию..." - "$proxy_bin" codex auth logout 2>/dev/null - if _claude_offer_reauth "ChatGPT"; then - echo "Запускаю повторную авторизацию..." - if ! "$proxy_bin" codex auth login 2>&1; then - echo "" - echo "Если браузер не открылся, попробуйте device flow:" - echo " claude-code-proxy codex auth device" - return 1 - fi - echo -n "Проверяю авторизацию после входа... " - _claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini" - if [ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ]; then - echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m" - return 1 - fi - echo -e "\033[0;32mOK\033[0m" - else - return 1 - fi + return 401 ;; 429) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") + _emsg=$(_claude_extract_error "$body") echo "" - echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Лимит ChatGPT исчерпан." + echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Баланс/лимит $provider исчерпан." [ -n "$_emsg" ] && echo " $_emsg" - return 1 - ;; - 400) - # Прокси работает — 400 означает только, что тест-запрос не содержит обязательное поле «instructions» - # Авторизация уже проверена через codex auth status; запускаем Claude - echo -e "\033[0;32mOK\033[0m" + [ -n "$topup_url" ] && echo " $topup_url" + return 429 ;; 000) echo "" - echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ChatGPT прокси (нет сети?). Продолжаю..." + echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ключ (нет сети?). Продолжаю..." + return 0 ;; *) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") + _emsg=$(_claude_extract_error "$body") echo "" - echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси вернул неожиданный HTTP $_CLAUDE_TEST_CODE." + echo -e "\033[0;31m[ОШИБКА]\033[0m API $provider вернул HTTP $code." [ -n "$_emsg" ] && echo " $_emsg" - echo "" - echo "Попробуйте:" - echo " • Перезапустить claude_gpt" - echo " • Переавторизоваться: claude-code-proxy codex auth logout && claude-code-proxy codex auth login" return 1 ;; esac +} - # Сохраняем Anthropic credentials перед запуском — команда /logout внутри Claude Code - # удаляет их, хотя в режиме GPT они не нужны, но потом сломают оригинальный claude - local _creds_file="$HOME/.claude/.credentials.json" - local _creds_backup="" - [ -f "$_creds_file" ] && _creds_backup=$(cat "$_creds_file") +_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" - echo -e "\033[0;33m[ИНФО]\033[0m Режим ChatGPT. Для выхода: Ctrl+C или /exit" - echo -e " Команда \033[1m/logout\033[0m выйдет из Anthropic, а не из ChatGPT." +# === claude_gpt === +cat > "$BIN_DIR/claude_gpt" << 'GPTEOF' +#!/usr/bin/env bash +source ~/.local/bin/claude_api_helpers.sh + +proxy_bin="$HOME/.local/bin/claude-code-proxy" +if [ ! -f "$proxy_bin" ]; then + echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh" + exit 1 +fi + +if ! "$proxy_bin" codex auth status &>/dev/null; then echo "" - - ANTHROPIC_BASE_URL=http://localhost:18765 \ - ANTHROPIC_AUTH_TOKEN=dummy \ - ANTHROPIC_MODEL=gpt-5.5 \ - ANTHROPIC_DEFAULT_OPUS_MODEL=gpt-5.5 \ - ANTHROPIC_DEFAULT_SONNET_MODEL=gpt-5.5 \ - ANTHROPIC_DEFAULT_HAIKU_MODEL=gpt-5.4-mini \ - CLAUDE_CODE_SUBAGENT_MODEL=gpt-5.4-mini \ - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ - claude "$@" - - # Восстанавливаем credentials если /logout их удалил - if [ -n "$_creds_backup" ] && [ ! -f "$_creds_file" ]; then - mkdir -p "$(dirname "$_creds_file")" - echo "$_creds_backup" > "$_creds_file" - echo "" - echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены после /logout внутри сессии." + echo "Авторизация ChatGPT не найдена." + echo "Сейчас появится ссылка или откроется браузер..." + echo "" + if ! "$proxy_bin" codex auth login 2>&1; then + echo "Если браузер не открылся, попробуйте device flow: claude-code-proxy codex auth device" + exit 1 fi +fi - if [ -n "$proxy_pid" ]; then - kill "$proxy_pid" 2>/dev/null - wait "$proxy_pid" 2>/dev/null - fi +proxy_pid="" +wrapper_pid="" +effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py" + +cleanup() { + [ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null + [ -n "$wrapper_pid" ] && kill "$wrapper_pid" 2>/dev/null } +trap cleanup EXIT INT TERM -# ── claude_deepseek ─────────────────────────────────────────── -claude_deepseek() { - local key_file="$HOME/.config/claude-launcher/deepseek_key" - local api_key="" reauth=0 - local _emsg +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 - # Read stored key - if [ -f "$key_file" ]; then - api_key=$(cat "$key_file") - fi +if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then + PORT=18766 "$proxy_bin" serve &>/tmp/claude-code-proxy.log & + proxy_pid=$! + _j=0; while [ $_j -lt 10 ]; do sleep 1; curl -sf --max-time 1 http://localhost:18766/ &>/dev/null; [ "$?" -ne 7 ] && break; _j=$((_j + 1)); done +fi - # 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" +if ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then + python3 "$effort_proxy" 18766 18765 &>/tmp/claude-gpt-effort-proxy.log & + wrapper_pid=$! + _i=0; while [ $_i -lt 10 ]; do sleep 1; curl -sf --max-time 1 http://localhost:18765/ &>/dev/null; [ "$?" -ne 7 ] && break; # exit 7 = connection refused + _i=$((_i + 1)); done +fi - 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 +echo -n "Проверка авторизации ChatGPT... " +_claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini" - # Re-prompt if key was invalidated - if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then - echo "" - echo -n "Хотите ввести новый DeepSeek ключ? [Y/n] " - local _ans; read -r _ans - case "${_ans:-Y}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac - fi - - # Prompt for key if missing - if [ -z "$api_key" ]; then - echo "" - echo "DeepSeek API ключ не найден." - echo "Получить ключ: https://platform.deepseek.com/api_keys" - echo "" - read -r -p "Введите ваш DeepSeek API ключ: " api_key - echo "" - - if [ -z "$api_key" ]; then - echo "Ключ не введён. Выход." - return 1 - fi - - echo "Проверяю ключ и баланс..." - _claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash" - - case "$_CLAUDE_TEST_CODE" in - 200) - mkdir -p "$(dirname "$key_file")" - echo "$api_key" > "$key_file" - chmod 600 "$key_file" - echo -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m" - ;; - 000) - echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..." - mkdir -p "$(dirname "$key_file")" - echo "$api_key" > "$key_file" - chmod 600 "$key_file" - ;; - 429) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован." - [ -n "$_emsg" ] && echo " Причина: $_emsg" - echo " Пополните баланс: https://platform.deepseek.com/top_up" - echo " Ключ НЕ сохранён — сначала пополните счёт." - return 1 - ;; - 401|403) - echo -e "\033[0;31mКлюч недействителен (HTTP $_CLAUDE_TEST_CODE).\033[0m Ключ не сохранён." - return 1 - ;; - *) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - [ -z "$_emsg" ] && _emsg="HTTP $_CLAUDE_TEST_CODE" - echo "Ошибка API: $_emsg" - echo "Ключ не сохранён." - return 1 - ;; - esac - fi - - ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \ - ANTHROPIC_AUTH_TOKEN="$api_key" \ - ANTHROPIC_MODEL=deepseek-v4-pro \ - ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-v4-pro \ - ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro \ - ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash \ - CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \ - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ - claude "$@" -} - -# ── claude_kimi ───────────────────────────────────────────── -claude_kimi() { - local key_file="$HOME/.config/claude-launcher/kimi_key" - local api_key="" reauth=0 - local _emsg - - # Read stored key - if [ -f "$key_file" ]; then - api_key=$(cat "$key_file") - fi - - # Validate stored key if present - if [ -n "$api_key" ]; then - echo -n "Проверка сохранённого Kimi ключа... " - _claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6" - - case "$_CLAUDE_TEST_CODE" in - 200) - echo -e "\033[0;32mOK\033[0m" - ;; - 401|403) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Сохранённый ключ недействителен (HTTP $_CLAUDE_TEST_CODE)." - [ -n "$_emsg" ] && echo " $_emsg" - echo "Удаляю недействительный ключ..." - rm -f "$key_file" - api_key="" - reauth=1 - ;; - 429) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Баланс Kimi пуст или превышен лимит." - [ -n "$_emsg" ] && echo " $_emsg" - echo " Пополните баланс: https://platform.moonshot.ai/console/billing" - echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " - local _ans; read -r _ans - case "${_ans:-N}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac - ;; - 000) - echo "" - echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ключ (нет сети?). Продолжаю..." - ;; - *) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;31m[ОШИБКА]\033[0m API вернул HTTP $_CLAUDE_TEST_CODE" - [ -n "$_emsg" ] && echo " $_emsg" - return 1 - ;; - esac - fi - - # Re-prompt if key was invalidated - if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then - echo "" - echo -n "Хотите ввести новый Kimi ключ? [Y/n] " - local _ans; read -r _ans - case "${_ans:-Y}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac - fi - - # Prompt for key if missing - if [ -z "$api_key" ]; then - echo "" - echo "Kimi (Moonshot AI) API ключ не найден." - echo "Получить ключ: https://platform.moonshot.ai/console/api-keys" - echo "" - read -r -p "Введите ваш Kimi API ключ (начинается с 'sk-'): " api_key - echo "" - - if [ -z "$api_key" ]; then - echo "Ключ не введён. Выход." - return 1 - fi - - echo "Проверяю ключ и баланс..." - _claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6" - - case "$_CLAUDE_TEST_CODE" in - 200) - mkdir -p "$(dirname "$key_file")" - echo "$api_key" > "$key_file" - chmod 600 "$key_file" - echo -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m" - ;; - 000) - echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..." - mkdir -p "$(dirname "$key_file")" - echo "$api_key" > "$key_file" - chmod 600 "$key_file" - ;; - 429) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;31m[ОШИБКА]\033[0m Ключ действителен, но аккаунт заблокирован." - [ -n "$_emsg" ] && echo " Причина: $_emsg" - echo " Пополните баланс: https://platform.moonshot.ai/console/billing" - echo " Ключ НЕ сохранён — сначала пополните счёт." - return 1 - ;; - 401|403) - echo -e "\033[0;31mКлюч недействителен (HTTP $_CLAUDE_TEST_CODE).\033[0m Ключ не сохранён." - return 1 - ;; - *) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - [ -z "$_emsg" ] && _emsg="HTTP $_CLAUDE_TEST_CODE" - echo "Ошибка API: $_emsg" - echo "Ключ не сохранён." - return 1 - ;; - esac - fi - - ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \ - ANTHROPIC_AUTH_TOKEN="$api_key" \ - ANTHROPIC_MODEL=kimi-k2.6 \ - ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \ - ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \ - ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \ - CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \ - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ - claude "$@" -} - -# ── claude_gemini ───────────────────────────────────────────── -claude_gemini() { - local acc_cmd="" - command -v antigravity-claude-proxy &>/dev/null && acc_cmd="antigravity-claude-proxy" - command -v acc &>/dev/null && acc_cmd="acc" - - if [ -z "$acc_cmd" ]; then - echo "Ошибка: antigravity-claude-proxy не найден. Перезапустите claude_setup.sh" - return 1 - fi - - # Запускаем прокси в фоне если не запущен - local proxy_pid="" - if ! curl -sf http://localhost:8080/health &>/dev/null; then - "$acc_cmd" start &>/tmp/antigravity-proxy.log & - proxy_pid=$! - echo "Запускаю Gemini прокси..." - local i=0 - while [ $i -lt 15 ]; do - sleep 1 - curl -sf http://localhost:8080/health &>/dev/null && break - i=$((i+1)) - done - fi - - # ── Проверка аккаунтов ── - local has_auth total_count invalid_count - has_auth=$(curl -sf "http://localhost:8080/account-limits" 2>/dev/null || echo "") - - if [ -n "$has_auth" ]; then - total_count=$(echo "$has_auth" | python3 -c " -import sys, json -try: - d = json.load(sys.stdin) - print(len(d.get('accounts', []))) -except: - print(0) -" 2>/dev/null || echo "0") - invalid_count=$(echo "$has_auth" | python3 -c " -import sys, json -try: - d = json.load(sys.stdin) - accounts = d.get('accounts', []) - invalid = [a for a in accounts if a.get('isInvalid')] - print(len(invalid)) -except: - print(0) -" 2>/dev/null || echo "0") - fi - - if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then - echo "" - echo "Google-аккаунт не найден." - echo "" - echo -e "\033[1;33m⚠️ ВНИМАНИЕ: Используйте ОТДЕЛЬНЫЙ Google-аккаунт!\033[0m" - echo " Google может заблокировать аккаунты, использующие этот прокси." - echo "" - echo "Открываю http://localhost:8080 в браузере..." - echo "Перейдите: Accounts → Add Account → войдите через Google." - echo "" - xdg-open "http://localhost:8080" 2>/dev/null || \ - sensible-browser "http://localhost:8080" 2>/dev/null || \ - echo "Откройте вручную: http://localhost:8080" - - echo "Нажмите Enter после завершения авторизации в браузере..." - read -r - elif [ "$invalid_count" -gt 0 ]; then - echo "" - echo -e "\033[0;33m[ПРЕДУПРЕЖДЕНИЕ]\033[0m Обнаружены проблемные аккаунты ($invalid_count из $total_count)." - echo "Проверьте статус: http://localhost:8080" - fi - - # ── Pre-launch API test through proxy ── - echo -n "Проверка доступа к Gemini API... " - _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" - - local _emsg - case "$_CLAUDE_TEST_CODE" in - 200) +if [ "$_CLAUDE_TEST_CODE" != "400" ]; then + _handle_api_response "ChatGPT" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "" + ret=$? + if [ $ret -eq 401 ]; then + "$proxy_bin" codex auth logout 2>/dev/null + if _claude_offer_reauth "ChatGPT"; then + "$proxy_bin" codex auth login || exit 1 + _claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini" + [ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; } echo -e "\033[0;32mOK\033[0m" - ;; - 401|403) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;31m[ОШИБКА АВТОРИЗАЦИИ]\033[0m Gemini аккаунты не авторизованы (HTTP $_CLAUDE_TEST_CODE)." - [ -n "$_emsg" ] && echo " $_emsg" - echo "Попробуйте переавторизоваться через http://localhost:8080" - if _claude_offer_reauth "Gemini"; then - xdg-open "http://localhost:8080" 2>/dev/null || \ - sensible-browser "http://localhost:8080" 2>/dev/null || \ - echo "Откройте http://localhost:8080" - echo "Нажмите Enter после авторизации..." - read -r - echo -n "Проверяю авторизацию Gemini... " - _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" - if [ "$_CLAUDE_TEST_CODE" != "200" ]; then - echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m" - return 1 - fi - echo -e "\033[0;32mOK\033[0m" - else - return 1 - fi - ;; - 429) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Все Gemini аккаунты исчерпали лимит." - [ -n "$_emsg" ] && echo " $_emsg" - echo "Подождите сброса лимитов или добавьте новый аккаунт." - if _claude_offer_reauth "Gemini (добавить аккаунт)"; then - xdg-open "http://localhost:8080" 2>/dev/null || \ - sensible-browser "http://localhost:8080" 2>/dev/null || \ - echo "Откройте http://localhost:8080" - echo "Нажмите Enter после добавления..." - read -r - echo -n "Проверяю авторизацию Gemini... " - _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" - if [ "$_CLAUDE_TEST_CODE" != "200" ]; then - echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m" - return 1 - fi - echo -e "\033[0;32mOK\033[0m" - else - return 1 - fi - ;; - 400) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - if echo "$_emsg" | grep -q "RESOURCE_EXHAUSTED"; then - local _reset - _reset=$(echo "$_emsg" | sed -n 's/.*reset after \([^.]*\).*/\1/p' 2>/dev/null || true) - echo -e "\033[0;33m[КВОТА ИСЧЕРПАНА]\033[0m Все Gemini аккаунты исчерпали лимит запросов." - [ -n "$_reset" ] && echo " Квота обновится через: $_reset" - echo "" - echo "Что делать:" - echo " • Подождите сброса квоты и повторите попытку" - echo " • Или добавьте новый аккаунт Google через http://localhost:8080" - if _claude_offer_reauth "Gemini (добавить аккаунт)"; then - xdg-open "http://localhost:8080" 2>/dev/null || \ - sensible-browser "http://localhost:8080" 2>/dev/null || \ - echo "Откройте http://localhost:8080" - echo "Нажмите Enter после добавления..." - read -r - echo -n "Проверяю авторизацию Gemini... " - _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" - if [ "$_CLAUDE_TEST_CODE" != "200" ]; then - echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m" - return 1 - fi - echo -e "\033[0;32mOK\033[0m" - else - return 1 - fi - else - echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси Gemini вернул HTTP 400." - [ -n "$_emsg" ] && echo " $_emsg" - echo "" - echo "Попробуйте:" - echo " • Перезапустить прокси: перезапустите claude_gemini" - echo " • Проверить статус аккаунтов: http://localhost:8080" - return 1 - fi - ;; - 000) - echo "" - echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить Gemini прокси (нет сети?). Продолжаю..." - ;; - *) - _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") - echo "" - echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси вернул неожиданный HTTP $_CLAUDE_TEST_CODE." - [ -n "$_emsg" ] && echo " $_emsg" - echo "" - echo "Попробуйте:" - echo " • Перезапустить claude_gemini" - echo " • Проверить статус аккаунтов: http://localhost:8080" - return 1 - ;; - esac - - echo "Запускаю Claude Code с Gemini..." - - ANTHROPIC_BASE_URL=http://localhost:8080 \ - ANTHROPIC_AUTH_TOKEN=dummy \ - ANTHROPIC_MODEL=gemini-pro-agent \ - ANTHROPIC_DEFAULT_OPUS_MODEL=gemini-pro-agent \ - ANTHROPIC_DEFAULT_SONNET_MODEL=gemini-3-flash-agent \ - ANTHROPIC_DEFAULT_HAIKU_MODEL=gemini-3.5-flash-low \ - CLAUDE_CODE_SUBAGENT_MODEL=gemini-3.5-flash-low \ - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ - claude "$@" - - if [ -n "$proxy_pid" ]; then - kill "$proxy_pid" 2>/dev/null - wait "$proxy_pid" 2>/dev/null + else + exit 1 + fi + elif [ $ret -ne 0 ]; then + exit 1 fi -} +else + echo -e "\033[0;32mOK\033[0m" +fi -# === END CLAUDE LAUNCHER === -BASHEOF +_creds_file="$HOME/.claude/.credentials.json" +_creds_backup="" +[ -f "$_creds_file" ] && _creds_backup=$(cat "$_creds_file") -success "Функции добавлены в $BASHRC" +echo -e "\033[0;33m[ИНФО]\033[0m Режим ChatGPT. Для выхода: Ctrl+C или /exit" +echo -e " Команда \033[1m/logout\033[0m выйдет из Anthropic, а не из ChatGPT." +echo "" -# ── 8. Итог ────────────────────────────────────────────────── +ANTHROPIC_BASE_URL=http://localhost:18765 \ +ANTHROPIC_AUTH_TOKEN=dummy \ +ANTHROPIC_MODEL=gpt-5.5 \ +ANTHROPIC_DEFAULT_OPUS_MODEL=gpt-5.5 \ +ANTHROPIC_DEFAULT_SONNET_MODEL=gpt-5.5 \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=gpt-5.4-mini \ +CLAUDE_CODE_SUBAGENT_MODEL=gpt-5.4-mini \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude "$@" + +if [ -n "$_creds_backup" ] && [ ! -f "$_creds_file" ]; then + mkdir -p "$(dirname "$_creds_file")" + echo "$_creds_backup" > "$_creds_file" + echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены." +fi +GPTEOF +chmod +x "$BIN_DIR/claude_gpt" + + +# === claude_deepseek === +cat > "$BIN_DIR/claude_deepseek" << 'DEEPSEEKEOF' +#!/usr/bin/env bash +source ~/.local/bin/claude_api_helpers.sh + +key_file="$HOME/.config/claude-launcher/deepseek_key" +api_key="" +reauth=0 + +[ -f "$key_file" ] && api_key=$(cat "$key_file") + +if [ -n "$api_key" ]; then + echo -n "Проверка сохранённого DeepSeek ключа... " + _claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash" + _handle_api_response "DeepSeek" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.deepseek.com/top_up" + ret=$? + if [ $ret -eq 401 ]; then + rm -f "$key_file" + api_key="" + reauth=1 + elif [ $ret -eq 429 ]; then + echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " + read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac + elif [ $ret -ne 0 ]; then + exit 1 + fi +fi + +if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then + echo -n "Хотите ввести новый DeepSeek ключ? [Y/n] " + read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac +fi + +if [ -z "$api_key" ]; then + echo "Получить ключ: https://platform.deepseek.com/api_keys" + read -r -p "Введите ваш DeepSeek API ключ: " api_key + [ -z "$api_key" ] && { echo "Выход."; exit 1; } + + echo -n "Проверяю ключ и баланс... " + _claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash" + _handle_api_response "DeepSeek" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.deepseek.com/top_up" + ret=$? + if [ $ret -eq 0 ]; then + mkdir -p "$(dirname "$key_file")" + echo "$api_key" > "$key_file" + chmod 600 "$key_file" + echo "Ключ сохранён." + else + echo "Ключ НЕ сохранён." + exit 1 + fi +fi + +ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \ +ANTHROPIC_AUTH_TOKEN="$api_key" \ +ANTHROPIC_MODEL=deepseek-v4-pro \ +ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-v4-pro \ +ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash \ +CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude "$@" +DEEPSEEKEOF +chmod +x "$BIN_DIR/claude_deepseek" + +# === claude_kimi === +cat > "$BIN_DIR/claude_kimi" << 'KIMIEOF' +#!/usr/bin/env bash +source ~/.local/bin/claude_api_helpers.sh + +key_file="$HOME/.config/claude-launcher/kimi_key" +api_key="" +reauth=0 + +[ -f "$key_file" ] && api_key=$(cat "$key_file") + +if [ -n "$api_key" ]; then + echo -n "Проверка сохранённого Kimi ключа... " + _claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6" + _handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.moonshot.ai/console/billing" + ret=$? + if [ $ret -eq 401 ]; then + rm -f "$key_file" + api_key="" + reauth=1 + elif [ $ret -eq 429 ]; then + echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " + read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac + elif [ $ret -ne 0 ]; then + exit 1 + fi +fi + +if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then + echo -n "Хотите ввести новый Kimi ключ? [Y/n] " + read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac +fi + +if [ -z "$api_key" ]; then + echo "Получить ключ: https://platform.moonshot.ai/console/api-keys" + read -r -p "Введите ваш Kimi API ключ: " api_key + [ -z "$api_key" ] && { echo "Выход."; exit 1; } + + echo -n "Проверяю ключ и баланс... " + _claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6" + _handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://platform.moonshot.ai/console/billing" + ret=$? + if [ $ret -eq 0 ]; then + mkdir -p "$(dirname "$key_file")" + echo "$api_key" > "$key_file" + chmod 600 "$key_file" + echo "Ключ сохранён." + else + echo "Ключ НЕ сохранён." + exit 1 + fi +fi + +ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \ +ANTHROPIC_AUTH_TOKEN="$api_key" \ +ANTHROPIC_MODEL=kimi-k2.6 \ +ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \ +ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \ +CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude "$@" +KIMIEOF +chmod +x "$BIN_DIR/claude_kimi" + +# === claude_gemini === +cat > "$BIN_DIR/claude_gemini" << 'GEMINIEOF' +#!/usr/bin/env bash +source ~/.local/bin/claude_api_helpers.sh + +acc_cmd="" +command -v antigravity-claude-proxy &>/dev/null && acc_cmd="antigravity-claude-proxy" +command -v acc &>/dev/null && acc_cmd="acc" + +[ -z "$acc_cmd" ] && { echo "Ошибка: antigravity-claude-proxy не найден."; exit 1; } + +proxy_pid="" +cleanup() { [ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null; } +trap cleanup EXIT INT TERM + +if ! curl -sf http://localhost:8080/health &>/dev/null; then + "$acc_cmd" start &>/tmp/antigravity-proxy.log & + proxy_pid=$! + echo "Запускаю Gemini прокси..." + i=0; while [ $i -lt 15 ]; do sleep 1; curl -sf http://localhost:8080/health &>/dev/null && break; i=$((i+1)); done +fi + +has_auth=$(curl -sf "http://localhost:8080/account-limits" 2>/dev/null || echo "") +if [ -n "$has_auth" ]; then + total_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len(json.load(sys.stdin).get('accounts', [])))" 2>/dev/null || echo "0") + invalid_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len([a for a in json.load(sys.stdin).get('accounts', []) if a.get('isInvalid')]))" 2>/dev/null || echo "0") +fi + +if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then + echo -e "\033[1;33m⚠️ ВНИМАНИЕ: Используйте ОТДЕЛЬНЫЙ Google-аккаунт!\033[0m" + _open_browser "http://localhost:8080" + echo "Нажмите Enter после завершения авторизации в браузере..." + read -r +elif [ "$invalid_count" -gt 0 ]; then + echo -e "\033[0;33m[ПРЕДУПРЕЖДЕНИЕ]\033[0m Обнаружены проблемные аккаунты ($invalid_count из $total_count)." +fi + +echo -n "Проверка доступа к Gemini API... " +_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" + +if [ "$_CLAUDE_TEST_CODE" != "400" ]; then + _handle_api_response "Gemini" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "" + ret=$? + if [ $ret -ne 0 ]; then + if _claude_offer_reauth "Gemini"; then + _open_browser "http://localhost:8080" + echo "Нажмите Enter после авторизации/добавления..." + read -r + _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" + [ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; } + echo -e "\033[0;32mOK\033[0m" + else + exit 1 + fi + fi +else + _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") + if echo "$_emsg" | grep -q "RESOURCE_EXHAUSTED"; then + echo -e "\033[0;33m[КВОТА ИСЧЕРПАНА]\033[0m Все Gemini аккаунты исчерпали лимит запросов." + exit 1 + fi + echo -e "\033[0;32mOK\033[0m" +fi + +ANTHROPIC_BASE_URL=http://localhost:8080 \ +ANTHROPIC_AUTH_TOKEN=dummy \ +ANTHROPIC_MODEL=gemini-pro-agent \ +ANTHROPIC_DEFAULT_OPUS_MODEL=gemini-pro-agent \ +ANTHROPIC_DEFAULT_SONNET_MODEL=gemini-3-flash-agent \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=gemini-3.5-flash-low \ +CLAUDE_CODE_SUBAGENT_MODEL=gemini-3.5-flash-low \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude "$@" +GEMINIEOF +chmod +x "$BIN_DIR/claude_gemini" + +success "Скрипты сгенерированы." + +# ── 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 "" diff --git a/test_interactive.py b/test_interactive.py deleted file mode 100644 index cf229a5..0000000 --- a/test_interactive.py +++ /dev/null @@ -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() diff --git a/test_sigint.sh b/test_sigint.sh deleted file mode 100644 index 430c805..0000000 --- a/test_sigint.sh +++ /dev/null @@ -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 diff --git a/tests/test_fixes.sh b/tests/test_fixes.sh index d2be17e..b42835b 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -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