#!/bin/bash # Claude Code Configuration Script for XCodeCLI # This script configures Claude Code to use your XCodeCLI instance # Automatically tests multiple API endpoints and selects the working one set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration DEFAULT_BASE_URL="https://api2.xcodecli.com" CLAUDE_CONFIG_DIR="$HOME/.claude" CLAUDE_SETTINGS_FILE="$CLAUDE_CONFIG_DIR/settings.json" TOOL_COMMAND="claude" TOOL_PACKAGE="@anthropic-ai/claude-code" TOOL_NAME="Claude Code" # ========== Shell 环境变量配置 ========== get_shell_rc() { if [ -n "${ZSH_VERSION:-}" ] || [ "${SHELL##*/}" = "zsh" ]; then echo "$HOME/.zshrc" elif [ -n "${BASH_VERSION:-}" ] || [ "${SHELL##*/}" = "bash" ]; then if [ -f "$HOME/.bashrc" ]; then echo "$HOME/.bashrc" else echo "$HOME/.bash_profile" fi else print_error "不支持当前 shell: ${SHELL##*/}" print_error "仅支持 bash 和 zsh" exit 1 fi } write_env_to_shell() { local var_name="$1" local var_value="$2" local rc_file rc_file=$(get_shell_rc) mkdir -p "$(dirname "$rc_file")" touch "$rc_file" if [ -s "$rc_file" ] && [ "$(tail -c1 "$rc_file" | wc -l)" -eq 0 ]; then echo "" >>"$rc_file" fi local export_line="export $var_name='$var_value'" local tmp_file tmp_file=$(mktemp) if [ -s "$rc_file" ]; then grep -v "^export $var_name=" "$rc_file" >"$tmp_file" 2>/dev/null || true fi echo "$export_line" >>"$tmp_file" cat "$tmp_file" >"$rc_file" rm -f "$tmp_file" export "$var_name=$var_value" } # Function to print colored output print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # ========== Node.js 环境检测 ========== get_node_version() { if command -v node >/dev/null 2>&1; then node --version 2>/dev/null | sed 's/v//' fi } get_node_major_version() { local version version=$(get_node_version) if [ -n "$version" ]; then echo "$version" | cut -d. -f1 fi } install_fnm() { echo "" print_info "正在安装 fnm (Fast Node Manager)..." if curl -fsSL https://fnm.vercel.app/install | bash; then # 加载 fnm 环境 export PATH="$HOME/.local/share/fnm:$PATH" if [ -f "$HOME/.local/share/fnm/fnm" ]; then eval "$(~/.local/share/fnm/fnm env)" fi if command -v fnm >/dev/null 2>&1; then print_success "fnm 安装成功!" return 0 else print_warning "fnm 可能已安装,但需要重新打开终端才能生效" print_info "请重新打开终端后再运行此脚本" return 1 fi else print_error "fnm 安装失败" return 1 fi } install_node_with_fnm() { print_info "使用 fnm 安装 Node.js 24.x..." if fnm install 24 && fnm use 24 && fnm default 24; then # 刷新 PATH eval "$(fnm env)" if command -v node >/dev/null 2>&1; then local version version=$(get_node_version) print_success "Node.js v$version 安装成功!" return 0 else print_warning "Node.js 可能已安装,但需要重新打开终端才能生效" return 1 fi else print_error "Node.js 安装失败" return 1 fi } ensure_node_environment() { local version major version=$(get_node_version) if [ -n "$version" ]; then major=$(get_node_major_version) print_info "检测到 Node.js v$version" if [ "$major" -lt 20 ]; then print_warning "Node.js 版本过低 (需要 >= 20.x)" read -p "是否使用 fnm 安装 Node.js 24.x? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then print_error "Node.js 版本不满足要求,请手动升级后重试" return 1 fi if ! command -v fnm >/dev/null 2>&1; then install_fnm || return 1 fi install_node_with_fnm || return 1 fi return 0 fi print_warning "未检测到 Node.js" print_info "将使用 fnm 安装 Node.js 24.x" read -p "是否继续? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then return 1 fi if ! command -v fnm >/dev/null 2>&1; then install_fnm || return 1 fi install_node_with_fnm || return 1 } install_tool() { ensure_node_environment || return 1 print_info "使用 npm 安装 $TOOL_NAME..." echo " 执行: npm install -g $TOOL_PACKAGE" if npm install -g "$TOOL_PACKAGE"; then if command -v "$TOOL_COMMAND" >/dev/null 2>&1; then print_success "$TOOL_NAME 安装成功!" return 0 else print_warning "$TOOL_NAME 可能已安装,但需要重新打开终端才能生效" read -p "是否继续进行配置? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then return 1 fi return 0 fi else print_error "安装失败" return 1 fi } # Function to check if jq is installed check_jq() { if ! command -v jq &>/dev/null; then print_error "jq is required but not installed." print_info "Please install jq:" print_info " macOS: brew install jq" print_info " Ubuntu/Debian: sudo apt-get install jq" print_info " CentOS/RHEL: sudo yum install jq" exit 1 fi } # Function to backup existing settings backup_settings() { if [ -f "$CLAUDE_SETTINGS_FILE" ]; then local backup_file="${CLAUDE_SETTINGS_FILE}.backup.$(date +%Y%m%d_%H%M%S)" cp "$CLAUDE_SETTINGS_FILE" "$backup_file" print_info "Backed up existing settings to: $backup_file" fi local config_json_path="$CLAUDE_CONFIG_DIR/config.json" if [ -f "$config_json_path" ]; then local backup_file="${config_json_path}.backup.$(date +%Y%m%d_%H%M%S)" cp "$config_json_path" "$backup_file" print_info "Backed up existing config to: $backup_file" fi } # Function to create settings directory create_settings_dir() { if [ ! -d "$CLAUDE_CONFIG_DIR" ]; then mkdir -p "$CLAUDE_CONFIG_DIR" print_info "Created Claude configuration directory: $CLAUDE_CONFIG_DIR" fi } # Function to validate API key format validate_api_key() { local api_key="$1" if [[ ! "$api_key" =~ ^[A-Za-z0-9_-]+$ ]]; then print_error "Invalid API key format. API key should contain only alphanumeric characters, hyphens, and underscores." return 1 fi return 0 } # Function to extract model count from API response (supports both data[] and models[]) get_model_count() { local response_file="$1" local count="0" if command -v jq >/dev/null 2>&1; then count=$(jq -r ' if (.data | type) == "array" and (.data | length) > 0 then (.data | length) elif (.models | type) == "array" and (.models | length) > 0 then (.models | length) else 0 end ' "$response_file" 2>/dev/null || echo "0") else if grep -qE '"(data|models)"[[:space:]]*:[[:space:]]*\[' "$response_file" 2>/dev/null; then count=$(grep -oE '"id"[[:space:]]*:' "$response_file" | wc -l | tr -d ' ') fi fi [ -z "$count" ] && count="0" echo "$count" } # Function to test API connection and return working base URL test_api_connection() { local api_key="$1" local test_urls=( "https://api2.xcodecli.com" "https://api.xcodecli.com" ) print_info "Testing API connections..." >&2 for base_url in "${test_urls[@]}"; do print_info "Testing $base_url..." >&2 local test_endpoint="$base_url/v1/models" # Test a simple request to the API local response response=$(curl -s -w "%{http_code}" -o /tmp/claude_test_response \ -X GET "$test_endpoint" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $api_key" \ 2>/dev/null || echo "000") if [ "$response" = "200" ]; then local model_count model_count=$(get_model_count /tmp/claude_test_response) if [ "$model_count" -gt "0" ]; then print_success "API connection successful! Found $model_count models at $base_url" >&2 rm -f /tmp/claude_test_response echo "$base_url" return 0 else print_warning "API responded but no models found at $base_url" >&2 fi elif [ "$response" = "401" ]; then print_warning "API key authentication failed for $base_url" >&2 elif [ "$response" = "000" ]; then print_warning "Cannot connect to $base_url" >&2 else print_warning "API test failed for $base_url with HTTP status: $response" >&2 fi rm -f /tmp/claude_test_response done print_error "All API connections failed. Please check your API key and internet connection." >&2 return 1 } # Function to create Claude Code settings create_settings() { local base_url="$1" local api_key="$2" # Create JSON using jq to ensure proper escaping local settings_json settings_json=$(jq -n \ --arg base_url "$base_url" \ --arg api_key "$api_key" \ '{ "env": { "ANTHROPIC_AUTH_TOKEN": $api_key, "ANTHROPIC_BASE_URL": $base_url, "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", "DISABLE_TELEMETRY": "1", "DISABLE_ERROR_REPORTING": "1", "API_TIMEOUT_MS": "600000" }, "permissions": { "allow": [], "deny": [] }, "model": "opus", "alwaysThinkingEnabled": true }') # Validate JSON if ! echo "$settings_json" | jq . >/dev/null 2>&1; then print_error "Generated settings JSON is invalid" return 1 fi # Write settings file echo "$settings_json" >"$CLAUDE_SETTINGS_FILE" print_success "Claude Code settings written to: $CLAUDE_SETTINGS_FILE" # Write config.json for VSCode Claude extension local config_json_path="$CLAUDE_CONFIG_DIR/config.json" cat <"$config_json_path" { "primaryApiKey": "xcodecli" } EOF print_success "VSCode Claude config written to: $config_json_path" # Write environment variables to shell config local rc_file rc_file=$(get_shell_rc) print_info "Writing environment variables to: $rc_file" write_env_to_shell "ANTHROPIC_BASE_URL" "$base_url" write_env_to_shell "ANTHROPIC_AUTH_TOKEN" "$api_key" print_success "Environment variables written to shell config" } # Function to display current settings display_settings() { if [ -f "$CLAUDE_SETTINGS_FILE" ]; then print_info "Current Claude Code settings:" echo "----------------------------------------" cat "$CLAUDE_SETTINGS_FILE" | jq . echo "----------------------------------------" else print_info "No existing Claude Code settings found." fi } # Main function main() { print_info "Claude Code Configuration Script for XCodeCLI" echo "=======================================================" echo # Check dependencies check_jq # Parse command line arguments local api_key="" local test_only=false local show_settings=false # Check for environment variable API_KEY if [ -n "$API_KEY" ]; then api_key="$API_KEY" fi while [[ $# -gt 0 ]]; do case $1 in -k | --key) api_key="$2" shift 2 ;; -t | --test) test_only=true shift ;; -s | --show) show_settings=true shift ;; -h | --help) cat </dev/null 2>&1; then echo "" print_warning "$TOOL_NAME 未安装" read -p "是否立即安装? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then print_info "已取消" exit 0 fi install_tool || exit 1 else print_success "$TOOL_NAME 已安装" fi # Interactive mode if no API key provided if [ -z "$api_key" ]; then print_info "Interactive setup mode" echo # Get API key while [ -z "$api_key" ]; do read -p "Enter your API key: " api_key if [ -z "$api_key" ]; then print_warning "API key is required" elif ! validate_api_key "$api_key"; then api_key="" fi done fi # Validate inputs if [ -z "$api_key" ]; then print_error "API key is required" print_info "Use --help for usage information" exit 1 fi # Validate API key if ! validate_api_key "$api_key"; then exit 1 fi print_info "API Key: ${api_key:0:8}...${api_key: -4}" echo # Test API connection and get working base URL local base_url base_url=$(test_api_connection "$api_key") if [ $? -ne 0 ] || [ -z "$base_url" ]; then if [ "$test_only" = true ]; then exit 1 fi read -p "All API tests failed. Continue anyway? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then print_info "Setup cancelled" exit 1 fi # If user chooses to continue anyway, use default URL base_url="$DEFAULT_BASE_URL" print_warning "Using default URL: $base_url" else print_info "Selected working base URL: $base_url" fi # Exit if test only if [ "$test_only" = true ]; then print_success "API test completed successfully" exit 0 fi # Create settings directory create_settings_dir # Backup existing settings backup_settings # Create new settings if create_settings "$base_url" "$api_key"; then echo print_success "Claude Code has been configured successfully!" print_info "You can now use Claude Code with your API router." print_info "" print_info "To verify the setup, run:" print_info " claude --version" print_info "" print_info "Configuration file location: $CLAUDE_SETTINGS_FILE" if [ -f "$CLAUDE_SETTINGS_FILE" ]; then echo print_info "Current settings:" cat "$CLAUDE_SETTINGS_FILE" | jq . fi echo print_warning "Please restart your terminal or run 'source $(get_shell_rc)' for environment variables to take effect." else print_error "Failed to create Claude Code settings" exit 1 fi } # Run main function main "$@"