<# .SYNOPSIS Bookworm 启动 wrapper (v3.1.0 引入) .DESCRIPTION 桌面 .lnk 调用此 wrapper 而非直调 claude.ps1, 让启动逻辑可热修复 (改 wrapper 不需重 bake .lnk). 职责: 1. 动态查找 claude.ps1 真实路径 (npm config get prefix → 兜底候选) 2. claude.ps1 stale (npm uninstall/换版本/换 prefix) → 弹清晰 GUI 引导, 不再"快捷方式失效" 3. 失败时不静默, 写日志 + GUI 弹窗 与 .lnk 的契约: .lnk Args = -NoLogo -NoExit -ExecutionPolicy Bypass -File "" --dangerously-skip-permissions $args[0..N] 转发给 claude.ps1 .NOTES v3.1.0 (2026-04-25) — 引入 wrapper 模式 (闭合 L4 局限: bake claude.ps1 路径 stale) 分发: Phase 7 安装时复制到 $BootDir\bw-launch.ps1 #> $ErrorActionPreference = "Continue" $bwLaunchLog = Join-Path $env:TEMP "bw-launch.log" function Write-BwLaunchLog { param([string]$Level, [string]$Msg) try { $line = "[$([DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))] [$Level] $Msg" $line | Out-File -FilePath $bwLaunchLog -Append -Encoding UTF8 -EA SilentlyContinue } catch {} } function Show-LaunchError { param([string]$Title, [string]$Body) Write-BwLaunchLog "ERROR" "$Title :: $Body" Write-Host "" Write-Host " [!] $Title" -ForegroundColor Red Write-Host "" Write-Host $Body -ForegroundColor Yellow Write-Host "" Write-Host " 日志: $bwLaunchLog" -ForegroundColor Gray Write-Host "" try { Add-Type -AssemblyName System.Windows.Forms -EA Stop [System.Windows.Forms.MessageBox]::Show("$Body`n`n详情见: $bwLaunchLog", "Bookworm 启动失败 — $Title", 'OK', 'Error') | Out-Null } catch {} Read-Host "按回车关闭" } Write-BwLaunchLog "INFO" "bw-launch wrapper 启动 args=$($args -join ' ')" # ── 1. PATH 三层重载 (即便桌面 .lnk 不依赖 PATH, 子 claude.ps1 调 node 仍依赖) ── $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User') try { $npmPrefix = (& npm config get prefix 2>$null | Select-Object -First 1).Trim() if ($npmPrefix -and (Test-Path $npmPrefix) -and ($env:Path -notlike "*$npmPrefix*")) { $env:Path = "$npmPrefix;$env:Path" } } catch { Write-BwLaunchLog "WARN" "npm config get prefix 失败: $_" } foreach ($p in @("$env:APPDATA\npm", "$env:ProgramFiles\nodejs", "$env:LOCALAPPDATA\npm")) { if ((Test-Path $p) -and ($env:Path -notlike "*$p*")) { $env:Path = "$p;$env:Path" } } # ── 2. 动态定位 claude.ps1 ── $claudePs1 = $null # 优先 Get-Command (PATH 已重载) $claudeCmd = Get-Command claude -ErrorAction SilentlyContinue if ($claudeCmd -and $claudeCmd.Source -and $claudeCmd.Source.EndsWith('.ps1') -and (Test-Path $claudeCmd.Source)) { $claudePs1 = $claudeCmd.Source Write-BwLaunchLog "INFO" "claude.ps1 from Get-Command: $claudePs1" } # 兜底 npm config get prefix if (-not $claudePs1) { try { $candidate = Join-Path $npmPrefix "claude.ps1" if (Test-Path $candidate) { $claudePs1 = $candidate Write-BwLaunchLog "INFO" "claude.ps1 from npm prefix: $claudePs1" } } catch {} } # 最终硬编码兜底 if (-not $claudePs1) { foreach ($p in @("$env:APPDATA\npm\claude.ps1", "$env:ProgramFiles\nodejs\claude.ps1", "$env:LOCALAPPDATA\npm\claude.ps1")) { if (Test-Path $p) { $claudePs1 = $p Write-BwLaunchLog "INFO" "claude.ps1 from hardcoded: $claudePs1" break } } } if (-not $claudePs1 -or -not (Test-Path $claudePs1)) { $diag = "未找到 claude.ps1 文件.`n`n" $diag += "诊断信息:`n" $diag += " npm config get prefix: $(if ($npmPrefix) { $npmPrefix } else { '(查询失败)' })`n" $diag += " PATH 中 npm/nodejs 片段:`n" foreach ($p in (($env:Path -split ';') | Where-Object { $_ -match 'npm|nodejs' })) { $diag += " $p`n" } $diag += "`n修复方案:`n" $diag += " 方案 A: 命令行运行 npm i -g @anthropic-ai/claude-code`n" $diag += " 方案 B: 重新双击 Bookworm-Setup.exe 修复安装" Show-LaunchError "Claude Code 未找到" $diag exit 1 } # ── 3. 启动 claude.ps1 + 转发 $args (--dangerously-skip-permissions 等) ── Write-BwLaunchLog "INFO" "调用 claude.ps1 args=$($args -join ' ')" try { & $claudePs1 @args $exitCode = $LASTEXITCODE Write-BwLaunchLog "INFO" "claude.ps1 退出码: $exitCode" if ($exitCode -ne 0) { Write-Host "" Write-Host " [!] Claude 进程退出码: $exitCode" -ForegroundColor Yellow Write-Host " 日志: $bwLaunchLog" -ForegroundColor Gray } exit $exitCode } catch { Show-LaunchError "Claude 启动异常" "异常信息: $($_.Exception.Message)" exit 1 }