chore: 清理过期文件 (-2731 行) + AuthGen 历史记录功能

删除:
- 5 个旧版 .bat 安装器 (已被 auto-setup.ps1 EXE 替代)
- 4 个旧版 HTML 指南 (已被 guide-unified.html 替代)
- encrypt-secrets.ps1 (已被 gen-authcode.js 替代)
- lessons-learned.md (开发备忘, 非功能文件)
- 6 个未提交测试 .enc + 备份文件 (本地清理)

新增:
- AuthGen GUI: 生成成功后自动追加 authcode-history.log
- AuthGen GUI: "推送到 Gitea" 按钮 (git add + commit + push 一键完成)
- AuthGen GUI: $args→$nodeArgs (PowerShell 保留变量冲突)
- AuthGen GUI: UTF-8 Process 编码 (修复 PS2EXE 中文乱码)
- .gitignore: +authcode-history.log +Bookworm-AuthGen.exe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
bookworm 2026-04-10 03:20:39 +08:00
parent c2e3f0e65f
commit d87b92319f
13 changed files with 293 additions and 2883 deletions

2
.gitignore vendored
View File

@ -1,8 +1,10 @@
secrets.txt
users.txt
authcode-history.log
auto-setup.ps1.bak-*
.tmp-authcodes.json
.tmp-release.json
.tmp-*.png
Bookworm-AuthGen.exe
管理员SOP.html
dist/

View File

@ -1,80 +0,0 @@
@echo off
chcp 65001 > nul
title Bookworm Portable - 全自动安装
echo.
echo ====================================================
echo Bookworm Portable - 全自动安装器
echo 双击即可完成全部配置,无需手动操作
echo ====================================================
echo.
:: 检查 auto-setup.ps1 是否在当前目录
if exist "%~dp0auto-setup.ps1" (
cd /d "%~dp0"
goto :RUN_SETUP
)
:: 检查 bookworm-boot 是否已克隆
if exist "%USERPROFILE%\bookworm-boot\auto-setup.ps1" (
echo [OK] 检测到 bookworm-boot 仓库
cd /d "%USERPROFILE%\bookworm-boot"
goto :RUN_SETUP
)
:: 检查 git
where git >nul 2>nul
if %errorlevel% neq 0 (
echo [!!] Git 未安装,请先安装 Git:
echo https://git-scm.com/download/win
echo.
echo 安装 Git 后重新双击本文件即可。
pause
exit /b 1
)
:: bookworm-boot 目录已存在但不完整 — 先删除再克隆
if exist "%USERPROFILE%\bookworm-boot" (
echo [..] 检测到不完整的 bookworm-boot重新下载...
rmdir /s /q "%USERPROFILE%\bookworm-boot" 2>nul
)
echo [..] 首次运行,下载配置文件 (需输入 Gitea 账号密码)...
echo.
git clone https://code.letcareme.com/bookworm/bookworm-boot.git "%USERPROFILE%\bookworm-boot"
if %errorlevel% neq 0 (
echo.
echo [!!] 克隆失败,请检查:
echo 1. 网络是否正常
echo 2. Gitea 账号密码是否正确
echo 3. 能否访问 https://code.letcareme.com
pause
exit /b 1
)
cd /d "%USERPROFILE%\bookworm-boot"
echo [OK] 引导仓库下载完成
echo.
:RUN_SETUP
:: 更新到最新版本
git pull >nul 2>nul
:: 检测 PowerShell 7 (pwsh): 新窗口启动, 退出 cmd
where pwsh >nul 2>nul
if %errorlevel% equ 0 (
echo [OK] 使用 PowerShell 7
start "Bookworm 全自动安装" pwsh -NoLogo -ExecutionPolicy Bypass -File "%CD%\auto-setup.ps1"
exit
)
:: 回退 PowerShell 5.1 (auto-setup 会安装 pwsh7, Phase 7 再切换)
echo [..] PowerShell 7 未安装, 先用 5.1 执行安装 (将自动安装 pwsh7)
powershell -ExecutionPolicy Bypass -File auto-setup.ps1
if %errorlevel% neq 0 (
echo.
echo [!!] 安装过程中出现错误
echo 请截图上方信息联系管理员
echo.
)
pause

View File

@ -1,37 +0,0 @@
@echo off
setlocal
chcp 65001 > nul 2>&1
title Bookworm Smart Assistant - 全自动安装 v3.0
:: 极简入口: 只负责确保 Node.js 存在, 核心逻辑全在 setup-all.js 中
:: 规则: 不超过 30 行, 不用 if(), 不嵌 PowerShell, 用 %~s 短路径
net session >nul 2>&1
if %errorlevel% equ 0 goto :HAS_ADMIN
echo Set s = CreateObject("Shell.Application") > "%TEMP%\bw_uac.vbs"
echo s.ShellExecute "cmd.exe", "/k cd /d ""%~sdp0"" ^& ""%~snx0""", "", "runas", 1 >> "%TEMP%\bw_uac.vbs"
cscript //nologo "%TEMP%\bw_uac.vbs"
del /f /q "%TEMP%\bw_uac.vbs" 2>nul
exit /b
:HAS_ADMIN
where node >nul 2>nul
if %errorlevel% equ 0 goto :HAS_NODE
echo [..] Node.js 未安装, 正在通过 winget 安装...
winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent 2>nul
for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul') do set "PATH=%%b"
set "PATH=%PATH%;C:\Program Files\nodejs"
where node >nul 2>nul
if %errorlevel% equ 0 goto :HAS_NODE
echo [!!] Node.js 安装失败。请手动下载: https://nodejs.org/
goto :END
:HAS_NODE
echo [OK] Node.js 就绪, 启动安装引擎...
node "%~dp0setup-all.js" %* || echo [!] 安装过程出错, 请查看上方日志
:END
echo.
echo 按任意键关闭...
pause > nul
endlocal

View File

@ -1,266 +0,0 @@
@echo off
chcp 65001 > nul 2>&1
title Bookworm Smart Assistant - 全自动安装 (Win10 兼容)
:: ─── 自动提升管理员权限 ──
:: 用 goto 而非 if() 避免文件名含括号(如"(2)")导致解析崩溃
net session >nul 2>&1
if %errorlevel% equ 0 goto :IS_ADMIN
echo 需要管理员权限来安装软件,正在请求...
echo Set objShell = CreateObject("Shell.Application") > "%TEMP%\bw_elevate.vbs"
echo objShell.ShellExecute "cmd.exe", "/k cd /d ""%~dp0"" & ""%~nx0""", "", "runas", 1 >> "%TEMP%\bw_elevate.vbs"
cscript //nologo "%TEMP%\bw_elevate.vbs"
del /f /q "%TEMP%\bw_elevate.vbs" 2>nul
exit /b
:IS_ADMIN
:: ─── 初始化 ─────────────────────────────────────────
setlocal EnableDelayedExpansion
color 1F
set "NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1"
set "no_proxy=%NO_PROXY%"
set "INSTALL_DIR=%USERPROFILE%\bookworm-boot"
set "GITEA_URL=https://code.letcareme.com/bookworm/bookworm-boot.git"
set "TEMP_DL=%TEMP%\bookworm-setup"
set "ERRORS=0"
set "NEED_PATH_REFRESH=0"
echo.
echo +============================================================+
echo ^| ^|
echo ^| Bookworm Smart Assistant ^|
echo ^| 全自动安装 v2.0 (Windows 10 兼容版) ^|
echo ^| ^|
echo ^| 兼容 Windows 10 1809+ / Windows 11 ^|
echo ^| 无需 winget, 通过直接下载安装包实现全自动 ^|
echo ^| ^|
echo +============================================================+
echo.
if not exist "%TEMP_DL%" mkdir "%TEMP_DL%"
:: ─── 检测安装方式: winget 优先, 回退直接下载 ─────────
set "HAS_WINGET=0"
where winget >nul 2>nul
if %errorlevel% equ 0 set "HAS_WINGET=1"
if "%HAS_WINGET%"=="1" (
echo [OK] 检测到 winget, 使用 winget 安装
) else (
echo [i] 未检测到 winget, 使用直接下载方式安装
)
echo.
:: ─── 步骤 1/7: 安装 Git ────────────────────────────
echo [1/7] 检查 Git...
where git >nul 2>nul
if %errorlevel% neq 0 (
if "%HAS_WINGET%"=="1" (
echo [..] 通过 winget 安装 Git...
winget install Git.Git --accept-source-agreements --accept-package-agreements --silent
) else (
echo [..] 下载 Git 安装包...
powershell -Command "& {[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri 'https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/Git-2.47.1.2-64-bit.exe' -OutFile '%TEMP_DL%\git-install.exe'}"
if exist "%TEMP_DL%\git-install.exe" (
echo [..] 静默安装 Git...
"%TEMP_DL%\git-install.exe" /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh"
if !errorlevel! neq 0 (
echo [!!] Git 安装失败 ^(exit: !errorlevel!^)
set /a ERRORS+=1
) else (
echo [OK] Git 安装完成
)
) else (
echo [!!] Git 下载失败, 请手动安装: https://git-scm.com
set /a ERRORS+=1
)
)
set "NEED_PATH_REFRESH=1"
) else (
echo [OK] Git 已安装
)
echo.
:: ─── 步骤 2/7: 安装 Node.js ────────────────────────
echo [2/7] 检查 Node.js...
where node >nul 2>nul
if %errorlevel% neq 0 (
if "%HAS_WINGET%"=="1" (
echo [..] 通过 winget 安装 Node.js LTS...
winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent
) else (
echo [..] 下载 Node.js LTS 安装包...
powershell -Command "& {[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri 'https://nodejs.org/dist/v22.14.0/node-v22.14.0-x64.msi' -OutFile '%TEMP_DL%\node-install.msi'}"
if exist "%TEMP_DL%\node-install.msi" (
echo [..] 静默安装 Node.js...
msiexec /i "%TEMP_DL%\node-install.msi" /qn /norestart
echo [OK] Node.js 安装完成
) else (
echo [!!] Node.js 下载失败, 请手动安装: https://nodejs.org
set /a ERRORS+=1
)
)
set "NEED_PATH_REFRESH=1"
) else (
echo [OK] Node.js 已安装
)
echo.
:: ─── 刷新 PATH ──────────────────────────────────────
if "%NEED_PATH_REFRESH%"=="1" (
echo [..] 刷新系统 PATH...
for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul') do set "SYS_PATH=%%b"
for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v Path 2^>nul') do set "USR_PATH=%%b"
set "PATH=!SYS_PATH!;!USR_PATH!"
set "PATH=!PATH!;C:\Program Files\nodejs;C:\Program Files\Git\cmd;C:\Program Files\Git\usr\bin"
set "PATH=!PATH!;%APPDATA%\npm"
echo [OK] PATH 已刷新
echo.
)
:: ─── 二次验证 ────────────────────────────────────────
where git >nul 2>nul
if %errorlevel% neq 0 (
echo [!!] Git 仍不可用 — 可能需要重启电脑后重新运行本程序
pause
exit /b 1
)
where node >nul 2>nul
if %errorlevel% neq 0 (
echo [!!] Node.js 仍不可用 — 可能需要重启电脑后重新运行本程序
pause
exit /b 1
)
:: ─── 步骤 3/7: 安装 Claude Code ─────────────────────
echo [3/7] 检查 Claude Code...
:: 国内 npm 镜像 - 淘宝源, 避免 npmjs.org 超时
call npm config set registry https://registry.npmmirror.com 2>nul
where claude >nul 2>nul
if %errorlevel% neq 0 (
echo [..] 通过 npm 安装 Claude Code - 淘宝镜像加速...
call npm i -g @anthropic-ai/claude-code 2>&1
if !errorlevel! neq 0 (
echo [!!] Claude Code 安装失败
set /a ERRORS+=1
) else (
echo [OK] Claude Code 安装成功
)
) else (
echo [OK] Claude Code 已安装
)
echo.
:: ─── 步骤 4/7: 代理检测 ──────────────────
echo [4/7] 检测网络代理...
:: 纯 batch 实现, 不依赖 PowerShell, 不含括号
set "PROXY_FOUND="
netstat -an 2>nul | findstr "LISTENING" | findstr ":7890 :7893 :7891 :10792 :1080 :8118" >nul 2>nul
if !errorlevel! equ 0 set "PROXY_FOUND=1"
if defined PROXY_FOUND echo [OK] 检测到本地代理端口
if not defined PROXY_FOUND echo [!] 未检测到代理, Claude Code 在国内可能无法启动
if not defined PROXY_FOUND echo 请确保代理软件已启动
echo.
:: ─── 步骤 5/7: 克隆/更新 Bookworm ──────────────────
echo [5/7] 同步 Bookworm 配置...
git config --global credential.helper manager 2>nul
if exist "%INSTALL_DIR%\.git" (
echo 已有安装, 更新到最新版...
pushd "%INSTALL_DIR%"
git pull 2>&1
popd
) else (
if exist "%INSTALL_DIR%" rmdir /s /q "%INSTALL_DIR%" 2>nul
echo 首次下载 - 需输入 Gitea 用户名密码...
git clone "%GITEA_URL%" "%INSTALL_DIR%" 2>&1
if !errorlevel! neq 0 (
echo [!!] 下载失败, 请检查网络和 Gitea 凭证
pause
exit /b 1
)
)
echo [OK] Bookworm 文件已就绪
echo.
:: ─── 步骤 6/7: 执行安装配置 ────────────────────────
echo [6/7] 执行安装配置...
echo.
echo 首次安装需要输入主密码来解密 API 凭证
echo - 主密码由管理员提供, 区分大小写
echo.
where pwsh >nul 2>nul
if !errorlevel! equ 0 (
pwsh -NoLogo -ExecutionPolicy Bypass -File "%INSTALL_DIR%\install.ps1"
) else (
powershell -NoLogo -ExecutionPolicy Bypass -File "%INSTALL_DIR%\install.ps1"
)
echo.
:: ─── 步骤 7/7: 桌面快捷方式 + 完成 ─────────────────
echo [7/7] 创建桌面快捷方式...
:: 用 VBScript 创建快捷方式, 避免 PowerShell 花括号被 cmd 截获
echo Set ws = CreateObject("WScript.Shell") > "%TEMP%\bw_shortcut.vbs"
echo Set sc = ws.CreateShortcut(ws.SpecialFolders("Desktop") ^& "\Bookworm.lnk") >> "%TEMP%\bw_shortcut.vbs"
echo sc.TargetPath = "%INSTALL_DIR%\启动Bookworm.bat" >> "%TEMP%\bw_shortcut.vbs"
echo sc.WorkingDirectory = "%INSTALL_DIR%" >> "%TEMP%\bw_shortcut.vbs"
echo sc.Description = "Bookworm Smart Assistant" >> "%TEMP%\bw_shortcut.vbs"
echo sc.Save >> "%TEMP%\bw_shortcut.vbs"
echo Set sc = ws.CreateShortcut(ws.SpecialFolders("Desktop") ^& "\更新Bookworm.lnk") >> "%TEMP%\bw_shortcut.vbs"
echo sc.TargetPath = "%INSTALL_DIR%\更新并启动Bookworm.bat" >> "%TEMP%\bw_shortcut.vbs"
echo sc.WorkingDirectory = "%INSTALL_DIR%" >> "%TEMP%\bw_shortcut.vbs"
echo sc.Save >> "%TEMP%\bw_shortcut.vbs"
cscript //nologo "%TEMP%\bw_shortcut.vbs" 2>nul
if !errorlevel! equ 0 echo [OK] 桌面快捷方式已创建
del /f /q "%TEMP%\bw_shortcut.vbs" 2>nul
if exist "%INSTALL_DIR%\guide.html" start "" "%INSTALL_DIR%\guide.html"
:: 清理临时文件
if exist "%TEMP_DL%" rmdir /s /q "%TEMP_DL%" 2>nul
echo.
echo +============================================================+
echo ^| ^|
echo ^| 安装完成! ^|
echo ^| ^|
echo ^| 已安装: ^|
echo ^| [v] Node.js LTS [v] Git ^|
echo ^| [v] Claude Code [v] Bookworm - 92 Skills ^|
echo ^| ^|
echo ^| 桌面快捷方式: ^|
echo ^| Bookworm — 日常启动 ^|
echo ^| 更新Bookworm — 同步最新版后启动 ^|
echo ^| ^|
echo +============================================================+
echo.
if %ERRORS% gtr 0 (
echo [注意] 安装过程中有 %ERRORS% 个警告, 请查看上方日志
echo.
)
echo 按任意键启动 Bookworm...
pause > nul
cd /d "%INSTALL_DIR%"
if exist "启动Bookworm.bat" (
call "启动Bookworm.bat"
) else (
where pwsh >nul 2>nul
if !errorlevel! equ 0 (
pwsh -NoLogo -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept
) else (
powershell -NoLogo -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept
)
)
endlocal
:: ─── 兜底: 任何情况下窗口不自动关闭 ──
echo.
echo 如看到此消息说明流程已结束,按任意键关闭窗口...
pause > nul

View File

