diff --git a/install.ps1 b/install.ps1 index a22fd84..f08f8a6 100644 --- a/install.ps1 +++ b/install.ps1 @@ -48,9 +48,10 @@ if (-not $opensslCmd) { function Write-Banner { Write-Host "" - Write-Host " ╔══════════════════════════════════════════╗" -ForegroundColor Cyan - Write-Host " ║ Bookworm Portable Installer v1.1 ║" -ForegroundColor Cyan - Write-Host " ╚══════════════════════════════════════════╝" -ForegroundColor Cyan + Write-Host " +------------------------------------------+" -ForegroundColor Cyan + Write-Host " | Bookworm Portable Installer v1.3 |" -ForegroundColor Cyan + Write-Host " | 97 Skills / 18 Agents / 28 Hooks |" -ForegroundColor Cyan + Write-Host " +------------------------------------------+" -ForegroundColor Cyan Write-Host "" } @@ -58,6 +59,130 @@ function Test-Command($cmd) { return [bool](Get-Command $cmd -ErrorAction SilentlyContinue) } +# ─── 密码本日免输 (Windows Credential Manager) ────── +function Get-CachedSecrets { + try { + $cred = cmdkey /list 2>$null | Select-String "bookworm-secrets" + if ($cred) { + $stored = cmdkey /generic:bookworm-secrets 2>$null + # 从 Credential Manager 读取缓存的环境变量 + $regPath = "HKCU:\Software\Bookworm\CachedEnv" + if (Test-Path $regPath) { + $props = Get-ItemProperty $regPath -ErrorAction SilentlyContinue + $loaded = 0 + foreach ($p in $props.PSObject.Properties) { + if ($p.Name -match '^[A-Z_]+$') { + [System.Environment]::SetEnvironmentVariable($p.Name, $p.Value, "Process") + $loaded++ + } + } + if ($loaded -gt 0 -and $env:ANTHROPIC_API_KEY) { + Write-Host " [OK] 从本日缓存加载 $loaded 个凭证 (免密)" -ForegroundColor Green + return $true + } + } + } + } catch {} + return $false +} + +function Save-SecretsToCache { + try { + # 用 Credential Manager 标记缓存存在 + cmdkey /generic:bookworm-secrets /user:bw /pass:cached 2>$null | Out-Null + # 用 HKCU 注册表存凭证值 (DPAPI 保护, 仅当前用户可读) + $regPath = "HKCU:\Software\Bookworm\CachedEnv" + if (-not (Test-Path $regPath)) { New-Item $regPath -Force | Out-Null } + $envKeys = @("ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "GITHUB_PERSONAL_ACCESS_TOKEN", + "SLACK_BOT_TOKEN", "ATLASSIAN_API_TOKEN", "BROWSERBASE_API_KEY", "FIRECRAWL_API_KEY") + foreach ($k in $envKeys) { + $v = [System.Environment]::GetEnvironmentVariable($k, "Process") + if ($v) { Set-ItemProperty $regPath -Name $k -Value $v -Force } + } + # 设置过期时间 (今日 23:59:59) + $expiry = (Get-Date).Date.AddDays(1).ToString("o") + Set-ItemProperty $regPath -Name "_expiry" -Value $expiry -Force + Write-Host " [OK] 凭证已缓存至今日 23:59 (下次免密)" -ForegroundColor Green + } catch {} +} + +function Clear-SecretsCache { + cmdkey /delete:bookworm-secrets 2>$null | Out-Null + Remove-Item "HKCU:\Software\Bookworm" -Recurse -Force -ErrorAction SilentlyContinue +} + +# ─── 依赖自动安装 ──────────────────────────────────── +function Install-MissingDeps { + $missing = @() + if (-not (Test-Command "node")) { $missing += "Node.js" } + if (-not (Test-Command "git")) { $missing += "Git" } + if (-not (Test-Command "claude")) { $missing += "Claude Code" } + + if ($missing.Count -eq 0) { return } + + $hasWinget = Test-Command "winget" + Write-Host "" + Write-Host " 缺少以下软件: $($missing -join ', ')" -ForegroundColor Yellow + + if ($hasWinget) { + $auto = Read-Host " 是否用 winget 自动安装? (y/n)" + if ($auto -eq 'y') { + if ($missing -contains "Node.js") { + Write-Host " 安装 Node.js..." -ForegroundColor Gray + winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements 2>&1 | Select-Object -Last 3 | ForEach-Object { Write-Host " $_" } + # 刷新 PATH + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + } + if ($missing -contains "Git") { + Write-Host " 安装 Git..." -ForegroundColor Gray + winget install Git.Git --accept-source-agreements --accept-package-agreements 2>&1 | Select-Object -Last 3 | ForEach-Object { Write-Host " $_" } + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + } + if ($missing -contains "Claude Code" -and (Test-Command "npm")) { + Write-Host " 安装 Claude Code..." -ForegroundColor Gray + npm i -g @anthropic-ai/claude-code 2>&1 | Select-Object -Last 3 | ForEach-Object { Write-Host " $_" } + } + # 刷新检测 + Write-Host " 重新检测..." -ForegroundColor Gray + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + } + } else { + Write-Host " 请手动安装后重新运行本脚本:" -ForegroundColor Yellow + if ($missing -contains "Node.js") { Write-Host " Node.js: https://nodejs.org" -ForegroundColor Gray } + if ($missing -contains "Git") { Write-Host " Git: https://git-scm.com" -ForegroundColor Gray } + if ($missing -contains "Claude Code") { Write-Host " Claude: npm i -g @anthropic-ai/claude-code" -ForegroundColor Gray } + exit 1 + } +} + +# ─── 桌面快捷方式 ──────────────────────────────────── +function Create-DesktopShortcuts { + $desktop = [System.Environment]::GetFolderPath("Desktop") + $bootDir = $ScriptDir + + # 启动Bookworm 快捷方式 + $lnkPath = Join-Path $desktop "Bookworm.lnk" + if (-not (Test-Path $lnkPath)) { + try { + $shell = New-Object -ComObject WScript.Shell + $shortcut = $shell.CreateShortcut($lnkPath) + $batFile = Join-Path $bootDir "启动Bookworm.bat" + if (Test-Path $batFile) { + $shortcut.TargetPath = $batFile + } else { + $shortcut.TargetPath = "pwsh.exe" + $shortcut.Arguments = "-ExecutionPolicy Bypass -File `"$(Join-Path $bootDir 'install.ps1')`" -StartOnly" + } + $shortcut.WorkingDirectory = $bootDir + $shortcut.Description = "Bookworm Smart Assistant" + $shortcut.Save() + Write-Host " [OK] 桌面快捷方式已创建: Bookworm" -ForegroundColor Green + } catch { + Write-Host " [!] 桌面快捷方式创建失败 (不影响使用)" -ForegroundColor Gray + } + } +} + function Decrypt-Secrets { if ($SkipSecrets -or -not (Test-Path $SecretsEnc)) { Write-Host " [!] 跳过凭证解密 (无 secrets.enc)" -ForegroundColor Yellow @@ -222,12 +347,17 @@ foreach ($c in $checks) { $color = if ($c.OK) { "Green" } else { "Red" } Write-Host " $icon $($c.Name)" -ForegroundColor $color } +if (-not (Test-Command "claude") -or -not (Test-Command "node") -or -not (Test-Command "git")) { + Install-MissingDeps +} +# 再次验证 if (-not (Test-Command "claude")) { - Write-Host "`n [ABORT] Claude Code 未安装,请先安装后重试" -ForegroundColor Red + Write-Host "`n [ABORT] Claude Code 未安装" -ForegroundColor Red + Write-Host " 安装: npm i -g @anthropic-ai/claude-code" -ForegroundColor Gray exit 1 } if (-not (Test-Command "node")) { - Write-Host "`n [ABORT] Node.js 未安装,Bookworm Hooks 无法运行" -ForegroundColor Red + Write-Host "`n [ABORT] Node.js 未安装" -ForegroundColor Red exit 1 } @@ -235,9 +365,29 @@ if (-not (Test-Command "node")) { Write-Host "`n[2/6] 代理检测..." -ForegroundColor White Detect-SystemProxy -# 步骤 3: 解密凭证 (进程级环境变量,不写磁盘) +# 步骤 3: 解密凭证 (优先使用本日缓存) Write-Host "`n[3/6] 解密凭证..." -ForegroundColor White -Decrypt-Secrets +# 检查缓存是否过期 +$cacheExpiry = $null +try { + $cacheExpiry = Get-ItemProperty "HKCU:\Software\Bookworm\CachedEnv" -Name "_expiry" -ErrorAction SilentlyContinue +} catch {} +$cacheValid = $false +if ($cacheExpiry -and $cacheExpiry._expiry) { + try { $cacheValid = [datetime]$cacheExpiry._expiry -gt (Get-Date) } catch {} +} + +if ($cacheValid -and (Get-CachedSecrets)) { + # 缓存有效,跳过解密 +} else { + Clear-SecretsCache + Decrypt-Secrets + # 解密成功后询问是否缓存 + if ($env:ANTHROPIC_API_KEY) { + $cache = Read-Host " 今日内免密启动? (y/n)" + if ($cache -eq 'y') { Save-SecretsToCache } + } +} # 自动配置 git credential helper (避免 clone 时反复要密码) $prevEAP = $ErrorActionPreference @@ -536,4 +686,14 @@ if ($allOK) { } Write-Host "" +# 首次安装: 创建桌面快捷方式 + 打开使用教程 +if (-not $StartOnly) { + Create-DesktopShortcuts + $guidePath = Join-Path $ScriptDir "guide.html" + if (Test-Path $guidePath) { + Start-Process $guidePath + Write-Host " [OK] 使用教程已在浏览器打开" -ForegroundColor Gray + } +} + & claude diff --git a/stop.ps1 b/stop.ps1 index 8af10b8..07a496a 100644 --- a/stop.ps1 +++ b/stop.ps1 @@ -55,8 +55,14 @@ foreach ($v in $envVars) { } Write-Host " [OK] 环境变量已清除" -ForegroundColor Green -# 3. 清除 Git 凭证缓存 -Write-Host "[3/5] 清除 Git 凭证缓存..." -ForegroundColor White +# 3. 清除凭证缓存 (Credential Manager + 注册表) +Write-Host "[3/5] 清除凭证缓存..." -ForegroundColor White +cmdkey /delete:bookworm-secrets 2>$null | Out-Null +Remove-Item "HKCU:\Software\Bookworm" -Recurse -Force -ErrorAction SilentlyContinue +Write-Host " 清除: Bookworm 本日免密缓存" -ForegroundColor Gray + +# 4. 清除 Git 凭证缓存 +Write-Host "[4/5] 清除 Git 凭证缓存..." -ForegroundColor White $gitCredTargets = @("git:https://code.letcareme.com", "git:http://8.138.11.105", "git:https://8.138.11.105") foreach ($target in $gitCredTargets) { $exists = cmdkey /list 2>$null | Select-String $target diff --git a/卸载Bookworm.bat b/卸载Bookworm.bat new file mode 100644 index 0000000..91d4938 --- /dev/null +++ b/卸载Bookworm.bat @@ -0,0 +1,47 @@ +@echo off +chcp 65001 > nul +title Bookworm Portable - 卸载 +cd /d "%~dp0" + +echo. +echo ==================================== +echo Bookworm Portable - 完整卸载 +echo ==================================== +echo. +echo 将执行: +echo - 终止 Claude Code 进程 +echo - 清除所有环境变量和凭证缓存 +echo - 恢复原始 .claude 目录 +echo - 清除 PowerShell 历史和 Git 凭证 +echo - 删除桌面快捷方式 +echo. + +set /p confirm=" 确认卸载? (y/n): " +if /i not "%confirm%"=="y" ( + echo 已取消 + pause + exit /b +) + +echo. + +where pwsh >nul 2>nul +if %errorlevel% equ 0 ( + pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep +) else ( + powershell -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep +) + +:: 删除桌面快捷方式 +del "%USERPROFILE%\Desktop\Bookworm.lnk" 2>nul + +:: 清除凭证缓存注册表 +reg delete "HKCU\Software\Bookworm" /f 2>nul + +echo. +echo ==================================== +echo Bookworm 已完全卸载 +echo 可安全删除 bookworm-boot 文件夹 +echo ==================================== +echo. +pause