feat(v3.1.2): 完整生命周期 (闭合 L9-L12)
新增: - 卸载Bookworm.bat + 卸载Bookworm-impl.ps1: 7 步清理 - bw-doctor.ps1: 13 维度健康体检工具 - 桌面快捷方式 2→4 (新增体检+卸载) 增强: - bw-launch.ps1: nvm/fnm/volta 探测 + log rotation - auto-setup.ps1: npm/uvx 国内镜像双 fallback 12/12 局限全部闭合, v3.1.x 生命周期完整. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3beb79af7a
commit
ec490c3f35
@ -48,7 +48,7 @@ trap {
|
||||
}
|
||||
|
||||
# ─── 版本号 (每次更新递增, build.ps1 自动读取) ──────
|
||||
$BWVersion = "3.1.1" # E2E 护栏 + 总结弹窗始终弹 + 更新bat 完成提示 + profile 文件锁 (闭合 L5/L6/L7/L8)
|
||||
$BWVersion = "3.1.2" # 完整生命周期: 卸载脚本 + 体检工具 + nvm探测 + 镜像fallback + log rotation + 4桌面lnk (闭合 L9/L10/L11/L12)
|
||||
|
||||
# DryRun 模式日志标记
|
||||
if ($DryRun) { $global:BWDryRun = $DryRun } else { $global:BWDryRun = $null }
|
||||
@ -984,7 +984,38 @@ function New-DesktopShortcuts {
|
||||
Log-Info "更新.bat 未找到, 跳过更新 lnk (启动 lnk 已就绪, 不影响使用)"
|
||||
}
|
||||
|
||||
Log-OK "v3.0.11 桌面快捷方式架构 (1 跳直调) 创建完成"
|
||||
# ── 8. 体检 .lnk: 调 pwsh + bw-doctor.ps1 (v3.1.2 新增) ──
|
||||
$doctorPs1 = Join-Path $BootDir "bw-doctor.ps1"
|
||||
if (Test-Path $doctorPs1) {
|
||||
$doctorLnk = "$desktop\体检Bookworm.lnk"
|
||||
$d = $shell.CreateShortcut($doctorLnk)
|
||||
$d.TargetPath = $pwshExe
|
||||
$d.Arguments = "-NoLogo -NoExit -ExecutionPolicy Bypass -File `"$doctorPs1`""
|
||||
$d.WorkingDirectory = $BootDir
|
||||
$d.Description = "Bookworm 13 维度健康体检"
|
||||
if (Test-Path $iconPath) { $d.IconLocation = "$iconPath,0" }
|
||||
$d.Save()
|
||||
Log-OK "体检Bookworm.lnk 创建"
|
||||
} else {
|
||||
Log-Info "bw-doctor.ps1 未找到, 跳过体检 lnk"
|
||||
}
|
||||
|
||||
# ── 9. 卸载 .lnk: 调 cmd + 卸载Bookworm.bat (v3.1.2 新增) ──
|
||||
$uninstBat = Join-Path $BootDir "卸载Bookworm.bat"
|
||||
if (Test-Path $uninstBat) {
|
||||
$uninstLnk = "$desktop\卸载Bookworm.lnk"
|
||||
$ui = $shell.CreateShortcut($uninstLnk)
|
||||
$ui.TargetPath = $uninstBat
|
||||
$ui.WorkingDirectory = $BootDir
|
||||
$ui.Description = "卸载 Bookworm (二次确认后执行)"
|
||||
if (Test-Path $iconPath) { $ui.IconLocation = "$iconPath,0" }
|
||||
$ui.Save()
|
||||
Log-OK "卸载Bookworm.lnk 创建"
|
||||
} else {
|
||||
Log-Info "卸载Bookworm.bat 未找到, 跳过卸载 lnk"
|
||||
}
|
||||
|
||||
Log-OK "v3.1.2 桌面快捷方式 (启动/更新/体检/卸载) 创建完成"
|
||||
} catch { Log-Warn "快捷方式创建失败: $_" }
|
||||
}
|
||||
|
||||
@ -2302,30 +2333,49 @@ $npxPackages = @(
|
||||
@{ Name = "google-drive"; Pkg = "@piotr-agier/google-drive-mcp" }
|
||||
)
|
||||
|
||||
# v3.1.2 L11: 国内镜像 fallback (npmjs 失败 → npmmirror 重试)
|
||||
$npmMirrorRegistry = "https://registry.npmmirror.com"
|
||||
|
||||
$mcpOK = 0; $mcpFail = 0
|
||||
foreach ($mcp in $npxPackages) {
|
||||
$idx = $mcpOK + $mcpFail + 1
|
||||
$label = "[$idx/$($npxPackages.Count)] $($mcp.Name)"
|
||||
Update-Progress-SubStatus "$label ..."
|
||||
|
||||
$cached = $false
|
||||
foreach ($registry in @($null, $npmMirrorRegistry)) {
|
||||
try {
|
||||
$outTmp = Join-Path $env:TEMP "bw-npm-$($mcp.Name).tmp"
|
||||
$errTmp = Join-Path $env:TEMP "bw-npm-$($mcp.Name)-err.tmp"
|
||||
$proc = Start-Process npm.cmd -ArgumentList "cache", "add", $mcp.Pkg `
|
||||
$npmArgs = @("cache", "add", $mcp.Pkg)
|
||||
if ($registry) {
|
||||
$npmArgs += "--registry=$registry"
|
||||
Update-Progress-SubStatus "$label (镜像重试)..."
|
||||
}
|
||||
$proc = Start-Process npm.cmd -ArgumentList $npmArgs `
|
||||
-NoNewWindow -PassThru `
|
||||
-RedirectStandardOutput $outTmp `
|
||||
-RedirectStandardError $errTmp
|
||||
$ok = Wait-ProcessWithUI $proc 60000 $label
|
||||
if ($ok -and $proc.ExitCode -eq 0) {
|
||||
Bw-Log "OK" "$label cached"
|
||||
$mcpOK++
|
||||
$src = if ($registry) { "mirror" } else { "npmjs" }
|
||||
Bw-Log "OK" "$label cached ($src)"
|
||||
$mcpOK++; $cached = $true
|
||||
} else { throw "exit=$($proc.ExitCode)" }
|
||||
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
|
||||
break
|
||||
} catch {
|
||||
Bw-Log "WARN" "$label failed: $_"
|
||||
$mcpFail++
|
||||
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
|
||||
if ($registry) {
|
||||
Bw-Log "WARN" "$label failed (mirror): $_"
|
||||
} else {
|
||||
Bw-Log "INFO" "$label npmjs failed, trying mirror..."
|
||||
}
|
||||
}
|
||||
Log-OK "npx 预缓存: $mcpOK/$($npxPackages.Count) 成功"
|
||||
}
|
||||
if (-not $cached) { $mcpFail++ }
|
||||
}
|
||||
Log-OK "npx 预缓存: $mcpOK/$($npxPackages.Count) 成功$(if ($mcpFail -gt 0) { " ($mcpFail 失败)" })"
|
||||
|
||||
# ── 6d: Playwright 浏览器安装 (非阻塞 UI) ──
|
||||
Log-Info "Playwright 浏览器安装..."
|
||||
@ -2352,6 +2402,9 @@ try {
|
||||
} catch { Log-Warn "Playwright 浏览器安装失败: $_ (不影响核心功能)" }
|
||||
|
||||
# ── 6e: Python MCP (uvx) 验证 (非阻塞 UI) ──
|
||||
# v3.1.2 L11: PyPI 失败 → tuna 镜像重试
|
||||
$pypiMirrorIndex = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
|
||||
if (Test-Cmd "uvx") {
|
||||
Log-Info "Python MCP 验证 (uvx)..."
|
||||
$uvxPackages = @(
|
||||
@ -2360,21 +2413,38 @@ if (Test-Cmd "uvx") {
|
||||
@{ Name = "atlassian"; Args = @("tool", "install", "mcp-atlassian") }
|
||||
)
|
||||
foreach ($pkg in $uvxPackages) {
|
||||
$installed = $false
|
||||
foreach ($indexUrl in @($null, $pypiMirrorIndex)) {
|
||||
try {
|
||||
$outTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name).tmp"
|
||||
$errTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name)-err.tmp"
|
||||
$installArgs = $pkg.Args
|
||||
if ($indexUrl) {
|
||||
$installArgs += @("--index-url", $indexUrl)
|
||||
Update-Progress-SubStatus "uvx $($pkg.Name) (镜像重试)..."
|
||||
}
|
||||
$proc = Start-Process uv -ArgumentList $installArgs `
|
||||
-NoNewWindow -PassThru `
|
||||
-RedirectStandardOutput $outTmp `
|
||||
-RedirectStandardError $errTmp
|
||||
$ok = Wait-ProcessWithUI $proc 90000 "uvx $($pkg.Name)"
|
||||
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
|
||||
if ($ok) { Bw-Log "OK" "uvx $($pkg.Name) ready" }
|
||||
if ($ok -and $proc.ExitCode -eq 0) {
|
||||
$src = if ($indexUrl) { "tuna" } else { "pypi" }
|
||||
Bw-Log "OK" "uvx $($pkg.Name) ready ($src)"
|
||||
$installed = $true; break
|
||||
} else { throw "exit=$($proc.ExitCode)" }
|
||||
} catch {
|
||||
Bw-Log "WARN" "uvx $($pkg.Name): $_"
|
||||
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
|
||||
if ($indexUrl) {
|
||||
Bw-Log "WARN" "uvx $($pkg.Name) failed (mirror): $_"
|
||||
} else {
|
||||
Bw-Log "INFO" "uvx $($pkg.Name) pypi failed, trying tuna..."
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $installed) { Bw-Log "WARN" "uvx $($pkg.Name): all sources failed" }
|
||||
}
|
||||
} else {
|
||||
Bw-Log "INFO" "uvx 不可用, 跳过 Python MCP"
|
||||
}
|
||||
|
||||
446
bw-doctor.ps1
Normal file
446
bw-doctor.ps1
Normal file
@ -0,0 +1,446 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Bookworm Portable 体检工具 (v3.1.2)
|
||||
|
||||
.DESCRIPTION
|
||||
13 维度自检, 覆盖 auto-setup.ps1 7 阶段所有安装产物.
|
||||
输出彩色 PASS/WARN/FAIL 报告, 不修改任何文件.
|
||||
|
||||
维度:
|
||||
[1] PowerShell 7 (pwsh)
|
||||
[2] Node.js
|
||||
[3] Git
|
||||
[4] Claude Code (claude.ps1 可达)
|
||||
[5] 桌面 .lnk (启动/更新 + Args 契约)
|
||||
[6] DPAPI 凭证 (HKCU CachedEnv)
|
||||
[7] Profile BW_CRED 块 (PS7 + PS5.1)
|
||||
[8] Profile BW_CLIP 块 (PS7)
|
||||
[9] 环境变量 (ANTHROPIC_*)
|
||||
[10] ~/.claude 完整性 (CLAUDE.md / Skills / Hooks / Settings)
|
||||
[11] API 中转站连通 (bww.letcareme.com)
|
||||
[12] Worker 连通 (bookworm-router)
|
||||
[13] Gitea 连通 (code.letcareme.com)
|
||||
|
||||
.NOTES
|
||||
用法: pwsh -NoProfile -ExecutionPolicy Bypass -File bw-doctor.ps1
|
||||
日志: $env:TEMP\bw-doctor.log
|
||||
退出码: 0 = 全 PASS / 1 = 有 FAIL
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$doctorLog = Join-Path $env:TEMP "bw-doctor.log"
|
||||
$pass = 0; $warn = 0; $fail = 0
|
||||
$results = @()
|
||||
|
||||
function Log-Doctor {
|
||||
param([string]$Msg)
|
||||
try { "[$([DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))] $Msg" | Out-File -FilePath $doctorLog -Append -Encoding UTF8 -EA SilentlyContinue } catch {}
|
||||
}
|
||||
|
||||
function Report {
|
||||
param([string]$Dim, [string]$Status, [string]$Detail)
|
||||
$color = switch ($Status) { 'PASS' { 'Green' } 'WARN' { 'Yellow' } 'FAIL' { 'Red' } default { 'Gray' } }
|
||||
$icon = switch ($Status) { 'PASS' { [char]0x2714 } 'WARN' { '!' } 'FAIL' { [char]0x2718 } default { '?' } }
|
||||
$line = " $icon [$Status] $Dim"
|
||||
if ($Detail) { $line += " — $Detail" }
|
||||
Write-Host $line -ForegroundColor $color
|
||||
Log-Doctor "$Status $Dim $Detail"
|
||||
switch ($Status) {
|
||||
'PASS' { $script:pass++ }
|
||||
'WARN' { $script:warn++ }
|
||||
'FAIL' { $script:fail++ }
|
||||
}
|
||||
$script:results += @{ Dim = $Dim; Status = $Status; Detail = $Detail }
|
||||
}
|
||||
|
||||
function Test-Cmd($name) { return [bool](Get-Command $name -EA SilentlyContinue) }
|
||||
|
||||
# ── Banner ──
|
||||
Write-Host ""
|
||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||
Write-Host " | Bookworm Doctor v3.1.2 |" -ForegroundColor Cyan
|
||||
Write-Host " | 13 维度健康体检 |" -ForegroundColor Cyan
|
||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Log-Doctor "=== Bookworm Doctor v3.1.2 START ==="
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [1/13] PowerShell 7
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [1/13] PowerShell 7" -ForegroundColor Cyan
|
||||
$pwshCmd = Get-Command pwsh -EA SilentlyContinue
|
||||
if ($pwshCmd) {
|
||||
try {
|
||||
$pwshVer = (& pwsh -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>$null).Trim()
|
||||
Report "[1] PowerShell 7" "PASS" "v$pwshVer ($($pwshCmd.Source))"
|
||||
} catch {
|
||||
Report "[1] PowerShell 7" "WARN" "pwsh 可达但版本查询失败"
|
||||
}
|
||||
} else {
|
||||
Report "[1] PowerShell 7" "FAIL" "pwsh 不在 PATH — 桌面 .lnk 强依赖 pwsh.exe"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [2/13] Node.js
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [2/13] Node.js" -ForegroundColor Cyan
|
||||
if (Test-Cmd "node") {
|
||||
try {
|
||||
$nodeVer = (& node --version 2>$null).Trim()
|
||||
Report "[2] Node.js" "PASS" "$nodeVer"
|
||||
} catch {
|
||||
Report "[2] Node.js" "WARN" "node 可达但版本查询失败"
|
||||
}
|
||||
} else {
|
||||
Report "[2] Node.js" "FAIL" "node 不在 PATH — Claude Code 强依赖"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [3/13] Git
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [3/13] Git" -ForegroundColor Cyan
|
||||
if (Test-Cmd "git") {
|
||||
try {
|
||||
$gitVer = (& git --version 2>$null).Trim()
|
||||
Report "[3] Git" "PASS" "$gitVer"
|
||||
} catch {
|
||||
Report "[3] Git" "WARN" "git 可达但版本查询失败"
|
||||
}
|
||||
} else {
|
||||
Report "[3] Git" "FAIL" "git 不在 PATH — 配置同步强依赖"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [4/13] Claude Code
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [4/13] Claude Code" -ForegroundColor Cyan
|
||||
$claudePs1Found = $null
|
||||
|
||||
$claudeCmd = Get-Command claude -EA SilentlyContinue
|
||||
if ($claudeCmd -and $claudeCmd.Source -and $claudeCmd.Source.EndsWith('.ps1') -and (Test-Path $claudeCmd.Source)) {
|
||||
$claudePs1Found = $claudeCmd.Source
|
||||
}
|
||||
if (-not $claudePs1Found) {
|
||||
try {
|
||||
$npmPrefix = (& npm config get prefix 2>$null | Select-Object -First 1).Trim()
|
||||
$candidate = Join-Path $npmPrefix "claude.ps1"
|
||||
if (Test-Path $candidate) { $claudePs1Found = $candidate }
|
||||
} catch {}
|
||||
}
|
||||
if (-not $claudePs1Found) {
|
||||
foreach ($p in @("$env:APPDATA\npm\claude.ps1", "$env:ProgramFiles\nodejs\claude.ps1", "$env:LOCALAPPDATA\npm\claude.ps1")) {
|
||||
if (Test-Path $p) { $claudePs1Found = $p; break }
|
||||
}
|
||||
}
|
||||
|
||||
if ($claudePs1Found) {
|
||||
try {
|
||||
$claudeVer = (& claude --version 2>$null | Select-Object -First 1).Trim()
|
||||
Report "[4] Claude Code" "PASS" "$claudeVer ($claudePs1Found)"
|
||||
} catch {
|
||||
Report "[4] Claude Code" "PASS" "claude.ps1 存在: $claudePs1Found (版本查询跳过)"
|
||||
}
|
||||
} else {
|
||||
Report "[4] Claude Code" "FAIL" "claude.ps1 不可达 — 运行 npm i -g @anthropic-ai/claude-code"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [5/13] 桌面 .lnk
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [5/13] 桌面 .lnk" -ForegroundColor Cyan
|
||||
$desktop = [Environment]::GetFolderPath('Desktop')
|
||||
$requiredLnks = @('启动Bookworm.lnk', '更新Bookworm.lnk')
|
||||
$optionalLnks = @('体检Bookworm.lnk', '卸载Bookworm.lnk')
|
||||
$lnkMissing = @()
|
||||
$lnkPresent = @()
|
||||
|
||||
foreach ($n in $requiredLnks) {
|
||||
$p = Join-Path $desktop $n
|
||||
if (Test-Path $p) { $lnkPresent += $n } else { $lnkMissing += $n }
|
||||
}
|
||||
|
||||
$optPresent = @()
|
||||
foreach ($n in $optionalLnks) {
|
||||
$p = Join-Path $desktop $n
|
||||
if (Test-Path $p) { $optPresent += $n }
|
||||
}
|
||||
|
||||
if ($lnkMissing.Count -eq 0) {
|
||||
# 验证启动 .lnk Args 契约 (4 项: pwsh TargetPath / bw-launch.ps1 / --dangerously-skip-permissions / -ExecutionPolicy Bypass)
|
||||
$launchLnk = Join-Path $desktop '启动Bookworm.lnk'
|
||||
$argsOK = $true
|
||||
$argsDetail = ""
|
||||
try {
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
$sc = $shell.CreateShortcut($launchLnk)
|
||||
$checks = @(
|
||||
@{ Name = "TargetPath=pwsh"; OK = ($sc.TargetPath -match 'pwsh\.exe$') }
|
||||
@{ Name = "bw-launch.ps1"; OK = ($sc.Arguments -match 'bw-launch\.ps1') }
|
||||
@{ Name = "--dangerously-skip-permissions"; OK = ($sc.Arguments -match '--dangerously-skip-permissions') }
|
||||
@{ Name = "-ExecutionPolicy Bypass"; OK = ($sc.Arguments -match '-ExecutionPolicy Bypass') }
|
||||
)
|
||||
$badChecks = $checks | Where-Object { -not $_.OK }
|
||||
if ($badChecks) {
|
||||
$argsOK = $false
|
||||
$argsDetail = "契约失败: " + (($badChecks | ForEach-Object { $_.Name }) -join ', ')
|
||||
}
|
||||
} catch { $argsOK = $false; $argsDetail = "读取 .lnk 异常" }
|
||||
|
||||
if ($argsOK) {
|
||||
$extra = if ($optPresent.Count -gt 0) { " + $($optPresent -join '/')" } else { "" }
|
||||
Report "[5] 桌面 .lnk" "PASS" "$($lnkPresent -join ' + ')$extra — 4 项契约 OK"
|
||||
} else {
|
||||
Report "[5] 桌面 .lnk" "FAIL" "$argsDetail"
|
||||
}
|
||||
} else {
|
||||
Report "[5] 桌面 .lnk" "FAIL" "缺失: $($lnkMissing -join ', ') — 重跑 Bookworm-Setup.exe"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [6/13] DPAPI 凭证
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [6/13] DPAPI 凭证" -ForegroundColor Cyan
|
||||
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
|
||||
if (Test-Path $regPath) {
|
||||
try {
|
||||
Add-Type -AssemblyName System.Security -EA Stop
|
||||
$props = Get-ItemProperty $regPath -EA Stop
|
||||
$envNames = $props.PSObject.Properties | Where-Object { $_.Name -match '^[A-Z_]+$' }
|
||||
$decrypted = 0
|
||||
foreach ($ev in $envNames) {
|
||||
try {
|
||||
$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect(
|
||||
[Convert]::FromBase64String($ev.Value), $null,
|
||||
[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
|
||||
$decrypted++
|
||||
} catch {}
|
||||
}
|
||||
if ($envNames.Count -gt 0 -and $decrypted -eq $envNames.Count) {
|
||||
Report "[6] DPAPI 凭证" "PASS" "$decrypted/$($envNames.Count) 密钥可解密"
|
||||
} elseif ($decrypted -gt 0) {
|
||||
Report "[6] DPAPI 凭证" "WARN" "$decrypted/$($envNames.Count) 可解密 (部分失败)"
|
||||
} else {
|
||||
Report "[6] DPAPI 凭证" "FAIL" "0/$($envNames.Count) 可解密 — DPAPI 可能跨用户失效"
|
||||
}
|
||||
} catch {
|
||||
Report "[6] DPAPI 凭证" "WARN" "HKCU 存在但读取异常: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Report "[6] DPAPI 凭证" "FAIL" "HKCU:\Software\Bookworm\CachedEnv 不存在 — 未安装或已卸载"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [7/13] Profile BW_CRED 块
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [7/13] Profile BW_CRED" -ForegroundColor Cyan
|
||||
$credBlockOK = 0; $credBlockMissing = @()
|
||||
foreach ($entry in @(
|
||||
@{ Name = "PS7"; Path = (Join-Path $env:USERPROFILE "Documents\PowerShell\profile.ps1") }
|
||||
@{ Name = "PS5.1"; Path = (Join-Path $env:USERPROFILE "Documents\WindowsPowerShell\profile.ps1") }
|
||||
)) {
|
||||
if (Test-Path $entry.Path) {
|
||||
$c = Get-Content $entry.Path -Raw -EA SilentlyContinue
|
||||
if ($c -match 'BW_CRED_START' -and $c -match 'BW_CRED_END') {
|
||||
$credBlockOK++
|
||||
} else {
|
||||
$credBlockMissing += $entry.Name
|
||||
}
|
||||
} else {
|
||||
$credBlockMissing += "$($entry.Name)(文件不存在)"
|
||||
}
|
||||
}
|
||||
if ($credBlockOK -ge 1 -and $credBlockMissing.Count -eq 0) {
|
||||
Report "[7] Profile BW_CRED" "PASS" "PS7 + PS5.1 双 profile sentinel 完整"
|
||||
} elseif ($credBlockOK -ge 1) {
|
||||
Report "[7] Profile BW_CRED" "WARN" "部分缺失: $($credBlockMissing -join ', ')"
|
||||
} else {
|
||||
Report "[7] Profile BW_CRED" "FAIL" "BW_CRED 块不存在 — 启动时无法自动加载凭证"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [8/13] Profile BW_CLIP 块
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [8/13] Profile BW_CLIP" -ForegroundColor Cyan
|
||||
$clipProfile = Join-Path $env:USERPROFILE "Documents\PowerShell\profile.ps1"
|
||||
if (Test-Path $clipProfile) {
|
||||
$c = Get-Content $clipProfile -Raw -EA SilentlyContinue
|
||||
if ($c -match 'BW_CLIP_START' -and $c -match 'BW_CLIP_END') {
|
||||
Report "[8] Profile BW_CLIP" "PASS" "截图粘贴助手 sentinel 完整"
|
||||
} else {
|
||||
Report "[8] Profile BW_CLIP" "WARN" "BW_CLIP 块缺失 (截图粘贴功能不可用, 非核心)"
|
||||
}
|
||||
} else {
|
||||
Report "[8] Profile BW_CLIP" "WARN" "PS7 profile.ps1 不存在"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [9/13] 环境变量
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [9/13] 环境变量" -ForegroundColor Cyan
|
||||
$envChecks = @(
|
||||
@{ Name = "ANTHROPIC_API_KEY"; Required = $true }
|
||||
@{ Name = "ANTHROPIC_BASE_URL"; Required = $false }
|
||||
@{ Name = "ANTHROPIC_MODEL"; Required = $false }
|
||||
)
|
||||
$envOK = @(); $envMissing = @(); $envOptMissing = @()
|
||||
foreach ($e in $envChecks) {
|
||||
$val = [Environment]::GetEnvironmentVariable($e.Name, 'User')
|
||||
if ($val) {
|
||||
$masked = if ($e.Name -eq 'ANTHROPIC_API_KEY') { $val.Substring(0, [Math]::Min(7, $val.Length)) + '...' } else { $val }
|
||||
$envOK += "$($e.Name)=$masked"
|
||||
} elseif ($e.Required) {
|
||||
$envMissing += $e.Name
|
||||
} else {
|
||||
$envOptMissing += $e.Name
|
||||
}
|
||||
}
|
||||
if ($envMissing.Count -eq 0) {
|
||||
$detail = ($envOK -join '; ')
|
||||
if ($envOptMissing.Count -gt 0) { $detail += " (可选未设: $($envOptMissing -join ', '))" }
|
||||
Report "[9] 环境变量" "PASS" $detail
|
||||
} else {
|
||||
Report "[9] 环境变量" "FAIL" "缺失: $($envMissing -join ', ')"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [10/13] ~/.claude 完整性
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [10/13] ~/.claude 完整性" -ForegroundColor Cyan
|
||||
$claudeDir = Join-Path $env:USERPROFILE ".claude"
|
||||
$intChecks = @()
|
||||
|
||||
# CLAUDE.md
|
||||
$claudeMdPath = Join-Path $claudeDir "CLAUDE.md"
|
||||
$claudeMdOK = $false
|
||||
if (Test-Path $claudeMdPath) {
|
||||
$cm = Get-Content $claudeMdPath -Raw -EA SilentlyContinue
|
||||
$claudeMdOK = $cm -match "Bookworm"
|
||||
}
|
||||
$intChecks += @{ Name = "CLAUDE.md"; OK = $claudeMdOK }
|
||||
|
||||
# Skills
|
||||
$skillsDir = Join-Path $claudeDir "skills"
|
||||
$skillCount = 0
|
||||
if (Test-Path $skillsDir) { $skillCount = @(Get-ChildItem $skillsDir -Directory -EA SilentlyContinue).Count }
|
||||
$intChecks += @{ Name = "Skills ($skillCount, 需>=10)"; OK = ($skillCount -ge 10) }
|
||||
|
||||
# Hooks
|
||||
$hooksDir = Join-Path $claudeDir "hooks"
|
||||
$hookCount = 0
|
||||
if (Test-Path $hooksDir) { $hookCount = @(Get-ChildItem $hooksDir -Filter "*.js" -File -EA SilentlyContinue).Count }
|
||||
$intChecks += @{ Name = "Hooks ($hookCount, 需>=3)"; OK = ($hookCount -ge 3) }
|
||||
|
||||
# Settings hooks
|
||||
$settingsFile = Join-Path $claudeDir "settings.json"
|
||||
$settingsOK = $false
|
||||
if (Test-Path $settingsFile) {
|
||||
$sc = Get-Content $settingsFile -Raw -EA SilentlyContinue
|
||||
$settingsOK = $sc -match '"hooks"'
|
||||
}
|
||||
$intChecks += @{ Name = "Settings hooks"; OK = $settingsOK }
|
||||
|
||||
$intFails = $intChecks | Where-Object { -not $_.OK }
|
||||
if ($intFails.Count -eq 0) {
|
||||
Report "[10] ~/.claude 完整性" "PASS" "CLAUDE.md + $skillCount Skills + $hookCount Hooks + Settings"
|
||||
} else {
|
||||
$failNames = ($intFails | ForEach-Object { $_.Name }) -join ', '
|
||||
Report "[10] ~/.claude 完整性" "FAIL" "缺失: $failNames"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [11/13] API 中转站连通
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [11/13] API 中转站" -ForegroundColor Cyan
|
||||
$apiBaseUrl = [Environment]::GetEnvironmentVariable('ANTHROPIC_BASE_URL', 'User')
|
||||
if (-not $apiBaseUrl) { $apiBaseUrl = $env:ANTHROPIC_BASE_URL }
|
||||
if (-not $apiBaseUrl) { $apiBaseUrl = "https://bww.letcareme.com" }
|
||||
|
||||
try {
|
||||
# 中转站对境外 IP 返 503, NO_PROXY 直连
|
||||
$savedProxy = $env:NO_PROXY
|
||||
$env:NO_PROXY = "bww.letcareme.com,letcareme.com,localhost,127.0.0.1"
|
||||
|
||||
$resp = Invoke-WebRequest -Uri "$apiBaseUrl/v1/models" -Method Head -TimeoutSec 10 -UseBasicParsing -EA Stop
|
||||
$env:NO_PROXY = $savedProxy
|
||||
Report "[11] API 中转站" "PASS" "$apiBaseUrl (HTTP $($resp.StatusCode))"
|
||||
} catch {
|
||||
$env:NO_PROXY = $savedProxy
|
||||
$errMsg = $_.Exception.Message
|
||||
if ($errMsg -match '40[0-9]|301|302|503') {
|
||||
Report "[11] API 中转站" "PASS" "$apiBaseUrl 可达 (HTTP 错误码 = 网络通)"
|
||||
} else {
|
||||
Report "[11] API 中转站" "FAIL" "$apiBaseUrl 不可达: $errMsg"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [12/13] Worker 连通
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [12/13] Worker" -ForegroundColor Cyan
|
||||
try {
|
||||
$workerUrl = "https://bookworm-router.bookworm-api.workers.dev/config"
|
||||
$resp = Invoke-RestMethod -Uri $workerUrl -TimeoutSec 10 -EA Stop
|
||||
Report "[12] Worker" "PASS" "bookworm-router /config 可达"
|
||||
} catch {
|
||||
$errMsg = $_.Exception.Message
|
||||
if ($errMsg -match '403|404') {
|
||||
Report "[12] Worker" "PASS" "Worker 可达 (HTTP 错误码 = 网络通)"
|
||||
} else {
|
||||
Report "[12] Worker" "WARN" "Worker 不可达: $errMsg (非核心, 仅影响默认 model)"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# [13/13] Gitea 连通
|
||||
# ══════════════════════════════════════════════════════
|
||||
Write-Host " [13/13] Gitea" -ForegroundColor Cyan
|
||||
try {
|
||||
$savedProxy = $env:NO_PROXY
|
||||
$env:NO_PROXY = "code.letcareme.com,letcareme.com,localhost,127.0.0.1"
|
||||
$resp = Invoke-WebRequest -Uri "https://code.letcareme.com" -Method Head -TimeoutSec 10 -UseBasicParsing -EA Stop
|
||||
$env:NO_PROXY = $savedProxy
|
||||
Report "[13] Gitea" "PASS" "code.letcareme.com 可达"
|
||||
} catch {
|
||||
$env:NO_PROXY = $savedProxy
|
||||
$errMsg = $_.Exception.Message
|
||||
if ($errMsg -match '40[0-9]|301|302|503') {
|
||||
Report "[13] Gitea" "PASS" "code.letcareme.com 可达 (HTTP 错误码 = 网络通)"
|
||||
} else {
|
||||
Report "[13] Gitea" "FAIL" "code.letcareme.com 不可达: $errMsg — 配置同步将失败"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# Summary
|
||||
# ══════════════════════════════════════════════════════
|
||||
$total = $pass + $warn + $fail
|
||||
Write-Host ""
|
||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||
Write-Host " | 体检报告 |" -ForegroundColor Cyan
|
||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||
|
||||
$passLine = " | PASS: $pass / $total"
|
||||
$warnLine = " | WARN: $warn"
|
||||
$failLine = " | FAIL: $fail"
|
||||
|
||||
Write-Host $passLine -ForegroundColor Green
|
||||
if ($warn -gt 0) { Write-Host $warnLine -ForegroundColor Yellow }
|
||||
if ($fail -gt 0) { Write-Host $failLine -ForegroundColor Red }
|
||||
|
||||
Write-Host " |" -ForegroundColor Cyan
|
||||
if ($fail -eq 0 -and $warn -eq 0) {
|
||||
Write-Host " | [ALL GREEN] Bookworm 完全健康" -ForegroundColor Green
|
||||
} elseif ($fail -eq 0) {
|
||||
Write-Host " | [HEALTHY] 核心功能正常, 有 $warn 项可优化" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " | [NEEDS FIX] $fail 项异常需修复" -ForegroundColor Red
|
||||
Write-Host " | 修复: 重跑 Bookworm-Setup.exe 或联系管理员" -ForegroundColor Red
|
||||
}
|
||||
Write-Host " | 日志: $doctorLog" -ForegroundColor Gray
|
||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
Log-Doctor "=== DONE: PASS=$pass WARN=$warn FAIL=$fail ==="
|
||||
|
||||
if ($fail -gt 0) { exit 1 } else { exit 0 }
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
.NOTES
|
||||
v3.1.0 (2026-04-25) — 引入 wrapper 模式 (闭合 L4 局限: bake claude.ps1 路径 stale)
|
||||
v3.1.2 (2026-04-26) — 加 nvm/fnm/volta shim 探测 (闭合 L9)
|
||||
分发: Phase 7 安装时复制到 $BootDir\bw-launch.ps1
|
||||
#>
|
||||
|
||||
@ -31,6 +32,21 @@ function Write-BwLaunchLog {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
# v3.1.2 L12: log rotation — >1MB 归档, 保留最近 3 个 .bak
|
||||
foreach ($logName in @("bw-launch.log", "bw-crash.log", "bw-phase4-validate.log", "bw-doctor.log")) {
|
||||
$logPath = Join-Path $env:TEMP $logName
|
||||
try {
|
||||
if ((Test-Path $logPath) -and ((Get-Item $logPath -EA SilentlyContinue).Length -gt 1MB)) {
|
||||
$ts = [DateTime]::Now.ToString('yyyyMMdd-HHmmss')
|
||||
Copy-Item $logPath "$logPath.bak.$ts" -Force -EA Stop
|
||||
Remove-Item $logPath -Force -EA Stop
|
||||
# 保留最近 3 个 .bak, 删旧的
|
||||
$baks = Get-ChildItem "$logPath.bak.*" -EA SilentlyContinue | Sort-Object LastWriteTime -Descending
|
||||
if ($baks.Count -gt 3) { $baks | Select-Object -Skip 3 | Remove-Item -Force -EA SilentlyContinue }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function Show-LaunchError {
|
||||
param([string]$Title, [string]$Body)
|
||||
Write-BwLaunchLog "ERROR" "$Title :: $Body"
|
||||
@ -68,6 +84,20 @@ foreach ($p in @("$env:APPDATA\npm", "$env:ProgramFiles\nodejs", "$env:LOCALAPPD
|
||||
}
|
||||
}
|
||||
|
||||
# v3.1.2 L9: nvm/fnm/volta shim 探测 (版本管理器装的 node 不在默认 PATH)
|
||||
$shimPaths = @()
|
||||
if ($env:NVM_HOME) { $shimPaths += "$env:NVM_HOME"; $shimPaths += (Join-Path $env:NVM_HOME "nodejs") }
|
||||
if ($env:NVM_SYMLINK) { $shimPaths += $env:NVM_SYMLINK }
|
||||
if ($env:FNM_DIR) { $shimPaths += (Join-Path $env:FNM_DIR "aliases\default") }
|
||||
if ($env:FNM_MULTISHELL_PATH) { $shimPaths += $env:FNM_MULTISHELL_PATH }
|
||||
if ($env:VOLTA_HOME) { $shimPaths += (Join-Path $env:VOLTA_HOME "bin") }
|
||||
foreach ($sp in $shimPaths) {
|
||||
if ($sp -and (Test-Path $sp) -and ($env:Path -notlike "*$sp*")) {
|
||||
$env:Path = "$sp;$env:Path"
|
||||
Write-BwLaunchLog "INFO" "shim PATH 补充: $sp"
|
||||
}
|
||||
}
|
||||
|
||||
# ── 2. 动态定位 claude.ps1 ──
|
||||
$claudePs1 = $null
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ function startSSEHeartbeat(res) {
|
||||
async function proxyChat(opts, res) {
|
||||
const {
|
||||
apiKey,
|
||||
model = 'claude-sonnet-4-5-20250514',
|
||||
model = 'claude-opus-4-7',
|
||||
messages,
|
||||
maxTokens = 8192,
|
||||
stream = false,
|
||||
|
||||
145
卸载Bookworm-impl.ps1
Normal file
145
卸载Bookworm-impl.ps1
Normal file
@ -0,0 +1,145 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
v3.1.2: Bookworm 完整卸载实现脚本
|
||||
|
||||
.DESCRIPTION
|
||||
由 卸载Bookworm.bat 调用. 集中所有 PS 清理逻辑, 避免 cmd 多行 PS 转义.
|
||||
|
||||
清理项:
|
||||
[1] 桌面 4-5 个 .lnk (启动/更新/体检/卸载/旧 Bookworm.lnk)
|
||||
[2] ~/.claude/ 整个目录 (skills/hooks/agents/凭证)
|
||||
[3] HKCU:\Software\Bookworm (DPAPI 缓存 + Toast 备份)
|
||||
[4] PS7 + PS5.1 双 profile.ps1 的 BW_CRED + BW_CLIP 块
|
||||
[5] HKCU 截图 Toast 设置还原 (从备份)
|
||||
[6] ANTHROPIC_* + BW_LICENSE_KEY User 环境变量
|
||||
[7] 提示用户手动删 bookworm-boot 目录
|
||||
|
||||
保留 (公共依赖, 不主动卸):
|
||||
- Node.js / Git / PowerShell 7 / Claude Code (npm i -g)
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
Add-Type -AssemblyName System.Windows.Forms -EA SilentlyContinue
|
||||
|
||||
# ── 二次确认 ──
|
||||
$msg = "确定要卸载 Bookworm 吗?`n`n"
|
||||
$msg += "将删除:`n"
|
||||
$msg += " - 桌面 4-5 个快捷方式`n"
|
||||
$msg += " - ~/.claude 配置目录 (skills/hooks/agents)`n"
|
||||
$msg += " - HKCU DPAPI 凭证缓存`n"
|
||||
$msg += " - PowerShell profile (PS7+PS5.1) 中的 BW_* 块`n"
|
||||
$msg += " - 还原截图 Toast 默认设置`n"
|
||||
$msg += " - ANTHROPIC_* User 环境变量`n`n"
|
||||
$msg += "保留 (公共依赖, 不主动卸):`n"
|
||||
$msg += " - Node.js / Git / PowerShell 7 / Claude Code"
|
||||
|
||||
$confirm = [System.Windows.Forms.MessageBox]::Show($msg, 'Bookworm 卸载二次确认', 'YesNo', 'Warning')
|
||||
if ($confirm -ne 'Yes') {
|
||||
Write-Host "Bookworm 卸载已取消" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
$logLines = @()
|
||||
function Log { param([string]$m); Write-Host $m -ForegroundColor Cyan; $script:logLines += $m }
|
||||
|
||||
# ── [1/7] 桌面 .lnk ──
|
||||
Log "[1/7] 删桌面快捷方式"
|
||||
$desk = [Environment]::GetFolderPath('Desktop')
|
||||
foreach ($n in @('启动Bookworm.lnk','更新Bookworm.lnk','体检Bookworm.lnk','卸载Bookworm.lnk','Bookworm.lnk')) {
|
||||
$p = Join-Path $desk $n
|
||||
if (Test-Path $p) {
|
||||
try { Remove-Item $p -Force -EA Stop; Log " ✓ 删 $n" }
|
||||
catch { Log " [!] 删 $n 失败: $_" }
|
||||
}
|
||||
}
|
||||
|
||||
# ── [2/7] ~/.claude 目录 ──
|
||||
Log "[2/7] 删 ~/.claude/ (配置 + 凭证 + skills/hooks/agents)"
|
||||
$claudeDir = Join-Path $env:USERPROFILE '.claude'
|
||||
if (Test-Path $claudeDir) {
|
||||
try { Remove-Item $claudeDir -Recurse -Force -EA Stop; Log " ✓ 已删 $claudeDir" }
|
||||
catch { Log " [!] 删除失败 (可能 claude 进程占用): $_" }
|
||||
}
|
||||
|
||||
# ── [3/7] HKCU DPAPI 凭证 + Toast 备份元数据 ──
|
||||
Log "[3/7] 清 HKCU:\Software\Bookworm DPAPI 凭证"
|
||||
# 先读 Toast 备份, 后面 [5] 用
|
||||
$toastOrig = $null
|
||||
$bak = 'HKCU:\Software\Bookworm\ToastBackup'
|
||||
if (Test-Path $bak) {
|
||||
try { $toastOrig = (Get-ItemProperty -Path $bak -Name 'ScreenSketchToast_Original' -EA SilentlyContinue).ScreenSketchToast_Original } catch {}
|
||||
}
|
||||
foreach ($r in @('HKCU:\Software\Bookworm\CachedEnv','HKCU:\Software\Bookworm\ToastBackup','HKCU:\Software\Bookworm')) {
|
||||
if (Test-Path $r) {
|
||||
try { Remove-Item $r -Recurse -Force -EA Stop; Log " ✓ 删 $r" }
|
||||
catch { Log " [!] 删 $r 失败: $_" }
|
||||
}
|
||||
}
|
||||
|
||||
# ── [4/7] profile.ps1 BW_CRED + BW_CLIP 块清理 (PS7+PS5.1) ──
|
||||
Log "[4/7] 清 profile.ps1 BW_CRED + BW_CLIP 块"
|
||||
foreach ($pdir in @(
|
||||
(Join-Path $env:USERPROFILE 'Documents\PowerShell'),
|
||||
(Join-Path $env:USERPROFILE 'Documents\WindowsPowerShell')
|
||||
)) {
|
||||
$pp = Join-Path $pdir 'profile.ps1'
|
||||
if (-not (Test-Path $pp)) { continue }
|
||||
try {
|
||||
$c = Get-Content $pp -Raw -Encoding UTF8
|
||||
$orig = $c.Length
|
||||
# 兼容 v3.0.x / v3.1.x 所有 sentinel 版本
|
||||
$c = [regex]::Replace($c, '# BW_CRED_START[\s\S]*?# BW_CRED_END\r?\n?', '')
|
||||
$c = [regex]::Replace($c, '# BW_CLIP_START[\s\S]*?# BW_CLIP_END\r?\n?', '')
|
||||
if ($c.Length -ne $orig) {
|
||||
[System.IO.File]::WriteAllText($pp, $c, [System.Text.UTF8Encoding]::new($false))
|
||||
Log " ✓ 清理 $pp (移除 $($orig - $c.Length) 字节)"
|
||||
} else {
|
||||
Log " · $pp 无 BW_* 块, 跳过"
|
||||
}
|
||||
} catch { Log " [!] $pp 处理失败: $_" }
|
||||
}
|
||||
|
||||
# ── [5/7] 还原截图 Toast ──
|
||||
Log "[5/7] 还原截图 Toast 设置"
|
||||
$toastReg = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\Microsoft.ScreenSketch_8wekyb3d8bbwe!App'
|
||||
if ($toastOrig -eq '__ABSENT__') {
|
||||
if (Test-Path $toastReg) {
|
||||
try { Remove-ItemProperty -Path $toastReg -Name 'Enabled' -EA Stop; Log " ✓ Toast Enabled 项已删除 (恢复默认)" }
|
||||
catch { Log " [!] Toast 还原失败: $_" }
|
||||
}
|
||||
} elseif ($toastOrig) {
|
||||
try {
|
||||
if (-not (Test-Path $toastReg)) { New-Item -Path $toastReg -Force | Out-Null }
|
||||
Set-ItemProperty -Path $toastReg -Name 'Enabled' -Value ([int]$toastOrig) -Type DWord -Force
|
||||
Log " ✓ Toast Enabled 还原为 $toastOrig"
|
||||
} catch { Log " [!] Toast 还原失败: $_" }
|
||||
} else {
|
||||
Log " · 无 Toast 备份记录, 跳过"
|
||||
}
|
||||
|
||||
# ── [6/7] ANTHROPIC_* User env ──
|
||||
Log "[6/7] 清 ANTHROPIC_* / BW_LICENSE_KEY User 环境变量"
|
||||
foreach ($ev in @('ANTHROPIC_API_KEY','ANTHROPIC_BASE_URL','ANTHROPIC_MODEL','BW_LICENSE_KEY')) {
|
||||
$v = [Environment]::GetEnvironmentVariable($ev, 'User')
|
||||
if ($v) {
|
||||
try { [Environment]::SetEnvironmentVariable($ev, $null, 'User'); Log " ✓ 清 $ev" }
|
||||
catch { Log " [!] 清 $ev 失败: $_" }
|
||||
}
|
||||
}
|
||||
|
||||
# ── [7/7] 提示手动删 bookworm-boot ──
|
||||
Log "[7/7] 手动收尾"
|
||||
Log " 请手动删除 bookworm-boot 目录 (本脚本无法删除自身所在目录):"
|
||||
Log " 例如: $env:USERPROFILE\Downloads\bookworm-boot"
|
||||
|
||||
# ── 完成弹窗 ──
|
||||
$endMsg = "Bookworm 卸载完成.`n`n"
|
||||
$endMsg += "执行摘要:`n"
|
||||
$endMsg += ($logLines -join "`n")
|
||||
$endMsg += "`n`n保留的公共依赖 (如不再需要可手动卸):`n"
|
||||
$endMsg += " - Node.js: %ProgramFiles%\nodejs`n"
|
||||
$endMsg += " - Git for Windows: %ProgramFiles%\Git`n"
|
||||
$endMsg += " - PowerShell 7: %ProgramFiles%\PowerShell\7`n"
|
||||
$endMsg += " - Claude Code: 命令行运行 npm uninstall -g @anthropic-ai/claude-code"
|
||||
[System.Windows.Forms.MessageBox]::Show($endMsg, 'Bookworm 卸载完成', 'OK', 'Information') | Out-Null
|
||||
exit 0
|
||||
@ -1,50 +1,22 @@
|
||||
@echo off
|
||||
chcp 65001 > nul
|
||||
title Bookworm Portable - 卸载
|
||||
cd /d "%~dp0"
|
||||
:: v3.1.2: Bookworm 完整卸载脚本
|
||||
:: 删除: 桌面 .lnk × 4 / ~/.claude / DPAPI HKCU 凭证 /
|
||||
:: 双 profile (PS7+PS5.1) 的 BW_CRED + BW_CLIP 块 / Toast 备份还原 /
|
||||
:: ANTHROPIC_* User 环境变量
|
||||
:: 保留: Node/Git/PS7/Claude Code (公共依赖, 不主动卸)
|
||||
:: 调用配套 PS 脚本完成所有清理 (cmd 不擅长 PS 多行)
|
||||
|
||||
echo.
|
||||
echo ====================================
|
||||
echo Bookworm Portable - 完整卸载
|
||||
echo ====================================
|
||||
echo.
|
||||
echo 将执行:
|
||||
echo - 终止 Claude Code 进程
|
||||
echo - 清除所有环境变量和凭证缓存
|
||||
echo - 恢复原始 .claude 目录
|
||||
echo - 清除 PowerShell 历史和 Git 凭证
|
||||
echo - 删除桌面快捷方式
|
||||
echo.
|
||||
setlocal
|
||||
where pwsh >nul 2>nul && set "PSH=pwsh" || set "PSH=powershell"
|
||||
|
||||
:: AutoAccept: 卸载确认已豁免
|
||||
:: set /p confirm=" 确认卸载? (y/n): "
|
||||
:: if /i not "%confirm%"=="y" (
|
||||
:: echo 已取消
|
||||
:: pause
|
||||
:: exit /b
|
||||
:: )
|
||||
echo [AutoAccept] 自动确认卸载
|
||||
|
||||
echo.
|
||||
|
||||
where pwsh >nul 2>nul
|
||||
if %errorlevel% equ 0 (
|
||||
pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep
|
||||
) else (
|
||||
powershell -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep
|
||||
set "UNINST_PS1=%~dp0卸载Bookworm-impl.ps1"
|
||||
if not exist "%UNINST_PS1%" (
|
||||
echo [!] 卸载实现脚本缺失: %UNINST_PS1%
|
||||
echo [!] 请重跑 Bookworm-Setup.exe Phase 3 重新克隆 bookworm-boot
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 删除桌面快捷方式
|
||||
del "%USERPROFILE%\Desktop\Bookworm.lnk" 2>nul
|
||||
del "%USERPROFILE%\Desktop\更新Bookworm.lnk" 2>nul
|
||||
|
||||
:: 清除凭证缓存注册表
|
||||
reg delete "HKCU\Software\Bookworm" /f 2>nul
|
||||
|
||||
echo.
|
||||
echo ====================================
|
||||
echo Bookworm 已完全卸载
|
||||
echo 可安全删除 bookworm-boot 文件夹
|
||||
echo ====================================
|
||||
echo.
|
||||
pause
|
||||
%PSH% -NoProfile -ExecutionPolicy Bypass -File "%UNINST_PS1%"
|
||||
endlocal
|
||||
|
||||
Loading…
Reference in New Issue
Block a user