bookworm-boot/auto-setup.ps1

1701 lines
78 KiB
PowerShell
Raw Normal View History

<#
.SYNOPSIS
Bookworm Portable - 全自动一键安装器
.DESCRIPTION
全新电脑从零到 Bookworm 完全就绪最大程度自动化
7 阶段: 环境检测 依赖安装 网络诊断 仓库克隆 凭证解密 MCP 验证 启动
需要人工输入时弹出 GUI 对话框
.USAGE
.\auto-setup.ps1
.\auto-setup.ps1 -SkipLaunch # 安装但不启动
#>
param(
[switch]$SkipLaunch
)
$ErrorActionPreference = "Stop"
# ─── 版本号 (每次更新递增, build.ps1 自动读取) ──────
$BWVersion = "2.3.0"
# ─── B4: 单实例保护 (防止双击两次导致竞态) ─────────
$mutexCreated = $false
$global:BWMutex = [System.Threading.Mutex]::new($true, "Global\BookwormPortableSetup", [ref]$mutexCreated)
if (-not $mutexCreated) {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.MessageBox]::Show("Bookworm 安装器已在运行中。`n请勿重复启动。", "提示", "OK", "Information") | Out-Null
exit 0
}
# ─── 路径定义 ────────────────────────────────────────
# PS2EXE 兼容: $MyInvocation.MyCommand.Path 在 EXE 启动时为空,需用 Process MainModule
$ScriptDir = if ($PSScriptRoot) {
$PSScriptRoot
} elseif ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName -match '\.exe$') {
Split-Path -Parent ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)
} elseif ($MyInvocation.MyCommand.Path) {
Split-Path -Parent $MyInvocation.MyCommand.Path
} else {
$PWD.Path
}
$ClaudeDir = Join-Path $env:USERPROFILE ".claude"
$BackupDir = Join-Path $env:USERPROFILE ".claude.bw-backup"
$GitUrl = "https://code.letcareme.com/bookworm/bookworm-config.git"
$BootUrl = "https://code.letcareme.com/bookworm/bookworm-boot.git"
$BootDir = Join-Path $ScriptDir "bookworm-boot"
$SecretsEnc = Join-Path $BootDir "secrets.enc"
$TOTAL_PHASES = 7
# ─── GUI 初始化 ─────────────────────────────────────
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# ─── 日志 + 进度 (PS2EXE -NoConsole -NoOutput 模式: console 全静默) ──
# 所有 Log-X 输出走文件 + GUI 进度窗口 (避免被 PS2EXE 弹窗化)
$BWLogFile = Join-Path $env:TEMP "bookworm-setup-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
function Bw-Log($level, $msg) {
try { Add-Content -Path $BWLogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] [$level] $msg" -Encoding utf8 } catch {}
}
function Log-OK($msg) { Bw-Log "OK" $msg; Update-Progress-SubStatus "$msg" }
function Log-Info($msg) { Bw-Log "INFO" $msg; Update-Progress-SubStatus "$msg" }
function Log-Warn($msg) { Bw-Log "WARN" $msg }
function Log-Fail($msg) { Bw-Log "FAIL" $msg }
function Log-Phase($n, $title) {
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
Bw-Log "PHASE" "[$n/$TOTAL_PHASES] $title"
Update-Progress $n $title
}
# ─── GUI 进度窗口 (常驻顶部, 替代 console 输出) ────
$global:BWProgressForm = $null
$global:BWPhaseLabel = $null
$global:BWStatusLabel = $null
$global:BWProgressBar = $null
function Show-ProgressForm {
# 统一品牌色
$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242) # Bookworm 蓝紫
$brandDark = [System.Drawing.Color]::FromArgb(30, 31, 46)
$uiFont = "Segoe UI"
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm = New-Object System.Windows.Forms.Form
$global:BWProgressForm.Text = "Bookworm Portable Setup v$BWVersion"
$global:BWProgressForm.Size = New-Object System.Drawing.Size(520, 230)
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm.StartPosition = "CenterScreen"
$global:BWProgressForm.FormBorderStyle = "FixedDialog"
$global:BWProgressForm.MaximizeBox = $false
$global:BWProgressForm.MinimizeBox = $false
$global:BWProgressForm.TopMost = $false # P2: 不遮挡其他窗口
$global:BWProgressForm.ControlBox = $true # P0 F1: 允许关闭 (触发确认)
$global:BWProgressForm.BackColor = [System.Drawing.Color]::White
# X 按钮关闭时弹确认
$global:BWProgressForm.Add_FormClosing({
param($s, $e)
if (-not $global:BWInstallDone) {
$r = [System.Windows.Forms.MessageBox]::Show(
"安装尚未完成。`n确定要取消安装吗?",
"取消安装", "YesNo", "Warning")
if ($r -eq "No") { $e.Cancel = $true; return }
Bw-Log "ABORT" "用户手动取消安装"
}
})
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Location = New-Object System.Drawing.Point(20, 16)
$titleLabel.Size = New-Object System.Drawing.Size(480, 26)
$titleLabel.Text = "Bookworm 智能助手 — 自动安装中"
$titleLabel.Font = New-Object System.Drawing.Font($uiFont, 12, [System.Drawing.FontStyle]::Bold)
$titleLabel.ForeColor = $brandDark
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm.Controls.Add($titleLabel)
$global:BWPhaseLabel = New-Object System.Windows.Forms.Label
$global:BWPhaseLabel.Location = New-Object System.Drawing.Point(20, 50)
$global:BWPhaseLabel.Size = New-Object System.Drawing.Size(480, 22)
$global:BWPhaseLabel.Text = "[0/$TOTAL_PHASES] 初始化..."
$global:BWPhaseLabel.Font = New-Object System.Drawing.Font($uiFont, 10)
$global:BWPhaseLabel.ForeColor = $brandBlue
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm.Controls.Add($global:BWPhaseLabel)
$global:BWStatusLabel = New-Object System.Windows.Forms.Label
$global:BWStatusLabel.Location = New-Object System.Drawing.Point(20, 78)
$global:BWStatusLabel.Size = New-Object System.Drawing.Size(480, 22)
$global:BWStatusLabel.Text = ""
$global:BWStatusLabel.Font = New-Object System.Drawing.Font($uiFont, 9)
$global:BWStatusLabel.ForeColor = [System.Drawing.Color]::FromArgb(120, 120, 140)
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm.Controls.Add($global:BWStatusLabel)
$global:BWProgressBar = New-Object System.Windows.Forms.ProgressBar
$global:BWProgressBar.Location = New-Object System.Drawing.Point(20, 112)
$global:BWProgressBar.Size = New-Object System.Drawing.Size(480, 20)
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressBar.Minimum = 0
$global:BWProgressBar.Maximum = $TOTAL_PHASES
$global:BWProgressBar.Value = 0
$global:BWProgressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Continuous # P3: 平滑
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm.Controls.Add($global:BWProgressBar)
$global:BWElapsedLabel = New-Object System.Windows.Forms.Label
$global:BWElapsedLabel.Location = New-Object System.Drawing.Point(400, 136)
$global:BWElapsedLabel.Size = New-Object System.Drawing.Size(100, 18)
$global:BWElapsedLabel.Text = ""
$global:BWElapsedLabel.Font = New-Object System.Drawing.Font($uiFont, 8)
$global:BWElapsedLabel.ForeColor = [System.Drawing.Color]::Silver
$global:BWElapsedLabel.TextAlign = [System.Drawing.ContentAlignment]::TopRight
$global:BWProgressForm.Controls.Add($global:BWElapsedLabel)
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$hint = New-Object System.Windows.Forms.Label
$hint.Location = New-Object System.Drawing.Point(20, 136)
$hint.Size = New-Object System.Drawing.Size(380, 32)
$hint.Text = "首次安装约 5-10 分钟 (依赖下载)`n关闭窗口可取消安装"
$hint.Font = New-Object System.Drawing.Font($uiFont, 8)
$hint.ForeColor = [System.Drawing.Color]::Silver
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWProgressForm.Controls.Add($hint)
$global:BWProgressForm.Show() | Out-Null
$global:BWProgressForm.Refresh()
[System.Windows.Forms.Application]::DoEvents()
}
# 全局安装完成标记 (Close-ProgressForm 前设为 $true, 避免 X 按钮弹确认)
$global:BWInstallDone = $false
# 全局计时器
$global:BWStartTime = [System.Diagnostics.Stopwatch]::StartNew()
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
function Update-Progress($phase, $title) {
if ($global:BWProgressForm -and -not $global:BWProgressForm.IsDisposed) {
try {
$global:BWPhaseLabel.Text = "[$phase/$TOTAL_PHASES] $title"
$global:BWStatusLabel.Text = ""
$global:BWProgressBar.Value = [Math]::Min($phase, $TOTAL_PHASES)
$global:BWProgressForm.Refresh()
[System.Windows.Forms.Application]::DoEvents()
} catch {}
}
}
function Update-Progress-SubStatus($msg) {
if ($global:BWProgressForm -and -not $global:BWProgressForm.IsDisposed -and $global:BWStatusLabel) {
try {
$shortMsg = if ($msg.Length -gt 70) { $msg.Substring(0, 67) + "..." } else { $msg }
$global:BWStatusLabel.Text = $shortMsg
# 刷新总耗时
if ($global:BWElapsedLabel -and $global:BWStartTime) {
$sec = [int]$global:BWStartTime.Elapsed.TotalSeconds
$global:BWElapsedLabel.Text = "$([int]($sec / 60))m $($sec % 60)s"
}
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$global:BWStatusLabel.Refresh()
[System.Windows.Forms.Application]::DoEvents()
} catch {}
}
}
function Close-ProgressForm {
$global:BWInstallDone = $true # 关闭时不再弹 "取消安装?" 确认
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
if ($global:BWProgressForm -and -not $global:BWProgressForm.IsDisposed) {
try { $global:BWProgressForm.Close(); $global:BWProgressForm.Dispose() } catch {}
}
}
function Test-Cmd($cmd) { [bool](Get-Command $cmd -ErrorAction SilentlyContinue) }
# ─── 非阻塞子进程执行 (解决 PS2EXE UI 冻结) ───────────
# 所有耗时子进程都必须经过这两个函数, 保持 GUI 消息泵活跃
function Wait-ProcessWithUI {
<# 替代 System.Diagnostics.Process.WaitForExit(N)
在等待期间每 200ms 泵一次 DoEvents, 防止 "(未响应)" #>
param(
[System.Diagnostics.Process]$proc,
[int]$timeoutMs = 60000,
[string]$label = ""
)
$sw = [System.Diagnostics.Stopwatch]::StartNew()
while (-not $proc.HasExited -and $sw.ElapsedMilliseconds -lt $timeoutMs) {
[System.Windows.Forms.Application]::DoEvents()
# 每 5 秒更新一次副状态, 显示等待耗时
if ($label -and ($sw.ElapsedMilliseconds % 5000) -lt 250) {
$elapsed = [int]($sw.ElapsedMilliseconds / 1000)
Update-Progress-SubStatus "$label ($($elapsed)s)"
}
Start-Sleep -Milliseconds 200
}
if (-not $proc.HasExited) {
try { $proc.Kill() } catch {}
Bw-Log "WARN" "子进程超时 ($timeoutMs ms): $label"
return $false
}
return $true
}
function Run-CmdWithUI {
<# 替代 & cmd args 2>&1 | ForEach-Object { Write-Host }
将阻塞调用转为 Start-Process + Wait-ProcessWithUI #>
param(
[string]$exe,
[string[]]$arguments,
[string]$label = "",
[int]$timeoutMs = 180000, # 默认 3 分钟
[switch]$captureOutput # 返回 stdout 内容
)
# W-01: Windows Start-Process 兼容 — npm/npx 无扩展名是 Unix shell 脚本
if ($exe -in @("npm", "npx") -and -not $exe.EndsWith(".cmd")) {
$exe = "$exe.cmd"
}
# B1: 脱敏日志 (去除 URL 内嵌凭证 user:pass@)
$sanitizedArgs = ($arguments -join ' ') -replace '://[^@]+@', '://***@'
Bw-Log "CMD" "$exe $sanitizedArgs"
Update-Progress-SubStatus $label
# V-04: 用 GetTempFileName (原子创建+加密随机) 替代 Get-Random
$outFile = [System.IO.Path]::GetTempFileName()
$errFile = [System.IO.Path]::GetTempFileName()
try {
$proc = Start-Process -FilePath $exe -ArgumentList $arguments `
-NoNewWindow -PassThru `
-RedirectStandardOutput $outFile `
-RedirectStandardError $errFile
$ok = Wait-ProcessWithUI $proc $timeoutMs $label
$exitCode = if ($proc.HasExited) { $proc.ExitCode } else { -1 }
# 日志记录 stdout/stderr (不超过 20 行)
if (Test-Path $outFile) {
$out = Get-Content $outFile -TotalCount 20 -ErrorAction SilentlyContinue
if ($out) { $out | ForEach-Object { Bw-Log "OUT" $_ } }
}
if (Test-Path $errFile) {
$err = Get-Content $errFile -TotalCount 10 -ErrorAction SilentlyContinue
if ($err) { $err | ForEach-Object { Bw-Log "ERR" $_ } }
}
if ($captureOutput -and (Test-Path $outFile)) {
return @{ OK = ($ok -and $exitCode -eq 0); Output = (Get-Content $outFile -Raw -ErrorAction SilentlyContinue); ExitCode = $exitCode }
}
return @{ OK = ($ok -and $exitCode -eq 0); ExitCode = $exitCode }
} finally {
Remove-Item $outFile, $errFile -Force -ErrorAction SilentlyContinue
}
}
# ─── GUI 对话框 ─────────────────────────────────────
function Show-MsgBox($text, $title = "Bookworm 安装", $buttons = "OK", $icon = "Information") {
[System.Windows.Forms.MessageBox]::Show($text, $title, $buttons, $icon)
}
function Parse-AuthCode-GUI {
param([string]$code)
$code = $code.Trim()
if ($code -notmatch '^BW-(\d{8})-([A-Fa-f0-9]{24})$') { return $null }
$expiryStr = $Matches[1]
$token = $Matches[2].ToLower()
$today = (Get-Date).ToString("yyyyMMdd")
if ([int]$expiryStr -lt [int]$today) { return 'EXPIRED' }
return $token
}
function Show-AuthCodeDialog($attempt = 1, $maxAttempts = 3) {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Bookworm - 授权码验证 ($attempt/$maxAttempts)"
$form.Size = New-Object System.Drawing.Size(480, 240)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$form.TopMost = $true
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(20, 18)
$label.Size = New-Object System.Drawing.Size(440, 36)
$label.Text = "请输入管理员提供的授权码:`n格式: BW-YYYYMMDD-XXXXXXXXXXXXXXXXXXXXXXXX"
$label.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$form.Controls.Add($label)
# 授权码可见 (用于粘贴验证), 不用 PasswordChar
$codeBox = New-Object System.Windows.Forms.TextBox
$codeBox.Location = New-Object System.Drawing.Point(20, 65)
$codeBox.Size = New-Object System.Drawing.Size(430, 30)
$codeBox.Font = New-Object System.Drawing.Font("Consolas", 11)
$codeBox.CharacterCasing = "Upper" # 自动转大写
$form.Controls.Add($codeBox)
$hint = New-Object System.Windows.Forms.Label
$hint.Location = New-Object System.Drawing.Point(20, 100)
$hint.Size = New-Object System.Drawing.Size(440, 20)
$hint.Text = "提示: 直接粘贴管理员发送的授权码即可 (Ctrl+V)"
$hint.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$hint.ForeColor = [System.Drawing.Color]::Gray
$form.Controls.Add($hint)
$btnOK = New-Object System.Windows.Forms.Button
$btnOK.Location = New-Object System.Drawing.Point(250, 145)
$btnOK.Size = New-Object System.Drawing.Size(90, 35)
$btnOK.Text = "验证"
$btnOK.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $btnOK
$form.Controls.Add($btnOK)
$btnCancel = New-Object System.Windows.Forms.Button
$btnCancel.Location = New-Object System.Drawing.Point(350, 145)
$btnCancel.Size = New-Object System.Drawing.Size(90, 35)
$btnCancel.Text = "取消安装"
$btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $btnCancel
$form.Controls.Add($btnCancel)
$form.Add_Shown({ $codeBox.Focus() })
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
return $codeBox.Text.Trim()
}
return $null
}
# ─── 中转站 API Key 输入对话框 + 验证 (v2.3 新增) ──
function Show-ApiKeyDialog($attempt = 1, $maxAttempts = 3, $existingKey = "") {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Bookworm - 中转站 API Key ($attempt/$maxAttempts)"
$form.Size = New-Object System.Drawing.Size(520, 280)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.TopMost = $true
$form.BackColor = [System.Drawing.Color]::White
$lblInfo = New-Object System.Windows.Forms.Label
$lblInfo.Location = New-Object System.Drawing.Point(20, 15)
$lblInfo.Size = New-Object System.Drawing.Size(470, 50)
$lblInfo.Text = "请粘贴你的中转站 API Key`n(bww.letcareme.com 注册后在后台获取, sk- 开头)"
$lblInfo.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$form.Controls.Add($lblInfo)
$keyBox = New-Object System.Windows.Forms.TextBox
$keyBox.Location = New-Object System.Drawing.Point(20, 75)
$keyBox.Size = New-Object System.Drawing.Size(470, 30)
$keyBox.Font = New-Object System.Drawing.Font("Consolas", 10)
$keyBox.Text = $existingKey
$keyBox.PasswordChar = '*'
$form.Controls.Add($keyBox)
$chkShow = New-Object System.Windows.Forms.CheckBox
$chkShow.Location = New-Object System.Drawing.Point(20, 110)
$chkShow.Size = New-Object System.Drawing.Size(150, 25)
$chkShow.Text = "显示 Key"
$chkShow.Add_CheckedChanged({ if ($chkShow.Checked) { $keyBox.PasswordChar = [char]0 } else { $keyBox.PasswordChar = '*' } })
$form.Controls.Add($chkShow)
$lblHint = New-Object System.Windows.Forms.Label
$lblHint.Location = New-Object System.Drawing.Point(20, 140)
$lblHint.Size = New-Object System.Drawing.Size(470, 40)
$lblHint.Text = "首次使用请先注册并充值:`nhttps://bww.letcareme.com"
$lblHint.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$lblHint.ForeColor = [System.Drawing.Color]::FromArgb(100, 110, 130)
$form.Controls.Add($lblHint)
$btnOK = New-Object System.Windows.Forms.Button
$btnOK.Location = New-Object System.Drawing.Point(280, 195)
$btnOK.Size = New-Object System.Drawing.Size(100, 35)
$btnOK.Text = "验证并保存"
$btnOK.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $btnOK
$form.Controls.Add($btnOK)
$btnCancel = New-Object System.Windows.Forms.Button
$btnCancel.Location = New-Object System.Drawing.Point(390, 195)
$btnCancel.Size = New-Object System.Drawing.Size(100, 35)
$btnCancel.Text = "取消"
$btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $btnCancel
$form.Controls.Add($btnCancel)
$form.Add_Shown({ $keyBox.Focus() })
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
return $keyBox.Text.Trim()
}
return $null
}
# 向中转站发一个最小请求验证 key 是否可用
function Test-ApiKey([string]$apiKey, [string]$baseUrl = "https://bww.letcareme.com") {
if (-not $apiKey -or $apiKey.Length -lt 10) { return $false }
try {
$body = '{"model":"claude-sonnet-4-5","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}'
$req = [System.Net.WebRequest]::Create("$baseUrl/v1/messages")
$req.Method = "POST"
$req.ContentType = "application/json"
$req.Headers["x-api-key"] = $apiKey
$req.Headers["anthropic-version"] = "2023-06-01"
$req.Timeout = 10000
$bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
$req.ContentLength = $bytes.Length
$stream = $req.GetRequestStream()
$stream.Write($bytes, 0, $bytes.Length)
$stream.Close()
$resp = $req.GetResponse()
$resp.Close()
return $true
} catch [System.Net.WebException] {
# 401/403 = 认证失败, 其他网络错误也返回 false
$statusCode = 0
try { $statusCode = [int]$_.Exception.Response.StatusCode } catch {}
# 如果返回 200/400 说明 key 有效 (400 可能是请求体问题, 但 key 本身通过)
if ($statusCode -ge 200 -and $statusCode -lt 500 -and $statusCode -ne 401 -and $statusCode -ne 403) {
return $true
}
return $false
} catch {
return $false
}
}
function Show-GiteaCredentialDialog {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Bookworm - Gitea 登录"
$form.Size = New-Object System.Drawing.Size(420, 300)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.TopMost = $true
$form.BackColor = [System.Drawing.Color]::White
$lblInfo = New-Object System.Windows.Forms.Label
$lblInfo.Location = New-Object System.Drawing.Point(20, 15)
$lblInfo.Size = New-Object System.Drawing.Size(360, 40)
$lblInfo.Text = "输入 Gitea 账号 (code.letcareme.com)`n用于下载 Bookworm 配置文件,由管理员提供。"
$lblInfo.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$form.Controls.Add($lblInfo)
$lblUser = New-Object System.Windows.Forms.Label
$lblUser.Location = New-Object System.Drawing.Point(20, 65)
$lblUser.Size = New-Object System.Drawing.Size(80, 25)
$lblUser.Text = "用户名:"
$form.Controls.Add($lblUser)
$txtUser = New-Object System.Windows.Forms.TextBox
$txtUser.Location = New-Object System.Drawing.Point(100, 63)
$txtUser.Size = New-Object System.Drawing.Size(280, 25)
$txtUser.Font = New-Object System.Drawing.Font("Consolas", 11)
$form.Controls.Add($txtUser)
$lblPass = New-Object System.Windows.Forms.Label
$lblPass.Location = New-Object System.Drawing.Point(20, 105)
$lblPass.Size = New-Object System.Drawing.Size(80, 25)
$lblPass.Text = "密码:"
$form.Controls.Add($lblPass)
$txtPass = New-Object System.Windows.Forms.TextBox
$txtPass.Location = New-Object System.Drawing.Point(100, 103)
$txtPass.Size = New-Object System.Drawing.Size(280, 25)
$txtPass.PasswordChar = '*'
$txtPass.Font = New-Object System.Drawing.Font("Consolas", 11)
$form.Controls.Add($txtPass)
# P1 F10: 空值验证提示
$lblError = New-Object System.Windows.Forms.Label
$lblError.Location = New-Object System.Drawing.Point(100, 135)
$lblError.Size = New-Object System.Drawing.Size(280, 20)
$lblError.Text = ""
$lblError.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$lblError.ForeColor = [System.Drawing.Color]::Red
$form.Controls.Add($lblError)
$btnOK = New-Object System.Windows.Forms.Button
$btnOK.Location = New-Object System.Drawing.Point(200, 165)
$btnOK.Size = New-Object System.Drawing.Size(90, 35)
$btnOK.Text = "登录"
$form.Controls.Add($btnOK)
$btnCancel = New-Object System.Windows.Forms.Button
$btnCancel.Location = New-Object System.Drawing.Point(300, 165)
$btnCancel.Size = New-Object System.Drawing.Size(80, 35)
$btnCancel.Text = "取消"
$btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $btnCancel
$form.Controls.Add($btnCancel)
# OK 按钮手动验证 (不用 DialogResult, 防止空值直接关闭)
$btnOK.Add_Click({
if (-not $txtUser.Text.Trim() -or -not $txtPass.Text) {
$lblError.Text = "用户名和密码不能为空"
return
}
$form.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Close()
})
$form.AcceptButton = $btnOK
$form.Add_Shown({ $txtUser.Focus() })
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
return @{ User = $txtUser.Text.Trim(); Pass = $txtPass.Text }
}
return $null
}
# ─── openssl 检测 ────────────────────────────────────
function Find-OpenSSL {
$cmd = Get-Command openssl -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
$paths = @(
"C:\Program Files\Git\usr\bin\openssl.exe",
"D:\Git\usr\bin\openssl.exe",
"C:\Program Files\Git\mingw64\bin\openssl.exe",
"D:\Git\mingw64\bin\openssl.exe"
)
return $paths | Where-Object { Test-Path $_ } | Select-Object -First 1
}
# ─── 凭证缓存 (DPAPI 加密, 绑定当前 Windows 用户) ──
# B2: 不再明文存注册表, 使用 ProtectedData 加密
# B9: 读取时使用白名单, 不加载任意 KEY
Add-Type -AssemblyName System.Security
$CacheAllowedKeys = @("ANTHROPIC_API_KEY","ANTHROPIC_BASE_URL","GITHUB_PERSONAL_ACCESS_TOKEN",
"SLACK_BOT_TOKEN","ATLASSIAN_API_TOKEN","BROWSERBASE_API_KEY","FIRECRAWL_API_KEY","GEMINI_API_KEY")
function Protect-String([string]$plain) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($plain)
$enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, "CurrentUser")
return [Convert]::ToBase64String($enc)
}
function Unprotect-String([string]$b64) {
$enc = [Convert]::FromBase64String($b64)
$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($enc, $null, "CurrentUser")
return [System.Text.Encoding]::UTF8.GetString($bytes)
}
function Get-CachedSecrets {
try {
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
if (-not (Test-Path $regPath)) { return $false }
$expiry = (Get-ItemProperty $regPath -Name "_expiry" -ErrorAction SilentlyContinue)._expiry
if (-not $expiry -or [datetime]$expiry -le (Get-Date)) {
Remove-Item $regPath -Recurse -Force -ErrorAction SilentlyContinue
return $false
}
$props = Get-ItemProperty $regPath -ErrorAction SilentlyContinue
$loaded = 0
$needMigrate = $false
foreach ($p in $props.PSObject.Properties) {
if ($CacheAllowedKeys -contains $p.Name) {
$val = $null
# 先尝试 DPAPI 解密 (新格式)
try { $val = Unprotect-String $p.Value } catch {}
# 回退: 旧版明文格式 (非 Base64 / DPAPI 失败)
if (-not $val -and $p.Value -and $p.Value.Length -lt 200) {
$val = $p.Value
$needMigrate = $true
}
if ($val) {
[System.Environment]::SetEnvironmentVariable($p.Name, $val, "Process")
[System.Environment]::SetEnvironmentVariable($p.Name, $val, "User")
$loaded++
}
}
}
# 旧缓存自动迁移为 DPAPI 格式
if ($needMigrate -and $loaded -gt 0) {
Save-SecretsToCache
Bw-Log "INFO" "旧版明文缓存已迁移为 DPAPI 加密"
}
return ($loaded -gt 0 -and $env:ANTHROPIC_API_KEY)
} catch { return $false }
}
function Save-SecretsToCache {
try {
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
if (-not (Test-Path $regPath)) { New-Item $regPath -Force | Out-Null }
foreach ($k in $CacheAllowedKeys) {
$v = [System.Environment]::GetEnvironmentVariable($k, "Process")
if ($v) {
$encrypted = Protect-String $v
Set-ItemProperty $regPath -Name $k -Value $encrypted -Force
}
}
Set-ItemProperty $regPath -Name "_expiry" -Value (Get-Date).Date.AddDays(1).ToUniversalTime().ToString("o") -Force
} catch {}
}
# ─── 桌面快捷方式 ──────────────────────────────────
function New-DesktopShortcuts {
try {
$shell = New-Object -ComObject WScript.Shell
$desktop = $shell.SpecialFolders("Desktop")
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# 桌面专用图标 (Bookworm 蓝紫渐变 B 圆, 多尺寸 ICO)
$iconPath = Join-Path $BootDir "bookworm-desktop.ico"
if (-not (Test-Path $iconPath)) {
# 回退到 EXE 图标 (bookworm.ico)
$iconPath = Join-Path $BootDir "bookworm.ico"
}
# 快速启动 (bat 文件位于 bookworm-boot 仓库内)
$shortcut = $shell.CreateShortcut("$desktop\Bookworm.lnk")
$batPath = Join-Path $BootDir "启动Bookworm.bat"
if (-not (Test-Path $batPath)) { $batPath = Join-Path $BootDir "Bookworm-OneClick.bat" }
$shortcut.TargetPath = $batPath
$shortcut.WorkingDirectory = $BootDir
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$shortcut.Description = "Bookworm Smart Assistant - 智能助手"
if (Test-Path $iconPath) { $shortcut.IconLocation = "$iconPath,0" }
$shortcut.Save()
# 更新启动
$shortcut2 = $shell.CreateShortcut("$desktop\更新Bookworm.lnk")
$updateBat = Join-Path $BootDir "更新并启动Bookworm.bat"
if (Test-Path $updateBat) {
$shortcut2.TargetPath = $updateBat
$shortcut2.WorkingDirectory = $BootDir
$shortcut2.Description = "更新并启动 Bookworm"
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
if (Test-Path $iconPath) { $shortcut2.IconLocation = "$iconPath,0" }
$shortcut2.Save()
}
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
Log-OK "桌面快捷方式已创建 (含 Bookworm 图标)"
} catch { Log-Warn "快捷方式创建失败: $_" }
}
# ========================================================================
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# 启动: 显示 GUI 进度窗口 (替代 console banner, PS2EXE -NoOutput 兼容)
# ========================================================================
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
Bw-Log "INIT" "Bookworm Portable Setup 启动 - 日志: $BWLogFile"
Show-ProgressForm
# ========================================================================
# Phase 1: 环境检测 + 依赖自动安装
# ========================================================================
Log-Phase 1 "环境检测 + 依赖自动安装"
# 刷新 PATH
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
$deps = @(
# 核心依赖 (缺失则尝试自动安装)
@{ Name = "Node.js"; Cmd = "node"; WingetId = "OpenJS.NodeJS.LTS"; NpmPkg = $null; PipPkg = $null; Core = $true }
@{ Name = "Git"; Cmd = "git"; WingetId = "Git.Git"; NpmPkg = $null; PipPkg = $null; Core = $true }
@{ Name = "PowerShell 7"; Cmd = "pwsh"; WingetId = "Microsoft.PowerShell"; NpmPkg = $null; PipPkg = $null; Core = $false }
@{ Name = "Claude Code"; Cmd = "claude"; WingetId = $null; NpmPkg = "@anthropic-ai/claude-code"; PipPkg = $null; Core = $true }
# Python 移到可选依赖 (不在此列表, 由 line 753 单独处理)
)
$hasWinget = Test-Cmd "winget"
$installed = @()
foreach ($dep in $deps) {
if (Test-Cmd $dep.Cmd) {
$ver = try { & $dep.Cmd --version 2>$null | Select-Object -First 1 } catch { "installed" }
Log-OK "$($dep.Name) $ver"
} else {
Log-Warn "$($dep.Name) 未安装, 正在自动安装..."
if ($dep.WingetId -and $hasWinget) {
try {
$r = Run-CmdWithUI "winget" @("install", $dep.WingetId, "--accept-source-agreements", "--accept-package-agreements") "安装 $($dep.Name)" 300000
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
if (Test-Cmd $dep.Cmd) {
Log-OK "$($dep.Name) 安装成功"
$installed += $dep.Name
} else {
Log-Fail "$($dep.Name) 安装后仍无法找到, 可能需要重启终端"
}
} catch {
Log-Fail "$($dep.Name) 安装失败: $_"
}
}
elseif ($dep.NpmPkg -and (Test-Cmd "npm")) {
try {
$r = Run-CmdWithUI "npm" @("i", "-g", $dep.NpmPkg) "npm 安装 $($dep.Name)" 120000
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
if (Test-Cmd $dep.Cmd) {
Log-OK "$($dep.Name) 安装成功"
$installed += $dep.Name
}
} catch { Log-Fail "$($dep.Name) npm 安装失败: $_" }
}
elseif (-not $hasWinget) {
if ($dep.Core) {
Log-Fail "$($dep.Name) 需要手动安装 (winget 不可用)"
Show-MsgBox "$($dep.Name) 未安装且 winget 不可用。`n请手动安装后重新运行。`n`nNode.js: https://nodejs.org`nGit: https://git-scm.com" "缺少依赖" "OK" "Error"
} else {
Log-Info "$($dep.Name) 未安装 (可选, 不影响核心功能)"
}
}
}
}
# ── bash PATH 自动修复 (Claude Code 的核心工具依赖 bash) ──
# Git 默认只把 cmd\ 加 PATH (有 git.exe), 但 bash.exe 在 bin\ 目录
if ((Test-Cmd "git") -and -not (Test-Cmd "bash")) {
$gitBinPaths = @(
"$env:ProgramFiles\Git\bin",
"${env:ProgramFiles(x86)}\Git\bin",
"D:\Git\bin",
"$env:LOCALAPPDATA\Programs\Git\bin"
)
$gitBin = $gitBinPaths | Where-Object { Test-Path (Join-Path $_ "bash.exe") } | Select-Object -First 1
if ($gitBin) {
# 加入用户 PATH (永久)
$userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
if ($userPath -notmatch [regex]::Escape($gitBin)) {
[System.Environment]::SetEnvironmentVariable("Path", "$userPath;$gitBin", "User")
$env:Path += ";$gitBin"
Log-OK "bash 已加入 PATH: $gitBin"
}
} else {
Log-Warn "Git 已安装但找不到 bash.exe (Claude Code Bash 工具可能不可用)"
}
} elseif (Test-Cmd "bash") {
Log-OK "bash 已就绪"
}
# Claude Code 依赖 npm, 需要在 Node.js 安装后再检查
if (-not (Test-Cmd "claude") -and (Test-Cmd "npm")) {
Log-Info "安装 Claude Code..."
$r = Run-CmdWithUI "npm" @("i", "-g", "@anthropic-ai/claude-code") "安装 Claude Code (首次约 2 分钟)" 180000
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
if (Test-Cmd "claude") { Log-OK "Claude Code 安装成功" } else { Log-Fail "Claude Code 安装失败" }
}
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# uv (Python 包管理器, 可选依赖) - 完全静默, 失败不阻断不弹窗
# 安装策略: 1) winget astral-sh.uv 2) Astral 官方脚本 3) pip fallback
$uvLogFile = Join-Path $env:TEMP "bookworm-uv-install.log"
$uvInstalled = $false
if (Test-Cmd "uv") {
$uvVer = try { (& uv --version 2>$null | Select-Object -First 1) } catch { "installed" }
Log-OK "uv $uvVer (已存在)"
$uvInstalled = $true
} else {
Log-Info "安装 uv (Python 包管理器, 可选)..."
# B8: try/finally 确保 ErrorActionPreference 恢复 (防止后续 Phase 静默吞错)
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$prevErrPref = $ErrorActionPreference
try {
$ErrorActionPreference = "SilentlyContinue"
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# 方案 A: winget (最可靠)
if (Test-Cmd "winget") {
try {
$null = & winget install --id=astral-sh.uv -e --silent --accept-source-agreements --accept-package-agreements 2>&1 |
Out-File -FilePath $uvLogFile -Encoding utf8 -Append
} catch { "[winget] $_" | Out-File -FilePath $uvLogFile -Encoding utf8 -Append }
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
$uvCargoBin = "$env:LOCALAPPDATA\Microsoft\WinGet\Links"
if (Test-Path $uvCargoBin) { $env:Path += ";$uvCargoBin" }
if (Test-Cmd "uv") { $uvInstalled = $true }
}
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# 方案 B: Astral 官方一行脚本
if (-not $uvInstalled) {
try {
$null = & powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://astral.sh/uv/install.ps1 | iex" 2>&1 |
Out-File -FilePath $uvLogFile -Encoding utf8 -Append
} catch { "[astral] $_" | Out-File -FilePath $uvLogFile -Encoding utf8 -Append }
$localBin = Join-Path $env:USERPROFILE ".local\bin"
if (Test-Path $localBin) { $env:Path += ";$localBin" }
if (Test-Cmd "uv") { $uvInstalled = $true }
}
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# 方案 C: pip fallback
if (-not $uvInstalled -and (Test-Cmd "python")) {
try {
$null = & python -m pip install --quiet uv 2>&1 |
Out-File -FilePath $uvLogFile -Encoding utf8 -Append
} catch { "[pip] $_" | Out-File -FilePath $uvLogFile -Encoding utf8 -Append }
try {
$pyScripts = Join-Path (Split-Path (& python -c "import sys; print(sys.executable)") -Parent) "Scripts"
if (Test-Path $pyScripts) { $env:Path += ";$pyScripts" }
} catch {}
if (Test-Cmd "uv") { $uvInstalled = $true }
}
} finally {
$ErrorActionPreference = $prevErrPref
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
}
if ($uvInstalled) {
Log-OK "uv 安装成功"
$installed += "uv"
} else {
# 静默 fallback: 仅写日志文件, 不调 Log-Warn 避免 PS2EXE 弹窗
"[fail] uv 三种安装方式均失败, Python MCP 将不可用. 详见上方日志." | Out-File -FilePath $uvLogFile -Encoding utf8 -Append
Log-Info "uv 未就绪 (可选, 不影响核心功能, 详情: $uvLogFile)"
}
}
# OpenSSL (随 Git 安装)
$opensslCmd = Find-OpenSSL
if ($opensslCmd) { Log-OK "OpenSSL: $opensslCmd" } else { Log-Warn "OpenSSL 未找到 (凭证解密可能失败)" }
# 最终检查 (仅 Node.js + Git + Claude Code 为硬性依赖, PowerShell 7 可选)
if (-not (Test-Cmd "node") -or -not (Test-Cmd "git") -or -not (Test-Cmd "claude")) {
$missing = @()
if (-not (Test-Cmd "node")) { $missing += "Node.js" }
if (-not (Test-Cmd "git")) { $missing += "Git" }
if (-not (Test-Cmd "claude")) { $missing += "Claude Code" }
Show-MsgBox "以下核心依赖安装失败: $($missing -join ', ')`n`n请手动安装后重新运行。`n`nNode.js: https://nodejs.org`nGit: https://git-scm.com" "安装中断" "OK" "Error"
exit 1
}
if (-not (Test-Cmd "pwsh")) {
Log-Info "PowerShell 7 未安装 (可选, 用系统 PowerShell 5.1 替代)"
}
# 定位 pwsh.exe 完整路径 (供后续 settings.json 配置使用)
$PwshPath = (Get-Command pwsh -ErrorAction SilentlyContinue).Source
if (-not $PwshPath) {
# winget 默认安装路径
$defaultPaths = @(
"$env:ProgramFiles\PowerShell\7\pwsh.exe",
"${env:ProgramFiles(x86)}\PowerShell\7\pwsh.exe",
"$env:LOCALAPPDATA\Microsoft\PowerShell\pwsh.exe"
)
$PwshPath = $defaultPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
}
if ($PwshPath) {
Log-OK "PowerShell 7 路径: $PwshPath"
} else {
Log-Warn "pwsh 可执行但无法定位完整路径, 使用 'pwsh'"
$PwshPath = "pwsh"
}
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
# 可选依赖检查 (不阻断, 用 Log-Info 避免 PS2EXE 弹窗化)
$optionalMissing = @()
if (-not (Test-Cmd "python")) { $optionalMissing += "Python 3.12" }
if (-not (Test-Cmd "uv")) { $optionalMissing += "uv" }
if ($optionalMissing.Count -gt 0) {
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
Log-Info "可选依赖未就绪: $($optionalMissing -join ', ') — 仅影响 Python 类 MCP, 核心功能正常"
}
if ($installed.Count -gt 0) {
Log-OK "本次新安装: $($installed -join ', ')"
}
# ========================================================================
# Phase 2: 网络诊断
# ========================================================================
Log-Phase 2 "网络诊断"
# 代理检测
$env:NO_PROXY = "bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1"
$env:no_proxy = $env:NO_PROXY
$proxyFound = $false
# .NET 系统代理
if (-not $env:HTTPS_PROXY) {
try {
$proxyUri = [System.Net.WebRequest]::DefaultWebProxy.GetProxy("https://api.anthropic.com")
if ($proxyUri -and $proxyUri.Authority -ne "api.anthropic.com") {
$env:HTTPS_PROXY = "http://$($proxyUri.Authority)"
$env:HTTP_PROXY = $env:HTTPS_PROXY
Log-OK "系统代理: $($env:HTTPS_PROXY)"
$proxyFound = $true
}
} catch {}
}
# 注册表 IE 代理
if (-not $proxyFound -and -not $env:HTTPS_PROXY) {
try {
$reg = Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -ErrorAction SilentlyContinue
if ($reg.ProxyEnable -eq 1 -and $reg.ProxyServer) {
$proxy = $reg.ProxyServer
if ($proxy -notmatch '^http') { $proxy = "http://$proxy" }
$env:HTTPS_PROXY = $proxy
$env:HTTP_PROXY = $proxy
Log-OK "IE 代理: $proxy"
$proxyFound = $true
}
} catch {}
}
# 端口扫描
if (-not $proxyFound -and -not $env:HTTPS_PROXY) {
$ports = @(7890,7891,7893,10792,10793,10808,10809,1080,1087,8080,8118)
foreach ($port in $ports) {
try {
$tcp = New-Object System.Net.Sockets.TcpClient
$ar = $tcp.BeginConnect("127.0.0.1", $port, $null, $null)
$ok = $ar.AsyncWaitHandle.WaitOne(500)
if ($ok) { $tcp.EndConnect($ar); $tcp.Close()
$env:HTTPS_PROXY = "http://127.0.0.1:$port"
$env:HTTP_PROXY = $env:HTTPS_PROXY
Log-OK "本地代理端口: $port"
$proxyFound = $true
break
}
$tcp.Close()
} catch {}
}
}
if ($env:HTTPS_PROXY) { $proxyFound = $true }
if (-not $proxyFound) {
Log-Warn "未检测到代理/VPN"
$r = Show-MsgBox "未检测到代理/VPN 软件。`n国内 Claude Code 需要代理才能启动。`n`n请先启动代理软件 (Clash / V2Ray / 快柠檬)`n然后点击 '重试'。`n`n或点击 '忽略' 继续 (可能失败)。" "网络警告" "AbortRetryIgnore" "Warning"
if ($r -eq "Retry") {
# 重试代理检测
try {
$proxyUri = [System.Net.WebRequest]::DefaultWebProxy.GetProxy("https://api.anthropic.com")
if ($proxyUri -and $proxyUri.Authority -ne "api.anthropic.com") {
$env:HTTPS_PROXY = "http://$($proxyUri.Authority)"
$env:HTTP_PROXY = $env:HTTPS_PROXY
Log-OK "系统代理: $($env:HTTPS_PROXY)"
}
} catch {}
} elseif ($r -eq "Abort") { exit 1 }
}
Log-OK "NO_PROXY: bww.letcareme.com, code.letcareme.com"
# 连通性测试
Log-Info "测试网络连通性..."
$netTests = @(
@{ Name = "Gitea 代码仓库"; Url = "https://code.letcareme.com"; Direct = $true }
@{ Name = "API 中转站"; Url = "https://bww.letcareme.com"; Direct = $true }
@{ Name = "Claude API"; Url = "https://api.anthropic.com"; Direct = $false }
)
foreach ($t in $netTests) {
try {
$req = [System.Net.HttpWebRequest]::Create($t.Url)
$req.Timeout = 8000
$req.Method = "HEAD"
if ($t.Direct) { $req.Proxy = [System.Net.GlobalProxySelection]::GetEmptyWebProxy() }
$resp = $req.GetResponse()
$code = [int]$resp.StatusCode
$resp.Close()
Log-OK "$($t.Name) ($($t.Url)) - HTTP $code"
} catch {
$errMsg = $_.Exception.InnerException.Message
if (-not $errMsg) { $errMsg = $_.Exception.Message }
# 非 200 但能连上也算成功 (如 401, 403)
if ($errMsg -match '40[0-9]|30[0-9]') {
Log-OK "$($t.Name) - 可达 (需认证)"
} else {
Log-Warn "$($t.Name) - 不可达: $($errMsg.Substring(0, [Math]::Min(60, $errMsg.Length)))"
}
}
}
# ========================================================================
# Phase 3: 仓库克隆
# ========================================================================
Log-Phase 3 "同步 Bookworm 配置"
# B3: 使用 Windows Credential Manager (DPAPI 加密) 替代明文 store
git config --global credential.helper manager 2>$null
# 克隆/更新 config 仓库 (.claude/) — 使用 Run-CmdWithUI 防止 UI 冻结
# 辅助函数: clone 后缓存凭证到 Windows Credential Manager
function Cache-GitCredentials($credObj) {
if (-not $credObj) { return }
try {
$approveInput = "protocol=https`nhost=code.letcareme.com`nusername=$($credObj.User)`npassword=$($credObj.Pass)`n`n"
$approveInput | & git credential approve 2>$null
Bw-Log "OK" "Gitea 凭证已缓存到 Windows Credential Manager"
} catch { Bw-Log "WARN" "凭证缓存失败: $_" }
}
if (Test-Path (Join-Path $ClaudeDir ".git")) {
Log-Info "配置仓库已存在, 更新中..."
# 设置 git 身份 (auto-resolve commit 需要)
& git -C $ClaudeDir config user.email "bookworm@auto.local" 2>$null
& git -C $ClaudeDir config user.name "Bookworm" 2>$null
try {
# 强制清除冲突状态 (运行时文件不重要, Phase 5 会重新渲染)
& git -C $ClaudeDir reset --hard HEAD 2>&1 | Out-Null
$r = Run-CmdWithUI "git" @("-C", $ClaudeDir, "pull", "--rebase", "--autostash") "同步配置仓库" 120000
if ($r.OK) {
Log-OK "配置仓库已更新"
} else {
# pull 失败可能是认证问题, 尝试重新输入凭证
Log-Warn "git pull 失败, 尝试重新认证..."
$cred = Show-GiteaCredentialDialog
if ($cred) {
Cache-GitCredentials $cred
$r2 = Run-CmdWithUI "git" @("-C", $ClaudeDir, "pull", "--rebase", "--autostash") "重试同步" 120000
if ($r2.OK) { Log-OK "配置仓库已更新 (重新认证成功)" }
else { Log-Warn "git pull 仍失败, 使用本地版本" }
} else { Log-Warn "用户取消认证, 使用本地版本" }
}
} catch { Log-Warn "git pull 异常: $_, 使用本地版本" }
}
elseif (Test-Path $ClaudeDir) {
Log-Info "备份现有 .claude/ 并克隆..."
if (Test-Path $BackupDir) { Remove-Item $BackupDir -Recurse -Force }
Rename-Item $ClaudeDir $BackupDir
$cred = Show-GiteaCredentialDialog
$cloneUrl = if ($cred) { $GitUrl -replace '://', "://$([System.Uri]::EscapeDataString($cred.User)):$([System.Uri]::EscapeDataString($cred.Pass))@" } else { $GitUrl }
$r = Run-CmdWithUI "git" @("clone", "--depth", "1", $cloneUrl, $ClaudeDir) "克隆配置仓库" 180000
if (Test-Path (Join-Path $ClaudeDir "CLAUDE.md")) {
Log-OK "配置仓库克隆成功 (旧目录已备份)"
Cache-GitCredentials $cred
} else {
Log-Fail "克隆失败"
if (Test-Path $BackupDir) { Rename-Item $BackupDir $ClaudeDir }
Show-MsgBox "配置仓库克隆失败。`n请检查网络和 Gitea 账号密码。" "克隆失败" "OK" "Error"
exit 1
}
}
else {
Log-Info "首次安装, 克隆配置仓库..."
$cred = Show-GiteaCredentialDialog
$cloneUrl = if ($cred) { $GitUrl -replace '://', "://$([System.Uri]::EscapeDataString($cred.User)):$([System.Uri]::EscapeDataString($cred.Pass))@" } else { $GitUrl }
$r = Run-CmdWithUI "git" @("clone", "--depth", "1", $cloneUrl, $ClaudeDir) "克隆配置仓库" 180000
if (Test-Path (Join-Path $ClaudeDir "CLAUDE.md")) {
Log-OK "配置仓库克隆成功"
Cache-GitCredentials $cred
} else {
Log-Fail "克隆失败"
Show-MsgBox "配置仓库克隆失败。`n请检查网络连接和 Gitea 账号。" "克隆失败" "OK" "Error"
exit 1
}
}
# 创建本地运行时目录
$dirs = @("debug","sessions","cache","backups","telemetry","shell-snapshots","projects","memory")
foreach ($d in $dirs) {
$p = Join-Path $ClaudeDir $d
if (-not (Test-Path $p)) { New-Item -ItemType Directory -Path $p -Force | Out-Null }
}
# ─── 克隆/更新 bookworm-boot (含 crypto-helper.js + secrets-*.enc + install.ps1) ───
if (Test-Path (Join-Path $BootDir ".git")) {
Log-Info "boot 仓库已存在, 更新中..."
try {
$r = Run-CmdWithUI "git" @("-C", $BootDir, "pull", "--rebase") "同步 boot 仓库" 120000
if ($r.OK) { Log-OK "boot 仓库已更新" } else { Log-Warn "boot 仓库更新失败, 使用本地版本" }
} catch { Log-Warn "boot 仓库更新失败, 使用本地版本" }
} else {
Log-Info "克隆 boot 仓库 (含解密工具与凭证)..."
# F-09 fix: 始终弹凭证对话框, 不依赖 config 分支的 $cred 残留值
$cred = Show-GiteaCredentialDialog
$bootCloneUrl = if ($cred) { $BootUrl -replace '://', "://$([System.Uri]::EscapeDataString($cred.User)):$([System.Uri]::EscapeDataString($cred.Pass))@" } else { $BootUrl }
$r = Run-CmdWithUI "git" @("clone", "--depth", "1", $bootCloneUrl, $BootDir) "克隆 boot 仓库" 180000
if (-not (Test-Path (Join-Path $BootDir "crypto-helper.js"))) {
Log-Fail "启动工具包下载失败"
Show-MsgBox "Bookworm 启动工具包下载失败。`n`n请检查:`n1. Gitea 账号和密码是否正确`n2. 网络连接是否正常`n3. 代理软件是否已启动`n`n然后重新运行安装器即可。" "下载失败" "OK" "Error"
exit 1
}
Log-OK "boot 仓库克隆成功 → $BootDir"
Cache-GitCredentials $cred # F-09 fix: 缓存 boot 仓库凭证
}
# ========================================================================
# Phase 4: 凭证解密 (GUI 弹窗)
# ========================================================================
Log-Phase 4 "凭证解密"
$secretsDecrypted = $false
# 优先级 1: User 级环境变量已有 (上次安装已永久写入)
$existingKey = [System.Environment]::GetEnvironmentVariable("ANTHROPIC_API_KEY", "User")
$existingUrl = [System.Environment]::GetEnvironmentVariable("ANTHROPIC_BASE_URL", "User")
if ($existingKey) {
# 注入到当前 Process (User 环境变量新终端才生效, 当前进程需手动加载)
$env:ANTHROPIC_API_KEY = $existingKey
if ($existingUrl) { $env:ANTHROPIC_BASE_URL = $existingUrl }
# 加载其他 Key (如果有)
foreach ($k in $CacheAllowedKeys) {
$v = [System.Environment]::GetEnvironmentVariable($k, "User")
if ($v) { [System.Environment]::SetEnvironmentVariable($k, $v, "Process") }
}
Log-OK "从系统环境变量加载凭证 (已有安装记录, 免输授权码)"
$secretsDecrypted = $true
}
# 优先级 2: Registry DPAPI 缓存
if (-not $secretsDecrypted -and (Get-CachedSecrets)) {
Log-OK "从 Registry 缓存加载凭证"
$secretsDecrypted = $true
}
# 优先级 3: 直接输入中转站 API Key (v2.3 新增)
if (-not $secretsDecrypted) {
Show-MsgBox "欢迎使用 Bookworm Portable`n`n接下来需要配置你的中转站 API Key。`n`n如果还没有, 请先去 https://bww.letcareme.com 注册并充值获取。" "配置 API Key" "OK" "Information"
$keyAttempts = 0
$maxKeyAttempts = 3
while ($keyAttempts -lt $maxKeyAttempts) {
$keyAttempts++
$apiKey = Show-ApiKeyDialog $keyAttempts $maxKeyAttempts
if (-not $apiKey) {
Log-Warn "用户取消 API Key 输入"
break
}
# 基础格式校验
if ($apiKey.Length -lt 20) {
Show-MsgBox "API Key 格式错误 (长度过短)。`n请检查后重试。" "格式错误" "OK" "Warning"
continue
}
Log-Info "正在验证 API Key..."
if (Test-ApiKey $apiKey) {
# 验证通过, 写入环境变量 + 缓存
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "Process")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "User")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", "https://bww.letcareme.com", "Process")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", "https://bww.letcareme.com", "User")
Log-OK "API Key 已验证并写入环境变量 (永久生效)"
$secretsDecrypted = $true
Save-SecretsToCache
Show-MsgBox "API Key 验证成功!`n`n已写入系统环境变量, 任何终端输入 claude 即可启动。`n`n以后想换 Key, 可以:`n1. 双击桌面 '更换Key.bat'`n2. 或 Claude Code 里输入 /change-key`n3. 或重跑安装器" "验证成功" "OK" "Information"
break
} else {
$remaining = $maxKeyAttempts - $keyAttempts
if ($remaining -gt 0) {
Show-MsgBox "API Key 验证失败 (无法连接或认证错误)。`n剩余重试: $remaining`n`n请检查:`n1. Key 是否正确 (sk- 开头)`n2. 中转站是否有余额`n3. 网络和代理是否正常" "验证失败" "OK" "Warning"
} else {
Show-MsgBox "3 次验证均失败。`n`n请检查 Key 和网络, 或联系管理员。" "验证失败" "OK" "Error"
}
}
}
}
# 优先级 4: 解密授权码 (向后兼容, 旧用户保留)
if (-not $secretsDecrypted) {
$cryptoHelper = Join-Path $BootDir "crypto-helper.js"
if (-not (Test-Cmd "node") -or -not (Test-Path $cryptoHelper)) {
Log-Info "跳过授权码解密"
}
elseif ((Test-Path $SecretsEnc) -or (Get-ChildItem $BootDir -Filter "secrets-*.enc" -ErrorAction SilentlyContinue)) {
# 强制要求授权码 — 不允许跳过 (跳过 = 无法使用)
Show-MsgBox "检测到加密凭证文件,需要输入授权码才能使用 Bookworm。`n`n授权码由管理员提供,格式: BW-YYYYMMDD-XXXX...`n如果没有授权码,请联系管理员获取。" "需要授权码" "OK" "Information"
$validAttempts = 0
while ($validAttempts -lt 3) {
$rawCode = Show-AuthCodeDialog ($validAttempts + 1) 3
if (-not $rawCode) {
# 不再静默跳过,明确警告
$skip = Show-MsgBox "未输入授权码。`n`n没有授权码将无法使用 Bookworm无 API 凭证)。`n`n确定要跳过吗?" "警告" "YesNo" "Warning"
if ($skip -eq "No") { continue }
Log-Warn "用户确认跳过授权码"
break
}
$token = Parse-AuthCode-GUI $rawCode
if ($token -eq 'EXPIRED') {
Show-MsgBox "授权码已过期。`n请联系管理员获取新授权码。" "授权码过期" "OK" "Warning"
continue
}
if (-not $token) {
Show-MsgBox "格式错误。`n正确格式: BW-YYYYMMDD-XXXXXXXXXXXXXXXXXXXXXXXX`n`n请检查后重新粘贴。" "格式错误" "OK" "Warning"
continue
}
# B7: 先检查文件存在, 再递增 validAttempts (文件缺失不消耗尝试次数)
$fileId = $token.Substring(0, 8)
$encFile = Join-Path $BootDir "secrets-$fileId.enc"
if (-not (Test-Path $encFile)) { $encFile = $SecretsEnc }
if (-not (Test-Path $encFile)) {
Show-MsgBox "未找到对应凭证文件。`n请确认管理员已推送 secrets-$fileId.enc 到 Gitea`n并重新运行安装器(会自动拉取)。`n`n(此次不计为失败尝试)" "文件未找到" "OK" "Warning"
$token = $null
continue
}
$validAttempts++ # B7: 只有真正尝试解密才计数
try {
$decrypted = & node $cryptoHelper decrypt $token $encFile 2>&1
$decExit = $LASTEXITCODE
$token = $null
if ($decExit -eq 0 -and $decrypted -and $decrypted -notmatch 'WRONG_PASSWORD|WRONG_FORMAT|bad decrypt|bad magic') {
$count = 0
foreach ($line in $decrypted -split "`n") {
$line = $line.Trim()
if (-not $line -or $line -notmatch '=') { continue }
$key = ($line -split '=', 2)[0].Trim()
$value = ($line -split '=', 2)[1].Trim()
# F-12 fix: 白名单 + 长度校验, 防止 secrets.enc 被污染注入恶意 env
if ($key -and $value -and ($key -in $CacheAllowedKeys) -and ($value.Length -lt 512)) {
[System.Environment]::SetEnvironmentVariable($key, $value, "Process")
[System.Environment]::SetEnvironmentVariable($key, $value, "User")
Log-OK "已注入: $key (永久)"
$count++
} elseif ($key -and ($key -notin $CacheAllowedKeys)) {
Bw-Log "WARN" "跳过未知 key: $key (不在白名单)"
}
}
$decrypted = $null
$secretsDecrypted = $true
Save-SecretsToCache # F-22 fix: 写入 DPAPI 缓存, 下次免授权码
Show-MsgBox "授权码验证成功!`n`n$count 个凭证已写入系统环境变量 (永久生效)。`n任何终端输入 claude 即可启动,无需再次输入授权码。" "验证成功" "OK" "Information"
break
} else {
$token = $null
$remaining = 3 - $validAttempts
if ($remaining -gt 0) {
Show-MsgBox "授权码无效(解密失败),剩余重试: $remaining" "验证失败" "OK" "Warning"
} else {
Show-MsgBox "3 次验证均失败。`n请联系管理员重新获取授权码。" "解密失败" "OK" "Error"
}
}
} catch {
$token = $null
Log-Warn "解密异常: $_"
}
}
}
else {
Log-Warn "未找到任何 secrets*.enc跳过凭证解密"
}
} # end if (-not $secretsDecrypted)
# ========================================================================
# Phase 5: 配置渲染
# ========================================================================
Log-Phase 5 "配置渲染"
$templateFile = Join-Path $ClaudeDir "settings.template.json"
$settingsFile = Join-Path $ClaudeDir "settings.json"
if (Test-Path $templateFile) {
$claudeRoot = $ClaudeDir.Replace('\', '/')
$homeDir = $env:USERPROFILE
# pwsh 路径转正斜杠供 JSON 使用 (C:/Program Files/PowerShell/7/pwsh.exe)
$pwshJsonPath = if ($PwshPath) { $PwshPath.Replace('\', '/') } else { "pwsh" }
$content = Get-Content $templateFile -Raw
$content = $content -replace '\{\{CLAUDE_ROOT\}\}', $claudeRoot
# HOME 必须用正斜杠: C:\Users\x 中 \U 是非法 JSON 转义序列
$homeForward = $homeDir.Replace('\', '/')
$content = $content -replace '\{\{HOME\}\}', $homeForward
$content = $content -replace '\{\{PWSH_PATH\}\}', $pwshJsonPath
Set-Content $settingsFile -Value $content -Encoding UTF8
Log-OK "settings.json 已渲染 (ROOT=$claudeRoot, SHELL=$pwshJsonPath)"
# settings.local.template.json (向后兼容)
$localTpl = Join-Path $ClaudeDir "settings.local.template.json"
$localSet = Join-Path $ClaudeDir "settings.local.json"
if (Test-Path $localTpl) {
$lc = Get-Content $localTpl -Raw
$lc = $lc -replace '\{\{CLAUDE_ROOT\}\}', $claudeRoot
$lc = $lc -replace '\{\{HOME\}\}', $homeForward
$lc = $lc -replace '\{\{USERNAME\}\}', $env:USERNAME
$lc = $lc -replace '\{\{PWSH_PATH\}\}', $pwshJsonPath
Set-Content $localSet -Value $lc -Encoding UTF8
Log-OK "settings.local.json 已渲染"
}
# ── ~/.claude.json (Claude Code v2.1+ MCP 服务器配置的正确位置) ──
# 优先用 config 仓库的 inject-mcp.js, 如果 git pull 失败则内嵌 fallback
$injectScript = Join-Path $ClaudeDir "inject-mcp.js"
$mcpInjected = $false
# 方案 A: 调用 config 仓库里的 inject-mcp.js
if (Test-Path $injectScript) {
try {
$nodeOut = & node $injectScript 2>&1
$firstLine = ($nodeOut | Select-Object -First 1).ToString().Trim()
Log-OK $firstLine
$mcpInjected = $true
} catch { Bw-Log "WARN" "inject-mcp.js 执行失败: $_" }
}
# 方案 B: 内嵌 fallback (git pull 失败时 inject-mcp.js 不存在)
if (-not $mcpInjected) {
Log-Info "inject-mcp.js 不可用, 使用内嵌 MCP 注入..."
$fallbackJs = Join-Path $env:TEMP "bw-mcp-fallback.js"
try {
$fbLines = @(
'var fs=require("fs"),p=require("path");'
'var H=process.env.USERPROFILE;'
'var f=p.join(H,".claude.json");'
'var d={};'
'try{d=JSON.parse(fs.readFileSync(f,"utf8"))}catch(e){}'
'var N="npx.cmd",Y="--yes";'
'var S={};'
'S.context7={command:N,args:[Y,"@upstash/context7-mcp@2.1.1"],type:"stdio"};'
'S.playwright={command:N,args:[Y,"@playwright/mcp@0.0.68","--headless"],type:"stdio"};'
'S["session-continuity"]={command:N,args:[Y,"claude-session-continuity-mcp@1.13.0"],type:"stdio"};'
'S["browser-mcp"]={command:N,args:[Y,"@browsermcp/mcp@latest"],type:"stdio"};'
'S["desktop-commander"]={command:N,args:[Y,"@wonderwhy-er/desktop-commander@latest"],type:"stdio"};'
'S["chrome-devtools"]={command:N,args:[Y,"chrome-devtools-mcp@0.18.1"],type:"stdio"};'
'S.github={command:N,args:[Y,"@modelcontextprotocol/server-github"],type:"stdio"};'
'S.slack={command:N,args:[Y,"@modelcontextprotocol/server-slack"],type:"stdio"};'
'S.firecrawl={command:N,args:[Y,"firecrawl-mcp"],type:"stdio"};'
'S["mcp-image"]={command:N,args:[Y,"mcp-image"],type:"stdio"};'
'S["google-drive"]={command:N,args:[Y,"@piotr-agier/google-drive-mcp"],type:"stdio"};'
'S.browserbase={command:N,args:[Y,"@anthropic-ai/browserbase-mcp"],type:"stdio"};'
'S.notebooklm={command:N,args:[Y,"notebooklm-mcp@latest"],type:"stdio"};'
'S.cloudflare={command:N,args:[Y,"mcp-remote","https://docs.mcp.cloudflare.com/sse"],type:"stdio"};'
'S.mobile={command:N,args:[Y,"@mobilenext/mobile-mcp@0.0.35"],type:"stdio"};'
'var K="@modelcontextprotocol/server-sequential-thinking";'
'S["sequential-thinking"]={command:N,args:[Y,K+"@2025.12.18"],type:"stdio"};'
'S.linear={type:"http",url:"https://mcp.linear.app/mcp"};'
'S.supabase={type:"http",url:"https://mcp.supabase.com/mcp?project_ref=oepmihbtoylosbsxlmfo"};'
'S.figma={type:"http",url:"https://mcp.figma.com/mcp"};'
'S["windows-mcp"]={command:"uvx",args:["--python","3.13","windows-mcp"],type:"stdio"};'
'S.atlassian={command:"uvx",args:["mcp-atlassian"],type:"stdio"};'
'S["computer-control-mcp"]={command:"uvx",args:["computer-control-mcp@latest"],type:"stdio"};'
'd.mcpServers=S;'
'fs.writeFileSync(f,JSON.stringify(d,null,2));'
'console.log("OK: "+Object.keys(S).length+" MCP servers (fallback)");'
)
$fbLines -join "`n" | Set-Content $fallbackJs -Encoding UTF8
$nodeOut = & node $fallbackJs 2>&1
$firstLine = ($nodeOut | Select-Object -First 1).ToString().Trim()
Remove-Item $fallbackJs -Force -ErrorAction SilentlyContinue
Log-OK $firstLine
} catch { Bw-Log "WARN" "MCP fallback 注入失败: $_" }
}
} else {
Log-Warn "settings.template.json 不存在, 跳过渲染"
}
$env:CLAUDE_HOME = $ClaudeDir
# ========================================================================
# Phase 6: MCP 验证 + 自动安装
# ========================================================================
Log-Phase 6 "MCP 服务验证 + 预安装"
# ── 6a: Bookworm 完整性检查 ──
$skillCount = 0; $hookCount = 0
$skillsDir = Join-Path $ClaudeDir "skills"
$hooksDir = Join-Path $ClaudeDir "hooks"
if (Test-Path $skillsDir) { $skillCount = (Get-ChildItem $skillsDir -Directory -ErrorAction SilentlyContinue).Count }
if (Test-Path $hooksDir) { $hookCount = (Get-ChildItem $hooksDir -Filter "*.js" -File -ErrorAction SilentlyContinue).Count }
$claudeMdOK = $false
$claudeMdPath = Join-Path $ClaudeDir "CLAUDE.md"
if (Test-Path $claudeMdPath) {
$cm = Get-Content $claudeMdPath -Raw -ErrorAction SilentlyContinue
$claudeMdOK = $cm -match "Bookworm"
}
$settingsOK = $false
if (Test-Path $settingsFile) {
$sc = Get-Content $settingsFile -Raw -ErrorAction SilentlyContinue
$settingsOK = $sc -match '"hooks"'
}
$checks = @(
@{ Name = "CLAUDE.md (Bookworm 指令)"; OK = $claudeMdOK }
@{ Name = "Skills ($skillCount 个)"; OK = ($skillCount -gt 50) }
@{ Name = "Hooks ($hookCount 个)"; OK = ($hookCount -gt 10) }
@{ Name = "Settings hooks 配置"; OK = $settingsOK }
)
$allOK = $true
foreach ($c in $checks) {
if ($c.OK) { Log-OK $c.Name } else { Log-Fail $c.Name; $allOK = $false }
}
# ── 6b: API 凭证检查 ──
Log-Info "API 凭证检查..."
if ($env:ANTHROPIC_API_KEY) { Log-OK "ANTHROPIC_API_KEY 已配置" } else { Log-Fail "ANTHROPIC_API_KEY 未配置" }
if ($env:ANTHROPIC_BASE_URL) { Log-OK "ANTHROPIC_BASE_URL 已配置" } else { Log-Warn "ANTHROPIC_BASE_URL 未配置 (将使用默认)" }
# ── 6c: MCP npx 包预缓存 (非阻塞 UI) ──
Log-Info "MCP 预安装 (npx 包预缓存)..."
$npxPackages = @(
@{ Name = "context7"; Pkg = "@upstash/context7-mcp@2.1.1" }
@{ Name = "sequential-thinking"; Pkg = "@modelcontextprotocol/server-sequential-thinking@2025.12.18" }
@{ Name = "playwright"; Pkg = "@playwright/mcp@0.0.68" }
@{ Name = "session-continuity"; Pkg = "claude-session-continuity-mcp@1.13.0" }
@{ Name = "notebooklm"; Pkg = "notebooklm-mcp@latest" }
@{ Name = "cloudflare-docs"; Pkg = "mcp-remote" }
@{ Name = "chrome-devtools"; Pkg = "chrome-devtools-mcp@0.18.1" }
@{ Name = "github"; Pkg = "@modelcontextprotocol/server-github" }
@{ Name = "slack"; Pkg = "@modelcontextprotocol/server-slack" }
@{ Name = "firecrawl"; Pkg = "firecrawl-mcp" }
@{ Name = "mcp-image"; Pkg = "mcp-image" }
@{ Name = "google-drive"; Pkg = "@piotr-agier/google-drive-mcp" }
)
$mcpOK = 0; $mcpFail = 0
foreach ($mcp in $npxPackages) {
$idx = $mcpOK + $mcpFail + 1
$label = "[$idx/$($npxPackages.Count)] $($mcp.Name)"
Update-Progress-SubStatus "$label ..."
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 `
-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++
} else { throw "exit=$($proc.ExitCode)" }
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
} catch {
Bw-Log "WARN" "$label failed: $_"
$mcpFail++
}
}
Log-OK "npx 预缓存: $mcpOK/$($npxPackages.Count) 成功"
# ── 6d: Playwright 浏览器安装 (非阻塞 UI) ──
Log-Info "Playwright 浏览器安装..."
try {
$pwBrowserPath = Join-Path $env:USERPROFILE "AppData\Local\ms-playwright"
if (Test-Path (Join-Path $pwBrowserPath "chromium-*")) {
Log-OK "Playwright Chromium 已存在"
} else {
$outTmp = Join-Path $env:TEMP "bw-playwright.tmp"
$errTmp = Join-Path $env:TEMP "bw-playwright-err.tmp"
$pwProc = Start-Process npx.cmd -ArgumentList "-y", "playwright", "install", "chromium" `
-NoNewWindow -PassThru `
-RedirectStandardOutput $outTmp `
-RedirectStandardError $errTmp
$pwOk = Wait-ProcessWithUI $pwProc 300000 "下载 Chromium (~150MB)"
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
if (-not $pwOk) { Log-Warn "Playwright 下载超时, 跳过" }
elseif (Test-Path (Join-Path $pwBrowserPath "chromium-*")) {
Log-OK "Playwright Chromium 安装成功"
} else {
Log-Warn "Playwright Chromium 安装可能未完成"
}
}
} catch { Log-Warn "Playwright 浏览器安装失败: $_ (不影响核心功能)" }
# ── 6e: Python MCP (uvx) 验证 (非阻塞 UI) ──
if (Test-Cmd "uvx") {
Log-Info "Python MCP 验证 (uvx)..."
$uvxPackages = @(
# F-17 fix: uv tool install 参数顺序 = 包名在前, --python 在后
@{ Name = "windows-mcp"; Args = @("tool", "install", "windows-mcp", "--python", "3.13") }
@{ Name = "atlassian"; Args = @("tool", "install", "mcp-atlassian") }
)
foreach ($pkg in $uvxPackages) {
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
$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" }
} catch {
Bw-Log "WARN" "uvx $($pkg.Name): $_"
}
}
} else {
Bw-Log "INFO" "uvx 不可用, 跳过 Python MCP"
}
# ── 6f: 可选 API Key 提示 ──
$optional = @(
@{ Key = "GITHUB_PERSONAL_ACCESS_TOKEN"; Name = "GitHub MCP" }
@{ Key = "FIRECRAWL_API_KEY"; Name = "Firecrawl MCP" }
@{ Key = "SLACK_BOT_TOKEN"; Name = "Slack MCP" }
@{ Key = "BROWSERBASE_API_KEY"; Name = "Browserbase MCP" }
@{ Key = "GEMINI_API_KEY"; Name = "MCP Image / Browserbase" }
@{ Key = "ATLASSIAN_API_TOKEN"; Name = "Atlassian MCP" }
)
$missingOpt = $optional | Where-Object { -not [System.Environment]::GetEnvironmentVariable($_.Key, "Process") }
if ($missingOpt.Count -gt 0) {
foreach ($m in $missingOpt) { Bw-Log "INFO" "可选 Key 未配置: $($m.Name) ($($m.Key))" }
}
# ========================================================================
# Phase 7: 环境加固 + 完成 + 启动
# ========================================================================
Log-Phase 7 "环境加固 + 启动"
# ── 7a: claude 命令默认带 --dangerously-skip-permissions ──
# PS2EXE 下 $PROFILE 可能为 $null, 需先检查
try {
# 构造 pwsh profile 路径 (不依赖 $PROFILE 自动变量, PS2EXE 下可能为空)
$pwshProfile = if ($PROFILE) { $PROFILE }
elseif ($PwshPath) { Join-Path (Split-Path $PwshPath -Parent) "profile.ps1" }
else { Join-Path "$env:USERPROFILE\Documents\PowerShell" "Microsoft.PowerShell_profile.ps1" }
if ($pwshProfile) {
$profileDir = Split-Path $pwshProfile -Parent
if ($profileDir -and -not (Test-Path $profileDir)) {
New-Item -ItemType Directory -Path $profileDir -Force | Out-Null
}
$aliasLine = 'function claude { $exe = (Get-Command claude.exe -EA SilentlyContinue).Source; if($exe){ & $exe --dangerously-skip-permissions @args } else { Write-Host "claude.exe not found" } }'
$hasAlias = (Test-Path $pwshProfile) -and (Select-String -Path $pwshProfile -Pattern 'dangerously-skip-permissions' -Quiet -ErrorAction SilentlyContinue)
if (-not $hasAlias) {
Add-Content -Path $pwshProfile -Value "`n# Bookworm: claude 默认免权限确认`n$aliasLine" -Encoding utf8
Bw-Log "OK" "PowerShell profile 已添加 claude alias"
}
}
} catch { Bw-Log "WARN" "7a claude alias 设置失败: $_" }
# ── 7b: 清理 OAuth 登录 (防止与 relay key 冲突) ──
try {
$credFile = Join-Path $ClaudeDir ".credentials.json"
if ($env:ANTHROPIC_BASE_URL -and (Test-Path $credFile)) {
$credContent = Get-Content $credFile -Raw -ErrorAction SilentlyContinue
if ($credContent -match '"claudeAiOauth"') {
Remove-Item $credFile -Force -ErrorAction SilentlyContinue
Bw-Log "OK" "已清理 OAuth 登录凭证 (改用中转站 relay key)"
}
}
} catch { Bw-Log "WARN" "7b OAuth 清理失败: $_" }
# ── 7c: 自动修复 .claude 仓库冲突 ──
try {
$claudeGit = Join-Path $ClaudeDir ".git"
if (Test-Path $claudeGit) {
# F-19 fix: 逐行匹配冲突状态 (Out-String 合并后 ^ 不匹配行内)
$gitLines = & git -C $ClaudeDir status --porcelain 2>&1
$hasConflict = $gitLines | Where-Object { $_ -match '^U|^.U' }
if ($hasConflict) {
& git -C $ClaudeDir checkout --theirs . 2>&1 | Out-Null
& git -C $ClaudeDir add -A 2>&1 | Out-Null
& git -C $ClaudeDir commit -m "auto-resolve merge conflicts" 2>&1 | Out-Null
Bw-Log "OK" "自动修复 .claude 仓库合并冲突"
}
}
} catch { Bw-Log "WARN" "7c 冲突修复失败: $_" }
# 关闭进度窗口
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
Close-ProgressForm
# 创建桌面快捷方式
New-DesktopShortcuts
if ($allOK -and $env:ANTHROPIC_API_KEY) {
Bw-Log "DONE" "v$BWVersion 安装成功 ($skillCount Skills / $hookCount Hooks)"
# ═══ 祝贺闪屏 (2.5 秒自动消失) ═══
$splash = New-Object System.Windows.Forms.Form
$splash.FormBorderStyle = "None"
$splash.StartPosition = "CenterScreen"
$splash.Size = New-Object System.Drawing.Size(480, 300)
$splash.BackColor = [System.Drawing.Color]::FromArgb(24, 25, 38)
$splash.TopMost = $true
$splash.ShowInTaskbar = $false
$splash.Opacity = 0.0
# 品牌蓝紫装饰条
$topBar = New-Object System.Windows.Forms.Panel
$topBar.Location = New-Object System.Drawing.Point(0, 0)
$topBar.Size = New-Object System.Drawing.Size(480, 4)
$topBar.BackColor = [System.Drawing.Color]::FromArgb(88, 101, 242)
$splash.Controls.Add($topBar)
# 大勾图标
$checkLabel = New-Object System.Windows.Forms.Label
$checkLabel.Location = New-Object System.Drawing.Point(0, 35)
$checkLabel.Size = New-Object System.Drawing.Size(480, 55)
$checkLabel.Text = [char]0x2714
$checkLabel.Font = New-Object System.Drawing.Font("Segoe UI", 36)
$checkLabel.ForeColor = [System.Drawing.Color]::FromArgb(46, 160, 67)
$checkLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$splash.Controls.Add($checkLabel)
# 主标题
$mainTitle = New-Object System.Windows.Forms.Label
$mainTitle.Location = New-Object System.Drawing.Point(0, 95)
$mainTitle.Size = New-Object System.Drawing.Size(480, 38)
$mainTitle.Text = "Bookworm v$BWVersion 安装成功"
$mainTitle.Font = New-Object System.Drawing.Font("Segoe UI", 18, [System.Drawing.FontStyle]::Bold)
$mainTitle.ForeColor = [System.Drawing.Color]::White
$mainTitle.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$splash.Controls.Add($mainTitle)
# 副标题
$subTitle = New-Object System.Windows.Forms.Label
$subTitle.Location = New-Object System.Drawing.Point(0, 140)
$subTitle.Size = New-Object System.Drawing.Size(480, 28)
$subTitle.Text = "$skillCount Skills / $hookCount Hooks / 全部就绪"
$subTitle.Font = New-Object System.Drawing.Font("Segoe UI", 11)
$subTitle.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200)
$subTitle.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$splash.Controls.Add($subTitle)
# 祝福语
$wish = New-Object System.Windows.Forms.Label
$wish.Location = New-Object System.Drawing.Point(0, 190)
$wish.Size = New-Object System.Drawing.Size(480, 30)
$wish.Text = "善读者,必善造。使用愉快!"
$wish.Font = New-Object System.Drawing.Font("Segoe UI", 12)
$wish.ForeColor = [System.Drawing.Color]::FromArgb(88, 101, 242)
$wish.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$splash.Controls.Add($wish)
# 底部提示
$hint = New-Object System.Windows.Forms.Label
$hint.Location = New-Object System.Drawing.Point(0, 250)
$hint.Size = New-Object System.Drawing.Size(480, 22)
$hint.Text = "双击桌面 Bookworm 图标即可随时启动"
$hint.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$hint.ForeColor = [System.Drawing.Color]::FromArgb(100, 110, 130)
$hint.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$splash.Controls.Add($hint)
# 底部装饰条
$bottomBar = New-Object System.Windows.Forms.Panel
$bottomBar.Location = New-Object System.Drawing.Point(0, 296)
$bottomBar.Size = New-Object System.Drawing.Size(480, 4)
$bottomBar.BackColor = [System.Drawing.Color]::FromArgb(88, 101, 242)
$splash.Controls.Add($bottomBar)
# 淡入动画 + 定时关闭
$splash.Show()
for ($i = 0; $i -le 10; $i++) {
$splash.Opacity = $i / 10.0
$splash.Refresh()
Start-Sleep -Milliseconds 30
}
# 停留 2.5 秒
$sw = [System.Diagnostics.Stopwatch]::StartNew()
while ($sw.ElapsedMilliseconds -lt 2500) {
[System.Windows.Forms.Application]::DoEvents()
Start-Sleep -Milliseconds 50
}
# 淡出
for ($i = 10; $i -ge 0; $i--) {
$splash.Opacity = $i / 10.0
$splash.Refresh()
Start-Sleep -Milliseconds 25
}
$splash.Close()
$splash.Dispose()
# 启动 Bookworm — 优先 Windows Terminal > pwsh > cmd.exe
if (-not $SkipLaunch) {
$startBat = Join-Path $BootDir "启动Bookworm.bat"
if (Test-Path $startBat) {
Start-Process -FilePath $startBat -WorkingDirectory $BootDir
} else {
$claudeCmd = "claude --dangerously-skip-permissions"
if (Get-Command wt.exe -ErrorAction SilentlyContinue) {
# Windows Terminal: 现代 UI, 支持全屏/标签页
Start-Process wt.exe -ArgumentList "new-tab", "-d", $BootDir, "--title", "Bookworm v$BWVersion", "cmd", "/k", $claudeCmd
} elseif ($PwshPath -and (Test-Path $PwshPath)) {
Start-Process $PwshPath -ArgumentList "-NoExit", "-Command", "cd '$BootDir'; $claudeCmd" -WorkingDirectory $BootDir
} else {
Start-Process cmd.exe -ArgumentList "/k", "title Bookworm v$BWVersion && cd /d `"$BootDir`" && $claudeCmd"
}
}
}
} else {
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
Bw-Log "DONE" "安装完成但部分受限 allOK=$allOK hasKey=$($env:ANTHROPIC_API_KEY -ne $null)"
$issues = @()
if (-not $allOK) { $issues += "- Bookworm 配置不完整" }
if (-not $env:ANTHROPIC_API_KEY) { $issues += "- API 凭证未解密" }
$issueText = $issues -join "`n"
feat(installer): GUI 进度窗 + uv 三层 fallback + 桌面专用图标 + 静默化 修复 v1.5.1 用户实测发现的两个体验问题: 1. **uv 安装失败 + RemoteException 弹窗** (auto-setup.ps1) - 旧逻辑: python -m pip install uv (网络/权限/$ErrorActionPreference=Stop 易触发) - 新逻辑: 三层 fallback (winget → Astral 官方脚本 → pip), 全程 SilentlyContinue - 失败仅写入 $TEMP/bookworm-uv-install.log, 不阻断不弹窗 (uv 是可选依赖) 2. **PS2EXE -NoConsole 把 Log-X 弹窗化** (build.ps1) - 加 -NoOutput + -NoError, 所有 Write-Host 静默吞掉 - 用户不再被 70+ 个 [!] 弹窗轰炸 3. **静默后无进度反馈 → GUI 进度窗口** (auto-setup.ps1) - 新增 Show-ProgressForm/Update-Progress/Update-Progress-SubStatus/Close-ProgressForm - 顶部常驻 Form: 标题 + Phase 标签 + 当前状态 + 进度条 + 日志路径 - 所有 Log-X 改写日志文件 ($TEMP/bookworm-setup-{ts}.log) + 更新进度窗口 4. **桌面专用图标** (auto-setup.ps1 + bookworm-desktop.ico) - 从 og-image.png 自动检测蓝紫渐变 B 圆 → 圆形 alpha mask → 7 尺寸 ICO (86 KB) - New-DesktopShortcuts 增加 IconLocation, 桌面快捷方式显示 Bookworm 主图标 - 主图比 favicon 神经螺旋更突出, 48px 也清晰可辨 5. **Phase 7 安装完成 banner**: Write-Host → Show-MsgBox 6. **Claude Code 启动**: 主进程启动 → Start-Process cmd /k claude (新窗口) 构建验证: 7/7 补丁字符串 (Show-ProgressForm/BWLogFile/winget/astral/ bookworm-desktop.ico/IconLocation 等) 经 EXE 字符串扫描确认编译进 build artifact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:02:53 +08:00
$launchResult = Show-MsgBox "安装完成, 但存在以下问题:`n$issueText`n`n是否仍然启动 Claude Code?`n(将以受限模式运行)`n`n日志: $BWLogFile" "安装警告" "YesNo" "Warning"
if ($launchResult -eq "Yes" -and -not $SkipLaunch) {
$claudeCmd = "claude --dangerously-skip-permissions"
if (Get-Command wt.exe -ErrorAction SilentlyContinue) {
Start-Process wt.exe -ArgumentList "new-tab", "--title", "Bookworm", "cmd", "/k", $claudeCmd
} else {
Start-Process cmd.exe -ArgumentList "/k", $claudeCmd
}
}
}