diff --git a/.gitignore b/.gitignore index 8c59990..a0d4c94 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/Bookworm-AutoSetup.bat b/Bookworm-AutoSetup.bat deleted file mode 100644 index fdf612e..0000000 --- a/Bookworm-AutoSetup.bat +++ /dev/null @@ -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 diff --git a/Bookworm-Install.bat b/Bookworm-Install.bat deleted file mode 100644 index f167576..0000000 --- a/Bookworm-Install.bat +++ /dev/null @@ -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 diff --git a/Bookworm-OneClick-Win10.bat b/Bookworm-OneClick-Win10.bat deleted file mode 100644 index 2ddf9b5..0000000 --- a/Bookworm-OneClick-Win10.bat +++ /dev/null @@ -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 diff --git a/Bookworm-Setup.bat b/Bookworm-Setup.bat deleted file mode 100644 index c908df2..0000000 --- a/Bookworm-Setup.bat +++ /dev/null @@ -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 -) diff --git a/admin-authcode-gui.ps1 b/admin-authcode-gui.ps1 index e1eb40c..c9133e5 100644 --- a/admin-authcode-gui.ps1 +++ b/admin-authcode-gui.ps1 @@ -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 + } +} diff --git a/download-panel.html b/download-panel.html deleted file mode 100644 index d5de565..0000000 --- a/download-panel.html +++ /dev/null @@ -1,85 +0,0 @@ - - diff --git a/download.html b/download.html deleted file mode 100644 index b4beb8f..0000000 --- a/download.html +++ /dev/null @@ -1,125 +0,0 @@ - - -
- - -
AI 编程助手 — 一键安装,即刻使用
-Bookworm-Setup.bat (4 KB) — 双击即可安装
- -____ _ - | __ ) ___ ___ | | ____ _____ _ __ _ __ ___ - | _ \ / _ \ / _ \| |/ /\ \ /\ / / _ \| '__| '_ ` _ \ - | |_) | (_) | (_) | < \ V V / (_) | | | | | | | | - |____/ \___/ \___/|_|\_\ \_/\_/ \___/|_| |_| |_| |_| --
从零开始,一步步教你在任意 Mac 电脑上激活 Bookworm
-下载 Bookworm-Setup.sh → 在终端运行 → 输入密码 → 完成
-脚本自动检测依赖、安装 Homebrew/Node.js/Git、下载配置、启动 Claude Code
-手动安装流程:
-首次安装约 10 分钟(含依赖下载),之后每次启动约 5-15 秒
-api.anthropic.com,国内无法直连。bww.letcareme.com 部署在国内阿里云,不需要通过代理访问。NO_PROXY=bww.letcareme.com,code.letcareme.com,无需手动配置。*.letcareme.com 加入直连列表。
- 需要安装以下软件。如果已装过可跳到下一步。
- - -Homebrew 是 macOS 上最常用的包管理器,后续用它安装 Node.js 和 Git。
-打开 终端(按 ⌘ + 空格 搜索 "终端" 或 "Terminal"),执行:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"# Apple Silicon Mac 需要执行(Intel Mac 不需要)
-echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
-eval "$(/opt/homebrew/bin/brew shellenv)"
- 验证安装成功:
-brew --version # 应显示 Homebrew 4.x.x
- 用 Homebrew 一行命令安装。
-brew install node
- 也可以从 nodejs.org 下载 .pkg 安装包。
- -验证安装成功:
-node -v # 应显示 v22.x.x
-npm -v # 应显示 10.x.x
- macOS 通常自带 Git,如没有则用 Homebrew 安装。
-# 检查是否已安装
-git --version
-
-# 如果提示安装 Xcode Command Line Tools,点击"安装"即可
-# 或者用 Homebrew 安装:
-brew install git
- 在终端中执行:
-npm i -g @anthropic-ai/claude-code
- 安装过程需要几分钟,等待完成即可。如果报权限错误,在前面加 sudo。
claude --version # 应显示版本号
- claude login。
- 在终端中执行以下命令。系统会提示输入用户名和密码。
-git clone https://code.letcareme.com/bookworm/bookworm-boot.git
-cd bookworm-boot
- 在终端中执行安装脚本:
-bash Bookworm-Setup.sh
- 如果提示权限不足:chmod +x Bookworm-Setup.sh && ./Bookworm-Setup.sh
脚本会提示 "输入主密码解密凭证",输入管理员提供的主密码(不是 Gitea 密码),按回车。
-密码输入时不显示字符,这是正常的。输错了可以重试,最多 3 次。
-脚本会显示步骤进度 [1/8] 到 [8/8],自动完成:
-安装脚本已自动添加别名到 ~/.zshrc,直接在终端输入:
bw # 快速启动
-bw-update # 同步更新
- 如果别名不可用,在终端中手动执行:
-| 操作 | 命令 | 说明 |
|---|---|---|
| 快速启动 | -bw |
- 直接启动 Claude Code + Bookworm | -
| 同步更新 | -bw-update |
- 更新 boot + 配置仓库 | -
bw-update 即可同步。
- | 名称 | 用途 | 何时输入 |
|---|---|---|
| Gitea 密码 | 下载文件(克隆仓库) | 首次安装时 git 弹出要求 |
| 主密码 | 解密 API 凭证 | 每次启动脚本提示输入 |
首次解密成功后,脚本会询问 "今日内免密启动? (y/n)"
-选 y 后,当天再次启动无需输入主密码,次日自动过期。
-凭证缓存在 macOS 钥匙串 (Keychain) 中,仅当前用户可读。
-在终端中执行清理命令:
-| 场景 | 命令 | 说明 |
|---|---|---|
| 基础清理 | -rm -rf ~/.claude |
- 删除 Bookworm 配置,保留引导仓库供重新安装 | -
| 完整恢复 | -rm -rf ~/.claude ~/bookworm-boot |
- 删除所有 Bookworm 文件 | -
| 深度清理 | -rm -rf ~/.claude ~/bookworm-boot && sed -i '' '/Bookworm Portable/,+2d' ~/.zshrc && git credential-osxkeychain erase <<< "host=code.letcareme.com" |
- 完整恢复 + 清除别名 + 清除 Git 凭证 | -
原因:Homebrew 未添加到 PATH(Apple Silicon Mac 常见)。
-解决:
-# Apple Silicon (M1/M2/M3/M4)
-eval "$(/opt/homebrew/bin/brew shellenv)"
-
-# Intel Mac
-eval "$(/usr/local/bin/brew shellenv)"
- 原因:macOS 默认目录权限限制。
-解决方式一(推荐):修改 npm 全局目录:
-mkdir -p ~/.npm-global
-npm config set prefix '~/.npm-global'
-echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc
-source ~/.zshrc
-
-# 然后重新安装
-npm i -g @anthropic-ai/claude-code
- 解决方式二:在命令前加 sudo(简单但不推荐长期使用)。
解决步骤:
-https://code.letcareme.com 确认网站可访问原因:主密码区分大小写,且无法找回。
-解决:仔细检查密码是否正确。如确认忘记,联系管理员重新生成 secrets.enc。
原因:macOS 自带 LibreSSL,部分加密参数可能不同。
-解决:安装 OpenSSL:
-brew install openssl
-# 脚本会自动检测 Homebrew 安装的 openssl 路径
- 原因:代理软件把国内中转站 bww.letcareme.com 的流量也走了国际线路。
解决:手动设置 NO_PROXY 后重试:
-# 设置中转站直连(不走代理)
-export NO_PROXY="bww.letcareme.com,code.letcareme.com"
-
-# 重新启动
-bw
- 或在代理软件中将 *.letcareme.com 加入直连规则。
原因:API 凭证是进程级环境变量,只在安装脚本启动的进程中有效。
-解决:不要直接运行 claude,必须通过以下方式启动:
bw(推荐)cd ~/bookworm-boot && bash Bookworm-Setup.sh解决:设置淘宝镜像:
-# npm 淘宝镜像
-npm config set registry https://registry.npmmirror.com
-
-# Homebrew 清华镜像
-export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
-
-# 然后重新安装
-npm i -g @anthropic-ai/claude-code
- 不需要。所有 API 请求通过中转站转发,消耗中转站额度。目标机不需要任何 Anthropic 账号或订阅。
-逐项确认,全部打勾即可开始使用:
-brew --version 显示版本号node -v 显示版本号git --version 显示版本号npm -v 显示版本号claude --version 显示版本号| 操作 | 快捷方式 | 完整命令 |
|---|---|---|
| 首次安装 | git clone + bash Bookworm-Setup.sh | cd ~/bookworm-boot && bash Bookworm-Setup.sh |
| 快速启动 | bw | NO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude |
| 同步更新 | bw-update | cd ~/bookworm-boot && git pull && cd ~/.claude && git pull |
| 基础清理 | rm -rf ~/.claude | |
| 完整恢复 | rm -rf ~/.claude ~/bookworm-boot | |
| 深度清理 | rm -rf ~/.claude ~/bookworm-boot && sed -i '' '/Bookworm/,+2d' ~/.zshrc | |
| 特性 | 规格 |
|---|---|
| 凭证加密 | AES-256-CBC + PBKDF2 (600,000 迭代) |
| 传输加密 | HTTPS (TLS 1.2+, Let's Encrypt 证书) |
| 凭证存储 | 进程级环境变量 + 可选本日缓存 (macOS Keychain, 当日 23:59 过期) |
| 登录保护 | fail2ban (5 次失败/小时 → 封禁 24 小时) |
- ____ _ - | __ ) ___ ___ | | ____ _____ _ __ _ __ ___ - | _ \ / _ \ / _ \| |/ /\ \ /\ / / _ \| '__| '_ ` _ \ - | |_) | (_) | (_) | < \ V V / (_) | | | | | | | | - |____/ \___/ \___/|_|\_\ \_/\_/ \___/|_| |_| |_| |_| --
从零开始,一步步教你在任意 Windows 电脑上激活 Bookworm
-获取 Bookworm-Setup.bat (4KB) → 双击运行 → 输入密码 → 完成
-安装器自动检测依赖、下载配置、创建桌面快捷方式、启动 Claude Code
-手动安装流程:
-首次安装约 10 分钟(含依赖下载),之后每次双击启动约 10-30 秒
-api.anthropic.com,国内无法直连。bww.letcareme.com 部署在国内阿里云,不需要通过代理访问。NO_PROXY=bww.letcareme.com,code.letcareme.com,无需手动配置。*.letcareme.com 加入直连列表。
- 需要安装以下软件。如果已装过可跳到下一步。
- - -去官网下载 LTS 版本安装包,双击安装,一路 Next 即可。
-打开浏览器访问 https://nodejs.org,点击绿色的 "LTS 推荐" 按钮下载,双击 .msi 文件安装,全部默认 Next。
-右键开始菜单 → 选择 "PowerShell (管理员)" 或 "终端 (管理员)",然后执行:
-# 下载 Node.js 安装包
-Invoke-WebRequest -Uri "https://nodejs.org/dist/v22.15.0/node-v22.15.0-x64.msi" -OutFile "$env:TEMP\node-install.msi"
-
-# 运行安装(会弹出安装向导,一路 Next)
-Start-Process msiexec.exe -ArgumentList "/i $env:TEMP\node-install.msi" -Wait
- node 和 npm 命令找不到。
- 验证安装成功:
-node -v # 应显示 v22.x.x
-npm -v # 应显示 10.x.x
- Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned去官网下载安装,全部默认设置即可。
-打开 https://git-scm.com/download/win,下载 "64-bit Git for Windows Setup",双击安装,全部 Next。
-验证:
-git --version # 应显示 git version 2.x.x
- Windows 自带的 PowerShell 5.1 有中文兼容问题,建议升级到 7。
-winget install Microsoft.PowerShell
- 如果没有 winget,去 GitHub Releases 下载 .msi 安装包。安装后用 pwsh 命令启动新版 PowerShell。
在 PowerShell 中执行(不需要管理员权限):
-npm i -g @anthropic-ai/claude-code
- 安装过程需要几分钟,等待完成即可。
- -claude --version # 应显示版本号
- claude login。
- 在 PowerShell 中执行以下命令。系统会提示输入用户名和密码。
-git clone https://code.letcareme.com/bookworm/bookworm-boot.git
-cd bookworm-boot
- 双击文件夹里的 更新并启动Bookworm.bat,脚本会自动完成所有配置。
-如果双击 .bat 不起作用,在 PowerShell 中手动执行:
-pwsh -ExecutionPolicy Bypass -File install.ps1
- 没有 pwsh 可用 powershell 替代。
脚本会提示 "输入主密码解密凭证",输入管理员提供的主密码(不是 Gitea 密码),按回车。
-密码输入时不显示字符,这是正常的。输错了可以重试,最多 3 次。
-脚本会显示步骤进度 [1/9] 到 [9/9],自动完成:
-bookworm-boot 文件夹里有两个 .bat 文件:
-| 文件 | 作用 | 适用场景 |
|---|---|---|
| 启动Bookworm.bat | -快速启动,不更新配置 | -每天日常使用 | -
| 更新并启动Bookworm.bat | -先同步最新 Skills 再启动 | -管理员通知有更新时 | -
双击即可,无需打开 PowerShell,无需记命令。脚本会自动检测 PowerShell 7/5.1。
- -如果 .bat 文件无法运行,在 PowerShell 中手动执行:
-# 快速启动
-cd bookworm-boot
-pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly
-
-# 同步更新后启动
-cd bookworm-boot
-pwsh -ExecutionPolicy Bypass -File install.ps1
- | 名称 | 用途 | 何时输入 |
|---|---|---|
| Gitea 密码 | 下载文件(克隆仓库) | 首次安装时 git 弹出要求 |
| 主密码 | 解密 API 凭证 | 每次启动脚本提示输入 |
首次解密成功后,脚本会询问 "今日内免密启动? (y/n)"
-选 y 后,当天再次启动无需输入主密码,次日自动过期。
-凭证缓存在 Windows Credential Manager 中(DPAPI 加密,仅当前用户可读)。
-bookworm-boot 文件夹里的 卸载Bookworm.bat,双击即可一键完整卸载:终止进程 + 清除凭证 + 恢复原始配置 + 删除桌面快捷方式。
-或者用命令行精细控制:
-| 场景 | 命令 | 说明 |
|---|---|---|
| 基础清理 | -pwsh -File stop.ps1 |
- 清除环境变量,保留配置供下次快速启动 | -
| 完整恢复 | -pwsh -File stop.ps1 -Restore |
- 删除 Bookworm,恢复电脑原始状态 | -
| 深度清理 | -pwsh -File stop.ps1 -Restore -Deep |
- 完整恢复 + 清除历史 + 清除 Git/凭证缓存 | -
pwsh -File stop.ps1 -Restore -Deep
- 原因:安装 Node.js 后没有重开 PowerShell 窗口,PATH 没刷新。
-解决:关闭当前 PowerShell,重新打开一个新的窗口再试。
-如果还不行,手动刷新 PATH:
-$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
-node -v
- 原因:Windows 默认禁止运行脚本。
-解决:
-Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
- 提示确认时输入 Y 回车。之后 npm 和 pwsh 脚本都能正常运行。
- -原因:解密凭证需要 openssl,它随 Git for Windows 一起安装。
-解决:确认 Git 已安装。脚本会自动搜索 C:\Program Files\Git 和 D:\Git 下的 openssl。
解决步骤:
-https://code.letcareme.com 确认网站可访问原因:主密码区分大小写,且无法找回。
-解决:仔细检查密码是否正确。如确认忘记,联系管理员重新生成 secrets.enc。
原因:配置文件未正确同步。
-解决:不加 -StartOnly 重新运行安装脚本,让它重新 clone:
pwsh -ExecutionPolicy Bypass -File install.ps1
- 解决:Node.js 官网在国内可能较慢,可以用淘宝镜像:
-# 设置 npm 淘宝镜像(加速下载)
-npm config set registry https://registry.npmmirror.com
-
-# 然后重新安装 Claude Code
-npm i -g @anthropic-ai/claude-code
- 原因:代理软件把国内中转站 bww.letcareme.com 的流量也走了国际线路,导致连接被重置。
解决:在 PowerShell 中手动设置 NO_PROXY 后重试:
-# 设置中转站直连(不走代理)
-$env:NO_PROXY = "bww.letcareme.com,code.letcareme.com"
-
-# 重新启动
-cd bookworm-boot
-pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly
- 新版安装脚本已自动设置 NO_PROXY,git pull 更新后此问题不再出现。
原因:API 凭证是进程级环境变量,只在安装脚本启动的进程中有效。新开 PowerShell 窗口直接运行 claude 没有凭证。
解决:不要直接运行 claude,必须通过以下方式启动:
pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly原因:本地配置文件已被更新(管理员推送了新版本),但 integrity.sha256 未同步更新。
解决:选 y 继续即可,不影响使用。管理员会在下个版本同步哈希文件。
-不需要。所有 API 请求通过中转站转发,消耗中转站额度。目标机不需要任何 Anthropic 账号或订阅。
-这是正常行为。Bookworm 的技能库、路由引擎、配置架构属于技术保密范围,AI 被设定为不披露这些信息。
-解决:直接告诉 AI 你要完成的任务(写代码、分析问题、设计方案等),它会自动调用最合适的专家能力来帮你。无需了解内部机制即可获得完整服务。
-逐项确认,全部打勾即可开始使用:
-node -v 显示版本号git --version 显示版本号npm -v 显示版本号(如报错先设 ExecutionPolicy)claude --version 显示版本号pwsh --version 显示 7.x(推荐但非必须)| 操作 | 最简方式 | 命令行方式 |
|---|---|---|
| 首次安装 | git clone + 双击 更新并启动Bookworm.bat | pwsh -ExecutionPolicy Bypass -File install.ps1 |
| 快速启动 | 双击 启动Bookworm.bat | pwsh -File install.ps1 -StartOnly |
| 同步更新 | 双击 更新并启动Bookworm.bat | pwsh -File install.ps1 |
| 基础清理 | pwsh -ExecutionPolicy Bypass -File stop.ps1 | |
| 完整恢复 | pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore | |
| 深度清理 | pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep | |
| 特性 | 规格 |
|---|---|
| 凭证加密 | AES-256-CBC + PBKDF2 (600,000 迭代) |
| 传输加密 | HTTPS (TLS 1.2+, Let's Encrypt 证书) |
| 凭证存储 | 进程级环境变量 + 可选本日缓存 (Windows Credential Manager, DPAPI 加密, 当日 23:59 过期) |
| 登录保护 | fail2ban (5 次失败/小时 → 封禁 24 小时) |