@ -1,163 +0,0 @@
@echo off
chcp 65001 > nul
title Bookworm Smart Assistant - 一键安装
color 1F
:: 中转站在国内,不走代理
set NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1
set no_proxy=%NO_PROXY%
echo.
echo +================================================+
echo ^| ^|
echo ^| Bookworm Smart Assistant ^|
echo ^| 一键安装程序 v1.5 ^|
echo ^| ^|
echo ^| 92 Skills / 18 Agents / 34 Hooks ^|
echo ^| ^|
echo +================================================+
echo.
:: ─── 检查依赖 ───────────────────────────────────────
echo [1/4] 检查环境...
echo.
where git >nul 2>nul
if %errorlevel% neq 0 (
echo [!!] Git 未安装
echo.
echo 请先安装 Git:
echo 下载: https://git-scm.com/download/win
echo 安装后重新运行本程序
echo.
pause
exit /b 1
)
echo [OK] Git
where node >nul 2>nul
if %errorlevel% neq 0 (
echo [!!] Node.js 未安装
echo.
echo 请先安装 Node.js:
echo 下载: https://nodejs.org (选 LTS 版本)
echo 安装后重新打开本程序
echo.
pause
exit /b 1
)
echo [OK] Node.js
where claude >nul 2>nul
if %errorlevel% neq 0 (
echo [..] Claude Code 未安装,正在安装...
call npm i -g @anthropic-ai/claude-code
if %errorlevel% neq 0 (
echo [!!] Claude Code 安装失败
echo 手动安装: npm i -g @anthropic-ai/claude-code
pause
exit /b 1
)
)
echo [OK] Claude Code
echo.
:: ─── 选择安装目录 ───────────────────────────────────
set "INSTALL_DIR=%USERPROFILE%\bookworm-boot"
:: ─── 克隆/更新仓库 ──────────────────────────────────
echo [2/4] 下载 Bookworm...
echo.
if exist "%INSTALL_DIR%\.git" (
echo 已安装,更新到最新版...
cd /d "%INSTALL_DIR%"
git pull 2>&1
) else (
if exist "%INSTALL_DIR%" (
echo 目录已存在但非 git 仓库,清理后重新下载...
rmdir /s /q "%INSTALL_DIR%" 2>nul
)
echo 首次下载...
git config --global credential.helper store
git clone https://code.letcareme.com/bookworm/bookworm-boot.git "%INSTALL_DIR%" 2>&1
if %errorlevel% neq 0 (
echo.
echo [!!] 下载失败
echo 请检查:
echo - 网络是否正常
echo - 是否能访问 https://code.letcareme.com
echo - Gitea 用户名密码是否正确
echo.
pause
exit /b 1
)
)
echo.
echo [OK] Bookworm 文件已就绪
echo.
:: ─── 创建桌面快捷方式 ───────────────────────────────
echo [3/4] 创建桌面快捷方式...
echo.
:: 用 PowerShell 创建 .lnk
powershell -ExecutionPolicy Bypass -Command ^
"$s=(New-Object -COM WScript.Shell).CreateShortcut('%USERPROFILE%\Desktop\Bookworm.lnk');^
$s.TargetPath='%INSTALL_DIR%\启动Bookworm.bat';^
$s.WorkingDirectory='%INSTALL_DIR%';^
$s.Description='Bookworm Smart Assistant';^
$s.Save()" 2>nul
if %errorlevel% equ 0 (
echo [OK] 桌面快捷方式: Bookworm
) else (
echo [!] 快捷方式创建失败 (不影响使用)
)
powershell -ExecutionPolicy Bypass -Command ^
"$s=(New-Object -COM WScript.Shell).CreateShortcut('%USERPROFILE%\Desktop\更新Bookworm.lnk');^
$s.TargetPath='%INSTALL_DIR%\更新并启动Bookworm.bat';^
$s.WorkingDirectory='%INSTALL_DIR%';^
$s.Description='Bookworm 更新并启动';^
$s.Save()" 2>nul
echo.
:: ─── 启动 Bookworm ──────────────────────────────────
echo [4/4] 启动 Bookworm...
echo.
echo +================================================+
echo ^| ^|
echo ^| 安装完成! ^|
echo ^| ^|
echo ^| 桌面已创建快捷方式: ^|
echo ^| Bookworm - 日常启动 ^|
echo ^| 更新Bookworm - 同步后启动 ^|
echo ^| ^|
echo ^| 接下来会启动 Bookworm ^|
echo ^| 请输入管理员提供的主密码 ^|
echo ^| ^|
echo +================================================+
echo.
cd /d "%INSTALL_DIR%"
:: 打开使用教程
if exist "%INSTALL_DIR%\guide.html" (
start "" "%INSTALL_DIR%\guide.html"
)
:: 启动安装脚本
where pwsh >nul 2>nul
if %errorlevel% equ 0 (
pwsh -ExecutionPolicy Bypass -File install.ps1 -AutoAccept
) else (
powershell -ExecutionPolicy Bypass -File install.ps1 -AutoAccept
)
if %errorlevel% neq 0 (
echo.
echo 启动失败,请查看上方错误信息
pause
)

View File

