perf(installer): 消除全部 UI 冻结 + Write-Host 清零
## P0: 子进程 UI 阻塞 → 非阻塞轮询 - 新增 Wait-ProcessWithUI: 替代所有 WaitForExit(), 每 200ms 泵 DoEvents(), 进度窗口保持响应, 显示等待计时 - 新增 Run-CmdWithUI: 替代 & cmd 2>&1 | ForEach-Object 模式, 所有子进程输出走临时文件→日志, 不阻塞 UI ## P1: Phase 3 git clone/pull 无超时 → 带超时 + UI 泵 - 9 处 git 调用全部改为 Run-CmdWithUI (120s/180s 超时) - 消除 Push-Location/Pop-Location, 改用 git -C <dir> ## P1: Phase 1 winget/npm 阻塞 → 带超时 + UI 泵 - winget install: 5 分钟超时 + 进度状态显示 - npm i -g: 2-3 分钟超时 + 进度状态显示 - Claude Code npm install: 3 分钟超时 ## P2: Write-Host 归零 - 27 处非注释 Write-Host 全部替换为 Bw-Log + Update-Progress-SubStatus - EXE 在 PS2EXE -NoOutput 下不再有任何静默丢失的输出 验证: 7/7 补丁字符串确认编译; Write-Host 仅剩 1 处 (注释内) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e8a7250c2
commit
7d60911f65
203
auto-setup.ps1
203
auto-setup.ps1
@ -146,6 +146,77 @@ function Close-ProgressForm {
|
||||
|
||||
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 内容
|
||||
)
|
||||
Bw-Log "CMD" "$exe $($arguments -join ' ')"
|
||||
Update-Progress-SubStatus $label
|
||||
|
||||
$outFile = Join-Path $env:TEMP "bw-cmd-out-$(Get-Random).tmp"
|
||||
$errFile = Join-Path $env:TEMP "bw-cmd-err-$(Get-Random).tmp"
|
||||
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") {
|
||||
@ -407,9 +478,7 @@ foreach ($dep in $deps) {
|
||||
|
||||
if ($dep.WingetId -and $hasWinget) {
|
||||
try {
|
||||
$output = winget install $dep.WingetId --accept-source-agreements --accept-package-agreements 2>&1
|
||||
$output | Select-Object -Last 3 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
# 刷新 PATH
|
||||
$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) 安装成功"
|
||||
@ -423,8 +492,7 @@ foreach ($dep in $deps) {
|
||||
}
|
||||
elseif ($dep.NpmPkg -and (Test-Cmd "npm")) {
|
||||
try {
|
||||
Write-Host " npm i -g $($dep.NpmPkg) ..." -ForegroundColor Gray
|
||||
npm i -g $dep.NpmPkg 2>&1 | Select-Object -Last 3 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
$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) 安装成功"
|
||||
@ -442,7 +510,7 @@ foreach ($dep in $deps) {
|
||||
# Claude Code 依赖 npm, 需要在 Node.js 安装后再检查
|
||||
if (-not (Test-Cmd "claude") -and (Test-Cmd "npm")) {
|
||||
Log-Info "安装 Claude Code..."
|
||||
npm i -g @anthropic-ai/claude-code 2>&1 | Select-Object -Last 3 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
$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 安装失败" }
|
||||
}
|
||||
@ -638,7 +706,6 @@ if (-not $proxyFound) {
|
||||
Log-OK "NO_PROXY: bww.letcareme.com, code.letcareme.com"
|
||||
|
||||
# 连通性测试
|
||||
Write-Host ""
|
||||
Log-Info "测试网络连通性..."
|
||||
|
||||
$netTests = @(
|
||||
@ -677,33 +744,24 @@ Log-Phase 3 "同步 Bookworm 配置"
|
||||
# 配置 git credential helper
|
||||
git config --global credential.helper store 2>$null
|
||||
|
||||
# 克隆/更新 config 仓库 (.claude/)
|
||||
# 克隆/更新 config 仓库 (.claude/) — 使用 Run-CmdWithUI 防止 UI 冻结
|
||||
if (Test-Path (Join-Path $ClaudeDir ".git")) {
|
||||
Log-Info "配置仓库已存在, 更新中..."
|
||||
Push-Location $ClaudeDir
|
||||
try {
|
||||
$stash = git stash 2>&1
|
||||
git pull --rebase 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
if ($stash -notmatch 'No local changes') { git stash pop 2>&1 | Out-Null }
|
||||
Log-OK "配置仓库已更新"
|
||||
Run-CmdWithUI "git" @("-C", $ClaudeDir, "stash") "git stash" 15000 | Out-Null
|
||||
$r = Run-CmdWithUI "git" @("-C", $ClaudeDir, "pull", "--rebase") "同步配置仓库" 120000
|
||||
if ($r.OK) { Log-OK "配置仓库已更新" } else { Log-Warn "git pull 失败, 使用本地版本" }
|
||||
Run-CmdWithUI "git" @("-C", $ClaudeDir, "stash", "pop") "git stash pop" 15000 | Out-Null
|
||||
} catch { Log-Warn "git pull 失败, 使用本地版本" }
|
||||
finally { Pop-Location }
|
||||
}
|
||||
elseif (Test-Path $ClaudeDir) {
|
||||
# 已有 .claude 但非 git — 备份后克隆
|
||||
Log-Info "备份现有 .claude/ 并克隆..."
|
||||
if (Test-Path $BackupDir) { Remove-Item $BackupDir -Recurse -Force }
|
||||
Rename-Item $ClaudeDir $BackupDir
|
||||
|
||||
# 可能需要 Gitea 凭证
|
||||
$cred = Show-GiteaCredentialDialog
|
||||
if ($cred) {
|
||||
$credUrl = $GitUrl -replace '://', "://$($cred.User):$($cred.Pass)@"
|
||||
git clone --depth 1 $credUrl $ClaudeDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
} else {
|
||||
git clone --depth 1 $GitUrl $ClaudeDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
}
|
||||
|
||||
$cloneUrl = if ($cred) { $GitUrl -replace '://', "://$($cred.User):$($cred.Pass)@" } else { $GitUrl }
|
||||
$r = Run-CmdWithUI "git" @("clone", "--depth", "1", $cloneUrl, $ClaudeDir) "克隆配置仓库" 180000
|
||||
if (Test-Path (Join-Path $ClaudeDir "CLAUDE.md")) {
|
||||
Log-OK "配置仓库克隆成功 (旧目录已备份)"
|
||||
} else {
|
||||
@ -714,16 +772,10 @@ elseif (Test-Path $ClaudeDir) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
# 全新安装
|
||||
Log-Info "首次安装, 克隆配置仓库..."
|
||||
$cred = Show-GiteaCredentialDialog
|
||||
if ($cred) {
|
||||
$credUrl = $GitUrl -replace '://', "://$($cred.User):$($cred.Pass)@"
|
||||
git clone --depth 1 $credUrl $ClaudeDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
} else {
|
||||
git clone --depth 1 $GitUrl $ClaudeDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
}
|
||||
|
||||
$cloneUrl = if ($cred) { $GitUrl -replace '://', "://$($cred.User):$($cred.Pass)@" } else { $GitUrl }
|
||||
$r = Run-CmdWithUI "git" @("clone", "--depth", "1", $cloneUrl, $ClaudeDir) "克隆配置仓库" 180000
|
||||
if (Test-Path (Join-Path $ClaudeDir "CLAUDE.md")) {
|
||||
Log-OK "配置仓库克隆成功"
|
||||
} else {
|
||||
@ -743,21 +795,15 @@ foreach ($d in $dirs) {
|
||||
# ─── 克隆/更新 bookworm-boot (含 crypto-helper.js + secrets-*.enc + install.ps1) ───
|
||||
if (Test-Path (Join-Path $BootDir ".git")) {
|
||||
Log-Info "boot 仓库已存在, 更新中..."
|
||||
Push-Location $BootDir
|
||||
try {
|
||||
git pull --rebase 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
Log-OK "boot 仓库已更新"
|
||||
$r = Run-CmdWithUI "git" @("-C", $BootDir, "pull", "--rebase") "同步 boot 仓库" 120000
|
||||
if ($r.OK) { Log-OK "boot 仓库已更新" } else { Log-Warn "boot 仓库更新失败, 使用本地版本" }
|
||||
} catch { Log-Warn "boot 仓库更新失败, 使用本地版本" }
|
||||
finally { Pop-Location }
|
||||
} else {
|
||||
Log-Info "克隆 boot 仓库 (含解密工具与凭证)..."
|
||||
if (-not $cred) { $cred = Show-GiteaCredentialDialog }
|
||||
if ($cred) {
|
||||
$bootCredUrl = $BootUrl -replace '://', "://$($cred.User):$($cred.Pass)@"
|
||||
git clone --depth 1 $bootCredUrl $BootDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
} else {
|
||||
git clone --depth 1 $BootUrl $BootDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
}
|
||||
$bootCloneUrl = if ($cred) { $BootUrl -replace '://', "://$($cred.User):$($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 "boot 仓库克隆失败 (crypto-helper.js 缺失)"
|
||||
Show-MsgBox "boot 仓库克隆失败。`n请检查网络和 Gitea 凭证。" "克隆失败" "OK" "Error"
|
||||
@ -945,16 +991,13 @@ foreach ($c in $checks) {
|
||||
}
|
||||
|
||||
# ── 6b: API 凭证检查 ──
|
||||
Write-Host ""
|
||||
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 包预缓存 ──
|
||||
Write-Host ""
|
||||
# ── 6c: MCP npx 包预缓存 (非阻塞 UI) ──
|
||||
Log-Info "MCP 预安装 (npx 包预缓存)..."
|
||||
|
||||
# Tier 1: 纯 npx, 无需 Key
|
||||
$npxPackages = @(
|
||||
@{ Name = "context7"; Pkg = "@upstash/context7-mcp@2.1.1" }
|
||||
@{ Name = "sequential-thinking"; Pkg = "@modelcontextprotocol/server-sequential-thinking@2025.12.18" }
|
||||
@ -963,7 +1006,6 @@ $npxPackages = @(
|
||||
@{ Name = "notebooklm"; Pkg = "notebooklm-mcp@latest" }
|
||||
@{ Name = "cloudflare-docs"; Pkg = "mcp-remote" }
|
||||
@{ Name = "chrome-devtools"; Pkg = "chrome-devtools-mcp@0.18.1" }
|
||||
# Tier 2: npx + Key
|
||||
@{ Name = "github"; Pkg = "@modelcontextprotocol/server-github" }
|
||||
@{ Name = "slack"; Pkg = "@modelcontextprotocol/server-slack" }
|
||||
@{ Name = "firecrawl"; Pkg = "firecrawl-mcp" }
|
||||
@ -974,35 +1016,44 @@ $npxPackages = @(
|
||||
$mcpOK = 0; $mcpFail = 0
|
||||
foreach ($mcp in $npxPackages) {
|
||||
$idx = $mcpOK + $mcpFail + 1
|
||||
Write-Host " [$idx/$($npxPackages.Count)] $($mcp.Name) " -NoNewline -ForegroundColor Gray
|
||||
$label = "[$idx/$($npxPackages.Count)] $($mcp.Name)"
|
||||
Update-Progress-SubStatus "$label ..."
|
||||
try {
|
||||
# npm cache add 只下载到缓存, 不执行 (MCP 是 stdio 长驻进程, 不能用 --help)
|
||||
$proc = Start-Process npm.cmd -ArgumentList "cache", "add", $mcp.Pkg -NoNewWindow -PassThru -RedirectStandardOutput "NUL" -RedirectStandardError "NUL"
|
||||
$exited = $proc.WaitForExit(60000) # 60 秒超时
|
||||
if (-not $exited) { $proc.Kill(); throw "timeout" }
|
||||
if ($proc.ExitCode -eq 0) {
|
||||
Write-Host "[cached]" -ForegroundColor Green
|
||||
$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)" }
|
||||
} else { throw "exit=$($proc.ExitCode)" }
|
||||
Remove-Item $outTmp, $errTmp -Force -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
Write-Host "[FAIL: $_]" -ForegroundColor Red
|
||||
Bw-Log "WARN" "$label failed: $_"
|
||||
$mcpFail++
|
||||
}
|
||||
}
|
||||
Log-OK "npx 预缓存: $mcpOK 成功, $mcpFail 失败 (共 $($npxPackages.Count))"
|
||||
Log-OK "npx 预缓存: $mcpOK/$($npxPackages.Count) 成功"
|
||||
|
||||
# ── 6d: Playwright 浏览器安装 ──
|
||||
Write-Host ""
|
||||
# ── 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 {
|
||||
Write-Host " 下载 Chromium (首次约 150MB, 最长等 5 分钟)..." -ForegroundColor Gray
|
||||
$pwProc = Start-Process npx.cmd -ArgumentList "-y", "playwright", "install", "chromium" -NoNewWindow -PassThru
|
||||
$pwExited = $pwProc.WaitForExit(300000) # 5 分钟超时
|
||||
if (-not $pwExited) { $pwProc.Kill(); Log-Warn "Playwright 下载超时, 跳过" }
|
||||
$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 {
|
||||
@ -1011,31 +1062,31 @@ try {
|
||||
}
|
||||
} catch { Log-Warn "Playwright 浏览器安装失败: $_ (不影响核心功能)" }
|
||||
|
||||
# ── 6e: Python MCP (uvx) 验证 ──
|
||||
Write-Host ""
|
||||
# ── 6e: Python MCP (uvx) 验证 (非阻塞 UI) ──
|
||||
if (Test-Cmd "uvx") {
|
||||
Log-Info "Python MCP 验证 (uvx)..."
|
||||
|
||||
$uvxPackages = @(
|
||||
@{ Name = "windows-mcp"; Args = @("--python", "3.13", "windows-mcp") }
|
||||
@{ Name = "atlassian"; Args = @("mcp-atlassian") }
|
||||
)
|
||||
|
||||
foreach ($pkg in $uvxPackages) {
|
||||
Write-Host " $($pkg.Name) " -NoNewline -ForegroundColor Gray
|
||||
try {
|
||||
# uv tool install 只下载不启动 (MCP 是 stdio 长驻进程, --help 会挂起)
|
||||
$outTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name).tmp"
|
||||
$errTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name)-err.tmp"
|
||||
$installArgs = @("tool", "install") + $pkg.Args
|
||||
$proc = Start-Process uv -ArgumentList $installArgs -NoNewWindow -PassThru -RedirectStandardOutput "NUL" -RedirectStandardError "NUL"
|
||||
$exited = $proc.WaitForExit(90000) # 90 秒超时 (Python 包较大)
|
||||
if (-not $exited) { $proc.Kill(); throw "timeout" }
|
||||
Write-Host "[ready]" -ForegroundColor Green
|
||||
$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 {
|
||||
Write-Host "[skip: $_]" -ForegroundColor Yellow
|
||||
Bw-Log "WARN" "uvx $($pkg.Name): $_"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log-Warn "uvx 不可用, 跳过 Python MCP (windows-mcp, atlassian)"
|
||||
Bw-Log "INFO" "uvx 不可用, 跳过 Python MCP"
|
||||
}
|
||||
|
||||
# ── 6f: 可选 API Key 提示 ──
|
||||
@ -1049,9 +1100,7 @@ $optional = @(
|
||||
)
|
||||
$missingOpt = $optional | Where-Object { -not [System.Environment]::GetEnvironmentVariable($_.Key, "Process") }
|
||||
if ($missingOpt.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host " 可选 MCP (Key 未配置, 不影响核心功能):" -ForegroundColor DarkGray
|
||||
foreach ($m in $missingOpt) { Write-Host " [-] $($m.Name) ($($m.Key))" -ForegroundColor DarkGray }
|
||||
foreach ($m in $missingOpt) { Bw-Log "INFO" "可选 Key 未配置: $($m.Name) ($($m.Key))" }
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user