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 #!/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 # Запуск: bash claude_setup.sh
# ============================================================ # ============================================================
BASHRC="$HOME/.bashrc"
CONFIG_DIR="$HOME/.config/claude-launcher" CONFIG_DIR="$HOME/.config/claude-launcher"
DEEPSEEK_KEY_FILE="$CONFIG_DIR/deepseek_key" BIN_DIR="$HOME/.local/bin"
NPM_GLOBAL="$HOME/.npm-global" 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' 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} $*"; } info() { echo -e "${CYAN}[INFO]${NC} $*"; }
@@ -23,46 +22,46 @@ if [ "$EUID" -eq 0 ]; then
exit 1 exit 1
fi fi
info "Проверяю зависимости (python3)..."
if ! command -v python3 &>/dev/null; then
err "Python 3 не установлен, но он требуется для работы скрипта."
fi
success "Python 3 найден"
# ── 1. npm prefix в домашнюю папку ────────────────────────── # ── 1. npm prefix в домашнюю папку ──────────────────────────
info "Настраиваю npm prefix..." info "Настраиваю npm prefix..."
mkdir -p "$NPM_GLOBAL" mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL" npm config set prefix "$NPM_GLOBAL"
success "npm prefix -> $NPM_GLOBAL" success "npm prefix -> $NPM_GLOBAL"
# Добавляем npm-global в PATH прямо сейчас (для этого запуска скрипта) export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH"
export PATH="$NPM_GLOBAL/bin:$HOME/.local/bin:$PATH"
# Прописываем в .bashrc если ещё нет
if ! grep -q 'NPM_GLOBAL' "$BASHRC" 2>/dev/null; then
cat >> "$BASHRC" << 'PATHEOF'
# npm global без sudo
export NPM_GLOBAL="$HOME/.npm-global"
export PATH="$NPM_GLOBAL/bin:$HOME/.local/bin:$PATH"
PATHEOF
success "npm PATH добавлен в $BASHRC"
else
success "npm PATH уже есть в $BASHRC"
fi
# ── 2. Node.js ─────────────────────────────────────────────── # ── 2. Node.js ───────────────────────────────────────────────
info "Проверяю Node.js..." info "Проверяю Node.js..."
if ! command -v node &>/dev/null; then 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 - curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs sudo apt-get install -y nodejs
elif command -v dnf &>/dev/null; then
sudo dnf install -y nodejs
else
warn "Не удалось определить пакетный менеджер. Установите Node.js вручную."
fi fi
fi
if command -v node &>/dev/null; then
success "Node.js $(node --version)" success "Node.js $(node --version)"
else
warn "Node.js не найден. Некоторые функции могут не работать."
fi
# ── 3. Claude Code ─────────────────────────────────────────── # ── 3. Claude Code ───────────────────────────────────────────
info "Проверяю Claude Code..." info "Проверяю Claude Code..."
if ! command -v claude &>/dev/null; then if ! command -v claude &>/dev/null; then
info "Устанавливаю Claude Code..." info "Устанавливаю Claude Code..."
# Пробуем официальный инсталлер
if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
success "Claude Code установлен (официальный инсталлер)" success "Claude Code установлен (официальный инсталлер)"
else else
# Fallback: npm в наш prefix (без sudo)
npm install -g @anthropic-ai/claude-code npm install -g @anthropic-ai/claude-code
success "Claude Code установлен (npm)" success "Claude Code установлен (npm)"
fi fi
@@ -71,7 +70,7 @@ else
fi fi
# ── 4. claude-code-proxy (GPT) ─────────────────────────────── # ── 4. claude-code-proxy (GPT) ───────────────────────────────
mkdir -p "$HOME/.local/bin" mkdir -p "$BIN_DIR"
install_proxy() { install_proxy() {
info "Устанавливаю claude-code-proxy..." info "Устанавливаю claude-code-proxy..."
@@ -106,11 +105,11 @@ else
install_proxy install_proxy
fi fi
# ── 4b. effort-proxy wrapper (патч xhighmax для claude-code-proxy) ───────── # ── 4b. effort-proxy wrapper (патч xhigh->max для claude-code-proxy) ─────────
EFFORT_PROXY_BIN="$HOME/.local/bin/claude-gpt-effort-proxy.py" EFFORT_PROXY_BIN="$BIN_DIR/claude-gpt-effort-proxy.py"
cat > "$EFFORT_PROXY_BIN" << 'PYEOF' cat > "$EFFORT_PROXY_BIN" << 'PYEOF'
#!/usr/bin/env python3 #!/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 import http.client, http.server, sys
UPSTREAM_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 18766 UPSTREAM_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 18766
@@ -173,14 +172,12 @@ fi
# ── 6. Папка для конфигов ──────────────────────────────────── # ── 6. Папка для конфигов ────────────────────────────────────
mkdir -p "$CONFIG_DIR" mkdir -p "$CONFIG_DIR"
# ── 7. Функции в .bashrc ───────────────────────────────────── # ── 7. Очистка старых функций из .bashrc / .zshrc ───────────
info "Прописываю функции запуска в $BASHRC..." clean_rc() {
local rc_file="$1"
MARKER="# === CLAUDE LAUNCHER ===" if [ -f "$rc_file" ] && grep -q "# === CLAUDE LAUNCHER ===" "$rc_file"; then
info "Очищаю старые функции из $rc_file..."
if grep -q "$MARKER" "$BASHRC" 2>/dev/null; then python3 - "$rc_file" <<'PYEOF'
warn "Блок уже есть в $BASHRC — обновляю..."
python3 - "$BASHRC" <<'PYEOF'
import sys import sys
path = sys.argv[1] path = sys.argv[1]
with open(path, 'r') as f: with open(path, 'r') as f:
@@ -194,18 +191,38 @@ if start != -1 and end != -1:
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(new_content) f.write(new_content)
PYEOF PYEOF
success "Старые функции удалены из $rc_file"
fi 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 # _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() { _claude_test_api() {
local url="$1" auth_header="$2" model="${3:-claude-sonnet-4-6}" local url="$1" auth_header="$2" model="${3:-claude-sonnet-4-6}"
local response local response
@@ -219,7 +236,6 @@ _claude_test_api() {
_CLAUDE_TEST_BODY=$(echo "$response" | sed '$d') _CLAUDE_TEST_BODY=$(echo "$response" | sed '$d')
} }
# _claude_extract_error: Extract error.message from Anthropic-style error JSON
_claude_extract_error() { _claude_extract_error() {
local body="$1" local body="$1"
echo "$body" | python3 -c " echo "$body" | python3 -c "
@@ -238,8 +254,6 @@ except:
" 2>/dev/null " 2>/dev/null
} }
# _claude_offer_reauth: Ask user if they want to re-authenticate
# Returns 0 for yes, 1 for no
_claude_offer_reauth() { _claude_offer_reauth() {
local provider="$1" local provider="$1"
echo "" echo ""
@@ -252,138 +266,133 @@ _claude_offer_reauth() {
esac esac
} }
# ── claude_gpt ──────────────────────────────────────────────── _handle_api_response() {
claude_gpt() { local provider="$1"
local proxy_bin="$HOME/.local/bin/claude-code-proxy" 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 if [ ! -f "$proxy_bin" ]; then
echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh" echo "Ошибка: claude-code-proxy не найден. Перезапустите claude_setup.sh"
return 1 exit 1
fi fi
# Проверяем авторизацию
if ! "$proxy_bin" codex auth status &>/dev/null; then if ! "$proxy_bin" codex auth status &>/dev/null; then
echo "" echo ""
echo "Авторизация ChatGPT не найдена." echo "Авторизация ChatGPT не найдена."
echo "Сейчас появится ссылка или откроется браузер..." echo "Сейчас появится ссылка или откроется браузер..."
echo "" echo ""
if ! "$proxy_bin" codex auth login 2>&1; then if ! "$proxy_bin" codex auth login 2>&1; then
echo "" echo "Если браузер не открылся, попробуйте device flow: claude-code-proxy codex auth device"
echo "Если браузер не открылся, попробуйте device flow:" exit 1
echo " claude-code-proxy codex auth device"
return 1
fi fi
fi fi
# Запускаем прокси в фоне (если ещё не запущен) proxy_pid=""
local proxy_pid="" wrapper_pid="" wrapper_pid=""
local effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py" effort_proxy="$HOME/.local/bin/claude-gpt-effort-proxy.py"
# Если прокси занял порт 18765 (старый формат без wrapper) — мигрируем cleanup() {
if pgrep -f "claude-code-proxy serve" &>/dev/null && \ [ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null
! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then [ -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 pkill -f "claude-code-proxy serve" 2>/dev/null
sleep 0.5 sleep 0.5
fi fi
# Реальный прокси на 18766
if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then if ! pgrep -f "claude-code-proxy serve" &>/dev/null; then
PORT=18766 "$proxy_bin" serve &>/tmp/claude-code-proxy.log & PORT=18766 "$proxy_bin" serve &>/tmp/claude-code-proxy.log &
proxy_pid=$! proxy_pid=$!
local _j=0 _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
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 fi
# Wrapper (xhigh→max патч) на 18765
if ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then if ! pgrep -f "claude-gpt-effort-proxy" &>/dev/null; then
python3 "$effort_proxy" 18766 18765 &>/tmp/claude-gpt-effort-proxy.log & python3 "$effort_proxy" 18766 18765 &>/tmp/claude-gpt-effort-proxy.log &
wrapper_pid=$! wrapper_pid=$!
local _i=0 _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
while [ $_i -lt 10 ]; do _i=$((_i + 1)); done
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 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... " echo -n "Проверка авторизации ChatGPT... "
_claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini" _claude_test_api "http://localhost:18765/v1/messages" "x-api-key: dummy" "gpt-5.4-mini"
local _emsg if [ "$_CLAUDE_TEST_CODE" != "400" ]; then
case "$_CLAUDE_TEST_CODE" in _handle_api_response "ChatGPT" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" ""
200) ret=$?
echo -e "\033[0;32mOK\033[0m" if [ $ret -eq 401 ]; then
;;
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 "$proxy_bin" codex auth logout 2>/dev/null
if _claude_offer_reauth "ChatGPT"; then if _claude_offer_reauth "ChatGPT"; then
echo "Запускаю повторную авторизацию..." "$proxy_bin" codex auth login || exit 1
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" _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 [ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; }
echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"
return 1
fi
echo -e "\033[0;32mOK\033[0m" echo -e "\033[0;32mOK\033[0m"
else else
return 1 exit 1
fi fi
;; elif [ $ret -ne 0 ]; then
429) exit 1
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") fi
echo "" else
echo -e "\033[0;33m[ЛИМИТ ИСЧЕРПАН]\033[0m Лимит ChatGPT исчерпан."
[ -n "$_emsg" ] && echo " $_emsg"
return 1
;;
400)
# Прокси работает — 400 означает только, что тест-запрос не содержит обязательное поле «instructions»
# Авторизация уже проверена через codex auth status; запускаем Claude
echo -e "\033[0;32mOK\033[0m" echo -e "\033[0;32mOK\033[0m"
;; fi
000)
echo ""
echo -e "\033[0;33m[СЕТЬ]\033[0m Не удалось проверить ChatGPT прокси (нет сети?). Продолжаю..."
;;
*)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
echo -e "\033[0;31m[ОШИБКА]\033[0m Прокси вернул неожиданный HTTP $_CLAUDE_TEST_CODE."
[ -n "$_emsg" ] && echo " $_emsg"
echo ""
echo "Попробуйте:"
echo " • Перезапустить claude_gpt"
echo " • Переавторизоваться: claude-code-proxy codex auth logout && claude-code-proxy codex auth login"
return 1
;;
esac
# Сохраняем Anthropic credentials перед запуском — команда /logout внутри Claude Code _creds_file="$HOME/.claude/.credentials.json"
# удаляет их, хотя в режиме GPT они не нужны, но потом сломают оригинальный claude _creds_backup=""
local _creds_file="$HOME/.claude/.credentials.json"
local _creds_backup=""
[ -f "$_creds_file" ] && _creds_backup=$(cat "$_creds_file") [ -f "$_creds_file" ] && _creds_backup=$(cat "$_creds_file")
echo -e "\033[0;33m[ИНФО]\033[0m Режим ChatGPT. Для выхода: Ctrl+C или /exit" echo -e "\033[0;33m[ИНФО]\033[0m Режим ChatGPT. Для выхода: Ctrl+C или /exit"
@@ -400,133 +409,66 @@ claude_gpt() {
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@" claude "$@"
# Восстанавливаем credentials если /logout их удалил
if [ -n "$_creds_backup" ] && [ ! -f "$_creds_file" ]; then if [ -n "$_creds_backup" ] && [ ! -f "$_creds_file" ]; then
mkdir -p "$(dirname "$_creds_file")" mkdir -p "$(dirname "$_creds_file")"
echo "$_creds_backup" > "$_creds_file" echo "$_creds_backup" > "$_creds_file"
echo "" echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены."
echo -e "\033[0;33m[ИНФО]\033[0m Anthropic credentials восстановлены после /logout внутри сессии."
fi 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 ===
claude_deepseek() { cat > "$BIN_DIR/claude_deepseek" << 'DEEPSEEKEOF'
local key_file="$HOME/.config/claude-launcher/deepseek_key" #!/usr/bin/env bash
local api_key="" reauth=0 source ~/.local/bin/claude_api_helpers.sh
local _emsg
# Read stored key key_file="$HOME/.config/claude-launcher/deepseek_key"
if [ -f "$key_file" ]; then api_key=""
api_key=$(cat "$key_file") reauth=0
fi
[ -f "$key_file" ] && api_key=$(cat "$key_file")
# Validate stored key if present
if [ -n "$api_key" ]; then if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого DeepSeek ключа... " echo -n "Проверка сохранённого DeepSeek ключа... "
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash" _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"
case "$_CLAUDE_TEST_CODE" in ret=$?
200) if [ $ret -eq 401 ]; then
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" rm -f "$key_file"
api_key="" api_key=""
reauth=1 reauth=1
;; elif [ $ret -eq 429 ]; then
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] " echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
local _ans; read -r _ans read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
case "${_ans:-N}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac elif [ $ret -ne 0 ]; then
;; exit 1
000) fi
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 fi
# Re-prompt if key was invalidated
if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then
echo ""
echo -n "Хотите ввести новый DeepSeek ключ? [Y/n] " echo -n "Хотите ввести новый DeepSeek ключ? [Y/n] "
local _ans; read -r _ans read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac
case "${_ans:-Y}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac
fi fi
# Prompt for key if missing
if [ -z "$api_key" ]; then if [ -z "$api_key" ]; then
echo ""
echo "DeepSeek API ключ не найден."
echo "Получить ключ: https://platform.deepseek.com/api_keys" echo "Получить ключ: https://platform.deepseek.com/api_keys"
echo ""
read -r -p "Введите ваш DeepSeek API ключ: " api_key read -r -p "Введите ваш DeepSeek API ключ: " api_key
echo "" [ -z "$api_key" ] && { echo "Выход."; exit 1; }
if [ -z "$api_key" ]; then echo -n "Проверяю ключ и баланс... "
echo "Ключ не введён. Выход."
return 1
fi
echo "Проверяю ключ и баланс..."
_claude_test_api "https://api.deepseek.com/anthropic/v1/messages" "Authorization: Bearer $api_key" "deepseek-v4-flash" _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"
case "$_CLAUDE_TEST_CODE" in ret=$?
200) if [ $ret -eq 0 ]; then
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 -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m" echo "Ключ сохранён."
;; else
000) echo "Ключ НЕ сохранён."
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..." exit 1
mkdir -p "$(dirname "$key_file")" fi
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 fi
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \ ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
@@ -538,121 +480,60 @@ claude_deepseek() {
CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \ CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@" claude "$@"
} DEEPSEEKEOF
chmod +x "$BIN_DIR/claude_deepseek"
# ── claude_kimi ───────────────────────────────────────────── # === claude_kimi ===
claude_kimi() { cat > "$BIN_DIR/claude_kimi" << 'KIMIEOF'
local key_file="$HOME/.config/claude-launcher/kimi_key" #!/usr/bin/env bash
local api_key="" reauth=0 source ~/.local/bin/claude_api_helpers.sh
local _emsg
# Read stored key key_file="$HOME/.config/claude-launcher/kimi_key"
if [ -f "$key_file" ]; then api_key=""
api_key=$(cat "$key_file") reauth=0
fi
[ -f "$key_file" ] && api_key=$(cat "$key_file")
# Validate stored key if present
if [ -n "$api_key" ]; then if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого Kimi ключа... " echo -n "Проверка сохранённого Kimi ключа... "
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6" _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"
case "$_CLAUDE_TEST_CODE" in ret=$?
200) if [ $ret -eq 401 ]; then
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" rm -f "$key_file"
api_key="" api_key=""
reauth=1 reauth=1
;; elif [ $ret -eq 429 ]; then
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] " echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
local _ans; read -r _ans read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
case "${_ans:-N}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac elif [ $ret -ne 0 ]; then
;; exit 1
000) fi
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 fi
# Re-prompt if key was invalidated
if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then
echo ""
echo -n "Хотите ввести новый Kimi ключ? [Y/n] " echo -n "Хотите ввести новый Kimi ключ? [Y/n] "
local _ans; read -r _ans read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac
case "${_ans:-Y}" in [Yy]|[Yy][Ee][Ss]) ;; *) return 1 ;; esac
fi fi
# Prompt for key if missing
if [ -z "$api_key" ]; then if [ -z "$api_key" ]; then
echo ""
echo "Kimi (Moonshot AI) API ключ не найден."
echo "Получить ключ: https://platform.moonshot.ai/console/api-keys" echo "Получить ключ: https://platform.moonshot.ai/console/api-keys"
echo "" read -r -p "Введите ваш Kimi API ключ: " api_key
read -r -p "Введите ваш Kimi API ключ (начинается с 'sk-'): " api_key [ -z "$api_key" ] && { echo "Выход."; exit 1; }
echo ""
if [ -z "$api_key" ]; then echo -n "Проверяю ключ и баланс... "
echo "Ключ не введён. Выход."
return 1
fi
echo "Проверяю ключ и баланс..."
_claude_test_api "https://api.moonshot.ai/anthropic/v1/messages" "Authorization: Bearer $api_key" "kimi-k2.6" _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"
case "$_CLAUDE_TEST_CODE" in ret=$?
200) if [ $ret -eq 0 ]; then
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 -e "\033[0;32mКлюч действителен, баланс в порядке. Ключ сохранён.\033[0m" echo "Ключ сохранён."
;; else
000) echo "Ключ НЕ сохранён."
echo "Не удалось проверить ключ (нет сети?). Сохраняю без проверки..." exit 1
mkdir -p "$(dirname "$key_file")" fi
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 fi
ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \ ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic \
@@ -664,191 +545,72 @@ claude_kimi() {
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \ CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@" claude "$@"
} KIMIEOF
chmod +x "$BIN_DIR/claude_kimi"
# ── claude_gemini ───────────────────────────────────────────── # === claude_gemini ===
claude_gemini() { cat > "$BIN_DIR/claude_gemini" << 'GEMINIEOF'
local acc_cmd="" #!/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 antigravity-claude-proxy &>/dev/null && acc_cmd="antigravity-claude-proxy"
command -v acc &>/dev/null && acc_cmd="acc" command -v acc &>/dev/null && acc_cmd="acc"
if [ -z "$acc_cmd" ]; then [ -z "$acc_cmd" ] && { echo "Ошибка: antigravity-claude-proxy не найден."; exit 1; }
echo "Ошибка: antigravity-claude-proxy не найден. Перезапустите claude_setup.sh"
return 1 proxy_pid=""
fi 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 if ! curl -sf http://localhost:8080/health &>/dev/null; then
"$acc_cmd" start &>/tmp/antigravity-proxy.log & "$acc_cmd" start &>/tmp/antigravity-proxy.log &
proxy_pid=$! proxy_pid=$!
echo "Запускаю Gemini прокси..." echo "Запускаю Gemini прокси..."
local i=0 i=0; while [ $i -lt 15 ]; do sleep 1; curl -sf http://localhost:8080/health &>/dev/null && break; i=$((i+1)); done
while [ $i -lt 15 ]; do
sleep 1
curl -sf http://localhost:8080/health &>/dev/null && break
i=$((i+1))
done
fi fi
# ── Проверка аккаунтов ──
local has_auth total_count invalid_count
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 [ -n "$has_auth" ]; then if [ -n "$has_auth" ]; then
total_count=$(echo "$has_auth" | python3 -c " total_count=$(echo "$has_auth" | python3 -c "import sys, json; print(len(json.load(sys.stdin).get('accounts', [])))" 2>/dev/null || echo "0")
import sys, json 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")
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 fi
if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then if [ -z "$has_auth" ] || [ "$total_count" = "0" ]; then
echo ""
echo "Google-аккаунт не найден."
echo ""
echo -e "\033[1;33m⚠ ВНИМАНИЕ: Используйте ОТДЕЛЬНЫЙ Google-аккаунт!\033[0m" echo -e "\033[1;33m⚠ ВНИМАНИЕ: Используйте ОТДЕЛЬНЫЙ Google-аккаунт!\033[0m"
echo " Google может заблокировать аккаунты, использующие этот прокси." _open_browser "http://localhost:8080"
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 после завершения авторизации в браузере..." echo "Нажмите Enter после завершения авторизации в браузере..."
read -r read -r
elif [ "$invalid_count" -gt 0 ]; then elif [ "$invalid_count" -gt 0 ]; then
echo ""
echo -e "\033[0;33m[ПРЕДУПРЕЖДЕНИЕ]\033[0m Обнаружены проблемные аккаунты ($invalid_count из $total_count)." 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... " echo -n "Проверка доступа к Gemini API... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent"
local _emsg if [ "$_CLAUDE_TEST_CODE" != "400" ]; then
case "$_CLAUDE_TEST_CODE" in _handle_api_response "Gemini" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" ""
200) ret=$?
echo -e "\033[0;32mOK\033[0m" if [ $ret -ne 0 ]; then
;;
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 if _claude_offer_reauth "Gemini"; then
xdg-open "http://localhost:8080" 2>/dev/null || \ _open_browser "http://localhost:8080"
sensible-browser "http://localhost:8080" 2>/dev/null || \ echo "Нажмите Enter после авторизации/добавления..."
echo "Откройте http://localhost:8080"
echo "Нажмите Enter после авторизации..."
read -r read -r
echo -n "Проверяю авторизацию Gemini... "
_claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent" _claude_test_api "http://localhost:8080/v1/messages" "x-api-key: dummy" "gemini-3-flash-agent"
if [ "$_CLAUDE_TEST_CODE" != "200" ]; then [ "$_CLAUDE_TEST_CODE" != "200" ] && [ "$_CLAUDE_TEST_CODE" != "400" ] && { echo "ОШИБКА"; exit 1; }
echo -e "\033[0;31mОШИБКА (HTTP $_CLAUDE_TEST_CODE)\033[0m"
return 1
fi
echo -e "\033[0;32mOK\033[0m" echo -e "\033[0;32mOK\033[0m"
else else
return 1 exit 1
fi 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 fi
echo -e "\033[0;32mOK\033[0m"
else else
return 1
fi
;;
400)
_emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY") _emsg=$(_claude_extract_error "$_CLAUDE_TEST_BODY")
echo ""
if echo "$_emsg" | grep -q "RESOURCE_EXHAUSTED"; then 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 аккаунты исчерпали лимит запросов." echo -e "\033[0;33m[КВОТА ИСЧЕРПАНА]\033[0m Все Gemini аккаунты исчерпали лимит запросов."
[ -n "$_reset" ] && echo " Квота обновится через: $_reset" exit 1
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 fi
echo -e "\033[0;32mOK\033[0m" echo -e "\033[0;32mOK\033[0m"
else
return 1
fi 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_BASE_URL=http://localhost:8080 \
ANTHROPIC_AUTH_TOKEN=dummy \ ANTHROPIC_AUTH_TOKEN=dummy \
@@ -859,47 +621,22 @@ except:
CLAUDE_CODE_SUBAGENT_MODEL=gemini-3.5-flash-low \ CLAUDE_CODE_SUBAGENT_MODEL=gemini-3.5-flash-low \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude "$@" claude "$@"
GEMINIEOF
chmod +x "$BIN_DIR/claude_gemini"
if [ -n "$proxy_pid" ]; then success "Скрипты сгенерированы."
kill "$proxy_pid" 2>/dev/null
wait "$proxy_pid" 2>/dev/null
fi
}
# === END CLAUDE LAUNCHER === # ── 9. Итог ──────────────────────────────────────────────────
BASHEOF
success "Функции добавлены в $BASHRC"
# ── 8. Итог ──────────────────────────────────────────────────
echo "" echo ""
echo -e "${GREEN}════════════════════════════════════════════════════${NC}" echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Установка завершена!${NC}" echo -e "${GREEN} Установка завершена!${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════${NC}" echo -e "${GREEN}════════════════════════════════════════════════════${NC}"
echo "" echo ""
echo "Доступные команды:" echo "Доступные команды (теперь это независимые скрипты в ~/.local/bin):"
echo -e " ${CYAN}claude_gpt${NC} — GPT-5.5 (ChatGPT Plus/Pro, браузерная авторизация)" echo -e " ${CYAN}claude_gpt${NC} — GPT-5.5 (ChatGPT Plus/Pro, браузерная авторизация)"
echo -e " ${CYAN}claude_deepseek${NC} — DeepSeek (API ключ сохраняется)" echo -e " ${CYAN}claude_deepseek${NC} — DeepSeek (API ключ сохраняется)"
echo -e " ${CYAN}claude_kimi${NC} — Kimi K2.6 (Moonshot AI, API ключ сохраняется)" echo -e " ${CYAN}claude_kimi${NC} — Kimi K2.6 (Moonshot AI, API ключ сохраняется)"
echo -e " ${CYAN}claude_gemini${NC} — Gemini (Google OAuth через браузер)" echo -e " ${CYAN}claude_gemini${NC} — Gemini (Google OAuth через браузер)"
echo "" echo ""
echo "Все команды принимают стандартные флаги:"
echo -e " ${CYAN}claude_gpt --dangerously-skip-permissions${NC}"
echo ""
echo -e "${YELLOW}⚠️ Для Gemini используйте отдельный Google-аккаунт!${NC}" echo -e "${YELLOW}⚠️ Для Gemini используйте отдельный Google-аккаунт!${NC}"
echo "" 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 #!/usr/bin/env bash
# tests/test_fixes.sh — unit tests for code-review fixes in claude_setup.sh # tests/test_fixes.sh — unit tests for code-review fixes in claude_setup.sh
# Run: bash tests/test_fixes.sh # Run: bash tests/test_fixes.sh
# Requires: bash 4+, curl (can be mocked via PATH)
set -euo pipefail set -euo pipefail
@@ -11,56 +10,28 @@ PASS=0; FAIL=0
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); } ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
fail() { echo "[FAIL] $1"; FAIL=$((FAIL+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. # ── Fix 2: trap EXIT kills proxy ──────────────────────────────────────────────
# The heredoc begins after "cat >> \"$BASHRC\" << 'BASHEOF'" and contains test_fix2_trap_exit() {
# all the launcher functions; we extract and source that block directly. if echo "$GPT_SECTION" | grep -q "trap .* EXIT"; then
_source_functions() { ok "Fix2: trap EXIT for proxy cleanup present in claude_gpt"
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 else
fail "Fix1: [K] branch missing 'export' for ANTHROPIC_API_KEY" fail "Fix2: trap EXIT for proxy cleanup missing in claude_gpt"
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 fi
} }
# ── Fix 3: readiness loop replaces bare sleep 1 ────────────────────────────── # ── Fix 3: readiness loop replaces bare sleep 1 ──────────────────────────────
test_fix3_readiness_loop() { test_fix3_readiness_loop() {
# The old code had just "sleep 1" after starting proxy; now there's a while loop if echo "$GPT_SECTION" | grep -q 'while \[ \$_i -lt'; then
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" ok "Fix3: readiness poll loop present in claude_gpt proxy start"
else else
fail "Fix3: readiness poll loop missing in claude_gpt" fail "Fix3: readiness poll loop missing in claude_gpt"
fi fi
# Confirm bare "sleep 1" is gone from the proxy-start section (the loop contains sleep 1 but in context) if echo "$GPT_SECTION" | grep -qP 'proxy_pid=\$!\n\s+sleep 1\n\s+fi'; then
# 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=\$!" fail "Fix3: bare 'sleep 1' still present right after proxy_pid=\$!"
else else
ok "Fix3: bare 'sleep 1; fi' pattern removed" ok "Fix3: bare 'sleep 1; fi' pattern removed"
@@ -69,15 +40,14 @@ test_fix3_readiness_loop() {
# ── Fix 3b: curl exit-7 logic correct ──────────────────────────────────────── # ── Fix 3b: curl exit-7 logic correct ────────────────────────────────────────
test_fix3b_exit7_logic() { test_fix3b_exit7_logic() {
# Verify the comment and condition are as expected if echo "$GPT_SECTION" | grep -q 'exit 7 = connection refused'; then
if grep -q 'exit 7 = connection refused' "$SCRIPT"; then
ok "Fix3b: exit-7 comment present (connection refused check documented)" ok "Fix3b: exit-7 comment present (connection refused check documented)"
else else
fail "Fix3b: exit-7 comment missing" fail "Fix3b: exit-7 comment missing"
fi fi
if grep -q '_ce.*-ne 7' "$SCRIPT"; then if echo "$GPT_SECTION" | grep -q '\[ "\$?" -ne 7 \]'; then
ok "Fix3b: [ \$_ce -ne 7 ] break condition present" ok "Fix3b: [ \$? -ne 7 ] break condition present"
else else
fail "Fix3b: exit-7 break condition missing" fail "Fix3b: exit-7 break condition missing"
fi fi
@@ -85,27 +55,51 @@ test_fix3b_exit7_logic() {
# ── Fix 4: re-validate after claude_gpt reauth ─────────────────────────────── # ── Fix 4: re-validate after claude_gpt reauth ───────────────────────────────
test_fix4_gpt_revalidate() { test_fix4_gpt_revalidate() {
local gpt_section if echo "$GPT_SECTION" | grep -q '_claude_test_api.*http://localhost:18765'; then
gpt_section=$(awk '/^claude_gpt\(\)/,/^}/' "$SCRIPT") ok "Fix4: _claude_test_api called in claude_gpt"
if echo "$gpt_section" | grep -q 'Проверяю авторизацию после входа'; then
ok "Fix4: re-validate after codex auth login present in claude_gpt"
else else
fail "Fix4: re-validate after codex auth login missing in claude_gpt" fail "Fix4: _claude_test_api missing in claude_gpt"
fi fi
} }
# ── Fix 5: re-validate after claude_gemini reauth (both 401 and 429) ───────── # ── Fix 5: re-validate after claude_gemini reauth (both 401 and 429) ─────────
test_fix5_gemini_revalidate() { test_fix5_gemini_revalidate() {
local gemini_section
gemini_section=$(awk '/^claude_gemini\(\)/,/^}/' "$SCRIPT")
local count 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 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 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 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