From d8440a1ccf5b7f20328a72b60a31993ab0eeabac Mon Sep 17 00:00:00 2001 From: sususu98 Date: Fri, 26 Dec 2025 15:32:05 +0800 Subject: [PATCH] fix: replace claude setup symlinks with files --- CLAUDE.md | 8 +- ClaudeCode/setup-claude-code.ps1 | 221 +++++++++++++++++++++++++-- ClaudeCode/setup-claude-code.sh | 195 ++++++++++++++++++++++-- GeminiCLI/setup-gemini.ps1 | 229 +++++++++++++++++++++++++++- GeminiCLI/setup-gemini.sh | 232 ++++++++++++++++++++++++++++- codex/setup-codex.ps1 | 200 ++++++++++++++++++++++++- codex/setup-codex.sh | 184 +++++++++++++++++++++-- setup-claude-code.ps1 | 247 +++++++++++++++++++++++++++++-- setup-claude-code.sh | 241 +++++++++++++++++++++++++++++- setup.ps1 | 177 +++++++++++++++------- setup.sh | 200 ++++++++++++++++--------- 11 files changed, 1931 insertions(+), 203 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c0ff9a6..20d248e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,13 +28,13 @@ xcodecli-shells/ | Gemini CLI | `~/.gemini/.env` | ENV 文件 | `x-goog-api-key` | | Codex | `~/.codex/config.toml` + `auth.json` | TOML + JSON | `Authorization: Bearer` | -## 统一启动器 (setup.ps1) +## 统一启动器 (setup.ps1 / setup.sh) -Windows 专用的一站式安装配置工具: +跨平台一站式安装配置工具: -- 检测 Node.js/Bun 包管理器,缺失时引导安装 Bun +- 检测 Node.js 环境,缺失时使用 fnm 安装 - 显示三个工具的安装状态 -- 自动安装缺失的工具 (`npm install -g` 或 `bun add -g`) +- 自动安装缺失的工具 (`npm install -g`) - 下载并执行对应工具的配置脚本 ```powershell diff --git a/ClaudeCode/setup-claude-code.ps1 b/ClaudeCode/setup-claude-code.ps1 index fc9ccf5..3e49887 100644 --- a/ClaudeCode/setup-claude-code.ps1 +++ b/ClaudeCode/setup-claude-code.ps1 @@ -17,6 +17,19 @@ if (-not $ApiKey -and (Test-Path Variable:key)) { $ApiKey = $key } $DefaultBaseUrl = "https://api2.xcodecli.com" $ClaudeConfigDir = "$env:USERPROFILE\.claude" $ClaudeSettingsFile = "$ClaudeConfigDir\settings.json" +$ToolCommand = "claude" +$ToolPackage = "@anthropic-ai/claude-code" +$ToolName = "Claude Code" + +# ========== 工具函数 ========== +function Test-Command { + param([string]$Name) + return [bool](Get-Command $Name -ErrorAction SilentlyContinue) +} + +function Refresh-Path { + $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") +} # Function to write environment variable (User level) function Set-EnvVariable { @@ -56,6 +69,155 @@ function Write-Error { Write-Host " $Message" } +# ========== Node.js 环境检测 ========== +function Get-NodeVersion { + if (Test-Command "node") { + try { + $versionStr = (& node --version 2>$null) -replace 'v', '' + $major = [int]($versionStr -split '\.')[0] + return @{ Version = $versionStr; Major = $major } + } + catch { } + } + return $null +} + +function Install-Fnm { + Write-Host "" + Write-Info "正在安装 fnm (Fast Node Manager)..." + + try { + if (Test-Command "winget") { + Write-Info "使用 winget 安装 fnm..." + & winget install Schniz.fnm -e --accept-source-agreements --accept-package-agreements + } + else { + Write-Info "使用 PowerShell 脚本安装 fnm..." + Invoke-Expression "& { $(Invoke-RestMethod https://fnm.vercel.app/install.ps1) }" + } + + Refresh-Path + + if (Test-Command "fnm") { + Write-Success "fnm 安装成功!" + $fnmEnv = & fnm env --use-on-cd 2>$null + if ($fnmEnv) { + $fnmEnv | Out-String | Invoke-Expression + } + return $true + } + else { + Write-Warning "fnm 可能已安装,但需要重新打开终端才能生效" + Write-Info "请重新打开 PowerShell 后再运行此脚本" + return $false + } + } + catch { + Write-Error "fnm 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Install-NodeWithFnm { + Write-Info "使用 fnm 安装 Node.js 24.x..." + + try { + & fnm install 24 + & fnm use 24 + & fnm default 24 + Refresh-Path + + if (Test-Command "node") { + $nodeInfo = Get-NodeVersion + Write-Success "Node.js v$($nodeInfo.Version) 安装成功!" + return $true + } + else { + Write-Warning "Node.js 可能已安装,但需要重新打开终端才能生效" + return $false + } + } + catch { + Write-Error "Node.js 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Ensure-NodeEnvironment { + $nodeInfo = Get-NodeVersion + + if ($nodeInfo) { + Write-Info "检测到 Node.js v$($nodeInfo.Version)" + + if ($nodeInfo.Major -lt 20) { + Write-Warning "Node.js 版本过低 (需要 >= 20.x)" + + $upgrade = Read-Host "是否使用 fnm 安装 Node.js 24.x? (Y/n)" + if ($upgrade -eq "n" -or $upgrade -eq "N") { + Write-Error "Node.js 版本不满足要求,请手动升级后重试" + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm + } + + return $true + } + + Write-Warning "未检测到 Node.js" + Write-Info "将使用 fnm 安装 Node.js 24.x" + + $install = Read-Host "是否继续? (Y/n)" + if ($install -eq "n" -or $install -eq "N") { + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm +} + +function Install-Tool { + if (-not (Ensure-NodeEnvironment)) { + return $false + } + + Write-Info "使用 npm 安装 $ToolName..." + $installCmd = "npm install -g $ToolPackage" + Write-Host " 执行: $installCmd" -ForegroundColor Gray + + try { + Invoke-Expression $installCmd + $exitCode = $LASTEXITCODE + Refresh-Path + + if ($exitCode -ne 0) { + Write-Error "安装命令返回错误码: $exitCode" + return $false + } + + if (Test-Command $ToolCommand) { + Write-Success "$ToolName 安装成功!" + return $true + } + else { + Write-Warning "$ToolName 可能已安装,但需要重新打开终端才能生效" + $continue = Read-Host "是否继续进行配置? (Y/n)" + return ($continue -ne "n" -and $continue -ne "N") + } + } + catch { + Write-Error "安装失败: $($_.Exception.Message)" + return $false + } +} + # Function to show help function Show-Help { Write-Host @" @@ -116,7 +278,7 @@ function New-SettingsDirectory { # Function to validate API key format function Test-ApiKey { param([string]$ApiKey) - + if ($ApiKey -match '^[A-Za-z0-9_-]+$') { return $true } else { @@ -125,6 +287,15 @@ function Test-ApiKey { } } +# Function to extract model count from API response (supports both data[] and models[]) +function Get-ModelCount { + param([object]$Response) + if ($null -eq $Response) { return 0 } + if ($Response.data -is [Array] -and $Response.data.Count -gt 0) { return $Response.data.Count } + if ($Response.models -is [Array] -and $Response.models.Count -gt 0) { return $Response.models.Count } + return 0 +} + # Function to test API connection and return working base URL function Test-ApiConnection { param( @@ -150,8 +321,9 @@ function Test-ApiConnection { $uri = "$baseUrl/v1/models" $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers -ErrorAction Stop - if ($response.data -and $response.data.Count -gt 0) { - Write-Success "API connection successful! Found $($response.data.Count) models at $baseUrl" + $modelCount = Get-ModelCount -Response $response + if ($modelCount -gt 0) { + Write-Success "API connection successful! Found $modelCount models at $baseUrl" return $baseUrl } else { Write-Warning "API responded but no models found at $baseUrl" @@ -181,12 +353,19 @@ function New-Settings { $settings = @{ env = @{ - ANTHROPIC_BASE_URL = $BaseUrl ANTHROPIC_AUTH_TOKEN = $ApiKey - DISABLE_TELEMETRY = 1 - DISABLE_ERROR_REPORTING = 1 - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = 1 + ANTHROPIC_BASE_URL = $BaseUrl + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1" + DISABLE_TELEMETRY = "1" + DISABLE_ERROR_REPORTING = "1" + API_TIMEOUT_MS = "600000" } + permissions = @{ + allow = @() + deny = @() + } + model = "opus" + alwaysThinkingEnabled = $true } try { @@ -232,17 +411,36 @@ function Main { Write-Info "Claude Code Configuration Script for XCodeCLI" Write-Host "=======================================================" Write-Host "" - + # Handle command line arguments if ($Help) { Show-Help } - + if ($Show) { Show-Settings exit 0 } - + + # 检测工具是否已安装 + if (-not (Test-Command $ToolCommand)) { + Write-Host "" + Write-Warning "$ToolName 未安装" + $install = Read-Host "是否立即安装? (Y/n)" + + if ($install -eq "n" -or $install -eq "N") { + Write-Info "已取消" + exit 0 + } + + if (-not (Install-Tool)) { + exit 1 + } + } + else { + Write-Success "$ToolName 已安装" + } + # Interactive mode if no Key provided if (-not $ApiKey) { Write-Info "Interactive setup mode" @@ -329,6 +527,9 @@ function Main { $settings = Get-Content $ClaudeSettingsFile -Raw | ConvertFrom-Json $settings | ConvertTo-Json -Depth 10 } + + Write-Host "" + Write-Warning "Please restart your terminal for environment variables to take effect." } else { Write-Error "Failed to create Claude Code settings" exit 1 diff --git a/ClaudeCode/setup-claude-code.sh b/ClaudeCode/setup-claude-code.sh index a4492d3..24d4d95 100644 --- a/ClaudeCode/setup-claude-code.sh +++ b/ClaudeCode/setup-claude-code.sh @@ -17,9 +17,11 @@ NC='\033[0m' # No Color 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 环境变量配置 ========== -# 检测当前 shell 配置文件 get_shell_rc() { if [ -n "${ZSH_VERSION:-}" ] || [ "${SHELL##*/}" = "zsh" ]; then echo "$HOME/.zshrc" @@ -36,26 +38,21 @@ get_shell_rc() { fi } -# 写入环境变量到 shell 配置文件 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 @@ -63,11 +60,9 @@ write_env_to_shell() { fi echo "$export_line" >>"$tmp_file" - # 保留原文件权限 cat "$tmp_file" >"$rc_file" rm -f "$tmp_file" - # 立即生效 export "$var_name=$var_value" } @@ -88,6 +83,134 @@ 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 @@ -133,6 +256,25 @@ validate_api_key() { 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" @@ -159,7 +301,7 @@ test_api_connection() { if [ "$response" = "200" ]; then local model_count - model_count=$(cat /tmp/claude_test_response | jq -r '.data | length' 2>/dev/null || echo "0") + 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 @@ -195,12 +337,19 @@ create_settings() { --arg api_key "$api_key" \ '{ "env": { - "ANTHROPIC_BASE_URL": $base_url, "ANTHROPIC_AUTH_TOKEN": $api_key, - "DISABLE_TELEMETRY": 1, - "DISABLE_ERROR_REPORTING": 1, - "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1 - } + "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 @@ -317,6 +466,21 @@ EOF exit 0 fi + # 检测工具是否已安装 + if ! command -v "$TOOL_COMMAND" >/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" @@ -399,6 +563,9 @@ EOF 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 diff --git a/GeminiCLI/setup-gemini.ps1 b/GeminiCLI/setup-gemini.ps1 index a968042..115874f 100644 --- a/GeminiCLI/setup-gemini.ps1 +++ b/GeminiCLI/setup-gemini.ps1 @@ -17,6 +17,30 @@ if (-not $ApiKey -and (Test-Path Variable:key)) { $ApiKey = $key } $DefaultBaseUrl = "https://api2.xcodecli.com" $GeminiConfigDir = "$env:USERPROFILE\.gemini" $GeminiEnvFile = "$GeminiConfigDir\.env" +$ToolCommand = "gemini" +$ToolPackage = "@google/gemini-cli" +$ToolName = "Gemini CLI" + +# ========== 工具函数 ========== +function Test-Command { + param([string]$Name) + return [bool](Get-Command $Name -ErrorAction SilentlyContinue) +} + +function Refresh-Path { + $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") +} + +# Function to write environment variable (User level) +function Set-EnvVariable { + param( + [string]$Name, + [string]$Value + ) + [Environment]::SetEnvironmentVariable($Name, $Value, [System.EnvironmentVariableTarget]::Process) + [Environment]::SetEnvironmentVariable($Name, $Value, [System.EnvironmentVariableTarget]::User) + Write-Info "Environment variable set: $Name" +} # Color functions for output function Write-Info { @@ -43,6 +67,155 @@ function Write-Error { Write-Host " $Message" } +# ========== Node.js 环境检测 ========== +function Get-NodeVersion { + if (Test-Command "node") { + try { + $versionStr = (& node --version 2>$null) -replace 'v', '' + $major = [int]($versionStr -split '\.')[0] + return @{ Version = $versionStr; Major = $major } + } + catch { } + } + return $null +} + +function Install-Fnm { + Write-Host "" + Write-Info "正在安装 fnm (Fast Node Manager)..." + + try { + if (Test-Command "winget") { + Write-Info "使用 winget 安装 fnm..." + & winget install Schniz.fnm -e --accept-source-agreements --accept-package-agreements + } + else { + Write-Info "使用 PowerShell 脚本安装 fnm..." + Invoke-Expression "& { $(Invoke-RestMethod https://fnm.vercel.app/install.ps1) }" + } + + Refresh-Path + + if (Test-Command "fnm") { + Write-Success "fnm 安装成功!" + $fnmEnv = & fnm env --use-on-cd 2>$null + if ($fnmEnv) { + $fnmEnv | Out-String | Invoke-Expression + } + return $true + } + else { + Write-Warning "fnm 可能已安装,但需要重新打开终端才能生效" + Write-Info "请重新打开 PowerShell 后再运行此脚本" + return $false + } + } + catch { + Write-Error "fnm 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Install-NodeWithFnm { + Write-Info "使用 fnm 安装 Node.js 24.x..." + + try { + & fnm install 24 + & fnm use 24 + & fnm default 24 + Refresh-Path + + if (Test-Command "node") { + $nodeInfo = Get-NodeVersion + Write-Success "Node.js v$($nodeInfo.Version) 安装成功!" + return $true + } + else { + Write-Warning "Node.js 可能已安装,但需要重新打开终端才能生效" + return $false + } + } + catch { + Write-Error "Node.js 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Ensure-NodeEnvironment { + $nodeInfo = Get-NodeVersion + + if ($nodeInfo) { + Write-Info "检测到 Node.js v$($nodeInfo.Version)" + + if ($nodeInfo.Major -lt 20) { + Write-Warning "Node.js 版本过低 (需要 >= 20.x)" + + $upgrade = Read-Host "是否使用 fnm 安装 Node.js 24.x? (Y/n)" + if ($upgrade -eq "n" -or $upgrade -eq "N") { + Write-Error "Node.js 版本不满足要求,请手动升级后重试" + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm + } + + return $true + } + + Write-Warning "未检测到 Node.js" + Write-Info "将使用 fnm 安装 Node.js 24.x" + + $install = Read-Host "是否继续? (Y/n)" + if ($install -eq "n" -or $install -eq "N") { + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm +} + +function Install-Tool { + if (-not (Ensure-NodeEnvironment)) { + return $false + } + + Write-Info "使用 npm 安装 $ToolName..." + $installCmd = "npm install -g $ToolPackage" + Write-Host " 执行: $installCmd" -ForegroundColor Gray + + try { + Invoke-Expression $installCmd + $exitCode = $LASTEXITCODE + Refresh-Path + + if ($exitCode -ne 0) { + Write-Error "安装命令返回错误码: $exitCode" + return $false + } + + if (Test-Command $ToolCommand) { + Write-Success "$ToolName 安装成功!" + return $true + } + else { + Write-Warning "$ToolName 可能已安装,但需要重新打开终端才能生效" + $continue = Read-Host "是否继续进行配置? (Y/n)" + return ($continue -ne "n" -and $continue -ne "N") + } + } + catch { + Write-Error "安装失败: $($_.Exception.Message)" + return $false + } +} + # Function to show help function Show-Help { Write-Host @" @@ -106,7 +279,7 @@ function New-SettingsDirectory { # Function to validate API key format function Test-ApiKey { param([string]$ApiKey) - + if ($ApiKey -match '^[A-Za-z0-9_-]+$') { return $true } else { @@ -115,6 +288,15 @@ function Test-ApiKey { } } +# Function to extract model count from API response (supports both data[] and models[]) +function Get-ModelCount { + param([object]$Response) + if ($null -eq $Response) { return 0 } + if ($Response.data -is [Array] -and $Response.data.Count -gt 0) { return $Response.data.Count } + if ($Response.models -is [Array] -and $Response.models.Count -gt 0) { return $Response.models.Count } + return 0 +} + # Function to test API connection and return working base URL function Test-ApiConnection { param([string]$ApiKey) @@ -138,9 +320,8 @@ function Test-ApiConnection { $testEndpoint = "$baseUrl/v1/models" $response = Invoke-RestMethod -Uri $testEndpoint -Method Get -Headers $headers -ErrorAction Stop - # Check if response contains models (supports both data and models arrays) - if ($response.data -or $response.models) { - $modelCount = if ($response.data) { $response.data.Count } else { $response.models.Count } + $modelCount = Get-ModelCount -Response $response + if ($modelCount -gt 0) { Write-Success "API connection successful! Found $modelCount models at $baseUrl" return $baseUrl } else { @@ -168,14 +349,18 @@ function New-Settings { [string]$BaseUrl, [string]$ApiKey ) - + $envContent = @" -GOOGLE_GEMINI_BASE_URL="$BaseUrl" GEMINI_API_KEY="$ApiKey" -GEMINI_MODEL="gemini-3-pro-preview" +GOOGLE_GEMINI_BASE_URL="$BaseUrl" +GEMINI_MODEL="gemini-2.5-pro" "@ $settingsJson = @{ + ide = @{ + enabled = $true + hasSeenNudge = $true + } general = @{ previewFeatures = $true } @@ -193,6 +378,14 @@ GEMINI_MODEL="gemini-3-pro-preview" $settingsJsonPath = "$GeminiConfigDir\settings.json" $settingsJson | ConvertTo-Json -Depth 10 | Set-Content -Path $settingsJsonPath -Encoding UTF8 Write-Success "Gemini CLI settings written to: $settingsJsonPath" + + # Set environment variables + Write-Info "Setting environment variables..." + Set-EnvVariable -Name "GEMINI_API_KEY" -Value $ApiKey + Set-EnvVariable -Name "GOOGLE_GEMINI_BASE_URL" -Value $BaseUrl + Set-EnvVariable -Name "GEMINI_MODEL" -Value "gemini-2.5-pro" + Write-Success "Environment variables configured" + return $true } catch { @@ -231,6 +424,25 @@ function Main { exit 0 } + # 检测工具是否已安装 + if (-not (Test-Command $ToolCommand)) { + Write-Host "" + Write-Warning "$ToolName 未安装" + $install = Read-Host "是否立即安装? (Y/n)" + + if ($install -eq "n" -or $install -eq "N") { + Write-Info "已取消" + exit 0 + } + + if (-not (Install-Tool)) { + exit 1 + } + } + else { + Write-Success "$ToolName 已安装" + } + # Interactive mode if no API key provided if ([string]::IsNullOrWhiteSpace($ApiKey)) { Write-Info "Interactive setup mode" @@ -316,6 +528,9 @@ function Main { Write-Info "Current settings:" Get-Content $GeminiEnvFile } + + Write-Host "" + Write-Warning "Please restart your terminal for environment variables to take effect." } else { Write-Error "Failed to create Gemini CLI settings" exit 1 diff --git a/GeminiCLI/setup-gemini.sh b/GeminiCLI/setup-gemini.sh index a26d39d..de9f3ab 100755 --- a/GeminiCLI/setup-gemini.sh +++ b/GeminiCLI/setup-gemini.sh @@ -17,6 +17,51 @@ NC='\033[0m' # No Color DEFAULT_BASE_URL="https://api2.xcodecli.com" GEMINI_CONFIG_DIR="$HOME/.gemini" GEMINI_ENV_FILE="$GEMINI_CONFIG_DIR/.env" +TOOL_COMMAND="gemini" +TOOL_PACKAGE="@google/gemini-cli" +TOOL_NAME="Gemini CLI" + +# ========== 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() { @@ -35,6 +80,132 @@ 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 + 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 + 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 backup existing settings backup_settings() { if [ -f "$GEMINI_ENV_FILE" ]; then @@ -68,6 +239,25 @@ validate_api_key() { 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" @@ -93,10 +283,9 @@ test_api_connection() { 2>/dev/null || echo "000") if [ "$response" = "200" ]; then - # Check if response contains models (supports both {data:[]} and {models:[]} formats) - if grep -qE '"(data|models)"' /tmp/gemini_test_response 2>/dev/null; then - local model_count - model_count=$(grep -oE '"id"[[:space:]]*:' /tmp/gemini_test_response | wc -l | tr -d ' ') + local model_count + model_count=$(get_model_count /tmp/gemini_test_response) + if [ "$model_count" -gt "0" ]; then print_success "API connection successful! Found $model_count models at $base_url" >&2 rm -f /tmp/gemini_test_response echo "$base_url" @@ -126,9 +315,9 @@ create_settings_file() { # Write to .env file cat <"$GEMINI_ENV_FILE" -GOOGLE_GEMINI_BASE_URL="$base_url" GEMINI_API_KEY="$api_key" -GEMINI_MODEL="gemini-3-pro-preview" +GOOGLE_GEMINI_BASE_URL="$base_url" +GEMINI_MODEL="gemini-2.5-pro" EOF print_success "Gemini CLI settings written to: $GEMINI_ENV_FILE" @@ -136,6 +325,10 @@ EOF local settings_json_path="$GEMINI_CONFIG_DIR/settings.json" cat <"$settings_json_path" { + "ide": { + "enabled": true, + "hasSeenNudge": true + }, "general": { "previewFeatures": true }, @@ -147,6 +340,15 @@ EOF } EOF print_success "Gemini CLI settings written to: $settings_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 "GEMINI_API_KEY" "$api_key" + write_env_to_shell "GOOGLE_GEMINI_BASE_URL" "$base_url" + write_env_to_shell "GEMINI_MODEL" "gemini-2.5-pro" + print_success "Environment variables written to shell config" } # Function to display current settings @@ -232,6 +434,21 @@ EOF exit 0 fi + # 检测工具是否已安装 + if ! command -v "$TOOL_COMMAND" >/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" @@ -315,6 +532,9 @@ EOF print_info "Current settings:" cat "$GEMINI_ENV_FILE" fi + + echo + print_warning "Please restart your terminal or run 'source $(get_shell_rc)' for environment variables to take effect." } # Run main function diff --git a/codex/setup-codex.ps1 b/codex/setup-codex.ps1 index 711c07f..7c1887e 100644 --- a/codex/setup-codex.ps1 +++ b/codex/setup-codex.ps1 @@ -17,6 +17,19 @@ if (-not $ApiKey -and (Test-Path Variable:key)) { $ApiKey = $key } $DefaultBaseUrl = "https://api2.xcodecli.com" $CodexConfigDir = "$env:USERPROFILE\.codex" $CodexConfigFile = "$CodexConfigDir\config.toml" +$ToolCommand = "codex" +$ToolPackage = "@openai/codex" +$ToolName = "Codex" + +# ========== 工具函数 ========== +function Test-Command { + param([string]$Name) + return [bool](Get-Command $Name -ErrorAction SilentlyContinue) +} + +function Refresh-Path { + $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") +} # Function to write environment variable (User level) function Set-EnvVariable { @@ -56,6 +69,155 @@ function Write-Error { Write-Host " $Message" } +# ========== Node.js 环境检测 ========== +function Get-NodeVersion { + if (Test-Command "node") { + try { + $versionStr = (& node --version 2>$null) -replace 'v', '' + $major = [int]($versionStr -split '\.')[0] + return @{ Version = $versionStr; Major = $major } + } + catch { } + } + return $null +} + +function Install-Fnm { + Write-Host "" + Write-Info "正在安装 fnm (Fast Node Manager)..." + + try { + if (Test-Command "winget") { + Write-Info "使用 winget 安装 fnm..." + & winget install Schniz.fnm -e --accept-source-agreements --accept-package-agreements + } + else { + Write-Info "使用 PowerShell 脚本安装 fnm..." + Invoke-Expression "& { $(Invoke-RestMethod https://fnm.vercel.app/install.ps1) }" + } + + Refresh-Path + + if (Test-Command "fnm") { + Write-Success "fnm 安装成功!" + $fnmEnv = & fnm env --use-on-cd 2>$null + if ($fnmEnv) { + $fnmEnv | Out-String | Invoke-Expression + } + return $true + } + else { + Write-Warning "fnm 可能已安装,但需要重新打开终端才能生效" + Write-Info "请重新打开 PowerShell 后再运行此脚本" + return $false + } + } + catch { + Write-Error "fnm 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Install-NodeWithFnm { + Write-Info "使用 fnm 安装 Node.js 24.x..." + + try { + & fnm install 24 + & fnm use 24 + & fnm default 24 + Refresh-Path + + if (Test-Command "node") { + $nodeInfo = Get-NodeVersion + Write-Success "Node.js v$($nodeInfo.Version) 安装成功!" + return $true + } + else { + Write-Warning "Node.js 可能已安装,但需要重新打开终端才能生效" + return $false + } + } + catch { + Write-Error "Node.js 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Ensure-NodeEnvironment { + $nodeInfo = Get-NodeVersion + + if ($nodeInfo) { + Write-Info "检测到 Node.js v$($nodeInfo.Version)" + + if ($nodeInfo.Major -lt 20) { + Write-Warning "Node.js 版本过低 (需要 >= 20.x)" + + $upgrade = Read-Host "是否使用 fnm 安装 Node.js 24.x? (Y/n)" + if ($upgrade -eq "n" -or $upgrade -eq "N") { + Write-Error "Node.js 版本不满足要求,请手动升级后重试" + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm + } + + return $true + } + + Write-Warning "未检测到 Node.js" + Write-Info "将使用 fnm 安装 Node.js 24.x" + + $install = Read-Host "是否继续? (Y/n)" + if ($install -eq "n" -or $install -eq "N") { + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm +} + +function Install-Tool { + if (-not (Ensure-NodeEnvironment)) { + return $false + } + + Write-Info "使用 npm 安装 $ToolName..." + $installCmd = "npm install -g $ToolPackage" + Write-Host " 执行: $installCmd" -ForegroundColor Gray + + try { + Invoke-Expression $installCmd + $exitCode = $LASTEXITCODE + Refresh-Path + + if ($exitCode -ne 0) { + Write-Error "安装命令返回错误码: $exitCode" + return $false + } + + if (Test-Command $ToolCommand) { + Write-Success "$ToolName 安装成功!" + return $true + } + else { + Write-Warning "$ToolName 可能已安装,但需要重新打开终端才能生效" + $continue = Read-Host "是否继续进行配置? (Y/n)" + return ($continue -ne "n" -and $continue -ne "N") + } + } + catch { + Write-Error "安装失败: $($_.Exception.Message)" + return $false + } +} + # Function to show help function Show-Help { Write-Host @" @@ -128,6 +290,15 @@ function Test-ApiKey { } } +# Function to extract model count from API response (supports both data[] and models[]) +function Get-ModelCount { + param([object]$Response) + if ($null -eq $Response) { return 0 } + if ($Response.data -is [Array] -and $Response.data.Count -gt 0) { return $Response.data.Count } + if ($Response.models -is [Array] -and $Response.models.Count -gt 0) { return $Response.models.Count } + return 0 +} + # Function to test API connection and return working base URL function Test-ApiConnection { param([string]$ApiKey) @@ -151,8 +322,8 @@ function Test-ApiConnection { $testEndpoint = "$baseUrl/v1/models" $response = Invoke-RestMethod -Uri $testEndpoint -Method Get -Headers $headers -ErrorAction Stop - if ($response.data) { - $modelCount = $response.data.Count + $modelCount = Get-ModelCount -Response $response + if ($modelCount -gt 0) { Write-Success "API connection successful! Found $modelCount models at $baseUrl" return $baseUrl } else { @@ -201,7 +372,8 @@ env_key = "XCODECLI_OAI_KEY" Write-Success "Codex configuration written to: $CodexConfigFile" $authJsonPath = "$CodexConfigDir\auth.json" - '{ "OPENAI_API_KEY": null }' | Set-Content -Path $authJsonPath -Encoding UTF8 + $authJson = @{ OPENAI_API_KEY = $ApiKey } | ConvertTo-Json + $authJson | Set-Content -Path $authJsonPath -Encoding UTF8 Write-Success "Codex auth file written to: $authJsonPath" # Set environment variable XCODECLI_OAI_KEY @@ -266,6 +438,25 @@ function Main { exit 0 } + # 检测工具是否已安装 + if (-not (Test-Command $ToolCommand)) { + Write-Host "" + Write-Warning "$ToolName 未安装" + $install = Read-Host "是否立即安装? (Y/n)" + + if ($install -eq "n" -or $install -eq "N") { + Write-Info "已取消" + exit 0 + } + + if (-not (Install-Tool)) { + exit 1 + } + } + else { + Write-Success "$ToolName 已安装" + } + # Interactive mode if no API key provided if ([string]::IsNullOrWhiteSpace($ApiKey)) { Write-Info "Interactive setup mode" @@ -346,6 +537,9 @@ function Main { Write-Host "" Show-Settings + + Write-Host "" + Write-Warning "Please restart your terminal for environment variables to take effect." } else { Write-Error "Failed to create Codex settings" exit 1 diff --git a/codex/setup-codex.sh b/codex/setup-codex.sh index 895c581..1bea3fe 100644 --- a/codex/setup-codex.sh +++ b/codex/setup-codex.sh @@ -26,9 +26,11 @@ print_error() { TEST_ONLY=false SHOW_SETTINGS=false DEFAULT_BASE_URL="https://api2.xcodecli.com" +TOOL_COMMAND="codex" +TOOL_PACKAGE="@openai/codex" +TOOL_NAME="Codex" # ========== Shell 环境变量配置 ========== -# 检测当前 shell 配置文件 get_shell_rc() { if [ -n "${ZSH_VERSION:-}" ] || [ "${SHELL##*/}" = "zsh" ]; then echo "$HOME/.zshrc" @@ -45,26 +47,21 @@ get_shell_rc() { fi } -# 写入环境变量到 shell 配置文件 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 @@ -72,14 +69,138 @@ write_env_to_shell() { fi echo "$export_line" >>"$tmp_file" - # 保留原文件权限 cat "$tmp_file" >"$rc_file" rm -f "$tmp_file" - # 立即生效 export "$var_name=$var_value" } +# ========== 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 + 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 + 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 validate API key format validate_api_key() { local api_key="$1" @@ -90,6 +211,25 @@ validate_api_key() { 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" +} + # Check for environment variable API_KEY if [ -n "$API_KEY" ]; then API_KEY_FROM_ENV="$API_KEY" @@ -199,9 +339,9 @@ test_api_connection() { 2>/dev/null || echo "000") if [ "$response" = "200" ]; then - if grep -qE '"data".*\[|"object"' /tmp/codex_test_response 2>/dev/null; then - local model_count - model_count=$(grep -oE '"id"[[:space:]]*:' /tmp/codex_test_response | wc -l | tr -d ' ') + local model_count + model_count=$(get_model_count /tmp/codex_test_response) + if [ "$model_count" -gt "0" ]; then print_success "API connection successful! Found $model_count models at $base_url" >&2 rm -f /tmp/codex_test_response echo "$base_url" @@ -248,10 +388,10 @@ requires_openai_auth = true env_key = "XCODECLI_OAI_KEY" EOF - # Create auth.json with OPENAI_API_KEY set to null + # Create auth.json with OPENAI_API_KEY cat >"$HOME/.codex/auth.json" </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" @@ -396,6 +551,9 @@ main() { # Show current settings echo "" show_current_settings + + echo "" + print_warning "Please restart your terminal or run 'source $(get_shell_rc)' for environment variables to take effect." } # Run main function diff --git a/setup-claude-code.ps1 b/setup-claude-code.ps1 index e17fc8b..3e49887 100644 --- a/setup-claude-code.ps1 +++ b/setup-claude-code.ps1 @@ -17,6 +17,32 @@ if (-not $ApiKey -and (Test-Path Variable:key)) { $ApiKey = $key } $DefaultBaseUrl = "https://api2.xcodecli.com" $ClaudeConfigDir = "$env:USERPROFILE\.claude" $ClaudeSettingsFile = "$ClaudeConfigDir\settings.json" +$ToolCommand = "claude" +$ToolPackage = "@anthropic-ai/claude-code" +$ToolName = "Claude Code" + +# ========== 工具函数 ========== +function Test-Command { + param([string]$Name) + return [bool](Get-Command $Name -ErrorAction SilentlyContinue) +} + +function Refresh-Path { + $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") +} + +# Function to write environment variable (User level) +function Set-EnvVariable { + param( + [string]$Name, + [string]$Value + ) + # Set for current session + [Environment]::SetEnvironmentVariable($Name, $Value, [System.EnvironmentVariableTarget]::Process) + # Set permanently for user + [Environment]::SetEnvironmentVariable($Name, $Value, [System.EnvironmentVariableTarget]::User) + Write-Info "Environment variable set: $Name" +} # Color functions for output function Write-Info { @@ -43,6 +69,155 @@ function Write-Error { Write-Host " $Message" } +# ========== Node.js 环境检测 ========== +function Get-NodeVersion { + if (Test-Command "node") { + try { + $versionStr = (& node --version 2>$null) -replace 'v', '' + $major = [int]($versionStr -split '\.')[0] + return @{ Version = $versionStr; Major = $major } + } + catch { } + } + return $null +} + +function Install-Fnm { + Write-Host "" + Write-Info "正在安装 fnm (Fast Node Manager)..." + + try { + if (Test-Command "winget") { + Write-Info "使用 winget 安装 fnm..." + & winget install Schniz.fnm -e --accept-source-agreements --accept-package-agreements + } + else { + Write-Info "使用 PowerShell 脚本安装 fnm..." + Invoke-Expression "& { $(Invoke-RestMethod https://fnm.vercel.app/install.ps1) }" + } + + Refresh-Path + + if (Test-Command "fnm") { + Write-Success "fnm 安装成功!" + $fnmEnv = & fnm env --use-on-cd 2>$null + if ($fnmEnv) { + $fnmEnv | Out-String | Invoke-Expression + } + return $true + } + else { + Write-Warning "fnm 可能已安装,但需要重新打开终端才能生效" + Write-Info "请重新打开 PowerShell 后再运行此脚本" + return $false + } + } + catch { + Write-Error "fnm 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Install-NodeWithFnm { + Write-Info "使用 fnm 安装 Node.js 24.x..." + + try { + & fnm install 24 + & fnm use 24 + & fnm default 24 + Refresh-Path + + if (Test-Command "node") { + $nodeInfo = Get-NodeVersion + Write-Success "Node.js v$($nodeInfo.Version) 安装成功!" + return $true + } + else { + Write-Warning "Node.js 可能已安装,但需要重新打开终端才能生效" + return $false + } + } + catch { + Write-Error "Node.js 安装失败: $($_.Exception.Message)" + return $false + } +} + +function Ensure-NodeEnvironment { + $nodeInfo = Get-NodeVersion + + if ($nodeInfo) { + Write-Info "检测到 Node.js v$($nodeInfo.Version)" + + if ($nodeInfo.Major -lt 20) { + Write-Warning "Node.js 版本过低 (需要 >= 20.x)" + + $upgrade = Read-Host "是否使用 fnm 安装 Node.js 24.x? (Y/n)" + if ($upgrade -eq "n" -or $upgrade -eq "N") { + Write-Error "Node.js 版本不满足要求,请手动升级后重试" + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm + } + + return $true + } + + Write-Warning "未检测到 Node.js" + Write-Info "将使用 fnm 安装 Node.js 24.x" + + $install = Read-Host "是否继续? (Y/n)" + if ($install -eq "n" -or $install -eq "N") { + return $false + } + + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm +} + +function Install-Tool { + if (-not (Ensure-NodeEnvironment)) { + return $false + } + + Write-Info "使用 npm 安装 $ToolName..." + $installCmd = "npm install -g $ToolPackage" + Write-Host " 执行: $installCmd" -ForegroundColor Gray + + try { + Invoke-Expression $installCmd + $exitCode = $LASTEXITCODE + Refresh-Path + + if ($exitCode -ne 0) { + Write-Error "安装命令返回错误码: $exitCode" + return $false + } + + if (Test-Command $ToolCommand) { + Write-Success "$ToolName 安装成功!" + return $true + } + else { + Write-Warning "$ToolName 可能已安装,但需要重新打开终端才能生效" + $continue = Read-Host "是否继续进行配置? (Y/n)" + return ($continue -ne "n" -and $continue -ne "N") + } + } + catch { + Write-Error "安装失败: $($_.Exception.Message)" + return $false + } +} + # Function to show help function Show-Help { Write-Host @" @@ -103,7 +278,7 @@ function New-SettingsDirectory { # Function to validate API key format function Test-ApiKey { param([string]$ApiKey) - + if ($ApiKey -match '^[A-Za-z0-9_-]+$') { return $true } else { @@ -112,6 +287,15 @@ function Test-ApiKey { } } +# Function to extract model count from API response (supports both data[] and models[]) +function Get-ModelCount { + param([object]$Response) + if ($null -eq $Response) { return 0 } + if ($Response.data -is [Array] -and $Response.data.Count -gt 0) { return $Response.data.Count } + if ($Response.models -is [Array] -and $Response.models.Count -gt 0) { return $Response.models.Count } + return 0 +} + # Function to test API connection and return working base URL function Test-ApiConnection { param( @@ -137,8 +321,9 @@ function Test-ApiConnection { $uri = "$baseUrl/v1/models" $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers -ErrorAction Stop - if ($response.data -and $response.data.Count -gt 0) { - Write-Success "API connection successful! Found $($response.data.Count) models at $baseUrl" + $modelCount = Get-ModelCount -Response $response + if ($modelCount -gt 0) { + Write-Success "API connection successful! Found $modelCount models at $baseUrl" return $baseUrl } else { Write-Warning "API responded but no models found at $baseUrl" @@ -165,17 +350,24 @@ function New-Settings { [string]$BaseUrl, [string]$ApiKey ) - + $settings = @{ env = @{ - ANTHROPIC_BASE_URL = $BaseUrl ANTHROPIC_AUTH_TOKEN = $ApiKey - DISABLE_TELEMETRY = 1 - DISABLE_ERROR_REPORTING = 1 - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = 1 + ANTHROPIC_BASE_URL = $BaseUrl + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1" + DISABLE_TELEMETRY = "1" + DISABLE_ERROR_REPORTING = "1" + API_TIMEOUT_MS = "600000" } + permissions = @{ + allow = @() + deny = @() + } + model = "opus" + alwaysThinkingEnabled = $true } - + try { $json = $settings | ConvertTo-Json -Depth 10 Set-Content -Path $ClaudeSettingsFile -Value $json -Encoding UTF8 @@ -186,6 +378,13 @@ function New-Settings { $configJson = @{ primaryApiKey = "xcodecli" } $configJson | ConvertTo-Json | Set-Content -Path $configJsonPath -Encoding UTF8 Write-Success "VSCode Claude config written to: $configJsonPath" + + # Set environment variables + Write-Info "Setting environment variables..." + Set-EnvVariable -Name "ANTHROPIC_BASE_URL" -Value $BaseUrl + Set-EnvVariable -Name "ANTHROPIC_AUTH_TOKEN" -Value $ApiKey + Write-Success "Environment variables configured" + return $true } catch { @@ -212,17 +411,36 @@ function Main { Write-Info "Claude Code Configuration Script for XCodeCLI" Write-Host "=======================================================" Write-Host "" - + # Handle command line arguments if ($Help) { Show-Help } - + if ($Show) { Show-Settings exit 0 } - + + # 检测工具是否已安装 + if (-not (Test-Command $ToolCommand)) { + Write-Host "" + Write-Warning "$ToolName 未安装" + $install = Read-Host "是否立即安装? (Y/n)" + + if ($install -eq "n" -or $install -eq "N") { + Write-Info "已取消" + exit 0 + } + + if (-not (Install-Tool)) { + exit 1 + } + } + else { + Write-Success "$ToolName 已安装" + } + # Interactive mode if no Key provided if (-not $ApiKey) { Write-Info "Interactive setup mode" @@ -302,13 +520,16 @@ function Main { Write-Info " claude --version" Write-Info "" Write-Info "Configuration file location: $ClaudeSettingsFile" - + if (Test-Path $ClaudeSettingsFile) { Write-Host "" Write-Info "Current settings:" $settings = Get-Content $ClaudeSettingsFile -Raw | ConvertFrom-Json $settings | ConvertTo-Json -Depth 10 } + + Write-Host "" + Write-Warning "Please restart your terminal for environment variables to take effect." } else { Write-Error "Failed to create Claude Code settings" exit 1 diff --git a/setup-claude-code.sh b/setup-claude-code.sh index d058beb..24d4d95 100644 --- a/setup-claude-code.sh +++ b/setup-claude-code.sh @@ -17,6 +17,54 @@ NC='\033[0m' # No Color 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() { @@ -35,6 +83,134 @@ 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 @@ -80,6 +256,25 @@ validate_api_key() { 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" @@ -106,7 +301,7 @@ test_api_connection() { if [ "$response" = "200" ]; then local model_count - model_count=$(cat /tmp/claude_test_response | jq -r '.data | length' 2>/dev/null || echo "0") + 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 @@ -142,13 +337,19 @@ create_settings() { --arg api_key "$api_key" \ '{ "env": { - "ANTHROPIC_BASE_URL": $base_url, "ANTHROPIC_AUTH_TOKEN": $api_key, - "DISABLE_TELEMETRY": 1, - "DISABLE_ERROR_REPORTING": 1, - "API_TIMEOUT_MS": 600000, - "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1 - } + "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 @@ -169,6 +370,14 @@ create_settings() { } 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 @@ -257,6 +466,21 @@ EOF exit 0 fi + # 检测工具是否已安装 + if ! command -v "$TOOL_COMMAND" >/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" @@ -339,6 +563,9 @@ EOF 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 diff --git a/setup.ps1 b/setup.ps1 index 77be9e4..352e1c5 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -49,6 +49,10 @@ Options: -ApiKey 预设 API 密钥(可选) -Help 显示此帮助信息 +环境要求: + - Node.js >= 20.x + - 若未安装 Node.js,脚本会自动通过 fnm 安装 Node.js 24.x + 一行命令快速使用: `$key='YOUR_API_KEY'; iwr -useb https://gitea.sususu.cf/sususu/xcodecli-shells/raw/branch/main/setup.ps1 | iex "@ @@ -65,91 +69,144 @@ function Refresh-Path { $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") } -# ========== 包管理器检测 ========== -function Get-PackageManager { - # 优先检测 Node.js/npm +# ========== Node.js 版本检测 ========== +function Get-NodeVersion { if (Test-Command "node") { try { - $nodeVersion = (& node --version 2>$null) -replace 'v', '' - Write-Info "检测到 Node.js v$nodeVersion" - return @{ Type = "npm"; Cmd = "npm install -g" } + $versionStr = (& node --version 2>$null) -replace 'v', '' + $major = [int]($versionStr -split '\.')[0] + return @{ Version = $versionStr; Major = $major } } catch { } } - - # 其次检测 Bun - if (Test-Command "bun") { - try { - $bunVersion = & bun --version 2>$null - Write-Info "检测到 Bun v$bunVersion" - return @{ Type = "bun"; Cmd = "bun add -g" } - } - catch { } - } - return $null } -# ========== Bun 安装引导 ========== -function Install-Bun { - Write-Host "" - Write-Warning "未检测到 Node.js 或 Bun" - Write-Info "推荐安装 Bun(轻量级 JavaScript 运行时)" - Write-Host "" - Write-Host " 安装命令: " -NoNewline - Write-Host 'powershell -c "irm bun.sh/install.ps1|iex"' -ForegroundColor Cyan - Write-Host "" - Write-Host " 要求: Windows 10 version 1809 或更高" -ForegroundColor Gray +# ========== fnm 安装 ========== +function Install-Fnm { Write-Host "" + Write-Info "正在安装 fnm (Fast Node Manager)..." - $install = Read-Host "是否立即安装 Bun? (Y/n)" - if ($install -eq "n" -or $install -eq "N") { - return $false - } - - Write-Info "正在安装 Bun..." try { - Invoke-Expression 'irm bun.sh/install.ps1 | iex' + # 使用 winget 安装 fnm + if (Test-Command "winget") { + Write-Info "使用 winget 安装 fnm..." + & winget install Schniz.fnm -e --accept-source-agreements --accept-package-agreements + } + else { + # 备选:使用 cargo 或手动下载 + Write-Info "使用 PowerShell 脚本安装 fnm..." + Invoke-Expression "& { $(Invoke-RestMethod https://fnm.vercel.app/install.ps1) }" + } + Refresh-Path - if (Test-Command "bun") { - Write-Success "Bun 安装成功!" + # 配置 fnm 环境 + if (Test-Command "fnm") { + Write-Success "fnm 安装成功!" + # 初始化 fnm + $fnmEnv = & fnm env --use-on-cd 2>$null + if ($fnmEnv) { + $fnmEnv | Out-String | Invoke-Expression + } return $true } else { - Write-Warning "Bun 可能已安装,但需要重新打开终端才能生效" + Write-Warning "fnm 可能已安装,但需要重新打开终端才能生效" Write-Info "请重新打开 PowerShell 后再运行此脚本" return $false } } catch { - Write-Error "Bun 安装失败: $($_.Exception.Message)" + Write-Error "fnm 安装失败: $($_.Exception.Message)" return $false } } +# ========== 使用 fnm 安装 Node.js ========== +function Install-NodeWithFnm { + Write-Info "使用 fnm 安装 Node.js 24.x..." + + try { + & fnm install 24 + & fnm use 24 + & fnm default 24 + Refresh-Path + + if (Test-Command "node") { + $nodeInfo = Get-NodeVersion + Write-Success "Node.js v$($nodeInfo.Version) 安装成功!" + return $true + } + else { + Write-Warning "Node.js 可能已安装,但需要重新打开终端才能生效" + return $false + } + } + catch { + Write-Error "Node.js 安装失败: $($_.Exception.Message)" + return $false + } +} + +# ========== 确保 Node.js 环境就绪 ========== +function Ensure-NodeEnvironment { + $nodeInfo = Get-NodeVersion + + if ($nodeInfo) { + Write-Info "检测到 Node.js v$($nodeInfo.Version)" + + if ($nodeInfo.Major -lt 20) { + Write-Warning "Node.js 版本过低 (需要 >= 20.x)" + Write-Info "请升级 Node.js 或让脚本使用 fnm 安装新版本" + + $upgrade = Read-Host "是否使用 fnm 安装 Node.js 24.x? (Y/n)" + if ($upgrade -eq "n" -or $upgrade -eq "N") { + Write-Error "Node.js 版本不满足要求,请手动升级后重试" + return $false + } + + # 安装 fnm (如果未安装) + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm + } + + return $true + } + + # 未安装 Node.js + Write-Warning "未检测到 Node.js" + Write-Info "将使用 fnm 安装 Node.js 24.x" + + $install = Read-Host "是否继续? (Y/n)" + if ($install -eq "n" -or $install -eq "N") { + return $false + } + + # 安装 fnm + if (-not (Test-Command "fnm")) { + if (-not (Install-Fnm)) { return $false } + } + + return Install-NodeWithFnm +} + # ========== 工具安装 ========== function Install-Tool { param( [hashtable]$Tool ) - # 获取包管理器 - $pm = Get-PackageManager - if (-not $pm) { - if (-not (Install-Bun)) { - return $false - } - Refresh-Path - $pm = Get-PackageManager - if (-not $pm) { - Write-Error "仍未检测到可用的包管理器" - return $false - } + # 确保 Node.js 环境就绪 + if (-not (Ensure-NodeEnvironment)) { + return $false } - Write-Info "使用 $($pm.Type) 安装 $($Tool.Name)..." - $installCmd = "$($pm.Cmd) $($Tool.Package)" + Write-Info "使用 npm 安装 $($Tool.Name)..." + $installCmd = "npm install -g $($Tool.Package)" Write-Host " 执行: $installCmd" -ForegroundColor Gray try { @@ -157,7 +214,6 @@ function Install-Tool { $exitCode = $LASTEXITCODE Refresh-Path - # 检查安装命令退出码 if ($exitCode -ne 0) { Write-Error "安装命令返回错误码: $exitCode" return $false @@ -236,6 +292,21 @@ function Show-Menu { Write-Host " XCodeCLI Setup Launcher" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "" + + # 显示 Node.js 环境状态 + $nodeInfo = Get-NodeVersion + if ($nodeInfo) { + $nodeStatus = if ($nodeInfo.Major -ge 20) { "[OK]" } else { "[需升级]" } + $nodeColor = if ($nodeInfo.Major -ge 20) { "Green" } else { "Yellow" } + Write-Host " Node.js: v$($nodeInfo.Version) " -NoNewline + Write-Host $nodeStatus -ForegroundColor $nodeColor + } + else { + Write-Host " Node.js: " -NoNewline + Write-Host "[未安装]" -ForegroundColor Red + } + Write-Host "" + Write-Host "选择要安装和配置的工具:" -ForegroundColor Yellow Write-Host "" diff --git a/setup.sh b/setup.sh index f0b9bc5..d4c231d 100755 --- a/setup.sh +++ b/setup.sh @@ -5,8 +5,7 @@ set -e # 确保 PATH 包含常见的包管理器路径 -export PATH="$HOME/.bun/bin:$HOME/.local/bin:$HOME/.npm-global/bin:/usr/local/bin:$PATH" -export BUN_INSTALL="$HOME/.bun" +export PATH="$HOME/.local/share/fnm:$HOME/.fnm:$HOME/.local/bin:$HOME/.npm-global/bin:/usr/local/bin:$PATH" # ========== 颜色定义 ========== RED='\033[0;31m' @@ -45,12 +44,15 @@ reload_env() { rc_file=$(get_shell_rc) # 重新加载 PATH - export PATH="$HOME/.bun/bin:$HOME/.local/bin:$HOME/.npm-global/bin:/usr/local/bin:$PATH" - export BUN_INSTALL="$HOME/.bun" + export PATH="$HOME/.local/share/fnm:$HOME/.fnm:$HOME/.local/bin:$HOME/.npm-global/bin:/usr/local/bin:$PATH" + + # 加载 fnm 环境(如果存在) + if command_exists fnm; then + eval "$(fnm env)" 2>/dev/null || true + fi # 从配置文件中提取并加载环境变量 if [ -f "$rc_file" ]; then - # 提取 export 行并执行 while IFS= read -r line; do if [[ "$line" =~ ^export\ ([A-Za-z_][A-Za-z0-9_]*)= ]]; then eval "$line" 2>/dev/null || true @@ -163,7 +165,7 @@ check_system_deps() { missing_deps+=("curl") fi - # 检测 unzip (bun 安装需要) + # 检测 unzip (fnm 安装需要) if ! command_exists unzip; then missing_deps+=("unzip") fi @@ -246,88 +248,128 @@ command_exists() { command -v "$1" &> /dev/null } -# ========== 获取包管理器 ========== -get_package_manager() { +# ========== Node.js 版本检测 ========== +get_node_version() { if command_exists node; then - local node_version=$(node --version 2>/dev/null | sed 's/v//') - info "检测到 Node.js v$node_version" >&2 - echo "npm" - return 0 + node --version 2>/dev/null | sed 's/v//' fi - - if command_exists bun; then - local bun_version=$(bun --version 2>/dev/null) - info "检测到 Bun v$bun_version" >&2 - echo "bun" - return 0 - fi - - return 1 } -# ========== 安装 Bun ========== -install_bun() { +get_node_major_version() { + local version + version=$(get_node_version) + if [ -n "$version" ]; then + echo "$version" | cut -d. -f1 + fi +} + +# ========== 安装 fnm ========== +install_fnm() { echo "" - warning "未检测到 Node.js 或 Bun" - info "推荐安装 Bun(轻量级 JavaScript 运行时)" - echo "" - echo -e " 安装命令: ${CYAN}curl -fsSL https://bun.sh/install | bash${NC}" + info "正在安装 fnm (Fast Node Manager)..." + + if curl -fsSL https://fnm.vercel.app/install | bash; then + export PATH="$HOME/.local/share/fnm:$PATH" + if [ -f "$HOME/.local/share/fnm/fnm" ]; then + eval "$($HOME/.local/share/fnm/fnm env)" 2>/dev/null || true + fi + + if command_exists fnm; then + success "fnm 安装成功!" + return 0 + else + warning "fnm 可能已安装,但需要重新打开终端才能生效" + info "请重新打开终端后再运行此脚本" + return 1 + fi + else + error "fnm 安装失败" + return 1 + fi +} + +# ========== 使用 fnm 安装 Node.js ========== +install_node_with_fnm() { + info "使用 fnm 安装 Node.js 24.x..." + + if fnm install 24 && fnm use 24 && fnm default 24; then + eval "$(fnm env)" 2>/dev/null || true + + if command_exists node; then + local version + version=$(get_node_version) + success "Node.js v$version 安装成功!" + return 0 + else + warning "Node.js 可能已安装,但需要重新打开终端才能生效" + return 1 + fi + else + error "Node.js 安装失败" + return 1 + fi +} + +# ========== 确保 Node.js 环境就绪 ========== +ensure_node_environment() { + local version major + + version=$(get_node_version) + if [ -n "$version" ]; then + major=$(get_node_major_version) + info "检测到 Node.js v$version" + + if [ "$major" -lt 20 ]; then + warning "Node.js 版本过低 (需要 >= 20.x)" + echo "" + + read -p "是否使用 fnm 安装 Node.js 24.x? (Y/n) " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Nn]$ ]]; then + error "Node.js 版本不满足要求,请手动升级后重试" + return 1 + fi + + if ! command_exists fnm; then + install_fnm || return 1 + fi + + install_node_with_fnm || return 1 + fi + + return 0 + fi + + warning "未检测到 Node.js" + info "将使用 fnm 安装 Node.js 24.x" echo "" - read -p "是否立即安装 Bun? (Y/n) " -n 1 -r + read -p "是否继续? (Y/n) " -n 1 -r echo "" if [[ $REPLY =~ ^[Nn]$ ]]; then return 1 fi - info "正在安装 Bun..." - if curl -fsSL https://bun.sh/install | bash; then - # 刷新 PATH - export BUN_INSTALL="$HOME/.bun" - export PATH="$BUN_INSTALL/bin:$PATH" - - if command_exists bun; then - success "Bun 安装成功!" - return 0 - else - warning "Bun 可能已安装,但需要重新打开终端才能生效" - info "请重新打开终端后再运行此脚本" - return 1 - fi - else - error "Bun 安装失败" - return 1 + if ! command_exists fnm; then + install_fnm || return 1 fi + + install_node_with_fnm || return 1 } # ========== 安装工具 ========== install_tool() { local tool_num=$1 - local pm=$2 local tool_name=$(get_tool_name "$tool_num") local tool_pkg=$(get_tool_pkg "$tool_num") info "安装 $tool_name..." - local install_cmd="" - if [[ "$pm" == "npm" ]]; then - install_cmd="npm install -g $tool_pkg" - else - install_cmd="bun add -g $tool_pkg" - fi + local install_cmd="npm install -g $tool_pkg" echo -e " 执行: ${CYAN}$install_cmd${NC}" if eval "$install_cmd"; then hash -r 2>/dev/null || true - - # 如果使用 bun 安装,创建 node 符号链接(如果不存在) - if [[ "$pm" == "bun" ]] && ! command_exists node; then - if [[ -f "$HOME/.bun/bin/bun" ]]; then - ln -sf "$HOME/.bun/bin/bun" "$HOME/.bun/bin/node" 2>/dev/null || true - info "已创建 node 符号链接 (bun -> node)" - fi - fi - success "$tool_name 安装成功!" return 0 else @@ -364,6 +406,26 @@ show_status() { echo -e "获取 API 密钥: ${CYAN}https://api2.xcodecli.com/console/token${NC}" echo -e "步骤: 添加令牌 → 输入令牌名称 → 提交" echo "" + + # 显示 Node.js 状态 + local version major + version=$(get_node_version) + if [ -n "$version" ]; then + major=$(get_node_major_version) + local status color + if [ "$major" -ge 20 ]; then + status="[OK]" + color="${GREEN}" + else + status="[需升级]" + color="${YELLOW}" + fi + echo -e " Node.js: v$version ${color}${status}${NC}" + else + echo -e " Node.js: ${RED}[未安装]${NC}" + fi + echo "" + echo -e "${YELLOW}工具安装状态:${NC}" echo "" @@ -452,17 +514,9 @@ main() { info "API 密钥: ${api_key:0:8}..." echo "" - # 获取包管理器 - local pm=$(get_package_manager) - if [[ -z "$pm" ]]; then - if ! install_bun; then - exit 1 - fi - pm=$(get_package_manager) - if [[ -z "$pm" ]]; then - error "仍未检测到可用的包管理器" - exit 1 - fi + # 确保 Node.js 环境就绪 + if ! ensure_node_environment; then + exit 1 fi echo "" @@ -487,7 +541,7 @@ main() { # 检测是否已安装 if ! command_exists "$tool_cmd"; then - if ! install_tool "$i" "$pm"; then + if ! install_tool "$i"; then failed=$((failed + 1)) continue fi