377 lines
11 KiB
PowerShell
377 lines
11 KiB
PowerShell
# 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 <KEY> 预设 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
|