@ -5,6 +5,10 @@
内部调用 node gen-authcode.js, 提供可视化界面生成多用户授权码
打包命令: build.ps1 -Admin
#>
# 全局错误捕获 (PS2EXE -NoOutput 会吞掉所有错误, 这里确保弹窗显示)
try {
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
@ -28,216 +32,268 @@ if (-not (Test-Path $GenScript)) {
$SecretsTxt = Join-Path $ScriptDir "secrets.txt"
# ─── 品牌色 ───────────────────────────────────────────
$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242)
$brandDark = [System.Drawing.Color]::FromArgb(30, 31, 46)
$brandLight = [System.Drawing.Color]::FromArgb(245, 245, 250)
$successGreen = [System.Drawing.Color]::FromArgb(46, 160, 67)
$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242)
$brandDark = [System.Drawing.Color]::FromArgb(24, 25, 38)
$brandLight = [System.Drawing.Color]::FromArgb(245, 246, 250)
$cardBg = [System.Drawing.Color]::FromArgb(248, 249, 253)
$successGreen = [System.Drawing.Color]::FromArgb(35, 134, 54)
$warningOrange = [System.Drawing.Color]::FromArgb(227, 137, 29)
$textPrimary = [System.Drawing.Color]::FromArgb(36, 41, 47)
$textSecondary = [System.Drawing.Color]::FromArgb(110, 119, 129)
$inputBorder = [System.Drawing.Color]::FromArgb(208, 215, 222)
# ─── 主窗口 ───────────────────────────────────────────
$form = New-Object System.Windows.Forms.Form
$form.Text = "Bookworm 授权码生成器 — 管理员工具"
$form.Size = New-Object System.Drawing.Size(560, 580)
$form.Text = "Bookworm 授权码生成器"
$form.ClientSize = New-Object System.Drawing.Size(540, 594)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.FormBorderStyle = "FixedSingle"
$form.MaximizeBox = $false
$form.BackColor = [System.Drawing.Color]::White
$form.Font = New-Object System.Drawing.Font("Segoe UI", 9)
# ─── 标题栏 ───────────────────────────────────────────
# ─── 标题栏 (深色渐变 + 副标题) ──────────────────────
$header = New-Object System.Windows.Forms.Panel
$header.Location = New-Object System.Drawing.Point(0, 0)
$header.Size = New-Object System.Drawing.Size(560, 56)
$header.Dock = "Top"
$header.Size = New-Object System.Drawing.Size(540, 70)
$header.BackColor = $brandDark
$form.Controls.Add($header)
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Location = New-Object System.Drawing.Point(20, 14)
$titleLabel.Size = New-Object System.Drawing.Size(520, 28)
$titleLabel.Location = New-Object System.Drawing.Point(24, 12)
$titleLabel.Size = New-Object System.Drawing.Size(500, 28)
$titleLabel.Text = "Bookworm 授权码生成器"
$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold)
$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold)
$titleLabel.ForeColor = [System.Drawing.Color]::White
$header.Controls.Add($titleLabel)
# ─── 输入区 ───────────────────────────────────────────
[int]$y = 72
$subtitleLabel = New-Object System.Windows.Forms.Label
$subtitleLabel.Location = New-Object System.Drawing.Point(24, 42)
$subtitleLabel.Size = New-Object System.Drawing.Size(500, 18)
$subtitleLabel.Text = "管理员专用 · 为每位用户生成独立授权码与加密凭证"
$subtitleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 8.5)
$subtitleLabel.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200)
$header.Controls.Add($subtitleLabel)
# 用户名
# ═══ 输入卡片 (Panel 模拟卡片) ═══════════════════════
$inputCard = New-Object System.Windows.Forms.Panel
$inputCard.Location = New-Object System.Drawing.Point(20, 84)
$inputCard.Size = New-Object System.Drawing.Size(500, 186)
$inputCard.BackColor = $cardBg
$form.Controls.Add($inputCard)
# ── 卡片标题
$inputTitle = New-Object System.Windows.Forms.Label
$inputTitle.Location = New-Object System.Drawing.Point(16, 10)
$inputTitle.Size = New-Object System.Drawing.Size(200, 20)
$inputTitle.Text = "用户信息"
$inputTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
$inputTitle.ForeColor = $brandBlue
$inputCard.Controls.Add($inputTitle)
# ── 用户名
$lblUser = New-Object System.Windows.Forms.Label
$lblUser.Location = New-Object System.Drawing.Point(24, $y)
$lblUser.Size = New-Object System.Drawing.Size(120, 22)
$lblUser.Text = "用户标识 (--user):"
$form.Controls.Add($lblUser)
$lblUser.Location = New-Object System.Drawing.Point(16, 40)
$lblUser.Size = New-Object System.Drawing.Size(90, 22)
$lblUser.Text = "用户标识"
$lblUser.ForeColor = $textPrimary
$lblUser.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$inputCard.Controls.Add($lblUser)
$txtUser = New-Object System.Windows.Forms.TextBox
$txtUser.Location = New-Object System.Drawing.Point(150, $y - 2)
$txtUser.Size = New-Object System.Drawing.Size(370, 28)
$txtUser.Font = New-Object System.Drawing.Font("Consolas", 11)
$txtUser.PlaceholderText = "例如: alice, 张三, 茶师兄"
$form.Controls.Add($txtUser)
$y += 38
$txtUser.Location = New-Object System.Drawing.Point(114, 38)
$txtUser.Size = New-Object System.Drawing.Size(370, 26)
$txtUser.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$txtUser.BorderStyle = "FixedSingle"
$inputCard.Controls.Add($txtUser)
# Relay Sub-Key
# ── Relay Key
$lblKey = New-Object System.Windows.Forms.Label
$lblKey.Location = New-Object System.Drawing.Point(24, $y)
$lblKey.Size = New-Object System.Drawing.Size(120, 22)
$lblKey.Text = "Relay Sub-Key:"
$form.Controls.Add($lblKey)
$lblKey.Location = New-Object System.Drawing.Point(16, 74)
$lblKey.Size = New-Object System.Drawing.Size(90, 22)
$lblKey.Text = "Relay Key"
$lblKey.ForeColor = $textPrimary
$lblKey.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$inputCard.Controls.Add($lblKey)
$txtKey = New-Object System.Windows.Forms.TextBox
$txtKey.Location = New-Object System.Drawing.Point(150, $y - 2)
$txtKey.Size = New-Object System.Drawing.Size(370, 28)
$txtKey.Font = New-Object System.Drawing.Font("Consolas", 10)
$txtKey.Location = New-Object System.Drawing.Point(114, 72)
$txtKey.Size = New-Object System.Drawing.Size(370, 26)
$txtKey.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$txtKey.PasswordChar = '*'
$txtKey.PlaceholderText = "sk-... (中转站后台创建的子 Key)"
$form.Controls.Add($txtKey)
$y += 28
$txtKey.BorderStyle = "FixedSingle"
$inputCard.Controls.Add($txtKey)
# 显示/隐藏 Key
$chkShowKey = New-Object System.Windows.Forms.CheckBox
$chkShowKey.Location = New-Object System.Drawing.Point(150, $y)
$chkShowKey.Size = New-Object System.Drawing.Size(120, 22)
$chkShowKey.Location = New-Object System.Drawing.Point(114, 102)
$chkShowKey.Size = New-Object System.Drawing.Size(100, 20)
$chkShowKey.Text = "显示 Key"
$chkShowKey.ForeColor = $textSecondary
$chkShowKey.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$chkShowKey.Add_CheckedChanged({ $txtKey.PasswordChar = if ($chkShowKey.Checked) { [char]0 } else { '*' } })
$form.Controls.Add($chkShowKey)
$y += 32
$inputCard.Controls.Add($chkShowKey)
# 有效期
# ── 有效期
$lblDays = New-Object System.Windows.Forms.Label
$lblDays.Location = New-Object System.Drawing.Point(24, $y)
$lblDays.Size = New-Object System.Drawing.Size(120, 22)
$lblDays.Text = "有效期 (天):"
$form.Controls.Add($lblDays)
$lblDays.Location = New-Object System.Drawing.Point(16, 132)
$lblDays.Size = New-Object System.Drawing.Size(90, 22)
$lblDays.Text = "有效期"
$lblDays.ForeColor = $textPrimary
$lblDays.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$inputCard.Controls.Add($lblDays)
$cmbDays = New-Object System.Windows.Forms.ComboBox
$cmbDays.Location = New-Object System.Drawing.Point(150, $y - 2)
$cmbDays.Size = New-Object System.Drawing.Size(100, 28)
$cmbDays.Location = New-Object System.Drawing.Point(114, 130)
$cmbDays.Size = New-Object System.Drawing.Size(80, 26)
$cmbDays.DropDownStyle = "DropDownList"
@(7, 14, 30, 60, 90, 180, 365) | ForEach-Object { $cmbDays.Items.Add($_) | Out-Null }
$cmbDays.SelectedIndex = 2 # 默认 30 天
$form.Controls.Add($cmbDays)
$cmbDays.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$null = $cmbDays.Items.AddRange(@(7, 14, 30, 60, 90, 180, 365))
$cmbDays.SelectedIndex = 2
$inputCard.Controls.Add($cmbDays)
$lblDaysUnit = New-Object System.Windows.Forms.Label
$lblDaysUnit.Location = New-Object System.Drawing.Point(198, 132)
$lblDaysUnit.Size = New-Object System.Drawing.Size(30, 22)
$lblDaysUnit.Text = ""
$lblDaysUnit.ForeColor = $textSecondary
$inputCard.Controls.Add($lblDaysUnit)
$lblDaysHint = New-Object System.Windows.Forms.Label
$lblDaysHint.Location = New-Object System.Drawing.Point(260, $y)
$lblDaysHint.Size = New-Object System.Drawing.Size(260, 22)
$lblDaysHint.Location = New-Object System.Drawing.Point(240, 132)
$lblDaysHint.Size = New-Object System.Drawing.Size(240, 22)
$lblDaysHint.Text = ""
$lblDaysHint.ForeColor = [System.Drawing.Color]::Gray
$form.Controls.Add($lblDaysHint)
$lblDaysHint.ForeColor = $textSecondary
$lblDaysHint.Font = New-Object System.Drawing.Font("Segoe UI", 8.5)
$inputCard.Controls.Add($lblDaysHint)
$cmbDays.Add_SelectedIndexChanged({
$d = [int]$cmbDays.SelectedItem
$exp = (Get-Date).AddDays($d).ToString("yyyy-MM-dd")
$lblDaysHint.Text = "到期: $exp"
$lblDaysHint.Text = "-> 到期: " + (Get-Date).AddDays($d).ToString("yyyy-MM-dd")
})
# 触发初始值
$cmbDays.SelectedIndex = 2
$y += 38
# secrets.txt 状态
$lblSecrets = New-Object System.Windows.Forms.Label
$lblSecrets.Location = New-Object System.Drawing.Point(24, $y)
$lblSecrets.Size = New-Object System.Drawing.Size(500, 22)
if (Test-Path $SecretsTxt) {
$lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count
$lblSecrets.Text = "secrets.txt: $lineCount 个凭证已配置"
$lblSecrets.ForeColor = $successGreen
} else {
$lblSecrets.Text = "secrets.txt: 未找到 ($SecretsTxt)"
$lblSecrets.ForeColor = [System.Drawing.Color]::Red
}
$form.Controls.Add($lblSecrets)
$y += 28
# Node.js 状态
$lblNode = New-Object System.Windows.Forms.Label
$lblNode.Location = New-Object System.Drawing.Point(24, $y)
$lblNode.Size = New-Object System.Drawing.Size(500, 22)
# ── 环境状态行 ──
$nodeOK = [bool](Get-Command node -ErrorAction SilentlyContinue)
$secretsOK = Test-Path $SecretsTxt
$lblStatus1 = New-Object System.Windows.Forms.Label
$lblStatus1.Location = New-Object System.Drawing.Point(16, 160)
$lblStatus1.Size = New-Object System.Drawing.Size(230, 18)
$lblStatus1.Font = New-Object System.Drawing.Font("Segoe UI", 8)
if ($secretsOK) {
$lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count
$lblStatus1.Text = "secrets.txt: $lineCount 个凭证"
$lblStatus1.ForeColor = $successGreen
} else {
$lblStatus1.Text = "secrets.txt: 未找到"
$lblStatus1.ForeColor = [System.Drawing.Color]::Red
}
$inputCard.Controls.Add($lblStatus1)
$lblStatus2 = New-Object System.Windows.Forms.Label
$lblStatus2.Location = New-Object System.Drawing.Point(250, 160)
$lblStatus2.Size = New-Object System.Drawing.Size(230, 18)
$lblStatus2.Font = New-Object System.Drawing.Font("Segoe UI", 8)
if ($nodeOK) {
$nodeVer = try { (& node --version 2>$null) } catch { "" }
$lblNode.Text = "Node.js: $nodeVer"
$lblNode.ForeColor = $successGreen
$lblStatus2.Text = "Node.js: $nodeVer"
$lblStatus2.ForeColor = $successGreen
} else {
$lblNode.Text = "Node.js: 未安装 (必需)"
$lblNode.ForeColor = [System.Drawing.Color]::Red
$lblStatus2.Text = "Node.js: 未安装"
$lblStatus2.ForeColor = [System.Drawing.Color]::Red
}
$form.Controls.Add($lblNode)
$y += 36
$inputCard.Controls.Add($lblStatus2)
# ─── 生成按钮 ─────────────────────────────────────────
# ═══ 操作按钮 ═════════════════════════════════════════
$btnGenerate = New-Object System.Windows.Forms.Button
$btnGenerate.Location = New-Object System.Drawing.Point(150, $y)
$btnGenerate.Size = New-Object System.Drawing.Size(160, 40)
$btnGenerate.Text = "生成授权码"
$btnGenerate.Location = New-Object System.Drawing.Point(138, 282)
$btnGenerate.Size = New-Object System.Drawing.Size(180, 42)
$btnGenerate.Text = " 生成授权码"
$btnGenerate.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
$btnGenerate.FlatStyle = "Flat"
$btnGenerate.BackColor = $brandBlue
$btnGenerate.ForeColor = [System.Drawing.Color]::White
$btnGenerate.FlatAppearance.BorderSize = 0
$btnGenerate.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnGenerate.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$form.Controls.Add($btnGenerate)
$btnClear = New-Object System.Windows.Forms.Button
$btnClear.Location = New-Object System.Drawing.Point(320, $y)
$btnClear.Size = New-Object System.Drawing.Size(80, 40)
$btnClear.Location = New-Object System.Drawing.Point(330, 282)
$btnClear.Size = New-Object System.Drawing.Size(72, 42)
$btnClear.Text = "清空"
$btnClear.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$btnClear.FlatStyle = "Flat"
$btnClear.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray
$btnClear.BackColor = [System.Drawing.Color]::White
$btnClear.ForeColor = $textSecondary
$btnClear.FlatAppearance.BorderColor = $inputBorder
$btnClear.FlatAppearance.BorderSize = 1
$form.Controls.Add($btnClear)
$y += 54
# ─── 分隔线 ───────────────────────────────────────────
$sep = New-Object System.Windows.Forms.Label
$sep.Location = New-Object System.Drawing.Point(20, $y)
$sep.Size = New-Object System.Drawing.Size(510, 1)
$sep.BorderStyle = "Fixed3D"
$form.Controls.Add($sep)
$y += 10
# ═══ 结果卡片 ═════════════════════════════════════════
$resultCard = New-Object System.Windows.Forms.Panel
$resultCard.Location = New-Object System.Drawing.Point(20, 338)
$resultCard.Size = New-Object System.Drawing.Size(500, 220)
$resultCard.BackColor = $cardBg
$form.Controls.Add($resultCard)
# ─── 结果区 ───────────────────────────────────────────
$lblResultTitle = New-Object System.Windows.Forms.Label
$lblResultTitle.Location = New-Object System.Drawing.Point(24, $y)
$lblResultTitle.Size = New-Object System.Drawing.Size(200, 22)
$lblResultTitle.Location = New-Object System.Drawing.Point(16, 10)
$lblResultTitle.Size = New-Object System.Drawing.Size(200, 20)
$lblResultTitle.Text = "生成结果"
$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
$lblResultTitle.ForeColor = $brandDark
$form.Controls.Add($lblResultTitle)
$y += 26
$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
$lblResultTitle.ForeColor = $brandBlue
$resultCard.Controls.Add($lblResultTitle)
# 授权码 (大字 Consolas, 可选中复制)
$txtAuthCode = New-Object System.Windows.Forms.TextBox
$txtAuthCode.Location = New-Object System.Drawing.Point(24, $y)
$txtAuthCode.Size = New-Object System.Drawing.Size(400, 32)
$txtAuthCode.Location = New-Object System.Drawing.Point(16, 38)
$txtAuthCode.Size = New-Object System.Drawing.Size(380, 34)
$txtAuthCode.Font = New-Object System.Drawing.Font("Consolas", 13, [System.Drawing.FontStyle]::Bold)
$txtAuthCode.ReadOnly = $true
$txtAuthCode.BackColor = $brandLight
$txtAuthCode.BackColor = [System.Drawing.Color]::White
$txtAuthCode.ForeColor = $brandDark
$txtAuthCode.BorderStyle = "FixedSingle"
$txtAuthCode.Text = ""
$form.Controls.Add($txtAuthCode)
$resultCard.Controls.Add($txtAuthCode)
$btnCopy = New-Object System.Windows.Forms.Button
$btnCopy.Location = New-Object System.Drawing.Point(430, $y)
$btnCopy.Size = New-Object System.Drawing.Size(90, 32)
$btnCopy.Location = New-Object System.Drawing.Point(404, 38)
$btnCopy.Size = New-Object System.Drawing.Size(80, 34)
$btnCopy.Text = "复制"
$btnCopy.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$btnCopy.FlatStyle = "Flat"
$btnCopy.BackColor = $brandLight
$btnCopy.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray
$btnCopy.BackColor = $brandBlue
$btnCopy.ForeColor = [System.Drawing.Color]::White
$btnCopy.FlatAppearance.BorderSize = 0
$btnCopy.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnCopy.Enabled = $false
$form.Controls.Add($btnCopy)
$y += 40
$resultCard.Controls.Add($btnCopy)
# 详细信息
$lblDetails = New-Object System.Windows.Forms.Label
$lblDetails.Location = New-Object System.Drawing.Point(24, $y)
$lblDetails.Size = New-Object System.Drawing.Size(500, 66)
$lblDetails.Text = ""
$lblDetails.ForeColor = [System.Drawing.Color]::FromArgb(100, 100, 120)
$form.Controls.Add($lblDetails)
$y += 70
$lblDetails.Location = New-Object System.Drawing.Point(16, 82)
$lblDetails.Size = New-Object System.Drawing.Size(468, 66)
$lblDetails.Text = "点击「生成授权码」后,结果将显示在此处。"
$lblDetails.ForeColor = $textSecondary
$lblDetails.Font = New-Object System.Drawing.Font("Segoe UI", 8.5)
$resultCard.Controls.Add($lblDetails)
# 状态栏
# 推送到 Gitea 按钮
$btnPush = New-Object System.Windows.Forms.Button
$btnPush.Location = New-Object System.Drawing.Point(16, 156)
$btnPush.Size = New-Object System.Drawing.Size(468, 38)
$btnPush.Text = "推送到 Gitea (git add + commit + push)"
$btnPush.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$btnPush.FlatStyle = "Flat"
$btnPush.BackColor = $successGreen
$btnPush.ForeColor = [System.Drawing.Color]::White
$btnPush.FlatAppearance.BorderSize = 0
$btnPush.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnPush.Enabled = $false
$resultCard.Controls.Add($btnPush)
# ═══ 状态栏 ═══════════════════════════════════════════
$statusBar = New-Object System.Windows.Forms.Label
$statusBar.Location = New-Object System.Drawing.Point(0, $y)
$statusBar.Size = New-Object System.Drawing.Size(560, 24)
$statusBar.BackColor = $brandLight
$statusBar.ForeColor = [System.Drawing.Color]::Gray
$statusBar.Text = " 就绪 | $ScriptDir"
$statusBar.Location = New-Object System.Drawing.Point(0, 568)
$statusBar.Size = New-Object System.Drawing.Size(540, 26)
$statusBar.BackColor = $brandDark
$statusBar.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200)
$statusBar.Text = " 就绪 | $ScriptDir"
$statusBar.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$statusBar.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$form.Controls.Add($statusBar)
@ -252,6 +308,65 @@ $btnCopy.Add_Click({
}
})
$btnPush.Add_Click({
$btnPush.Enabled = $false
$btnPush.Text = "推送中..."
$statusBar.Text = " git add + commit + push ..."
$statusBar.ForeColor = $warningOrange
[System.Windows.Forms.Application]::DoEvents()
try {
$gitExe = (Get-Command git -ErrorAction Stop).Source
$userName = if ($global:lastGenUser) { $global:lastGenUser } else { "user" }
# git add secrets-*.enc
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $gitExe
$psi.Arguments = "add secrets-*.enc"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$psi.CreateNoWindow = $true
$psi.WorkingDirectory = $ScriptDir
$p = [System.Diagnostics.Process]::Start($psi)
$p.WaitForExit(15000)
# git commit
$psi.Arguments = "commit -m `"add user $userName`""
$p = [System.Diagnostics.Process]::Start($psi)
$p.StandardOutput.ReadToEnd() | Out-Null
$p.WaitForExit(15000)
# git push
$statusBar.Text = " git push 中 (可能需要几秒)..."
[System.Windows.Forms.Application]::DoEvents()
$psi.Arguments = "push"
$p = [System.Diagnostics.Process]::Start($psi)
$pushOut = $p.StandardError.ReadToEnd() # git push 输出在 stderr
$p.WaitForExit(30000)
if ($p.ExitCode -eq 0) {
$btnPush.Text = "已推送"
$btnPush.BackColor = [System.Drawing.Color]::FromArgb(200, 220, 200)
$btnPush.ForeColor = $successGreen
$statusBar.Text = " 推送成功 — 现在可以把授权码发给 $userName"
$statusBar.ForeColor = $successGreen
} else {
throw "git push 失败: $pushOut"
}
} catch {
$btnPush.Text = "推送失败 (点击重试)"
$btnPush.BackColor = [System.Drawing.Color]::FromArgb(220, 200, 200)
$btnPush.ForeColor = [System.Drawing.Color]::Red
$btnPush.Enabled = $true
$statusBar.Text = " 推送失败: $_"
$statusBar.ForeColor = [System.Drawing.Color]::Red
[System.Windows.Forms.MessageBox]::Show("推送失败:`n$_`n`n请检查 Git 配置和网络。", "Git 错误", "OK", "Error")
}
})
$btnClear.Add_Click({
$txtUser.Text = ""
$txtKey.Text = ""
@ -291,38 +406,45 @@ $btnGenerate.Add_Click({
[System.Windows.Forms.Application]::DoEvents()
try {
$args = @($GenScript, $days, "-k", $key, "-u", $user)
$proc = Start-Process node -ArgumentList $args -NoNewWindow -PassThru `
-RedirectStandardOutput "$env:TEMP\bw-gen-out.tmp" `
-RedirectStandardError "$env:TEMP\bw-gen-err.tmp" `
-WorkingDirectory $ScriptDir
$sw = [System.Diagnostics.Stopwatch]::StartNew()
while (-not $proc.HasExited -and $sw.ElapsedMilliseconds -lt 30000) {
[System.Windows.Forms.Application]::DoEvents()
Start-Sleep -Milliseconds 100
}
if (-not $proc.HasExited) { $proc.Kill(); throw "超时 (30s)" }
$stdout = Get-Content "$env:TEMP\bw-gen-out.tmp" -Raw -ErrorAction SilentlyContinue
$stderr = Get-Content "$env:TEMP\bw-gen-err.tmp" -Raw -ErrorAction SilentlyContinue
Remove-Item "$env:TEMP\bw-gen-out.tmp", "$env:TEMP\bw-gen-err.tmp" -Force -ErrorAction SilentlyContinue
# 用 Process + UTF8 编码读取 (PS2EXE 默认 GBK 会乱码)
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = (Get-Command node -ErrorAction Stop).Source
$psi.Arguments = "`"$GenScript`" $days -k `"$key`" -u `"$user`""
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$psi.CreateNoWindow = $true
$psi.WorkingDirectory = $ScriptDir
$proc = [System.Diagnostics.Process]::Start($psi)
$stdout = $proc.StandardOutput.ReadToEnd()
$stderr = $proc.StandardError.ReadToEnd()
$proc.WaitForExit()
if ($proc.ExitCode -ne 0) {
throw "gen-authcode.js 退出码 $($proc.ExitCode)`n$stderr"
}
# 解析输出
$authCode = if ($stdout -match '授权码:\s*(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" }
$fileId = if ($stdout -match '文件 ID:\s*([a-f0-9]{8})\s*→\s*(secrets-[a-f0-9]{8}\.enc)') { $Matches[2] } else { "" }
$expiry = if ($stdout -match '有效期:\s*\d+\s*天\s*\(至\s*(\d{4}-\d{2}-\d{2})\)') { $Matches[1] } else { "" }
# 解析输出 (编码无关: PS2EXE 下中文可能乱码, 只匹配 ASCII 格式)
$authCode = if ($stdout -match '(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" }
$fileId = if ($stdout -match '(secrets-[a-f0-9]{8}\.enc)') { $Matches[1] } else { "" }
$expiry = if ($stdout -match '(\d{4}-\d{2}-\d{2})') { $Matches[1] } else { "" }
if ($authCode) {
$txtAuthCode.Text = $authCode
$lblDetails.Text = "用户: $user`n加密文件: $fileId`n有效期: $days 天 (至 $expiry)`n路径: $ScriptDir\$fileId"
$btnCopy.Enabled = $true
$statusBar.Text = " 生成成功 — $fileId | 请 git add + push 后将授权码发给 $user"
$btnPush.Enabled = $true
$global:lastGenUser = $user
$global:lastGenFile = $fileId
$statusBar.Text = " 生成成功 — 点击「推送到 Gitea」完成部署"
$statusBar.ForeColor = $successGreen
# 追加历史记录 (仅本机, 已在 .gitignore)
$historyFile = Join-Path $ScriptDir "authcode-history.log"
$logLine = "$(Get-Date -Format 'yyyy-MM-dd HH:mm') $($user.PadRight(12)) $fileId $($days)天 至$expiry $authCode"
try { Add-Content -Path $historyFile -Value $logLine -Encoding utf8 } catch {}
} else {
throw "无法解析授权码输出:`n$stdout"
}
@ -341,3 +463,20 @@ $btnGenerate.Add_Click({
# ─── 启动 ─────────────────────────────────────────────
$form.Add_Shown({ $txtUser.Focus() })
[System.Windows.Forms.Application]::Run($form)
} catch {
# 全局错误弹窗 (PS2EXE 下唯一的错误可见方式)
$errMsg = "Bookworm AuthGen 启动失败:`n`n" +
"错误: $($_.Exception.Message)`n" +
"行号: $($_.InvocationInfo.ScriptLineNumber)`n" +
"代码: $($_.InvocationInfo.Line.Trim())`n`n" +
"ScriptDir: $ScriptDir`n" +
"GenScript: $GenScript`n" +
"SecretsTxt: $SecretsTxt"
try {
[System.Windows.Forms.MessageBox]::Show($errMsg, "AuthGen 错误", "OK", "Error")
} catch {
# 如果连 MsgBox 都失败 (WinForms 未加载), 写文件
$errMsg | Out-File "$env:TEMP\bookworm-authgen-error.txt" -Encoding utf8
}
}

View File

@ -1,85 +0,0 @@
<!-- DOWNLOAD Panel -->
<section class="panel" data-panel="download" style="display:none">
<div style="max-width:640px;margin:0 auto;padding:2rem 1rem">
<div style="text-align:center;margin-bottom:2rem">
<div style="font-size:3rem;margin-bottom:0.5rem">&#128218;</div>
<h2 style="font-size:1.6rem;margin-bottom:0.3rem">Bookworm <span style="color:var(--accent,#58a6ff)">Portable</span></h2>
<p style="color:var(--text-secondary,#8b949e);font-size:0.95rem">AI 编程助手桌面版 &#8212; 一键安装到你的电脑</p>
</div>
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap;margin-bottom:1.5rem">
<span style="background:var(--bg-tertiary,#21262d);border:1px solid var(--border,#30363d);border-radius:20px;padding:0.25rem 0.75rem;font-size:0.8rem"><b>92</b> Skills</span>
<span style="background:var(--bg-tertiary,#21262d);border:1px solid var(--border,#30363d);border-radius:20px;padding:0.25rem 0.75rem;font-size:0.8rem"><b>18</b> Agents</span>
<span style="background:var(--bg-tertiary,#21262d);border:1px solid var(--border,#30363d);border-radius:20px;padding:0.25rem 0.75rem;font-size:0.8rem"><b>34</b> Hooks</span>
<span style="background:var(--bg-tertiary,#21262d);border:1px solid var(--border,#30363d);border-radius:20px;padding:0.25rem 0.75rem;font-size:0.8rem"><b>AES-256</b> &#21152;&#23494;</span>
</div>
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:12px;padding:1.5rem;text-align:center;margin-bottom:1.5rem">
<h3 style="margin-bottom:0.8rem;font-size:1.1rem">Windows &#26700;&#38754;&#29256;</h3>
<p style="color:var(--text-secondary,#8b949e);font-size:0.9rem;margin-bottom:1.2rem">
&#22312;&#20320;&#30340;&#30005;&#33041;&#19978;&#36816;&#34892; Bookworm&#65292;&#23436;&#25972;&#30340; Claude Code + 97 &#20010;&#19987;&#23478; Skills<br>
&#31163;&#32447;&#37197;&#32622;&#65292;API &#36890;&#36807;&#21152;&#23494;&#20013;&#36716;&#31449;&#36716;&#21457;
</p>
<a id="downloadSetupBtn" href="/api/download/setup" download="Bookworm-Setup.bat"
style="display:inline-block;background:linear-gradient(135deg,#238636,#2ea043);color:#fff;font-size:1rem;font-weight:600;padding:0.75rem 2rem;border-radius:8px;text-decoration:none;cursor:pointer">
&#11015; &#19979;&#36733;&#23433;&#35013;&#31243;&#24207; (4 KB)
</a>
<p style="color:var(--text-secondary,#8b949e);font-size:0.75rem;margin-top:0.5rem">Bookworm-Setup.bat &#8212; &#21452;&#20987;&#21363;&#21487;&#23433;&#35013;</p>
</div>
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:12px;padding:1.2rem 1.5rem;margin-bottom:1.5rem">
<h4 style="margin-bottom:0.8rem;color:var(--accent,#58a6ff)">&#23433;&#35013;&#21482;&#38656; 3 &#27493;</h4>
<div style="display:flex;gap:0.8rem;align-items:flex-start;margin-bottom:0.6rem">
<span style="background:var(--accent,#58a6ff);color:#000;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0">1</span>
<span>&#19979;&#36733;&#19978;&#26041;&#23433;&#35013;&#31243;&#24207;&#65292;&#21452;&#20987;&#36816;&#34892;</span>
</div>
<div style="display:flex;gap:0.8rem;align-items:flex-start;margin-bottom:0.6rem">
<span style="background:var(--accent,#58a6ff);color:#000;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0">2</span>
<span>&#36755;&#20837;&#31649;&#29702;&#21592;&#25552;&#20379;&#30340;&#23494;&#30721;</span>
</div>
<div style="display:flex;gap:0.8rem;align-items:flex-start">
<span style="background:#3fb950;color:#000;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0">&#10003;</span>
<span>&#23436;&#25104;&#65281;&#26700;&#38754;&#20986;&#29616; Bookworm &#22270;&#26631;&#65292;&#21452;&#20987;&#21551;&#21160;</span>
</div>
</div>
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:12px;padding:1.2rem 1.5rem;margin-bottom:1.5rem">
<h4 style="margin-bottom:0.5rem">&#31995;&#32479;&#35201;&#27714;</h4>
<table style="width:100%;font-size:0.85rem;border-collapse:collapse">
<tr style="border-bottom:1px solid var(--border,#30363d)"><td style="padding:0.4rem 0"><b>&#25805;&#20316;&#31995;&#32479;</b></td><td>Windows 10/11</td></tr>
<tr style="border-bottom:1px solid var(--border,#30363d)"><td style="padding:0.4rem 0"><b>Node.js</b></td><td>18+ (<a href="https://nodejs.org" target="_blank" style="color:var(--accent,#58a6ff)">&#19979;&#36733;</a>)</td></tr>
<tr style="border-bottom:1px solid var(--border,#30363d)"><td style="padding:0.4rem 0"><b>Git</b></td><td><a href="https://git-scm.com" target="_blank" style="color:var(--accent,#58a6ff)">&#19979;&#36733;</a></td></tr>
<tr><td style="padding:0.4rem 0"><b>&#32593;&#32476;</b></td><td>&#38656;&#35201;&#20195;&#29702;/VPN (&#22269;&#20869;)</td></tr>
</table>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.8rem;margin-bottom:1.5rem">
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:8px;padding:1rem;text-align:center">
<div style="font-size:1.5rem;margin-bottom:0.3rem">&#9889;</div>
<div style="font-weight:600;font-size:0.9rem">30 &#31186;&#21551;&#21160;</div>
<div style="color:var(--text-secondary,#8b949e);font-size:0.75rem">&#23433;&#35013;&#21518;&#27599;&#27425;&#21452;&#20987;&#21363;&#21487;</div>
</div>
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:8px;padding:1rem;text-align:center">
<div style="font-size:1.5rem;margin-bottom:0.3rem">&#128274;</div>
<div style="font-weight:600;font-size:0.9rem">AES-256 &#21152;&#23494;</div>
<div style="color:var(--text-secondary,#8b949e);font-size:0.75rem">&#20973;&#35777;&#19981;&#33853;&#30424;&#65292;&#36827;&#31243;&#32423;&#38548;&#31163;</div>
</div>
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:8px;padding:1rem;text-align:center">
<div style="font-size:1.5rem;margin-bottom:0.3rem">&#128640;</div>
<div style="font-weight:600;font-size:0.9rem">97 &#20010; AI &#19987;&#23478;</div>
<div style="color:var(--text-secondary,#8b949e);font-size:0.75rem">&#20840;&#26632;&#24320;&#21457;/&#23433;&#20840;/&#26550;&#26500;/&#27979;&#35797;</div>
</div>
<div style="background:var(--bg-secondary,#161b22);border:1px solid var(--border,#30363d);border-radius:8px;padding:1rem;text-align:center">
<div style="font-size:1.5rem;margin-bottom:0.3rem">&#127760;</div>
<div style="font-weight:600;font-size:0.9rem">&#20013;&#36716;&#31449; API</div>
<div style="color:var(--text-secondary,#8b949e);font-size:0.75rem">&#26080;&#38656; Claude &#36134;&#21495;</div>
</div>
</div>
<div style="text-align:center;color:var(--text-secondary,#8b949e);font-size:0.75rem">
Bookworm Portable v1.3 &#8212; &#22914;&#38656;&#24110;&#21161;&#35831;&#32852;&#31995;&#31649;&#29702;&#21592;
</div>
</div>
</section>

View File

@ -1,125 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bookworm - 下载安装</title>
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32.png">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #0d1117;
color: #e6edf3;
font-family: -apple-system, 'Segoe UI', 'Microsoft YaHei', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 2rem;
}
.card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 16px;
padding: 3rem;
max-width: 520px;
width: 100%;
text-align: center;
}
.logo {
font-size: 3rem;
margin-bottom: 1rem;
}
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
h1 span { color: #58a6ff; }
.subtitle { color: #8b949e; margin-bottom: 2rem; font-size: 0.95rem; }
.badges {
display: flex; gap: 0.5rem; justify-content: center;
margin-bottom: 2rem; flex-wrap: wrap;
}
.badge {
background: #0d1117; border: 1px solid #30363d; border-radius: 20px;
padding: 0.25rem 0.75rem; font-size: 0.8rem; color: #8b949e;
}
.badge b { color: #e6edf3; }
.btn {
display: inline-block;
background: linear-gradient(135deg, #238636, #2ea043);
color: white;
font-size: 1.1rem;
font-weight: 600;
padding: 0.9rem 2.5rem;
border-radius: 8px;
text-decoration: none;
border: none;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
margin-bottom: 1rem;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(35,134,54,0.4); }
.btn:active { transform: translateY(0); }
.size { color: #8b949e; font-size: 0.8rem; margin-bottom: 2rem; }
.steps {
text-align: left;
background: #0d1117;
border: 1px solid #30363d;
border-radius: 8px;
padding: 1.2rem 1.5rem;
margin-top: 1rem;
font-size: 0.9rem;
line-height: 2;
}
.steps .num {
display: inline-block;
background: #58a6ff;
color: #000;
width: 20px; height: 20px;
border-radius: 50%;
text-align: center;
font-size: 0.75rem;
font-weight: 700;
line-height: 20px;
margin-right: 0.5rem;
}
.steps .dim { color: #8b949e; }
.req {
text-align: left;
margin-top: 1.5rem;
font-size: 0.8rem;
color: #8b949e;
}
.req b { color: #d29922; }
</style>
</head>
<body>
<div class="card">
<div class="logo"><img src="assets/logo-200x200.png" alt="Bookworm" style="width:80px;height:80px;border-radius:18%;"></div>
<h1>Bookworm <span>Portable</span></h1>
<p class="subtitle">AI 编程助手 — 一键安装,即刻使用</p>
<div class="badges">
<span class="badge"><b>92</b> Skills</span>
<span class="badge"><b>18</b> Agents</span>
<span class="badge"><b>34</b> Hooks</span>
<span class="badge"><b>AES-256</b> 加密</span>
</div>
<a class="btn" href="https://code.letcareme.com/bookworm/bookworm-boot/raw/branch/main/Bookworm-Setup.bat" download="Bookworm-Setup.bat">
&#11015; 下载安装程序
</a>
<p class="size">Bookworm-Setup.bat (4 KB) — 双击即可安装</p>
<div class="steps">
<span class="num">1</span> 下载上方 .bat 文件<br>
<span class="num">2</span> 双击运行 <span class="dim">(如提示安全警告,选 "仍要运行")</span><br>
<span class="num">3</span> 输入管理员提供的密码<br>
<span class="num">&#10003;</span> 完成!桌面出现 Bookworm 图标
</div>
<div class="req">
<b>&#9888; 前置要求:</b><br>
&#8226; Node.js (<a href="https://nodejs.org" style="color:#58a6ff">下载</a>) + Git (<a href="https://git-scm.com" style="color:#58a6ff">下载</a>)<br>
&#8226; 代理/VPN 软件 (国内必须,用于首次连接验证)
</div>
</div>
</body>
</html>

View File

@ -1,177 +0,0 @@
<#
.SYNOPSIS
Bookworm Portable - 凭证加密工具
.DESCRIPTION
API Key MCP 凭证加密为 secrets.enc,
存放于 Gitea/USB , 安装时解密为进程级环境变量.
.USAGE
# 交互式创建加密凭证文件
.\encrypt-secrets.ps1
# 从现有 .env 文件加密
.\encrypt-secrets.ps1 -FromFile "C:\path\to\.env"
# 解密验证
.\encrypt-secrets.ps1 -Decrypt
#>
param(
[string]$FromFile,
[switch]$Decrypt
)
$ScriptDir = if ($MyInvocation.MyCommand.Path) {
Split-Path -Parent $MyInvocation.MyCommand.Path
} else { $PWD.Path }
$SecretsEnc = Join-Path $ScriptDir "secrets.enc"
$TempFile = Join-Path $env:TEMP "bw-secrets-$([guid]::NewGuid().ToString('N').Substring(0,8)).tmp"
# 检查 openssl
$opensslCmd = Get-Command openssl -ErrorAction SilentlyContinue
if (-not $opensslCmd) {
# 搜索常见 Git 安装路径
$searchPaths = @(
"C:\Program Files\Git\usr\bin\openssl.exe",
"D:\Git\usr\bin\openssl.exe",
"D:\Git\mingw64\bin\openssl.exe",
"C:\Program Files\Git\mingw64\bin\openssl.exe",
"C:\Program Files (x86)\Git\usr\bin\openssl.exe"
)
$found = $searchPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($found) {
$opensslCmd = $found
Write-Host "[INFO] 使用 Git 内置 openssl: $found" -ForegroundColor Gray
}
else {
Write-Host "[ERROR] openssl 未找到。请确认 Git for Windows 已安装。" -ForegroundColor Red
exit 1
}
} else {
$opensslCmd = $opensslCmd.Source
}
# ─── 解密模式 ─────────────────────────────────────────
if ($Decrypt) {
if (-not (Test-Path $SecretsEnc)) {
Write-Host "[ERROR] secrets.enc 不存在" -ForegroundColor Red
exit 1
}
$password = Read-Host "输入主密码" -AsSecureString
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
$plainPwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
Write-Host "`n=== 解密内容 ===" -ForegroundColor Cyan
# 通过 stdin 传入密码,避免进程列表泄露
$plainPwd | & $opensslCmd enc -aes-256-cbc -d -pbkdf2 -iter 600000 -in $SecretsEnc -pass stdin
Write-Host "`n=== 结束 ===" -ForegroundColor Cyan
$plainPwd = $null
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
exit 0
}
# ─── 加密模式 ─────────────────────────────────────────
Write-Host ""
Write-Host " Bookworm Portable - 凭证加密工具" -ForegroundColor Cyan
Write-Host " =================================" -ForegroundColor Cyan
Write-Host ""
try {
if ($FromFile -and (Test-Path $FromFile)) {
Write-Host "从文件加载: $FromFile" -ForegroundColor Gray
$invalidLines = Get-Content $FromFile | Where-Object { $_.Trim() -and $_ -notmatch '^[A-Z][A-Z0-9_]+=.+$' }
if ($invalidLines) {
Write-Host "[WARN] 以下行格式不正确 (应为 KEY=value):" -ForegroundColor Yellow
$invalidLines | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
}
Copy-Item $FromFile $TempFile
}
else {
Write-Host "请输入凭证 (key=value 格式, 每行一个, 空行结束):" -ForegroundColor White
Write-Host "常见 Key:" -ForegroundColor Gray
Write-Host " ANTHROPIC_API_KEY=sk-ant-..." -ForegroundColor DarkGray
Write-Host " ANTHROPIC_BASE_URL=https://your-relay.com/v1" -ForegroundColor DarkGray
Write-Host " GITHUB_PERSONAL_ACCESS_TOKEN=ghp_..." -ForegroundColor DarkGray
Write-Host " SLACK_BOT_TOKEN=xoxb-..." -ForegroundColor DarkGray
Write-Host " ATLASSIAN_API_TOKEN=..." -ForegroundColor DarkGray
Write-Host " BROWSERBASE_API_KEY=..." -ForegroundColor DarkGray
Write-Host " BROWSERBASE_PROJECT_ID=..." -ForegroundColor DarkGray
Write-Host " FIRECRAWL_API_KEY=..." -ForegroundColor DarkGray
Write-Host ""
$lines = @()
while ($true) {
$line = Read-Host ">"
if ([string]::IsNullOrWhiteSpace($line)) { break }
if ($line -match '^[A-Z][A-Z0-9_]+=.+$') {
$lines += $line
$key = $line.Substring(0, $line.IndexOf('='))
Write-Host " [+] $key" -ForegroundColor Green
}
else {
Write-Host " [!] 格式不正确,应为 KEY=value" -ForegroundColor Yellow
}
}
if ($lines.Count -eq 0) {
Write-Host "[!] 未输入任何凭证,退出" -ForegroundColor Yellow
exit 0
}
$lines -join "`n" | Set-Content $TempFile -NoNewline
}
# 设置密码并加密
Write-Host ""
$password1 = Read-Host "设置主密码 (用于解密, 至少 12 位)" -AsSecureString
$password2 = Read-Host "确认主密码" -AsSecureString
$bstr1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password1)
$bstr2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password2)
$pwd1 = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr1)
$pwd2 = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr2)
if ($pwd1 -ne $pwd2) {
Write-Host "[ERROR] 密码不匹配" -ForegroundColor Red
exit 1
}
if ($pwd1.Length -lt 12) {
Write-Host "[ERROR] 密码至少 12 位 (推荐 16+ 位混合字符)" -ForegroundColor Red
exit 1
}
# AES-256-CBC 加密, PBKDF2 600000 迭代 (OWASP 2023), 通过 stdin 传入密码
$pwd1 | & $opensslCmd enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in $TempFile -out $SecretsEnc -pass stdin
# 安全删除临时文件
if (Test-Path $TempFile) {
$bytes = [System.IO.File]::ReadAllBytes($TempFile)
[Array]::Clear($bytes, 0, $bytes.Length)
[System.IO.File]::WriteAllBytes($TempFile, $bytes)
Remove-Item $TempFile -Force
}
# 清除内存
$pwd1 = $null; $pwd2 = $null
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2)
$size = (Get-Item $SecretsEnc).Length
Write-Host ""
Write-Host " [OK] secrets.enc 已生成 ($size bytes)" -ForegroundColor Green
Write-Host " 加密: AES-256-CBC + PBKDF2 (600000 迭代)" -ForegroundColor Gray
Write-Host " 路径: $SecretsEnc" -ForegroundColor Gray
Write-Host ""
Write-Host " 重要提醒:" -ForegroundColor Yellow
Write-Host " - 主密码无法找回,请牢记" -ForegroundColor Yellow
Write-Host " - 推送到 Gitea bookworm-boot 仓库即可" -ForegroundColor Yellow
Write-Host " - 验证: .\encrypt-secrets.ps1 -Decrypt" -ForegroundColor Yellow
} finally {
# 确保任何退出路径都清理临时文件
if (Test-Path $TempFile -ErrorAction SilentlyContinue) {
Remove-Item $TempFile -Force -ErrorAction SilentlyContinue
}
}

View File

@ -1,788 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bookworm Portable for Mac - 保姆式安装手册</title>
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32.png">
<style>
:root {
--bg: #0d1117;
--card: #161b22;
--border: #30363d;
--text: #e6edf3;
--text-dim: #8b949e;
--accent: #58a6ff;
--green: #3fb950;
--yellow: #d29922;
--red: #f85149;
--purple: #bc8cff;
--cyan: #39d2c0;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, 'SF Pro Display', 'PingFang SC', 'Helvetica Neue', sans-serif;
line-height: 1.7;
padding: 0;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.header {
background: linear-gradient(135deg, #1a1f35 0%, #0d1117 100%);
border-bottom: 1px solid var(--border);
padding: 3rem 2rem;
text-align: center;
}
.header pre {
color: var(--cyan);
font-size: 0.65rem;
line-height: 1.2;
margin-bottom: 1rem;
display: inline-block;
text-align: left;
}
.header h1 { font-size: 2rem; font-weight: 700; margin-bottom: 0.5rem; }
.header h1 span { color: var(--accent); }
.header .platform-badge {
display: inline-flex; align-items: center; gap: 0.4rem;
background: rgba(88,166,255,0.15); border: 1px solid rgba(88,166,255,0.3);
border-radius: 20px; padding: 0.3rem 0.9rem; font-size: 0.9rem;
color: var(--accent); margin-bottom: 0.8rem;
}
.header p { color: var(--text-dim); font-size: 1.05rem; }
.badge-row { display: flex; gap: 0.8rem; justify-content: center; margin-top: 1rem; flex-wrap: wrap; }
.badge {
display: inline-flex; align-items: center; gap: 0.4rem;
background: var(--card); border: 1px solid var(--border);
border-radius: 20px; padding: 0.3rem 0.9rem; font-size: 0.85rem; color: var(--text-dim);
}
.badge strong { color: var(--text); }
.platform-switch {
display: flex; gap: 0.6rem; justify-content: center; margin-top: 1.2rem;
}
.platform-switch a {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1.2rem; border-radius: 8px; font-size: 0.9rem;
font-weight: 600; text-decoration: none; transition: all 0.2s;
}
.platform-switch .active {
background: var(--accent); color: #000;
}
.platform-switch .inactive {
background: var(--card); color: var(--text-dim);
border: 1px solid var(--border);
}
.platform-switch .inactive:hover {
border-color: var(--accent); color: var(--text); text-decoration: none;
}
.container { max-width: 900px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
.section { margin-bottom: 2.5rem; }
.section h2 {
font-size: 1.4rem; margin-bottom: 1rem; padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 0.6rem;
}
.section h2 .num {
background: var(--accent); color: #000; width: 28px; height: 28px;
border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
font-size: 0.85rem; font-weight: 700; flex-shrink: 0;
}
.code-block {
background: #0d1117; border: 1px solid var(--border); border-radius: 8px;
padding: 1rem 1.2rem; margin: 0.8rem 0; overflow-x: auto; position: relative;
cursor: pointer; transition: border-color 0.2s;
}
.code-block:hover { border-color: var(--accent); }
.code-block::after {
content: "点击复制"; position: absolute; top: 0.4rem; right: 0.6rem;
font-size: 0.7rem; color: var(--text-dim); background: var(--card);
padding: 0.15rem 0.5rem; border-radius: 4px; border: 1px solid var(--border);
opacity: 0; transition: opacity 0.2s;
}
.code-block:hover::after { opacity: 1; }
.code-block.copied::after { content: "已复制!"; color: var(--green); opacity: 1; }
.code-block code {
font-family: 'SF Mono', 'Menlo', 'Monaco', 'Cascadia Code', monospace;
font-size: 0.9rem; line-height: 1.6; color: var(--text);
white-space: pre-wrap; word-break: break-all;
}
.code-block .label {
position: absolute; top: 0.4rem; left: 0.6rem;
font-size: 0.65rem; color: var(--text-dim); background: var(--card);
padding: 0.1rem 0.5rem; border-radius: 4px; border: 1px solid var(--border);
text-transform: uppercase; letter-spacing: 0.5px;
}
.code-block.has-label { padding-top: 2rem; }
.cmd { color: var(--green); }
.flag { color: var(--yellow); }
.url { color: var(--accent); }
.comment { color: var(--text-dim); }
.card {
background: var(--card); border: 1px solid var(--border);
border-radius: 10px; padding: 1.2rem 1.5rem; margin: 0.8rem 0;
}
.card h3 { font-size: 1rem; margin-bottom: 0.5rem; color: var(--accent); }
.card p { color: var(--text-dim); font-size: 0.95rem; }
.step { display: flex; gap: 1rem; margin: 1.2rem 0; align-items: flex-start; }
.step-icon {
width: 40px; height: 40px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 1.2rem; font-weight: 700; flex-shrink: 0; margin-top: 0.1rem;
}
.step-icon.green { background: rgba(63,185,80,0.15); border: 1px solid rgba(63,185,80,0.3); color: var(--green); }
.step-icon.blue { background: rgba(88,166,255,0.15); border: 1px solid rgba(88,166,255,0.3); color: var(--accent); }
.step-icon.yellow { background: rgba(210,153,34,0.15); border: 1px solid rgba(210,153,34,0.3); color: var(--yellow); }
.step-icon.red { background: rgba(248,81,73,0.15); border: 1px solid rgba(248,81,73,0.3); color: var(--red); }
.step-icon.purple { background: rgba(188,140,255,0.15); border: 1px solid rgba(188,140,255,0.3); color: var(--purple); }
.step-content { flex: 1; }
.step-content h4 { font-size: 1.05rem; margin-bottom: 0.3rem; }
.step-content p { color: var(--text-dim); font-size: 0.9rem; }
table { width: 100%; border-collapse: collapse; margin: 0.8rem 0; font-size: 0.9rem; }
th, td { padding: 0.6rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
th { color: var(--text-dim); font-weight: 600; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.5px; }
td code { background: var(--bg); padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.85rem; border: 1px solid var(--border); }
.alert {
border-radius: 8px; padding: 1rem 1.2rem; margin: 1rem 0;
font-size: 0.9rem; display: flex; gap: 0.8rem; align-items: flex-start;
}
.alert-icon { font-size: 1.2rem; flex-shrink: 0; line-height: 1.5; }
.alert.warning { background: rgba(210,153,34,0.08); border: 1px solid rgba(210,153,34,0.3); }
.alert.danger { background: rgba(248,81,73,0.08); border: 1px solid rgba(248,81,73,0.3); }
.alert.info { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.3); }
.alert.success { background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.3); }
.checklist { list-style: none; padding: 0; }
.checklist li {
padding: 0.5rem 0; padding-left: 2rem; position: relative;
border-bottom: 1px solid rgba(48,54,61,0.5); color: var(--text-dim);
}
.checklist li::before {
content: ""; position: absolute; left: 0; top: 0.65rem;
width: 18px; height: 18px; border: 2px solid var(--border); border-radius: 4px;
}
.checklist li.done::before {
background: var(--green); border-color: var(--green);
content: ""; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
background-size: 14px; background-position: center; background-repeat: no-repeat;
}
.checklist li.done { color: var(--text); }
.checklist li strong { color: var(--text); }
.flow {
display: flex; align-items: center; justify-content: center;
gap: 0; margin: 1.5rem 0; flex-wrap: wrap;
}
.flow-node {
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
padding: 0.6rem 1rem; font-size: 0.85rem; text-align: center; min-width: 100px;
}
.flow-node.active { border-color: var(--green); box-shadow: 0 0 8px rgba(63,185,80,0.2); }
.flow-arrow { color: var(--text-dim); font-size: 1.2rem; padding: 0 0.3rem; }
.screenshot-note {
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
padding: 0.8rem 1rem; margin: 0.5rem 0; font-size: 0.85rem; color: var(--text-dim);
border-left: 3px solid var(--yellow);
}
.screenshot-note strong { color: var(--yellow); }
.footer {
text-align: center; padding: 2rem; color: var(--text-dim);
font-size: 0.8rem; border-top: 1px solid var(--border);
}
@media (max-width: 600px) {
.header { padding: 2rem 1rem; }
.header pre { font-size: 0.5rem; }
.header h1 { font-size: 1.5rem; }
.container { padding: 1.5rem 1rem; }
.flow { flex-direction: column; }
.flow-arrow { transform: rotate(90deg); }
.step { flex-direction: column; gap: 0.5rem; }
}
</style>
</head>
<body>
<div class="header">
<pre> ____ _
| __ ) ___ ___ | | ____ _____ _ __ _ __ ___
| _ \ / _ \ / _ \| |/ /\ \ /\ / / _ \| '__| '_ ` _ \
| |_) | (_) | (_) | < \ V V / (_) | | | | | | | |
|____/ \___/ \___/|_|\_\ \_/\_/ \___/|_| |_| |_| |_|
</pre>
<div class="platform-badge"> macOS Edition</div>
<h1>Bookworm <span>Portable</span> 保姆式安装手册</h1>
<p>从零开始,一步步教你在任意 Mac 电脑上激活 Bookworm</p>
<div class="badge-row">
<span class="badge"><strong>92</strong> Skills</span>
<span class="badge"><strong>18</strong> Agents</span>
<span class="badge"><strong>34</strong> Hooks</span>
<span class="badge"><strong>AES-256</strong> 加密</span>
<span class="badge"><strong>HTTPS</strong> 传输</span>
</div>
<div class="platform-switch">
<a href="/mac" class="active"> macOS</a>
<a href="/" class="inactive"> Windows</a>
</div>
<a href="/Bookworm-Setup.sh" download style="display:inline-block;margin-top:1.2rem;padding:0.7rem 2rem;background:var(--accent);color:#000;font-weight:700;border-radius:8px;font-size:1rem;text-decoration:none;transition:opacity 0.2s" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">&#11015; 下载一键安装脚本</a>
</div>
<div class="container">
<!-- 整体流程 -->
<div class="section">
<h2>整体流程概览</h2>
<div class="card" style="text-align:center;border-color:var(--green);margin-bottom:1.5rem">
<h3 style="color:var(--green);margin-bottom:0.5rem">最快方式:一键安装脚本</h3>
<p style="color:var(--text-dim);font-size:0.9rem;margin-bottom:0.8rem">下载 <strong>Bookworm-Setup.sh</strong> → 在终端运行 → 输入密码 → 完成</p>
<p style="font-size:0.8rem;color:var(--text-dim)">脚本自动检测依赖、安装 Homebrew/Node.js/Git、下载配置、启动 Claude Code</p>
</div>
<p style="text-align:center;color:var(--text-dim);font-size:0.85rem;margin-bottom:0.5rem">手动安装流程:</p>
<div class="flow">
<div class="flow-node">安装依赖<br><small style="color:var(--text-dim)">Homebrew + Node.js</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node">安装 Claude Code<br><small style="color:var(--text-dim)">npm 全局安装</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node">运行安装脚本<br><small style="color:var(--text-dim)">或 git clone</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node">输入密码<br><small style="color:var(--text-dim)">主密码</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node active">开始使用<br><small style="color:var(--green)">Bookworm 激活</small></div>
</div>
<p style="text-align:center;color:var(--text-dim);font-size:0.9rem">首次安装约 10 分钟(含依赖下载),之后每次启动约 5-15 秒</p>
</div>
<!-- ============================================================ -->
<!-- 第一步:安装依赖 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">1</span>安装依赖软件</h2>
<div class="alert danger">
<span class="alert-icon">&#9888;</span>
<div>
<strong>国内必须:代理/VPN 软件</strong><br>
Claude Code 启动时会检查 <code>api.anthropic.com</code>,国内无法直连。<br>
请先安装并启动代理软件ClashX / Surge / V2Ray / 任意 VPN安装脚本会<strong>自动检测</strong>系统代理。<br>
无代理 = Claude Code 无法启动。
</div>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>中转站不走代理</strong><br>
API 中转站 <code>bww.letcareme.com</code> 部署在国内阿里云,<strong>不需要通过代理访问</strong><br>
安装脚本已自动设置 <code>NO_PROXY=bww.letcareme.com,code.letcareme.com</code>,无需手动配置。<br>
如果代理软件有"绕过规则"设置,建议把 <code>*.letcareme.com</code> 加入直连列表。
</div>
</div>
<p>需要安装以下软件。如果已装过可跳到下一步。</p>
<!-- Homebrew -->
<div class="step">
<div class="step-icon blue">A</div>
<div class="step-content">
<h4>安装 HomebrewmacOS 包管理器)</h4>
<p>Homebrew 是 macOS 上最常用的包管理器,后续用它安装 Node.js 和 Git。</p>
</div>
</div>
<p>打开 <strong>终端</strong>(按 <code>&#8984; + 空格</code> 搜索 "终端" 或 "Terminal"),执行:</p>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">/bin/bash</span> -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</code>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>国内加速</strong><br>
如果下载太慢,可以使用清华镜像:<br>
<code>export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"</code><br>
<code>export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"</code><br>
设置后重新执行上面的安装命令。
</div>
</div>
<div class="alert warning">
<span class="alert-icon">&#9888;</span>
<div>
<strong>Apple Silicon (M1/M2/M3/M4) 用户注意!</strong><br>
安装完成后,需要将 Homebrew 添加到 PATH。终端会提示你执行以下命令
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># Apple Silicon Mac 需要执行Intel Mac 不需要)</span>
<span class="cmd">echo</span> 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
<span class="cmd">eval</span> "$(/opt/homebrew/bin/brew shellenv)"</code>
</div>
<p style="margin-top:0.8rem">验证安装成功:</p>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">brew</span> --version <span class="comment"># 应显示 Homebrew 4.x.x</span></code>
</div>
<!-- Node.js -->
<div class="step" style="margin-top:1.5rem">
<div class="step-icon blue">B</div>
<div class="step-content">
<h4>安装 Node.js必须</h4>
<p>用 Homebrew 一行命令安装。</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">brew</span> install node</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">也可以从 <a href="https://nodejs.org/zh-cn" target="_blank">nodejs.org</a> 下载 .pkg 安装包。</p>
<p style="margin-top:0.5rem">验证安装成功:</p>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">node</span> -v <span class="comment"># 应显示 v22.x.x</span>
<span class="cmd">npm</span> -v <span class="comment"># 应显示 10.x.x</span></code>
</div>
<!-- Git -->
<div class="step" style="margin-top:1.5rem">
<div class="step-icon blue">C</div>
<div class="step-content">
<h4>安装 Git必须</h4>
<p>macOS 通常自带 Git如没有则用 Homebrew 安装。</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># 检查是否已安装</span>
<span class="cmd">git</span> --version
<span class="comment"># 如果提示安装 Xcode Command Line Tools点击"安装"即可</span>
<span class="comment"># 或者用 Homebrew 安装:</span>
<span class="cmd">brew</span> install git</code>
</div>
</div>
<!-- ============================================================ -->
<!-- 第二步:安装 Claude Code -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">2</span>安装 Claude Code</h2>
<div class="step">
<div class="step-icon green">1</div>
<div class="step-content">
<h4>全局安装</h4>
<p>在终端中执行:</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">npm</span> i -g @anthropic-ai/claude-code</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">安装过程需要几分钟,等待完成即可。如果报权限错误,在前面加 <code>sudo</code></p>
<div class="step">
<div class="step-icon green">2</div>
<div class="step-content">
<h4>验证安装</h4>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">claude</span> --version <span class="comment"># 应显示版本号</span></code>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>不需要登录 Claude 账号!</strong><br>
Bookworm 使用中转站 API安装 Claude Code 后直接进入下一步,不用执行 <code>claude login</code>
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 第三步:安装 Bookworm -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">3</span>安装 Bookworm核心步骤</h2>
<div class="step">
<div class="step-icon purple">1</div>
<div class="step-content">
<h4>克隆引导仓库</h4>
<p>在终端中执行以下命令。系统会提示输入用户名和密码。</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">git clone</span> <span class="url">https://code.letcareme.com/bookworm/bookworm-boot.git</span>
<span class="cmd">cd</span> bookworm-boot</code>
</div>
<div class="screenshot-note">
<strong>弹出用户名密码?</strong> 输入管理员提供给你的 Gitea 账号密码。这是 Gitea 的密码,不是主密码。
</div>
<div class="step">
<div class="step-icon purple">2</div>
<div class="step-content">
<h4>运行安装脚本</h4>
<p>在终端中执行安装脚本:</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">bash</span> Bookworm-Setup.sh</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">如果提示权限不足:<code>chmod +x Bookworm-Setup.sh && ./Bookworm-Setup.sh</code></p>
<div class="step">
<div class="step-icon purple">3</div>
<div class="step-content">
<h4>输入主密码</h4>
<p>脚本会提示 <strong>"输入主密码解密凭证"</strong>,输入管理员提供的<strong>主密码</strong>(不是 Gitea 密码),按回车。</p>
<p>密码输入时不显示字符,这是正常的。<strong>输错了可以重试,最多 3 次。</strong></p>
</div>
</div>
<div class="screenshot-note">
<strong>两个密码不要搞混:</strong><br>
<strong>Gitea 密码</strong> = 克隆仓库时输入的,用于下载文件<br>
<strong>主密码</strong> = 解密 API 凭证时输入的,用于启动 Claude Code
</div>
<div class="step">
<div class="step-icon green">4</div>
<div class="step-content">
<h4>等待完成</h4>
<p>脚本会显示步骤进度 [1/8] 到 [8/8],自动完成:</p>
<ul style="color:var(--text-dim);font-size:0.9rem;padding-left:1.2rem;margin-top:0.3rem">
<li>[1/6] 检查依赖 (Homebrew / Node.js / Git / OpenSSL / Claude Code)</li>
<li>[2/6] 自动检测代理 + 设置 NO_PROXY</li>
<li>[3/6] 同步配置 (克隆/更新 92 个 Skills)</li>
<li>[4/6] 解密凭证 (输入主密码) + 渲染配置模板</li>
<li>[5/6] 配置终端别名 (bw / bw-update)</li>
<li>[6/6] 完成 — 可选立即启动</li>
</ul>
</div>
</div>
<div class="alert success">
<span class="alert-icon">&#10003;</span>
<div>
<strong>看到 "Bookworm 就绪" 绿色横幅就说明成功了!</strong><br>
Claude Code 启动后,脚本会验证 Skills/Hooks/配置 完整性,全部 [OK] 后进入 Bookworm 模式。<br>
所有 API 请求通过中转站转发,不需要自己的 Claude 账号。
</div>
</div>
<div class="alert warning">
<span class="alert-icon">&#9888;</span>
<div>
<strong>看到 "原生模式启动" 黄色横幅?</strong><br>
说明 Bookworm 配置不完整。请重新运行安装脚本,或联系管理员。
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 日常使用 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">4</span>日常使用</h2>
<div class="card">
<h3>方法一:终端别名(推荐,最简单)</h3>
<p>安装脚本已自动添加别名到 <code>~/.zshrc</code>,直接在终端输入:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">bw</span> <span class="comment"># 快速启动</span>
<span class="cmd">bw-update</span> <span class="comment"># 同步更新</span></code>
</div>
<div class="card" style="margin-top:1rem">
<h3>方法二:脚本命令</h3>
<p>如果别名不可用,在终端中手动执行:</p>
</div>
<table>
<tr><th>操作</th><th>命令</th><th>说明</th></tr>
<tr>
<td><strong>快速启动</strong></td>
<td><code>bw</code></td>
<td>直接启动 Claude Code + Bookworm</td>
</tr>
<tr>
<td><strong>同步更新</strong></td>
<td><code>bw-update</code></td>
<td>更新 boot + 配置仓库</td>
</tr>
</table>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>启动时显示 "有 N 个新更新可用"</strong><br>
说明管理员更新了 Skills 或 Hooks。执行 <code>bw-update</code> 即可同步。
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 密码说明 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">5</span>密码说明</h2>
<div class="card">
<h3>本系统有两个密码,不要搞混</h3>
<table>
<tr><th>名称</th><th>用途</th><th>何时输入</th></tr>
<tr><td><strong>Gitea 密码</strong></td><td>下载文件(克隆仓库)</td><td>首次安装时 git 弹出要求</td></tr>
<tr><td><strong>主密码</strong></td><td>解密 API 凭证</td><td>每次启动脚本提示输入</td></tr>
</table>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>密码输错了?</strong> 最多可以重试 3 次不用紧张。3 次都错才会退出。
</div>
</div>
<div class="card" style="margin-top:0.8rem">
<h3>本日免密功能</h3>
<p>首次解密成功后,脚本会询问 <strong>"今日内免密启动? (y/n)"</strong></p>
<p><strong>y</strong> 后,当天再次启动无需输入主密码,次日自动过期。</p>
<p style="color:var(--text-dim);font-size:0.85rem;margin-top:0.3rem">凭证缓存在 macOS 钥匙串 (Keychain) 中,仅当前用户可读。</p>
</div>
<div class="alert warning" style="margin-top:0.8rem">
<span class="alert-icon">&#128274;</span>
<div><strong>主密码无法找回</strong> — 忘记后联系管理员重新生成 secrets.enc。</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 使用完毕 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">6</span>使用完毕 — 清理 / 卸载</h2>
<p>在终端中执行清理命令:</p>
<table>
<tr><th>场景</th><th>命令</th><th>说明</th></tr>
<tr>
<td><strong>基础清理</strong></td>
<td><code>rm -rf ~/.claude</code></td>
<td>删除 Bookworm 配置,保留引导仓库供重新安装</td>
</tr>
<tr>
<td><strong>完整恢复</strong></td>
<td><code>rm -rf ~/.claude ~/bookworm-boot</code></td>
<td>删除所有 Bookworm 文件</td>
</tr>
<tr>
<td><strong>深度清理</strong></td>
<td><code>rm -rf ~/.claude ~/bookworm-boot && sed -i '' '/Bookworm Portable/,+2d' ~/.zshrc && git credential-osxkeychain erase &lt;&lt;&lt; "host=code.letcareme.com"</code></td>
<td>完整恢复 + 清除别名 + 清除 Git 凭证</td>
</tr>
</table>
<div class="alert danger">
<span class="alert-icon">&#9888;</span>
<div>
<strong>在他人电脑/公用电脑上务必清理:</strong><br>
执行深度清理命令,确保不留下任何凭证或配置
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 故障排查 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">!</span>常见问题排查</h2>
<div class="card">
<h3>&#10060; brew 命令找不到</h3>
<p><strong>原因:</strong>Homebrew 未添加到 PATHApple Silicon Mac 常见)。</p>
<p><strong>解决:</strong></p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># Apple Silicon (M1/M2/M3/M4)</span>
<span class="cmd">eval</span> "$(/opt/homebrew/bin/brew shellenv)"
<span class="comment"># Intel Mac</span>
<span class="cmd">eval</span> "$(/usr/local/bin/brew shellenv)"</code>
</div>
<div class="card">
<h3>&#10060; npm 全局安装报 Permission denied</h3>
<p><strong>原因:</strong>macOS 默认目录权限限制。</p>
<p><strong>解决方式一(推荐):</strong>修改 npm 全局目录:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">mkdir</span> -p ~/.npm-global
<span class="cmd">npm</span> config set prefix '~/.npm-global'
<span class="cmd">echo</span> 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc
<span class="cmd">source</span> ~/.zshrc
<span class="comment"># 然后重新安装</span>
<span class="cmd">npm</span> i -g @anthropic-ai/claude-code</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">解决方式二:在命令前加 <code>sudo</code>(简单但不推荐长期使用)。</p>
<div class="card">
<h3>&#10060; git clone 失败 / 认证失败</h3>
<p><strong>解决步骤:</strong></p>
<ol style="color:var(--text-dim);padding-left:1.5rem;margin-top:0.3rem">
<li>浏览器打开 <code>https://code.letcareme.com</code> 确认网站可访问</li>
<li>确认用户名密码正确(区分大小写)</li>
<li>如果 macOS 弹出钥匙串对话框,点"始终允许"</li>
<li>检查网络是否需要代理</li>
</ol>
</div>
<div class="card">
<h3>&#10060; 解密凭证失败 / 主密码错误</h3>
<p><strong>原因:</strong>主密码区分大小写,且无法找回。</p>
<p><strong>解决:</strong>仔细检查密码是否正确。如确认忘记,联系管理员重新生成 <code>secrets.enc</code></p>
</div>
<div class="card">
<h3>&#10060; openssl 版本不兼容</h3>
<p><strong>原因:</strong>macOS 自带 LibreSSL部分加密参数可能不同。</p>
<p><strong>解决:</strong>安装 OpenSSL</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">brew</span> install openssl
<span class="comment"># 脚本会自动检测 Homebrew 安装的 openssl 路径</span></code>
</div>
<div class="card">
<h3>&#10060; ECONNRESET / "Unable to connect to API"</h3>
<p><strong>原因:</strong>代理软件把国内中转站 <code>bww.letcareme.com</code> 的流量也走了国际线路。</p>
<p><strong>解决:</strong>手动设置 NO_PROXY 后重试:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># 设置中转站直连(不走代理)</span>
<span class="cmd">export</span> NO_PROXY="bww.letcareme.com,code.letcareme.com"
<span class="comment"># 重新启动</span>
<span class="cmd">bw</span></code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">或在代理软件中将 <code>*.letcareme.com</code> 加入直连规则。</p>
<div class="card">
<h3>&#10060; "Not logged in" / 直接运行 claude 报错</h3>
<p><strong>原因:</strong>API 凭证是进程级环境变量,只在安装脚本启动的进程中有效。</p>
<p><strong>解决:</strong><strong>不要直接运行 <code>claude</code></strong>,必须通过以下方式启动:</p>
<ul style="color:var(--text-dim);font-size:0.9rem;padding-left:1.2rem;margin-top:0.3rem">
<li>终端输入 <code>bw</code>(推荐)</li>
<li><code>cd ~/bookworm-boot && bash Bookworm-Setup.sh</code></li>
</ul>
</div>
<div class="card">
<h3>&#10060; 安装包下载太慢</h3>
<p><strong>解决:</strong>设置淘宝镜像:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># npm 淘宝镜像</span>
<span class="cmd">npm</span> config set registry https://registry.npmmirror.com
<span class="comment"># Homebrew 清华镜像</span>
<span class="cmd">export</span> HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
<span class="comment"># 然后重新安装</span>
<span class="cmd">npm</span> i -g @anthropic-ai/claude-code</code>
</div>
<div class="card">
<h3>&#10060; 需要自己的 Claude 账号吗?</h3>
<p><strong>不需要。</strong>所有 API 请求通过中转站转发,消耗中转站额度。目标机不需要任何 Anthropic 账号或订阅。</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 安装检查清单 -->
<!-- ============================================================ -->
<div class="section">
<h2>安装检查清单</h2>
<p style="color:var(--text-dim);margin-bottom:0.5rem">逐项确认,全部打勾即可开始使用:</p>
<ul class="checklist">
<li><strong>Homebrew 已安装</strong><code>brew --version</code> 显示版本号</li>
<li><strong>Node.js 已安装</strong><code>node -v</code> 显示版本号</li>
<li><strong>Git 已安装</strong><code>git --version</code> 显示版本号</li>
<li><strong>npm 可用</strong><code>npm -v</code> 显示版本号</li>
<li><strong>Claude Code 已安装</strong><code>claude --version</code> 显示版本号</li>
<li><strong>已获取 Gitea 账号密码</strong> — 管理员提供</li>
<li><strong>已获取主密码</strong> — 管理员提供(用于解密 API 凭证)</li>
<li><strong>能访问 code.letcareme.com</strong> — 浏览器打开确认</li>
<li><strong>代理/VPN 已启动</strong> — 国内必须,脚本自动检测 (ClashX/Surge/V2Ray 等)</li>
</ul>
</div>
<!-- ============================================================ -->
<!-- 快速参考卡片 -->
<!-- ============================================================ -->
<div class="section">
<h2>快速参考</h2>
<table>
<tr><th>操作</th><th>快捷方式</th><th>完整命令</th></tr>
<tr><td>首次安装</td><td>git clone + <code>bash Bookworm-Setup.sh</code></td><td><code>cd ~/bookworm-boot && bash Bookworm-Setup.sh</code></td></tr>
<tr><td>快速启动</td><td><code>bw</code></td><td><code>NO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude</code></td></tr>
<tr><td>同步更新</td><td><code>bw-update</code></td><td><code>cd ~/bookworm-boot && git pull && cd ~/.claude && git pull</code></td></tr>
<tr><td>基础清理</td><td colspan="2"><code>rm -rf ~/.claude</code></td></tr>
<tr><td>完整恢复</td><td colspan="2"><code>rm -rf ~/.claude ~/bookworm-boot</code></td></tr>
<tr><td>深度清理</td><td colspan="2"><code>rm -rf ~/.claude ~/bookworm-boot && sed -i '' '/Bookworm/,+2d' ~/.zshrc</code></td></tr>
</table>
</div>
<!-- ============================================================ -->
<!-- 安全须知 -->
<!-- ============================================================ -->
<div class="section">
<h2>安全须知</h2>
<table>
<tr><th>特性</th><th>规格</th></tr>
<tr><td>凭证加密</td><td>AES-256-CBC + PBKDF2 (600,000 迭代)</td></tr>
<tr><td>传输加密</td><td>HTTPS (TLS 1.2+, Let's Encrypt 证书)</td></tr>
<tr><td>凭证存储</td><td>进程级环境变量 + 可选本日缓存 (macOS Keychain, 当日 23:59 过期)</td></tr>
<tr><td>登录保护</td><td>fail2ban (5 次失败/小时 → 封禁 24 小时)</td></tr>
</table>
<div class="alert warning" style="margin-top:1rem">
<span class="alert-icon">&#128274;</span>
<div>
<strong>主密码无法找回</strong> — 请妥善保管。忘记后需管理员重新生成加密凭证。
</div>
</div>
</div>
</div>
<div class="footer">
Bookworm Portable v1.5 — macOS 保姆式安装手册<br>
&copy; 2026 Bookworm Smart Assistant
</div>
<script>
function copyCode(el) {
const code = el.querySelector('code').innerText;
navigator.clipboard.writeText(code).then(() => {
el.classList.add('copied');
setTimeout(() => el.classList.remove('copied'), 1500);
});
}
</script>
</body>
</html>

View File

@ -1,787 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bookworm Portable - 保姆式安装手册</title>
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32.png">
<style>
:root {
--bg: #0d1117;
--card: #161b22;
--border: #30363d;
--text: #e6edf3;
--text-dim: #8b949e;
--accent: #58a6ff;
--green: #3fb950;
--yellow: #d29922;
--red: #f85149;
--purple: #bc8cff;
--cyan: #39d2c0;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, 'Segoe UI', 'Microsoft YaHei', sans-serif;
line-height: 1.7;
padding: 0;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.header {
background: linear-gradient(135deg, #1a1f35 0%, #0d1117 100%);
border-bottom: 1px solid var(--border);
padding: 3rem 2rem;
text-align: center;
}
.header pre {
color: var(--cyan);
font-size: 0.65rem;
line-height: 1.2;
margin-bottom: 1rem;
display: inline-block;
text-align: left;
}
.header h1 { font-size: 2rem; font-weight: 700; margin-bottom: 0.5rem; }
.header h1 span { color: var(--accent); }
.header p { color: var(--text-dim); font-size: 1.05rem; }
.badge-row { display: flex; gap: 0.8rem; justify-content: center; margin-top: 1rem; flex-wrap: wrap; }
.badge {
display: inline-flex; align-items: center; gap: 0.4rem;
background: var(--card); border: 1px solid var(--border);
border-radius: 20px; padding: 0.3rem 0.9rem; font-size: 0.85rem; color: var(--text-dim);
}
.badge strong { color: var(--text); }
.container { max-width: 900px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
.section { margin-bottom: 2.5rem; }
.section h2 {
font-size: 1.4rem; margin-bottom: 1rem; padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 0.6rem;
}
.section h2 .num {
background: var(--accent); color: #000; width: 28px; height: 28px;
border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
font-size: 0.85rem; font-weight: 700; flex-shrink: 0;
}
.code-block {
background: #0d1117; border: 1px solid var(--border); border-radius: 8px;
padding: 1rem 1.2rem; margin: 0.8rem 0; overflow-x: auto; position: relative;
cursor: pointer; transition: border-color 0.2s;
}
.code-block:hover { border-color: var(--accent); }
.code-block::after {
content: "点击复制"; position: absolute; top: 0.4rem; right: 0.6rem;
font-size: 0.7rem; color: var(--text-dim); background: var(--card);
padding: 0.15rem 0.5rem; border-radius: 4px; border: 1px solid var(--border);
opacity: 0; transition: opacity 0.2s;
}
.code-block:hover::after { opacity: 1; }
.code-block.copied::after { content: "已复制!"; color: var(--green); opacity: 1; }
.code-block code {
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
font-size: 0.9rem; line-height: 1.6; color: var(--text);
white-space: pre-wrap; word-break: break-all;
}
.code-block .label {
position: absolute; top: 0.4rem; left: 0.6rem;
font-size: 0.65rem; color: var(--text-dim); background: var(--card);
padding: 0.1rem 0.5rem; border-radius: 4px; border: 1px solid var(--border);
text-transform: uppercase; letter-spacing: 0.5px;
}
.code-block.has-label { padding-top: 2rem; }
.cmd { color: var(--green); }
.flag { color: var(--yellow); }
.url { color: var(--accent); }
.comment { color: var(--text-dim); }
.card {
background: var(--card); border: 1px solid var(--border);
border-radius: 10px; padding: 1.2rem 1.5rem; margin: 0.8rem 0;
}
.card h3 { font-size: 1rem; margin-bottom: 0.5rem; color: var(--accent); }
.card p { color: var(--text-dim); font-size: 0.95rem; }
.step { display: flex; gap: 1rem; margin: 1.2rem 0; align-items: flex-start; }
.step-icon {
width: 40px; height: 40px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 1.2rem; font-weight: 700; flex-shrink: 0; margin-top: 0.1rem;
}
.step-icon.green { background: rgba(63,185,80,0.15); border: 1px solid rgba(63,185,80,0.3); color: var(--green); }
.step-icon.blue { background: rgba(88,166,255,0.15); border: 1px solid rgba(88,166,255,0.3); color: var(--accent); }
.step-icon.yellow { background: rgba(210,153,34,0.15); border: 1px solid rgba(210,153,34,0.3); color: var(--yellow); }
.step-icon.red { background: rgba(248,81,73,0.15); border: 1px solid rgba(248,81,73,0.3); color: var(--red); }
.step-icon.purple { background: rgba(188,140,255,0.15); border: 1px solid rgba(188,140,255,0.3); color: var(--purple); }
.step-content { flex: 1; }
.step-content h4 { font-size: 1.05rem; margin-bottom: 0.3rem; }
.step-content p { color: var(--text-dim); font-size: 0.9rem; }
table { width: 100%; border-collapse: collapse; margin: 0.8rem 0; font-size: 0.9rem; }
th, td { padding: 0.6rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
th { color: var(--text-dim); font-weight: 600; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.5px; }
td code { background: var(--bg); padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.85rem; border: 1px solid var(--border); }
.alert {
border-radius: 8px; padding: 1rem 1.2rem; margin: 1rem 0;
font-size: 0.9rem; display: flex; gap: 0.8rem; align-items: flex-start;
}
.alert-icon { font-size: 1.2rem; flex-shrink: 0; line-height: 1.5; }
.alert.warning { background: rgba(210,153,34,0.08); border: 1px solid rgba(210,153,34,0.3); }
.alert.danger { background: rgba(248,81,73,0.08); border: 1px solid rgba(248,81,73,0.3); }
.alert.info { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.3); }
.alert.success { background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.3); }
.checklist { list-style: none; padding: 0; }
.checklist li {
padding: 0.5rem 0; padding-left: 2rem; position: relative;
border-bottom: 1px solid rgba(48,54,61,0.5); color: var(--text-dim);
}
.checklist li::before {
content: ""; position: absolute; left: 0; top: 0.65rem;
width: 18px; height: 18px; border: 2px solid var(--border); border-radius: 4px;
}
.checklist li.done::before {
background: var(--green); border-color: var(--green);
content: ""; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
background-size: 14px; background-position: center; background-repeat: no-repeat;
}
.checklist li.done { color: var(--text); }
.checklist li strong { color: var(--text); }
.flow {
display: flex; align-items: center; justify-content: center;
gap: 0; margin: 1.5rem 0; flex-wrap: wrap;
}
.flow-node {
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
padding: 0.6rem 1rem; font-size: 0.85rem; text-align: center; min-width: 100px;
}
.flow-node.active { border-color: var(--green); box-shadow: 0 0 8px rgba(63,185,80,0.2); }
.flow-arrow { color: var(--text-dim); font-size: 1.2rem; padding: 0 0.3rem; }
.screenshot-note {
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
padding: 0.8rem 1rem; margin: 0.5rem 0; font-size: 0.85rem; color: var(--text-dim);
border-left: 3px solid var(--yellow);
}
.screenshot-note strong { color: var(--yellow); }
.footer {
text-align: center; padding: 2rem; color: var(--text-dim);
font-size: 0.8rem; border-top: 1px solid var(--border);
}
@media (max-width: 600px) {
.header { padding: 2rem 1rem; }
.header pre { font-size: 0.5rem; }
.header h1 { font-size: 1.5rem; }
.container { padding: 1.5rem 1rem; }
.flow { flex-direction: column; }
.flow-arrow { transform: rotate(90deg); }
.step { flex-direction: column; gap: 0.5rem; }
}
</style>
</head>
<body>
<div class="header">
<pre>
____ _
| __ ) ___ ___ | | ____ _____ _ __ _ __ ___
| _ \ / _ \ / _ \| |/ /\ \ /\ / / _ \| '__| '_ ` _ \
| |_) | (_) | (_) | < \ V V / (_) | | | | | | | |
|____/ \___/ \___/|_|\_\ \_/\_/ \___/|_| |_| |_| |_|
</pre>
<h1>Bookworm <span>Portable</span> 保姆式安装手册</h1>
<p>从零开始,一步步教你在任意 Windows 电脑上激活 Bookworm</p>
<div class="badge-row">
<span class="badge"><strong>92</strong> Skills</span>
<span class="badge"><strong>18</strong> Agents</span>
<span class="badge"><strong>34</strong> Hooks</span>
<span class="badge"><strong>AES-256</strong> 加密</span>
<span class="badge"><strong>NDA</strong> 技术保密</span>
</div>
<a href="/Bookworm-Setup.bat" download style="display:inline-block;margin-top:1.2rem;padding:0.7rem 2rem;background:var(--accent);color:#000;font-weight:700;border-radius:8px;font-size:1rem;text-decoration:none;transition:opacity 0.2s" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">&#11015; 下载一键安装器</a>
</div>
<div class="container">
<!-- 整体流程 -->
<div class="section">
<h2>整体流程概览</h2>
<div class="card" style="text-align:center;border-color:var(--green);margin-bottom:1.5rem">
<h3 style="color:var(--green);margin-bottom:0.5rem">最快方式:一键安装器</h3>
<p style="color:var(--text-dim);font-size:0.9rem;margin-bottom:0.8rem">获取 <strong>Bookworm-Setup.bat</strong> (4KB) &#8594; 双击运行 &#8594; 输入密码 &#8594; 完成</p>
<p style="font-size:0.8rem;color:var(--text-dim)">安装器自动检测依赖、下载配置、创建桌面快捷方式、启动 Claude Code</p>
</div>
<p style="text-align:center;color:var(--text-dim);font-size:0.85rem;margin-bottom:0.5rem">手动安装流程:</p>
<div class="flow">
<div class="flow-node">安装依赖<br><small style="color:var(--text-dim)">Node.js + Git</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node">安装 Claude Code<br><small style="color:var(--text-dim)">npm 全局安装</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node">双击安装器<br><small style="color:var(--text-dim)">或 git clone</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node">输入密码<br><small style="color:var(--text-dim)">主密码</small></div>
<span class="flow-arrow">&#10132;</span>
<div class="flow-node active">开始使用<br><small style="color:var(--green)">Bookworm 激活</small></div>
</div>
<p style="text-align:center;color:var(--text-dim);font-size:0.9rem">首次安装约 10 分钟(含依赖下载),之后每次双击启动约 10-30 秒</p>
</div>
<!-- ============================================================ -->
<!-- 第一步:安装依赖 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">1</span>安装依赖软件</h2>
<div class="alert danger">
<span class="alert-icon">&#9888;</span>
<div>
<strong>国内必须:代理/VPN 软件</strong><br>
Claude Code 启动时会检查 <code>api.anthropic.com</code>,国内无法直连。<br>
请先安装并启动代理软件Clash / V2Ray / 快柠檬 / 任意 VPN安装脚本会<strong>自动检测</strong>系统代理。<br>
无代理 = Claude Code 无法启动。
</div>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>中转站不走代理</strong><br>
API 中转站 <code>bww.letcareme.com</code> 部署在国内阿里云,<strong>不需要通过代理访问</strong><br>
安装脚本已自动设置 <code>NO_PROXY=bww.letcareme.com,code.letcareme.com</code>,无需手动配置。<br>
如果代理软件有"绕过规则"设置,建议把 <code>*.letcareme.com</code> 加入直连列表。
</div>
</div>
<p>需要安装以下软件。如果已装过可跳到下一步。</p>
<!-- Node.js -->
<div class="step">
<div class="step-icon blue">A</div>
<div class="step-content">
<h4>安装 Node.js必须</h4>
<p>去官网下载 LTS 版本安装包,双击安装,一路 Next 即可。</p>
</div>
</div>
<div class="card">
<h3>方式一:官网下载(推荐)</h3>
<p>打开浏览器访问 <a href="https://nodejs.org/zh-cn" target="_blank">https://nodejs.org</a>,点击绿色的 <strong>"LTS 推荐"</strong> 按钮下载,双击 .msi 文件安装,全部默认 Next。</p>
</div>
<div class="card">
<h3>方式二PowerShell 命令安装</h3>
<p>右键开始菜单 → 选择 <strong>"PowerShell (管理员)"</strong><strong>"终端 (管理员)"</strong>,然后执行:</p>
</div>
<div class="code-block has-label" onclick="copyCode(this)">
<span class="label">PowerShell (管理员)</span>
<code><span class="comment"># 下载 Node.js 安装包</span>
Invoke-WebRequest -Uri "https://nodejs.org/dist/v22.15.0/node-v22.15.0-x64.msi" -OutFile "$env:TEMP\node-install.msi"
<span class="comment"># 运行安装(会弹出安装向导,一路 Next</span>
Start-Process msiexec.exe -ArgumentList "/i $env:TEMP\node-install.msi" -Wait</code>
</div>
<div class="alert warning">
<span class="alert-icon">&#9888;</span>
<div>
<strong>安装完成后必须重开 PowerShell</strong><br>
关闭当前 PowerShell 窗口,重新打开一个新的,否则 <code>node</code><code>npm</code> 命令找不到。
</div>
</div>
<p style="margin-top:0.8rem">验证安装成功:</p>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">node</span> -v <span class="comment"># 应显示 v22.x.x</span>
<span class="cmd">npm</span> -v <span class="comment"># 应显示 10.x.x</span></code>
</div>
<div class="screenshot-note">
<strong>如果 npm 报 "执行策略" 错误?</strong> 执行以下命令后重试:<br>
<code>Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned</code><br>
提示确认时输入 <strong>Y</strong> 回车。
</div>
<!-- Git -->
<div class="step" style="margin-top:1.5rem">
<div class="step-icon blue">B</div>
<div class="step-content">
<h4>安装 Git必须</h4>
<p>去官网下载安装,全部默认设置即可。</p>
</div>
</div>
<div class="card">
<p>打开 <a href="https://git-scm.com/download/win" target="_blank">https://git-scm.com/download/win</a>,下载 <strong>"64-bit Git for Windows Setup"</strong>,双击安装,全部 Next。</p>
</div>
<p>验证:</p>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">git</span> --version <span class="comment"># 应显示 git version 2.x.x</span></code>
</div>
<!-- PowerShell 7 -->
<div class="step" style="margin-top:1.5rem">
<div class="step-icon yellow">C</div>
<div class="step-content">
<h4>安装 PowerShell 7推荐</h4>
<p>Windows 自带的 PowerShell 5.1 有中文兼容问题,建议升级到 7。</p>
</div>
</div>
<div class="code-block has-label" onclick="copyCode(this)">
<span class="label">PowerShell (管理员)</span>
<code><span class="cmd">winget</span> install Microsoft.PowerShell</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">如果没有 winget<a href="https://github.com/PowerShell/PowerShell/releases" target="_blank">GitHub Releases</a> 下载 .msi 安装包。安装后用 <code>pwsh</code> 命令启动新版 PowerShell。</p>
</div>
<!-- ============================================================ -->
<!-- 第二步:安装 Claude Code -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">2</span>安装 Claude Code</h2>
<div class="step">
<div class="step-icon green">1</div>
<div class="step-content">
<h4>全局安装</h4>
<p>在 PowerShell 中执行(不需要管理员权限):</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">npm</span> i -g @anthropic-ai/claude-code</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">安装过程需要几分钟,等待完成即可。</p>
<div class="step">
<div class="step-icon green">2</div>
<div class="step-content">
<h4>验证安装</h4>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">claude</span> --version <span class="comment"># 应显示版本号</span></code>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>不需要登录 Claude 账号!</strong><br>
Bookworm 使用中转站 API安装 Claude Code 后直接进入下一步,不用执行 <code>claude login</code>
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 第三步:安装 Bookworm -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">3</span>安装 Bookworm核心步骤</h2>
<div class="step">
<div class="step-icon purple">1</div>
<div class="step-content">
<h4>克隆引导仓库</h4>
<p>在 PowerShell 中执行以下命令。系统会提示输入用户名和密码。</p>
</div>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">git clone</span> <span class="url">https://code.letcareme.com/bookworm/bookworm-boot.git</span>
<span class="cmd">cd</span> bookworm-boot</code>
</div>
<div class="screenshot-note">
<strong>弹出用户名密码?</strong> 输入管理员提供给你的 Gitea 账号密码。这是 Gitea 的密码,不是主密码。
</div>
<div class="step">
<div class="step-icon purple">2</div>
<div class="step-content">
<h4>双击运行安装脚本</h4>
<p>双击文件夹里的 <strong>更新并启动Bookworm.bat</strong>,脚本会自动完成所有配置。</p>
</div>
</div>
<div class="card">
<h3>或者用命令行运行</h3>
<p>如果双击 .bat 不起作用,在 PowerShell 中手动执行:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">pwsh</span> <span class="flag">-ExecutionPolicy Bypass</span> -File install.ps1</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">没有 pwsh 可用 <code>powershell</code> 替代。</p>
<div class="step">
<div class="step-icon purple">3</div>
<div class="step-content">
<h4>输入主密码</h4>
<p>脚本会提示 <strong>"输入主密码解密凭证"</strong>,输入管理员提供的<strong>主密码</strong>(不是 Gitea 密码),按回车。</p>
<p>密码输入时不显示字符,这是正常的。<strong>输错了可以重试,最多 3 次。</strong></p>
</div>
</div>
<div class="screenshot-note">
<strong>两个密码不要搞混:</strong><br>
<strong>Gitea 密码</strong> = 克隆仓库时输入的,用于下载文件<br>
<strong>主密码</strong> = 解密 API 凭证时输入的,用于启动 Claude Code
</div>
<div class="step">
<div class="step-icon green">4</div>
<div class="step-content">
<h4>等待完成</h4>
<p>脚本会显示步骤进度 [1/9] 到 [9/9],自动完成:</p>
<ul style="color:var(--text-dim);font-size:0.9rem;padding-left:1.2rem;margin-top:0.3rem">
<li>[1/9] 前置检查 (Claude Code / Node.js / Git)</li>
<li>[2/9] 自动检测代理 + 设置 NO_PROXY</li>
<li>[3/9] 解密凭证 (输入主密码)</li>
<li>[4/9] 同步配置 (下载专家技能库)</li>
<li>[5/9] 完整性校验 (SHA256 哈希验证)</li>
<li>[6/9] 渲染配置模板</li>
<li>[7/9] 初始化本地目录</li>
<li>[8/9] Bookworm 系统验证 + MCP 检查</li>
<li>[9/9] 启动 Claude Code</li>
</ul>
</div>
</div>
<div class="alert success">
<span class="alert-icon">&#10003;</span>
<div>
<strong>看到 "Bookworm 就绪" 绿色横幅就说明成功了!</strong><br>
Claude Code 启动后,脚本会验证配置完整性,全部 [OK] 后进入 Bookworm 模式。<br>
所有 API 请求通过中转站转发,不需要自己的 Claude 账号。
</div>
</div>
<div class="alert warning">
<span class="alert-icon">&#9888;</span>
<div>
<strong>看到 "原生模式启动" 黄色横幅?</strong><br>
说明 Bookworm 配置不完整。请不加 -StartOnly 重新运行安装脚本,或联系管理员。
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 日常使用 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">4</span>日常使用</h2>
<div class="card">
<h3>方法一:双击 .bat 文件(推荐,最简单)</h3>
<p>bookworm-boot 文件夹里有两个 .bat 文件:</p>
</div>
<table>
<tr><th>文件</th><th>作用</th><th>适用场景</th></tr>
<tr>
<td><strong>启动Bookworm.bat</strong></td>
<td>快速启动,不更新配置</td>
<td>每天日常使用</td>
</tr>
<tr>
<td><strong>更新并启动Bookworm.bat</strong></td>
<td>先同步最新 Skills 再启动</td>
<td>管理员通知有更新时</td>
</tr>
</table>
<p style="color:var(--text-dim);font-size:0.85rem;margin-top:0.5rem">双击即可,无需打开 PowerShell无需记命令。脚本会自动检测 PowerShell 7/5.1。</p>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>启动时显示 "有新更新可用"</strong><br>
说明管理员推送了更新。双击 <strong>更新并启动Bookworm.bat</strong> 即可同步。
</div>
</div>
<div class="card" style="margin-top:1rem">
<h3>方法二:命令行(备用)</h3>
<p>如果 .bat 文件无法运行,在 PowerShell 中手动执行:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># 快速启动</span>
<span class="cmd">cd</span> bookworm-boot
<span class="cmd">pwsh</span> <span class="flag">-ExecutionPolicy Bypass</span> -File install.ps1 <span class="flag">-StartOnly</span>
<span class="comment"># 同步更新后启动</span>
<span class="cmd">cd</span> bookworm-boot
<span class="cmd">pwsh</span> <span class="flag">-ExecutionPolicy Bypass</span> -File install.ps1</code>
</div>
</div>
<!-- ============================================================ -->
<!-- 使用完毕 -->
<!-- ============================================================ -->
<!-- ============================================================ -->
<!-- 密码说明 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">5</span>密码说明</h2>
<div class="card">
<h3>本系统有两个密码,不要搞混</h3>
<table>
<tr><th>名称</th><th>用途</th><th>何时输入</th></tr>
<tr><td><strong>Gitea 密码</strong></td><td>下载文件(克隆仓库)</td><td>首次安装时 git 弹出要求</td></tr>
<tr><td><strong>主密码</strong></td><td>解密 API 凭证</td><td>每次启动脚本提示输入</td></tr>
</table>
</div>
<div class="alert info">
<span class="alert-icon">&#128161;</span>
<div>
<strong>密码输错了?</strong> 最多可以重试 3 次不用紧张。3 次都错才会退出。
</div>
</div>
<div class="card" style="margin-top:0.8rem">
<h3>本日免密功能</h3>
<p>首次解密成功后,脚本会询问 <strong>"今日内免密启动? (y/n)"</strong></p>
<p><strong>y</strong> 后,当天再次启动无需输入主密码,次日自动过期。</p>
<p style="color:var(--text-dim);font-size:0.85rem;margin-top:0.3rem">凭证缓存在 Windows Credential Manager 中DPAPI 加密,仅当前用户可读)。</p>
</div>
<div class="alert warning" style="margin-top:0.8rem">
<span class="alert-icon">&#128274;</span>
<div><strong>主密码无法找回</strong> &#8212; 忘记后联系管理员重新生成 secrets.enc。</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 使用完毕 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">6</span>使用完毕 &mdash; 清理 / 卸载</h2>
<div class="card" style="border-color:var(--green)">
<h3 style="color:var(--green)">最简单:双击 卸载Bookworm.bat</h3>
<p>bookworm-boot 文件夹里的 <strong>卸载Bookworm.bat</strong>,双击即可一键完整卸载:终止进程 + 清除凭证 + 恢复原始配置 + 删除桌面快捷方式。</p>
</div>
<p style="margin-top:1rem;color:var(--text-dim);font-size:0.9rem">或者用命令行精细控制:</p>
<table>
<tr><th>场景</th><th>命令</th><th>说明</th></tr>
<tr>
<td><strong>基础清理</strong></td>
<td><code>pwsh -File stop.ps1</code></td>
<td>清除环境变量,保留配置供下次快速启动</td>
</tr>
<tr>
<td><strong>完整恢复</strong></td>
<td><code>pwsh -File stop.ps1 -Restore</code></td>
<td>删除 Bookworm恢复电脑原始状态</td>
</tr>
<tr>
<td><strong>深度清理</strong></td>
<td><code>pwsh -File stop.ps1 -Restore -Deep</code></td>
<td>完整恢复 + 清除历史 + 清除 Git/凭证缓存</td>
</tr>
</table>
<div class="alert danger">
<span class="alert-icon">&#9888;</span>
<div>
<strong>在他人电脑/公用电脑上务必卸载:</strong><br>
双击 <strong>卸载Bookworm.bat</strong> 或执行 <code>pwsh -File stop.ps1 -Restore -Deep</code>
</div>
</div>
</div>
<!-- ============================================================ -->
<!-- 故障排查 -->
<!-- ============================================================ -->
<div class="section">
<h2><span class="num">!</span>常见问题排查</h2>
<div class="card">
<h3>&#10060; 输入 node -v 或 npm -v 提示 "无法识别"</h3>
<p><strong>原因:</strong>安装 Node.js 后没有重开 PowerShell 窗口PATH 没刷新。</p>
<p><strong>解决:</strong>关闭当前 PowerShell重新打开一个新的窗口再试。</p>
<p>如果还不行,手动刷新 PATH</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code>$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
<span class="cmd">node</span> -v</code>
</div>
<div class="card">
<h3>&#10060; npm 报 "执行策略" / "Execution Policy" 错误</h3>
<p><strong>原因:</strong>Windows 默认禁止运行脚本。</p>
<p><strong>解决:</strong></p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code>Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned</code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">提示确认时输入 <strong>Y</strong> 回车。之后 npm 和 pwsh 脚本都能正常运行。</p>
<div class="card">
<h3>&#10060; 提示 "openssl 未找到"</h3>
<p><strong>原因:</strong>解密凭证需要 openssl它随 Git for Windows 一起安装。</p>
<p><strong>解决:</strong>确认 Git 已安装。脚本会自动搜索 <code>C:\Program Files\Git</code><code>D:\Git</code> 下的 openssl。</p>
</div>
<div class="card">
<h3>&#10060; git clone 失败 / 认证失败</h3>
<p><strong>解决步骤:</strong></p>
<ol style="color:var(--text-dim);padding-left:1.5rem;margin-top:0.3rem">
<li>浏览器打开 <code>https://code.letcareme.com</code> 确认网站可访问</li>
<li>确认用户名密码正确(区分大小写)</li>
<li>如果开了 2FA需要用 Access Token 替代密码</li>
<li>检查网络是否需要代理</li>
</ol>
</div>
<div class="card">
<h3>&#10060; 解密凭证失败 / 主密码错误</h3>
<p><strong>原因:</strong>主密码区分大小写,且无法找回。</p>
<p><strong>解决:</strong>仔细检查密码是否正确。如确认忘记,联系管理员重新生成 <code>secrets.enc</code></p>
</div>
<div class="card">
<h3>&#10060; Claude Code 启动后没有 Bookworm 横幅</h3>
<p><strong>原因:</strong>配置文件未正确同步。</p>
<p><strong>解决:</strong>不加 <code>-StartOnly</code> 重新运行安装脚本,让它重新 clone</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="cmd">pwsh</span> <span class="flag">-ExecutionPolicy Bypass</span> -File install.ps1</code>
</div>
<div class="card">
<h3>&#10060; 安装包下载太慢</h3>
<p><strong>解决:</strong>Node.js 官网在国内可能较慢,可以用淘宝镜像:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># 设置 npm 淘宝镜像(加速下载)</span>
<span class="cmd">npm</span> config set registry https://registry.npmmirror.com
<span class="comment"># 然后重新安装 Claude Code</span>
<span class="cmd">npm</span> i -g @anthropic-ai/claude-code</code>
</div>
<div class="card">
<h3>&#10060; ECONNRESET / "Unable to connect to API"</h3>
<p><strong>原因:</strong>代理软件把国内中转站 <code>bww.letcareme.com</code> 的流量也走了国际线路,导致连接被重置。</p>
<p><strong>解决:</strong>在 PowerShell 中手动设置 NO_PROXY 后重试:</p>
</div>
<div class="code-block" onclick="copyCode(this)">
<code><span class="comment"># 设置中转站直连(不走代理)</span>
$env:NO_PROXY = "bww.letcareme.com,code.letcareme.com"
<span class="comment"># 重新启动</span>
<span class="cmd">cd</span> bookworm-boot
<span class="cmd">pwsh</span> <span class="flag">-ExecutionPolicy Bypass</span> -File install.ps1 <span class="flag">-StartOnly</span></code>
</div>
<p style="color:var(--text-dim);font-size:0.85rem">新版安装脚本已自动设置 NO_PROXY<code>git pull</code> 更新后此问题不再出现。</p>
<div class="card">
<h3>&#10060; "Not logged in" / 直接运行 claude 报错</h3>
<p><strong>原因:</strong>API 凭证是进程级环境变量,只在安装脚本启动的进程中有效。新开 PowerShell 窗口直接运行 <code>claude</code> 没有凭证。</p>
<p><strong>解决:</strong><strong>不要直接运行 <code>claude</code></strong>,必须通过以下方式启动:</p>
<ul style="color:var(--text-dim);font-size:0.9rem;padding-left:1.2rem;margin-top:0.3rem">
<li>双击桌面 <strong>Bookworm</strong> 快捷方式</li>
<li>双击 <strong>启动Bookworm.bat</strong></li>
<li>命令行:<code>pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly</code></li>
</ul>
</div>
<div class="card">
<h3>&#10060; 完整性校验不匹配(大量文件 WARN</h3>
<p><strong>原因:</strong>本地配置文件已被更新(管理员推送了新版本),但 <code>integrity.sha256</code> 未同步更新。</p>
<p><strong>解决:</strong><strong>y</strong> 继续即可,不影响使用。管理员会在下个版本同步哈希文件。</p>
</div>
<div class="card">
<h3>&#10060; 需要自己的 Claude 账号吗?</h3>
<p><strong>不需要。</strong>所有 API 请求通过中转站转发,消耗中转站额度。目标机不需要任何 Anthropic 账号或订阅。</p>
</div>
<div class="card">
<h3>&#10060; 询问 AI 系统内部信息时被拒绝了?</h3>
<p><strong>这是正常行为。</strong>Bookworm 的技能库、路由引擎、配置架构属于技术保密范围AI 被设定为不披露这些信息。</p>
<p><strong>解决:</strong>直接告诉 AI 你要完成的任务(写代码、分析问题、设计方案等),它会自动调用最合适的专家能力来帮你。无需了解内部机制即可获得完整服务。</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 安装检查清单 -->
<!-- ============================================================ -->
<div class="section">
<h2>安装检查清单</h2>
<p style="color:var(--text-dim);margin-bottom:0.5rem">逐项确认,全部打勾即可开始使用:</p>
<ul class="checklist">
<li><strong>Node.js 已安装</strong> &mdash; <code>node -v</code> 显示版本号</li>
<li><strong>Git 已安装</strong> &mdash; <code>git --version</code> 显示版本号</li>
<li><strong>npm 可用</strong> &mdash; <code>npm -v</code> 显示版本号(如报错先设 ExecutionPolicy</li>
<li><strong>Claude Code 已安装</strong> &mdash; <code>claude --version</code> 显示版本号</li>
<li><strong>PowerShell 7 已安装</strong> &mdash; <code>pwsh --version</code> 显示 7.x推荐但非必须</li>
<li><strong>已获取 Gitea 账号密码</strong> &mdash; 管理员提供</li>
<li><strong>已获取主密码</strong> &mdash; 管理员提供(用于解密 API 凭证)</li>
<li><strong>能访问 code.letcareme.com</strong> &mdash; 浏览器打开确认</li>
<li><strong>代理/VPN 已启动</strong> &mdash; 国内必须,脚本自动检测 (Clash/V2Ray/快柠檬等)</li>
</ul>
</div>
<!-- ============================================================ -->
<!-- 快速参考卡片 -->
<!-- ============================================================ -->
<div class="section">
<h2>快速参考</h2>
<table>
<tr><th>操作</th><th>最简方式</th><th>命令行方式</th></tr>
<tr><td>首次安装</td><td>git clone + 双击<br><strong>更新并启动Bookworm.bat</strong></td><td><code>pwsh -ExecutionPolicy Bypass -File install.ps1</code></td></tr>
<tr><td>快速启动</td><td>双击 <strong>启动Bookworm.bat</strong></td><td><code>pwsh -File install.ps1 -StartOnly</code></td></tr>
<tr><td>同步更新</td><td>双击 <strong>更新并启动Bookworm.bat</strong></td><td><code>pwsh -File install.ps1</code></td></tr>
<tr><td>基础清理</td><td colspan="2"><code>pwsh -ExecutionPolicy Bypass -File stop.ps1</code></td></tr>
<tr><td>完整恢复</td><td colspan="2"><code>pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore</code></td></tr>
<tr><td>深度清理</td><td colspan="2"><code>pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep</code></td></tr>
</table>
</div>
<!-- ============================================================ -->
<!-- 安全须知 -->
<!-- ============================================================ -->
<div class="section">
<h2>安全须知</h2>
<table>
<tr><th>特性</th><th>规格</th></tr>
<tr><td>凭证加密</td><td>AES-256-CBC + PBKDF2 (600,000 迭代)</td></tr>
<tr><td>传输加密</td><td>HTTPS (TLS 1.2+, Let's Encrypt 证书)</td></tr>
<tr><td>凭证存储</td><td>进程级环境变量 + 可选本日缓存 (Windows Credential Manager, DPAPI 加密, 当日 23:59 过期)</td></tr>
<tr><td>登录保护</td><td>fail2ban (5 次失败/小时 &#8594; 封禁 24 小时)</td></tr>
</table>
<div class="alert warning" style="margin-top:1rem">
<span class="alert-icon">&#128274;</span>
<div>
<strong>主密码无法找回</strong> &mdash; 请妥善保管。忘记后需管理员重新生成加密凭证。
</div>
</div>
</div>
</div>
<div class="footer">
Bookworm Portable v1.5-NDA &mdash; 保姆式安装手册<br>
&copy; 2026 Bookworm Smart Assistant
</div>
<script>
function copyCode(el) {
const code = el.querySelector('code').innerText;
navigator.clipboard.writeText(code).then(() => {
el.classList.add('copied');
setTimeout(() => el.classList.remove('copied'), 1500);
});
}
</script>
</body>
</html>

View File

@ -1,206 +0,0 @@
# Bookworm Portable 项目经验与避坑指南
> 2026-04-01 | 从可行性评估到 E2E 验证的完整实战记录
---
## 一、项目概要
**目标**: 让任意 Windows 电脑通过一行命令激活 Bookworm (97 Skills + 18 Agents + 28 Hooks) + 中转站 API
**最终架构**: Gitea 私有仓 (code.letcareme.com) + AES-256 加密凭证 + 自动代理检测 + 一键安装脚本
**耗时**: 1 天 (评估 → 设计 → 三路专家审查 → P0/P1 修复 → 部署 → E2E 验证 → 踩坑修复)
---
## 二、关键决策与转折点
### 决策 1: USB → Git 方案切换
- **初始方案**: U盘存储全部系统文件 + 加密凭证
- **问题**: Windows NTFS Junction 跨驱动器失败 (实测 Accessible: False)、USB IOPS 瓶颈 (hook 延迟 200-600ms)、体积 2.1GB
- **最终方案**: Git + 模板渲染USB 变为可选 (纯云端即可)
- **教训**: 方案评估阶段多投入时间做实测,不要假设跨驱动器操作能工作
### 决策 2: GitHub → 国内 Gitea
- **问题**: 国内无 VPN 无法访问 GitHub
- **方案**: 阿里云 ECS 自建 Gitea国内直连
- **教训**: 面向国内用户的工具链必须全程可达,不能依赖海外服务
### 决策 3: bookworm.letcareme.com → code.letcareme.com
- **问题**: bookworm.letcareme.com 已被 Bookworm Web 项目 Nginx 配置占用
- **解决**: 换用 code.letcareme.com申请新证书
- **教训**: 部署前检查域名占用,`grep -r "域名" /etc/nginx/` 先扫一遍
---
## 三、必踩的坑 (按严重度排序)
### 坑 1: Claude Code 国内启动检查 (CRITICAL)
```
现象: "Failed to connect to api.anthropic.com: ERR_BAD_REQUEST"
原因: Claude Code 启动时硬编码检查 api.anthropic.com与 ANTHROPIC_BASE_URL 无关
影响: 国内无代理 = Claude Code 完全无法启动
解决: 必须有代理/VPN且必须设 HTTPS_PROXY 环境变量 (Node.js 不读系统代理)
耗时: 2 小时排查
```
### 坑 2: Node.js 不读 Windows 系统代理 (CRITICAL)
```
现象: PowerShell (Invoke-WebRequest) 能通Claude Code 不通
原因: Node.js 只读 HTTPS_PROXY/HTTP_PROXY 环境变量,不读 Windows IE/系统代理设置
解决: 用 [System.Net.WebRequest]::DefaultWebProxy.GetProxy() 发现实际代理端口
然后 $env:HTTPS_PROXY = "http://127.0.0.1:端口"
教训: 系统代理 ≠ 进程代理Node.js 应用必须显式设环境变量
```
### 坑 3: settings.json 中 ${VAR} 不展开 (CRITICAL)
```
现象: "API Error: Invalid URL"
原因: settings.template.json 中 "ANTHROPIC_BASE_URL": "${ANTHROPIC_BASE_URL}"
Claude Code 把 ${ANTHROPIC_BASE_URL} 当字面字符串,不展开环境变量
实际发送的 URL 是 "${ANTHROPIC_BASE_URL}/v1/messages"
解决: 从 settings.json env 段删除 ANTHROPIC_API_KEY 和 ANTHROPIC_BASE_URL
让 install.ps1 注入的进程环境变量直接生效
教训: Claude Code settings.json 的 ${} 语法行为未文档化,不要假设它能展开
```
### 坑 4: PowerShell 5.1 的 ?. 语法 (HIGH)
```
现象: "表达式或语句中包含意外的标记 '?.Source'"
原因: ?. (null-conditional operator) 是 PS 7+ 专属语法PS 5.1 不支持
解决: 改为 if ($x) { $x.Property } else { $null }
教训: 目标机可能只有 PS 5.1 (Windows 自带),所有脚本必须 PS 5.1 兼容
```
### 坑 5: PowerShell 5.1 的 git stderr 问题 (HIGH)
```
现象: git clone 实际成功但脚本报 [ERROR] 克隆失败
原因: git 把进度信息写到 stderrPS 5.1 的 $ErrorActionPreference="Stop"
把 stderr 输出当成终止性错误抛出异常
解决: git 命令前临时设 $ErrorActionPreference = "Continue"
用 $LASTEXITCODE 判断实际成功/失败
教训: PS 5.1 + 外部命令 + Stop 模式 = 必炸,所有外部命令都要处理
```
### 坑 6: UTF-8 BOM 问题 (HIGH)
```
现象: "<# : 无法将 '<#' 项识别为 cmdlet" 或中文乱码
原因: Write 工具生成的文件无 BOMPS 5.1 按 ANSI 解析 → 中文乱码
修复时 BOM 加了两次 → 双 BOM 导致 <# 注释块被当成代码
解决: 统一用 bash 添加一次 BOM: printf '\xEF\xBB\xBF' > tmp && cat file >> tmp
每次编辑后检查: head -c 3 file | xxd -p 应为 efbbbf
教训: 面向 PS 5.1 的 .ps1 文件必须有且仅有一个 UTF-8 BOM
```
### 坑 7: openssl 路径不在默认位置 (MEDIUM)
```
现象: "[ERROR] openssl 未找到"
原因: Git for Windows 安装在 D:\Git 而非默认 C:\Program Files\Git
解决: 搜索多个路径: C:\Program Files\Git, D:\Git, D:\Git\mingw64\bin
教训: 不要硬编码第三方软件路径,提供多路径搜索
```
### 坑 8: Gitea 端口被占用 (MEDIUM)
```
现象: 部署脚本设 3000 端口但已被 my-oa-frontend 容器占用
解决: 改为 3300 端口
教训: 部署前 netstat -tlnp | grep 端口 先检查
```
### 坑 9: Nginx 反代不传 Authorization 头 (MEDIUM)
```
现象: git push 通过 Nginx 反代时 404
原因: Nginx 默认传 Authorization 但旧的 bookworm-web.conf 先匹配了域名
解决: 换域名 + 添加 proxy_set_header Authorization $http_authorization
教训: 多个 Nginx 配置文件可能竞争同一个 server_name
```
### 坑 10: git clone 私有仓需要凭证 (LOW)
```
现象: install.ps1 中 git clone 静默失败 (无密码提示)
解决: 先手动 git clone 触发凭证输入 + git config credential.helper store
教训: 自动化脚本中的 git 操作需要预配置凭证
```
---
## 四、安全经验
### 三路专家审查的价值
- **红队**: 发现 14 个攻击向量,其中 INSTALL_LOCK=false 成功率 95%
- **代码审查**: 发现 5 个 Blocker + 13 个 Warning
- **架构审查**: 发现仓库体积失控 (预估 50MB 实际 500MB+)
- **交叉验证**: 三路独立发现的 openssl 命令行密码泄露问题,置信度极高
- **教训**: 多专家并行审查在 P0 修复前执行,投入产出比极高
### 安全加固清单 (实际落地的)
- [x] Gitea INSTALL_LOCK=true + 注册关闭 + CLI 创建管理员
- [x] Gitea 二进制 SHA256 校验
- [x] HTTPS (Let's Encrypt + Nginx 反代 + HSTS)
- [x] fail2ban (5次/小时 → 封24h)
- [x] Gitea 仅 127.0.0.1 监听
- [x] openssl 密码通过 stdin 管道 (不暴露进程列表)
- [x] PBKDF2 600,000 迭代 (OWASP 2023)
- [x] 凭证仅进程级环境变量 (不写磁盘)
- [x] .gitignore 排除凭证/大文件/敏感数据
- [x] git add 前人工确认 + 大文件检测
- [x] stop.ps1 清理 Git Credential Manager 缓存
---
## 五、PowerShell 跨版本兼容速查表
| PS 7+ 写法 | PS 5.1 兼容写法 |
|---|---|
| `$obj?.Property` | `if ($obj) { $obj.Property } else { $null }` |
| `$cmd?.Source` | `$cmd = Get-Command x -EA 0; if ($cmd) { $cmd.Source }` |
| `??` 空合并 | `if ($x) { $x } else { $default }` |
| UTF-8 无 BOM | 必须有 BOM: `\xEF\xBB\xBF` |
| `$ErrorActionPreference="Stop"` + git | 临时改 `Continue`,用 `$LASTEXITCODE` 判断 |
---
## 六、代理/VPN 检测方法速查
```powershell
# 最可靠: .NET DefaultWebProxy (覆盖所有系统代理)
[System.Net.WebRequest]::DefaultWebProxy.GetProxy("https://api.anthropic.com")
# IE 注册表
Get-ItemProperty "HKCU:\...\Internet Settings" | Select ProxyServer, ProxyEnable
# 端口扫描 (Clash 7890, V2Ray 10808, 快柠檬 10792)
Test-NetConnection 127.0.0.1 -Port 7890
```
---
## 七、项目最终产出
```
13 个文件 (bookworm-portable/)
2 个 Gitea 仓库 (bookworm-config 514文件/14MB, bookworm-boot 6文件)
3 个 ECS 部署脚本 (Gitea + HTTPS + 防火墙)
7 项 P0 安全修复
4 项 P1 安全加固
1 个保姆式 HTML 教程
1 个快速参考 TXT 手册
1 份经验避坑指南 (本文档)
E2E 验证: 远程 Windows Server → 从零安装 → Claude Code 启动成功
```
---
## 八、如果重新来过,我会...
1. **一开始就做 PS 5.1 兼容测试** — 不假设目标机有 PS 7
2. **一开始就加代理检测** — 国内 Claude Code 的核心门槛
3. **不在 settings.json env 段放 ${} 变量** — 行为未文档化
4. **先检查域名和端口占用** — 再写部署脚本
5. **三路专家审查提前到设计阶段** — 而非代码写完后再审
---
*Bookworm Portable v1.2 | code.letcareme.com | 2026-04-01*

View File

@ -1,17 +0,0 @@
@echo off
setlocal
chcp 65001 > nul 2>&1
title Bookworm Smart Assistant
:: 日常启动入口: 静默更新 + 直接启动 (无需管理员权限)
set "NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1"
where node >nul 2>nul
if %errorlevel% equ 0 goto :RUN
echo [!!] Node.js 未安装, 请先运行 Bookworm-Install.bat
pause
exit /b 1
:RUN
node "%~dp0setup-all.js" --start
endlocal