# XCodeCLI Setup Launcher (Windows) # 一站式安装和配置 Claude Code, Gemini CLI, Codex param( [string]$ApiKey, [switch]$Help ) # 支持一行命令传入 API Key: $key='xxx'; iwr ... | iex if (-not $ApiKey -and (Test-Path Variable:key)) { $ApiKey = $key } # ========== 颜色输出函数 ========== function Write-Info { param([string]$Message); Write-Host "[INFO]" -ForegroundColor Blue -NoNewline; Write-Host " $Message" } function Write-Success { param([string]$Message); Write-Host "[SUCCESS]" -ForegroundColor Green -NoNewline; Write-Host " $Message" } function Write-Warning { param([string]$Message); Write-Host "[WARNING]" -ForegroundColor Yellow -NoNewline; Write-Host " $Message" } function Write-Error { param([string]$Message); Write-Host "[ERROR]" -ForegroundColor Red -NoNewline; Write-Host " $Message" } # ========== 工具配置表 ========== $ToolsConfig = @{ 1 = @{ Name = "Claude Code" Command = "claude" Package = "@anthropic-ai/claude-code" SetupUrl = "https://gitea.sususu.cf/sususu/xcodecli-shells/raw/branch/main/ClaudeCode/setup-claude-code.ps1" } 2 = @{ Name = "Gemini CLI" Command = "gemini" Package = "@google/gemini-cli@latest" SetupUrl = "https://gitea.sususu.cf/sususu/xcodecli-shells/raw/branch/main/GeminiCLI/setup-gemini.ps1" } 3 = @{ Name = "Codex" Command = "codex" Package = "@openai/codex" SetupUrl = "https://gitea.sususu.cf/sususu/xcodecli-shells/raw/branch/main/codex/setup-codex.ps1" } } # ========== 帮助信息 ========== function Show-Help { Write-Host @" XCodeCLI Setup Launcher (Windows) 一站式安装和配置 Claude Code, Gemini CLI, Codex Usage: powershell -ExecutionPolicy Bypass -File setup.ps1 [OPTIONS] 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 "@ exit 0 } # ========== 工具函数 ========== 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") } # ========== 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 } # ========== fnm 安装 ========== function Install-Fnm { Write-Host "" Write-Info "正在安装 fnm (Fast Node Manager)..." try { # 使用 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 # 配置 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 "fnm 可能已安装,但需要重新打开终端才能生效" Write-Info "请重新打开 PowerShell 后再运行此脚本" return $false } } catch { 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 ) # 确保 Node.js 环境就绪 if (-not (Ensure-NodeEnvironment)) { return $false } Write-Info "使用 npm 安装 $($Tool.Name)..." $installCmd = "npm install -g $($Tool.Package)" 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 $Tool.Command) { Write-Success "$($Tool.Name) 安装成功!" return $true } else { Write-Warning "$($Tool.Name) 可能已安装,但需要重新打开终端才能生效" $continue = Read-Host "是否继续进行配置? (Y/n)" return ($continue -ne "n" -and $continue -ne "N") } } catch { Write-Error "安装失败: $($_.Exception.Message)" return $false } } # ========== 远程配置脚本调用 ========== function Invoke-RemoteSetup { param( [hashtable]$Tool, [string]$ApiKey ) Write-Host "" Write-Info "下载并执行 $($Tool.Name) 配置脚本..." Write-Host " URL: $($Tool.SetupUrl)" -ForegroundColor Gray Write-Host "" $tempFile = $null try { # 下载脚本到临时文件 $tempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), ".ps1") Invoke-WebRequest -Uri $Tool.SetupUrl -UseBasicParsing -OutFile $tempFile # 执行脚本,正确传递参数 $LASTEXITCODE = 0 if ($ApiKey) { & $tempFile -ApiKey $ApiKey } else { & $tempFile } $scriptSucceeded = $? $exitCode = $LASTEXITCODE # 清理临时文件 Remove-Item $tempFile -ErrorAction SilentlyContinue # 检查执行结果 if (-not $scriptSucceeded -or $exitCode -ne 0) { Write-Error "配置脚本执行失败,退出码: $exitCode" return $false } Write-Host "" Write-Success "$($Tool.Name) 配置完成!" return $true } catch { if ($tempFile) { Remove-Item $tempFile -ErrorAction SilentlyContinue } Write-Error "配置脚本执行失败: $($_.Exception.Message)" return $false } } # ========== 菜单显示 ========== function Show-Menu { Write-Host "" Write-Host "========================================" -ForegroundColor Cyan 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 "" foreach ($key in ($ToolsConfig.Keys | Sort-Object)) { $tool = $ToolsConfig[$key] $installed = Test-Command $tool.Command $status = if ($installed) { "[已安装]" } else { "[未安装]" } $color = if ($installed) { "Green" } else { "Red" } $label = " [$key] $($tool.Name.PadRight(12))" Write-Host "$label " -NoNewline Write-Host $status -ForegroundColor $color } Write-Host "" Write-Host " [0] 退出" -ForegroundColor Gray Write-Host "" } # ========== 主函数 ========== function Main { if ($Help) { Show-Help } Show-Menu $choice = Read-Host "请选择 (0-3)" # 退出选项 if ($choice -eq "0") { Write-Info "再见!" exit 0 } # 验证选择 $choiceNum = 0 if (-not [int]::TryParse($choice, [ref]$choiceNum) -or $choiceNum -lt 1 -or $choiceNum -gt 3) { Write-Error "无效的选择" exit 1 } $tool = $ToolsConfig[$choiceNum] # 检测是否已安装 if (-not (Test-Command $tool.Command)) { Write-Host "" Write-Warning "$($tool.Name) 未安装" $install = Read-Host "是否立即安装? (Y/n)" if ($install -eq "n" -or $install -eq "N") { Write-Info "已取消" exit 0 } if (-not (Install-Tool -Tool $tool)) { exit 1 } } else { Write-Host "" Write-Success "$($tool.Name) 已安装" } # 调用远程配置脚本 if (-not (Invoke-RemoteSetup -Tool $tool -ApiKey $ApiKey)) { exit 1 } } Main