bookworm-boot/tools/gen-launcher-bats.ps1
bookworm a3b4ff3a78 fix(v3.0.9): Claude 装完 npm prefix 固化 + 三层 PATH + 快捷方式自验证
用户反馈 (核心): '不要客户端修修补补, EXE 必须一次对'

诊断: v3.0.8 Phase 1 全 OK, 但桌面快捷方式 claude.exe not found
  user npm config get prefix = C:\Users\Administrator\AppData\Roaming\npm
  但 Get-Command claude 在 PS 7 为空 → User PATH 没含这个目录
  Node.js MSI 写 User PATH 时机/行为被企业镜像/防病毒/组策略干扰

三层根治:

F1: auto-setup.ps1 装完 Claude Code 后立即 `npm config get prefix` 查真实路径,
    强制写入 User PATH (永久生效). 不依赖 npm 自己的 PATH 注入时机.
F2: gen-launcher-bats Base64 脚本加 3 层 PATH 修复:
    层 1 Machine+User env PATH / 层 2 npm config get prefix 动态查询 /
    层 3 硬编码候选 (%APPDATA%\npm / Program Files\nodejs / LOCALAPPDATA\npm)
F3: auto-setup.ps1:2256 创建桌面快捷方式前 Test-Cmd claude 自验证,
    失败则拒绝创建 + 弹明确错误, 不再静默产出坏快捷方式

EXE 217088 → 220160 bytes (+3072)
Base64 3544 → 5880 chars
产品原则: 点快捷方式要么成功要么给清晰诊断, 不再给 'claude not found' 无头案
2026-04-24 22:39:17 +08:00

