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>
This commit is contained in:
bookworm 2026-04-07 20:02:53 +08:00
parent 3cb6f1dac1
commit 17c600fe93
4 changed files with 192 additions and 60 deletions

2
.gitignore vendored
View File

@ -2,5 +2,7 @@ secrets.txt
users.txt
auto-setup.ps1.bak-*
.tmp-authcodes.json
.tmp-release.json
.tmp-*.png
管理员SOP.html
dist/

View File

@ -39,15 +39,109 @@ Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
# ─── 颜色输出 ────────────────────────────────────────
function Log-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
function Log-Info($msg) { Write-Host " [..] $msg" -ForegroundColor Cyan }
function Log-Warn($msg) { Write-Host " [!] $msg" -ForegroundColor Yellow }
function Log-Fail($msg) { Write-Host " [!!] $msg" -ForegroundColor Red }
# ─── 日志 + 进度 (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) {
Write-Host ""
Write-Host " [$n/$TOTAL_PHASES] $title" -ForegroundColor White -BackgroundColor DarkBlue
Write-Progress -Activity "Bookworm 自动安装" -Status "$title" -PercentComplete ([int]($n / $TOTAL_PHASES * 100))
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 {
$global:BWProgressForm = New-Object System.Windows.Forms.Form
$global:BWProgressForm.Text = "Bookworm Portable Setup"
$global:BWProgressForm.Size = New-Object System.Drawing.Size(520, 200)
$global:BWProgressForm.StartPosition = "CenterScreen"
$global:BWProgressForm.FormBorderStyle = "FixedDialog"
$global:BWProgressForm.MaximizeBox = $false
$global:BWProgressForm.MinimizeBox = $false
$global:BWProgressForm.TopMost = $true
$global:BWProgressForm.ControlBox = $false
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Location = New-Object System.Drawing.Point(20, 18)
$titleLabel.Size = New-Object System.Drawing.Size(480, 24)
$titleLabel.Text = "Bookworm 智能助手 — 自动安装中, 请稍候..."
$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
$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("Segoe UI", 10)
$global:BWPhaseLabel.ForeColor = [System.Drawing.Color]::DarkBlue
$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("Segoe UI", 9)
$global:BWStatusLabel.ForeColor = [System.Drawing.Color]::DimGray
$global:BWProgressForm.Controls.Add($global:BWStatusLabel)
$global:BWProgressBar = New-Object System.Windows.Forms.ProgressBar
$global:BWProgressBar.Location = New-Object System.Drawing.Point(20, 110)
$global:BWProgressBar.Size = New-Object System.Drawing.Size(480, 22)
$global:BWProgressBar.Minimum = 0
$global:BWProgressBar.Maximum = $TOTAL_PHASES
$global:BWProgressBar.Value = 0
$global:BWProgressForm.Controls.Add($global:BWProgressBar)
$hint = New-Object System.Windows.Forms.Label
$hint.Location = New-Object System.Drawing.Point(20, 140)
$hint.Size = New-Object System.Drawing.Size(480, 18)
$hint.Text = "首次安装约 5-10 分钟 (依赖下载) | 日志: $BWLogFile"
$hint.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$hint.ForeColor = [System.Drawing.Color]::Gray
$global:BWProgressForm.Controls.Add($hint)
$global:BWProgressForm.Show() | Out-Null
$global:BWProgressForm.Refresh()
[System.Windows.Forms.Application]::DoEvents()
}
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
$global:BWStatusLabel.Refresh()
[System.Windows.Forms.Application]::DoEvents()
} catch {}
}
}
function Close-ProgressForm {
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) }
@ -248,13 +342,21 @@ function New-DesktopShortcuts {
$shell = New-Object -ComObject WScript.Shell
$desktop = $shell.SpecialFolders("Desktop")
# 桌面专用图标 (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
$shortcut.Description = "Bookworm Smart Assistant"
$shortcut.Description = "Bookworm Smart Assistant - 智能助手"
if (Test-Path $iconPath) { $shortcut.IconLocation = "$iconPath,0" }
$shortcut.Save()
# 更新启动
@ -264,23 +366,18 @@ function New-DesktopShortcuts {
$shortcut2.TargetPath = $updateBat
$shortcut2.WorkingDirectory = $BootDir
$shortcut2.Description = "更新并启动 Bookworm"
if (Test-Path $iconPath) { $shortcut2.IconLocation = "$iconPath,0" }
$shortcut2.Save()
}
Log-OK "桌面快捷方式已创建"
Log-OK "桌面快捷方式已创建 (含 Bookworm 图标)"
} catch { Log-Warn "快捷方式创建失败: $_" }
}
# ========================================================================
# Banner
# 启动: 显示 GUI 进度窗口 (替代 console banner, PS2EXE -NoOutput 兼容)
# ========================================================================
Write-Host ""
Write-Host " +---------------------------------------------------+" -ForegroundColor Cyan
Write-Host " | |" -ForegroundColor Cyan
Write-Host " | Bookworm Portable - 全自动安装器 |" -ForegroundColor Cyan
Write-Host " | 92 Skills / 18 Agents / 34 Hooks |" -ForegroundColor Cyan
Write-Host " | |" -ForegroundColor Cyan
Write-Host " +---------------------------------------------------+" -ForegroundColor Cyan
Write-Host ""
Bw-Log "INIT" "Bookworm Portable Setup 启动 - 日志: $BWLogFile"
Show-ProgressForm
# ========================================================================
# Phase 1: 环境检测 + 依赖自动安装
@ -350,29 +447,70 @@ if (-not (Test-Cmd "claude") -and (Test-Cmd "npm")) {
if (Test-Cmd "claude") { Log-OK "Claude Code 安装成功" } else { Log-Fail "Claude Code 安装失败" }
}
# uv 依赖 Python, 需要在 Python 安装后再检查
if (Test-Cmd "python") {
if (Test-Cmd "uv") {
$uvVer = try { & uv --version 2>$null | Select-Object -First 1 } catch { "installed" }
Log-OK "uv $uvVer"
} else {
Log-Info "安装 uv (Python 包管理器)..."
# 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 包管理器, 可选)..."
# 静默执行子进程, 错误全部捕获到日志文件, 绝不冒泡到 PS2EXE
$prevErrPref = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
# 方案 A: winget (最可靠)
if (Test-Cmd "winget") {
try {
& python -m pip install uv --quiet 2>&1 | Select-Object -Last 2 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
$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")
# pip install 的 Scripts 目录可能不在 PATH, 追加
# winget 装到 %LOCALAPPDATA%\Microsoft\WinGet\Packages\astral-sh.uv_*
$uvCargoBin = "$env:LOCALAPPDATA\Microsoft\WinGet\Links"
if (Test-Path $uvCargoBin) { $env:Path += ";$uvCargoBin" }
if (Test-Cmd "uv") { $uvInstalled = $true }
}
# 方案 B: Astral 官方一行脚本 (走 https://astral.sh/uv/install.ps1)
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 }
# Astral 脚本默认装到 %USERPROFILE%\.local\bin
$localBin = Join-Path $env:USERPROFILE ".local\bin"
if (Test-Path $localBin) { $env:Path += ";$localBin" }
if (Test-Cmd "uv") { $uvInstalled = $true }
}
# 方案 C: pip fallback (Python 已装且前两个都失败)
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" }
if (Test-Cmd "uv") {
} catch {}
if (Test-Cmd "uv") { $uvInstalled = $true }
}
$ErrorActionPreference = $prevErrPref
if ($uvInstalled) {
Log-OK "uv 安装成功"
$installed += "uv"
} else {
Log-Warn "uv 安装后未找到, 尝试 uvx 替代检查..."
# 静默 fallback: 仅写日志文件, 不调 Log-Warn 避免 PS2EXE 弹窗
"[fail] uv 三种安装方式均失败, Python MCP 将不可用. 详见上方日志." | Out-File -FilePath $uvLogFile -Encoding utf8 -Append
Log-Info "uv 未就绪 (可选, 不影响核心功能, 详情: $uvLogFile)"
}
} catch { Log-Warn "uv 安装失败: $_" }
}
} else {
Log-Warn "Python 未安装, 跳过 uv (部分 MCP 不可用)"
}
# OpenSSL (随 Git 安装)
@ -408,12 +546,12 @@ if ($PwshPath) {
$PwshPath = "pwsh"
}
# 可选依赖警告 (不阻断)
# 可选依赖检查 (不阻断, 用 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) {
Log-Warn "可选依赖未就绪: $($optionalMissing -join ', ') (部分 MCP 不可用)"
Log-Info "可选依赖未就绪: $($optionalMissing -join ', ') — 仅影响 Python 类 MCP, 核心功能正常"
}
if ($installed.Count -gt 0) {
@ -921,40 +1059,30 @@ if ($missingOpt.Count -gt 0) {
# ========================================================================
Log-Phase 7 "安装完成"
Write-Progress -Activity "Bookworm 自动安装" -Completed
# 关闭 GUI 进度窗口 (后续由 Show-MsgBox 显示成功/失败)
Close-ProgressForm
# 创建桌面快捷方式
New-DesktopShortcuts
Write-Host ""
if ($allOK -and $env:ANTHROPIC_API_KEY) {
Write-Host " +---------------------------------------------------+" -ForegroundColor Green
Write-Host " | |" -ForegroundColor Green
Write-Host " | Bookworm Portable 安装成功! |" -ForegroundColor Green
Write-Host " | $skillCount Skills / $hookCount Hooks / 全部就绪 |" -ForegroundColor Green
Write-Host " | |" -ForegroundColor Green
Write-Host " +---------------------------------------------------+" -ForegroundColor Green
Bw-Log "DONE" "安装成功 ($skillCount Skills / $hookCount Hooks)"
Show-MsgBox "Bookworm Portable 安装成功!`n`n$skillCount Skills / $hookCount Hooks 全部就绪`n`n点击确定后将自动启动 Claude Code。`n`n日志: $BWLogFile" "安装成功" "OK" "Information" | Out-Null
if (-not $SkipLaunch) {
Write-Host ""
Log-Info "正在启动 Claude Code..."
Write-Host ""
& claude --dangerously-skip-permissions
# 启动 Claude Code: 用户的 Claude Code 由 cmd 窗口启动 (PS2EXE -NoConsole 下会创建新窗口)
Start-Process -FilePath "cmd.exe" -ArgumentList "/k", "claude --dangerously-skip-permissions"
}
} else {
Write-Host " +---------------------------------------------------+" -ForegroundColor Yellow
Write-Host " | |" -ForegroundColor Yellow
Write-Host " | 安装完成 (部分功能可能受限) |" -ForegroundColor Yellow
Write-Host " | |" -ForegroundColor Yellow
Write-Host " +---------------------------------------------------+" -ForegroundColor Yellow
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"
$launchResult = Show-MsgBox "安装完成, 但存在以下问题:`n$issueText`n`n是否仍然启动 Claude Code?`n(将以受限模式运行)" "安装警告" "YesNo" "Warning"
$launchResult = Show-MsgBox "安装完成, 但存在以下问题:`n$issueText`n`n是否仍然启动 Claude Code?`n(将以受限模式运行)`n`n日志: $BWLogFile" "安装警告" "YesNo" "Warning"
if ($launchResult -eq "Yes" -and -not $SkipLaunch) {
& claude --dangerously-skip-permissions
Start-Process -FilePath "cmd.exe" -ArgumentList "/k", "claude --dangerously-skip-permissions"
}
}

BIN
bookworm-desktop.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -72,8 +72,10 @@ if ($buildSetup) {
Title = "Bookworm Portable Setup"
Description = "Bookworm Smart Assistant 安装向导"
Company = "Bookworm"
Version = "1.5.0.0"
Version = "1.5.1.0"
NoConsole = $true
NoOutput = $true # 抑制所有 Write-Host 弹窗化, 只保留 Show-MsgBox / GUI dialog
NoError = $true # 抑制 stderr 弹窗化, 异常仍走 try-catch
}
if (Test-Path $iconFile) {
$ps2exeArgs.IconFile = $iconFile