Refactor architecture to use standalone scripts in ~/.local/bin/ instead of ~/.bashrc
This commit is contained in:
737
claude_setup.sh
737
claude_setup.sh
@@ -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 (патч xhigh→max для 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 ""
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user