#!/bin/bash # ============================================================ # llmapi.pro - Claude Code 一键诊断 & 配置脚本 # 运行方式: curl -fsSL https://llmapi.pro/setup.sh | bash # ============================================================ set -e BASE_URL="https://d.llmapi.pro:99" echo "" echo "========================================" echo " llmapi.pro Claude Code 智能配置工具" echo "========================================" echo "" # --- 检测并安装 Claude Code --- echo "[检测] Claude Code 安装状态..." CLAUDE_PATH=$(command -v claude 2>/dev/null || echo "") if [ -z "$CLAUDE_PATH" ] && [ -f "$HOME/.local/bin/claude" ]; then CLAUDE_PATH="$HOME/.local/bin/claude" fi if [ -n "$CLAUDE_PATH" ]; then echo " 已安装: $CLAUDE_PATH" else echo " 未安装 Claude Code,开始自动安装..." echo "" # 检测 npm if command -v npm >/dev/null 2>&1; then echo " [ok] 检测到 npm: $(command -v npm)" else if command -v node >/dev/null 2>&1; then echo " [!] 检测到 Node.js 但未找到 npm,请检查安装" exit 1 fi # 自动安装 Node.js echo " 未检测到 Node.js,正在安装..." if command -v apt-get >/dev/null 2>&1; then # Debian/Ubuntu echo " 使用 apt 安装 Node.js 22.x..." curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - 2>/dev/null || { # nodesource 不可用时用系统包 echo " NodeSource 不可用,使用系统包..." sudo apt-get update -qq && sudo apt-get install -y -qq nodejs npm } elif command -v yum >/dev/null 2>&1; then # CentOS/RHEL curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash - 2>/dev/null || { sudo yum install -y nodejs npm } elif command -v brew >/dev/null 2>&1; then # macOS brew install node elif command -v pacman >/dev/null 2>&1; then sudo pacman -Sy --noconfirm nodejs npm else echo " 无法自动安装 Node.js,请手动安装后重试" echo " 推荐: https://npmmirror.com/mirrors/node/" exit 1 fi if ! command -v npm >/dev/null 2>&1; then echo " Node.js 安装后未找到 npm,请重启终端后重试" exit 1 fi echo " Node.js 安装完成!" fi # 用 npm 安装 Claude Code(使用国内镜像加速) echo " 正在安装 Claude Code(npm)..." NPM_MIRROR="https://registry.npmmirror.com" echo " 使用国内镜像: $NPM_MIRROR" npm install -g @anthropic-ai/claude-code --registry "$NPM_MIRROR" # 验证安装 CLAUDE_PATH=$(command -v claude 2>/dev/null || echo "") if [ -z "$CLAUDE_PATH" ] && [ -f "$HOME/.local/bin/claude" ]; then CLAUDE_PATH="$HOME/.local/bin/claude" fi # 检查 npm 全局 bin if [ -z "$CLAUDE_PATH" ]; then NPM_BIN=$(npm bin -g 2>/dev/null || echo "") if [ -n "$NPM_BIN" ] && [ -f "$NPM_BIN/claude" ]; then CLAUDE_PATH="$NPM_BIN/claude" export PATH="$NPM_BIN:$PATH" fi fi if [ -z "$CLAUDE_PATH" ]; then echo " Claude Code 安装失败,请手动执行:" echo " npm install -g @anthropic-ai/claude-code --registry https://registry.npmmirror.com" exit 1 fi echo " Claude Code 安装成功: $CLAUDE_PATH" fi # --- 诊断 --- echo "" echo "[诊断] 检测现有配置..." ISSUES=0 CRED_FILE="$HOME/.claude/.credentials.json" if [ -f "$CRED_FILE" ] && grep -q "claudeAiOauth" "$CRED_FILE" 2>/dev/null; then echo " [!] 检测到 OAuth 登录 — 会覆盖 API Key" ISSUES=$((ISSUES + 1)) fi CLAUDE_JSON="$HOME/.claude.json" if [ -f "$CLAUDE_JSON" ] && grep -q '"rejected"' "$CLAUDE_JSON" 2>/dev/null; then if grep -q '"rejected".*\[.*"' "$CLAUDE_JSON" 2>/dev/null; then echo " [!] 检测到 API Key 被拒绝记录" ISSUES=$((ISSUES + 1)) fi fi if [ $ISSUES -eq 0 ]; then echo " 未检测到配置问题" fi # --- 输入 API Key --- echo "" echo "请输入你的 LLMAPI API Key(从 https://llmapi.pro/dashboard 获取)" echo "直接回车可跳过,之后手动配置" API_KEY="" if [ -r /dev/tty ]; then read -p "API Key (sk-relay-...): " API_KEY /dev/null 2>&1; then node -e " const fs = require('fs'); const f = process.argv[1], trunc = process.argv[2]; let j = {}; try { j = JSON.parse(fs.readFileSync(f, 'utf8')); } catch(e) {} if (!j.customApiKeyResponses) j.customApiKeyResponses = { approved: [], rejected: [] }; j.customApiKeyResponses.rejected = []; if (trunc) { const t = trunc.slice(-20); if (!j.customApiKeyResponses.approved.includes(t)) j.customApiKeyResponses.approved.push(t); } fs.writeFileSync(f, JSON.stringify(j, null, 2)); " "$CLAUDE_JSON" "$TRUNCATED" elif [ -f "$CLAUDE_JSON" ]; then sed -i.bak 's/"rejected"[[:space:]]*:[[:space:]]*\[[^]]*\]/"rejected": []/' "$CLAUDE_JSON" 2>/dev/null || true fi echo " 清除拒绝记录 + 预批准 Key" # 3. 写入 settings.json(合并已有配置) SETTINGS_DIR="$HOME/.claude" mkdir -p "$SETTINGS_DIR" SETTINGS_FILE="$SETTINGS_DIR/settings.json" if command -v node >/dev/null 2>&1; then WRITE_KEY="" if [ "$SKIP_KEY" = false ]; then WRITE_KEY="$API_KEY"; fi node -e " const fs = require('fs'); const f = process.argv[1], base = process.argv[2], key = process.argv[3]; let s = {}; try { s = JSON.parse(fs.readFileSync(f, 'utf8')); } catch(e) {} if (!s.env) s.env = {}; s.env.ANTHROPIC_BASE_URL = base; if (key) s.env.ANTHROPIC_API_KEY = key; s.env.ANTHROPIC_CUSTOM_MODEL_OPTION = 'claude-fable-5'; s.env.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME = 'Fable 5'; if (!s.mcpServers) s.mcpServers = {}; const mcpEnv = { LLMAPI_SYNC_URL: base + '/api/mcp' }; if (key) mcpEnv.LLMAPI_API_KEY = key; s.mcpServers.llmapi = { command: 'npx', args: ['-y', 'llmapi-mcp'], env: mcpEnv }; if (!s.permissions) s.permissions = {}; if (!s.permissions.allow) s.permissions.allow = []; if (!s.permissions.allow.includes('Bash(claude mcp *)')) s.permissions.allow.push('Bash(claude mcp *)'); fs.writeFileSync(f, JSON.stringify(s, null, 2)); " "$SETTINGS_FILE" "$BASE_URL" "$WRITE_KEY" else if [ "$SKIP_KEY" = true ]; then cat > "$SETTINGS_FILE" << JSONEOF { "env": { "ANTHROPIC_BASE_URL": "$BASE_URL", "ANTHROPIC_CUSTOM_MODEL_OPTION": "claude-fable-5", "ANTHROPIC_CUSTOM_MODEL_OPTION_NAME": "Fable 5" }, "mcpServers": { "llmapi": { "command": "npx", "args": ["-y", "llmapi-mcp"], "env": { "LLMAPI_SYNC_URL": "$BASE_URL/api/mcp" } } }, "permissions": { "allow": ["Bash(claude mcp *)"] } } JSONEOF else cat > "$SETTINGS_FILE" << JSONEOF { "env": { "ANTHROPIC_BASE_URL": "$BASE_URL", "ANTHROPIC_API_KEY": "$API_KEY", "ANTHROPIC_CUSTOM_MODEL_OPTION": "claude-fable-5", "ANTHROPIC_CUSTOM_MODEL_OPTION_NAME": "Fable 5" }, "mcpServers": { "llmapi": { "command": "npx", "args": ["-y", "llmapi-mcp"], "env": { "LLMAPI_API_KEY": "$API_KEY", "LLMAPI_SYNC_URL": "$BASE_URL/api/mcp" } } }, "permissions": { "allow": ["Bash(claude mcp *)"] } } JSONEOF fi fi echo " 写入 settings.json" # 4. 写入 shell profile SHELL_RC="$HOME/.bashrc" [ -f "$HOME/.zshrc" ] && SHELL_RC="$HOME/.zshrc" if ! grep -q "ANTHROPIC_BASE_URL" "$SHELL_RC" 2>/dev/null; then echo "" >> "$SHELL_RC" echo "# llmapi.pro Claude Code" >> "$SHELL_RC" echo "export ANTHROPIC_BASE_URL=\"$BASE_URL\"" >> "$SHELL_RC" if [ "$SKIP_KEY" = false ]; then echo "export ANTHROPIC_API_KEY=\"$API_KEY\"" >> "$SHELL_RC"; fi echo " 写入 $SHELL_RC" else sed -i.bak "s|ANTHROPIC_BASE_URL=.*|ANTHROPIC_BASE_URL=\"$BASE_URL\"|" "$SHELL_RC" && rm -f "$SHELL_RC.bak" if [ "$SKIP_KEY" = false ]; then sed -i.bak "s|ANTHROPIC_API_KEY=.*|ANTHROPIC_API_KEY=\"$API_KEY\"|" "$SHELL_RC" && rm -f "$SHELL_RC.bak"; fi echo " 更新 $SHELL_RC" fi # 4b. 让 Fable 5 出现在 /model 菜单底部(客户端 /model 默认只列内置机型,无自由输入框) if ! grep -q "ANTHROPIC_CUSTOM_MODEL_OPTION" "$SHELL_RC" 2>/dev/null; then echo "export ANTHROPIC_CUSTOM_MODEL_OPTION=\"claude-fable-5\"" >> "$SHELL_RC" echo "export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME=\"Fable 5\"" >> "$SHELL_RC" echo " 已把 Fable 5 加入 /model 菜单" fi # 5. 设置当前会话 export ANTHROPIC_BASE_URL="$BASE_URL" if [ "$SKIP_KEY" = false ]; then export ANTHROPIC_API_KEY="$API_KEY"; fi export ANTHROPIC_CUSTOM_MODEL_OPTION="claude-fable-5" export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="Fable 5" echo "" echo "========================================" echo " 配置完成!" if [ "$SKIP_KEY" = false ]; then echo " API Key 已预批准,无需再确认" else echo " 环境已修复,请在控制台获取 Key" echo " https://llmapi.pro/dashboard" fi echo "========================================" echo "" exec claude