184 lines
7.9 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bookworm Portable 启动器 bat 生成工具 (v3.0.6)
# 用途: 从单一明文 PowerShell 脚本生成两个 bat, 避免手工同步 Base64 字符串不一致
# 用法: pwsh -NoProfile -File tools/gen-launcher-bats.ps1
# 输出: 启动Bookworm.bat + 更新并启动Bookworm.bat (覆盖写入)
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
$launchBat = Join-Path $repoRoot "启动Bookworm.bat"
$updateBat = Join-Path $repoRoot "更新并启动Bookworm.bat"
# ─── 明文: 三层 PATH 修复 + DPAPI 加载 + claude 诊断 + 启动 ─────────
# v3.0.9: 增加 npm config get prefix 动态查询, 兼容 nvm/fnm/Program Files 等非标准 npm 位置
$plainScript = @'
Add-Type -AssemblyName System.Security
# 层 1: Machine + User env PATH (标准 Windows 环境变量)
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
# 层 2: npm config get prefix (真实 npm 全局目录, 兼容 nvm/fnm/标准安装/Program Files)
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 {}
# 层 3: 常见 npm global 硬编码兜底 (npm 本身不在 PATH 时无法 query)
$npmCandidates = @(
"$env:APPDATA\npm",
"$env:ProgramFiles\nodejs",
"${env:ProgramFiles(x86)}\nodejs",
"$env:LOCALAPPDATA\npm"
)
foreach ($p in $npmCandidates) {
if ((Test-Path $p) -and (Test-Path (Join-Path $p 'claude.ps1') -or (Test-Path (Join-Path $p 'claude.cmd'))) -and ($env:Path -notlike "*$p*")) {
$env:Path = "$p;$env:Path"
}
}
# DPAPI 加载缓存凭证
$r = 'HKCU:\Software\Bookworm\CachedEnv'
try {
(Get-ItemProperty $r -EA Stop).PSObject.Properties | Where-Object { $_.Name -match '^[A-Z_]+$' } | ForEach-Object {
$v = $_.Value
try {
$b = [Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String($v), $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
$v = [Text.Encoding]::UTF8.GetString($b)
} catch {}
[Environment]::SetEnvironmentVariable($_.Name, $v, 'Process')
}
} catch {}
if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {
Write-Host ''
Write-Host ' [!] claude 命令未找到 (已尝试 3 层 PATH 修复仍失败)' -ForegroundColor Red
Write-Host ''
Write-Host ' 诊断信息:' -ForegroundColor Yellow
Write-Host " npm prefix: $(try { (& npm config get prefix 2>$null) } catch { '(npm 不可用)' })" -ForegroundColor Gray
Write-Host ' PATH 片段 (npm/nodejs/pwsh/Git):' -ForegroundColor Gray
($env:Path -split ';') | Where-Object { $_ -match 'npm|nodejs|pwsh|Git' } | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
Write-Host ''
Write-Host ' 修复: 重新运行 Bookworm-Setup.exe (v3.0.9+) 即可自动补全' -ForegroundColor Green
Write-Host ''
Read-Host '按回车关闭'
return
}
& claude --dangerously-skip-permissions
'@
# ─── Base64-UTF-16LE 编码 ─────────────────────────────────
$bytes = [System.Text.Encoding]::Unicode.GetBytes($plainScript)
$enc = [Convert]::ToBase64String($bytes)
# 健康检查
if ($enc.Length -gt 7500) { throw "Base64 长度 $($enc.Length) 超 bat 变量安全上限 7500" }
$bad = $enc -replace '[A-Za-z0-9+/=]', ''
if ($bad) { throw "Base64 含非法字符: [$bad]" }
Write-Host "[gen-launcher-bats] Base64 长度: $($enc.Length), 纯字符集检查 OK" -ForegroundColor Green
# ─── bat 1: 启动Bookworm.bat ──────────────────────────────
$launch = @"
@echo off
chcp 65001 > nul
cd /d "%~dp0"
::
set NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1
set no_proxy=%NO_PROXY%
:: (bookworm-boot + .claude , )
echo [..] ...
git pull --rebase >nul 2>nul
git -C "%USERPROFILE%\.claude" pull --rebase >nul 2>nul
set USE_WT=0
where wt >nul 2>nul && set USE_WT=1
set USE_PWSH7=0
where pwsh >nul 2>nul && set USE_PWSH7=1
:: v3.0.6: Base64-UTF-16LE (PATH + DPAPI + claude + )
:: A-Za-z0-9+/= , wt.exe ';' tab ( 64856bc )
:: -d "%CD%" , -d "%~dp0" ( 0c33109 )
:: : pwsh -NoProfile -File tools/gen-launcher-bats.ps1
set ENC=$enc
:: : wt + pwsh7
if %USE_WT% equ 1 if %USE_PWSH7% equ 1 (
start "" wt new-tab --title "Bookworm Smart Assistant" -d "%CD%" -- pwsh -NoLogo -NoExit -EncodedCommand %ENC%
exit
)
:: 2: wt + powershell 5.1
if %USE_WT% equ 1 if %USE_PWSH7% equ 0 (
start "" wt new-tab --title "Bookworm Smart Assistant" -d "%CD%" -- powershell -NoLogo -ExecutionPolicy Bypass -NoExit -EncodedCommand %ENC%
exit
)
:: 3: conhost + pwsh7 ( wt ; tab , Base64 )
if %USE_PWSH7% equ 1 (
start "Bookworm Smart Assistant" pwsh -NoLogo -NoExit -EncodedCommand %ENC%
exit
)
:: 4: 退 PowerShell 5.1 (, install.ps1 -StartOnly )
title Bookworm Portable
echo.
echo [!] PowerShell 7 , 使 PowerShell 5.1
echo.
powershell -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept
if %errorlevel% neq 0 (
echo.
echo 退...
pause > nul
)
"@
# ─── bat 2: 更新并启动Bookworm.bat ───────────────────────
$update = @"
@echo off
chcp 65001 > nul
cd /d "%~dp0"
::
set NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1
set no_proxy=%NO_PROXY%
:: (bookworm-boot + .claude )
echo [..] ...
git pull --rebase >nul 2>nul
git -C "%USERPROFILE%\.claude" pull --rebase >nul 2>nul
:: v3.0.6: Bookworm.bat Base64 (DPAPI + PATH + claude )
set ENC=$enc
:: pwsh7
where pwsh >nul 2>nul
if %errorlevel% equ 0 (
:: pwsh7: (SkipLaunch claude), -EncodedCommand
pwsh -NoLogo -ExecutionPolicy Bypass -File "%~dp0install.ps1" -AutoAccept -SkipLaunch
start "Bookworm Smart Assistant" pwsh -NoLogo -NoExit -EncodedCommand %ENC%
exit
)
:: 退 PowerShell 5.1: ++ ()
title Bookworm Portable
powershell -ExecutionPolicy Bypass -File "%~dp0install.ps1" -AutoAccept
if %errorlevel% neq 0 (
echo.
echo 退...
pause > nul
)
"@
# ─── 写入 ─────────────────────────────────────────────────
# bat 文件默认期望 GBK/ANSI, 但脚本顶部 chcp 65001 已切换到 UTF-8, 用无 BOM UTF-8 写入
[System.IO.File]::WriteAllText($launchBat, $launch, [System.Text.UTF8Encoding]::new($false))
[System.IO.File]::WriteAllText($updateBat, $update, [System.Text.UTF8Encoding]::new($false))
Write-Host "[gen-launcher-bats] ✓ 启动Bookworm.bat ($((Get-Item $launchBat).Length) bytes)" -ForegroundColor Green
Write-Host "[gen-launcher-bats] ✓ 更新并启动Bookworm.bat ($((Get-Item $updateBat).Length) bytes)" -ForegroundColor Green
# ─── Round-trip 验证 ─────────────────────────────────────
$decoded = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($enc))
$err = $null
[void][System.Management.Automation.Language.Parser]::ParseInput($decoded, [ref]$null, [ref]$err)
if ($err) { throw "解码后脚本 PARSE ERR: $($err[0])" }
Write-Host "[gen-launcher-bats] ✓ Round-trip PARSE OK ($($decoded.Length) chars)" -ForegroundColor Green