# WCBHELPER **Repository Path**: harrydeep/wcbhelper ## Basic Information - **Project Name**: WCBHELPER - **Description**: No description available - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-27 - **Last Updated**: 2026-09-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cbhelper —— CodeBuddy CN 账号增强助手 Windows 上的本机多账号**切换**、**无感登录**与**注入面板**工具。 **阶段 1~5 已全部落地并实测**(引导 / 账号库 / 文件级切换 / 注入面板 / 无感登录 / 导入导出 / 兼容自愈)。 > 本工具为**个人本机客户端增强**,与腾讯官方无隶属关系。不修改安装包、不重签名、不逆向官方加密。 > 版权 / 许可 / 致谢 / 第三方组件 / 使用风险见 [`NOTICE.md`](NOTICE.md);联系:harrydeep@wardyan.top。 > > **前置条件**:Windows 10/11 + 已安装 **CodeBuddy CN** 且至少成功登录过一次。 > Node.js **不必需** —— 可直接借用客户端自带的 Node 运行时(见 `docs/发布与部署指南.md` §3 方案 A)。 > 数据全部落在用户态(`%APPDATA%\cbhelper`),不需要管理员权限。 --- ## 1. 它做什么 | 能力 | 说明 | 状态 | |---|---|---| | 引导(首次配置) | 定位并(保留注释地)写入 `argv.json` 的 `remote-debugging-port`,让 CDP 可用 | ✅ 阶段 1 | | 账号库 | 采集当前登录态入库;密文快照加密落盘;账号列表与时效提示 | ✅ 阶段 1 | | 账号切换 | 写 secret → 应用自重启 → **双证据**回读校验 → 失败重试(幂等) | ✅ 阶段 1 | | 环境诊断 | 11 项检查(客户端/CDP/argv.json/secret/扩展日志/口令/账号库/面板资源) | ✅ 阶段 1 | | 无感登录新账号 | 驱动官方登录流程并自动入库(真实 OAuth 已人工验收) | ✅ 阶段 4 | | 注入面板 UI | 可视化操作;**支持多窗口**(每个窗口独立注入);账号下拉选择 | ✅ 阶段 2 | | 导入导出 | 加密的账号包(PBKDF2 + AES-256-GCM) | ✅ 阶段 4 | | 兼容自愈 | `compat` 自检 + `--apply` 自愈;官方改目录名/版本/键名后不失效 | ✅ 阶段 5 | --- ## 2. 快速开始 **不需要预装 Node.js**:`cbhelper.cmd` 会先找 PATH 里的 `node`,找不到就**借用 CodeBuddy 客户端自带的 Node 运行时**(实测客户端内置 Node 22.20.0,`node:sqlite` 与 `WebSocket` 均可用)。 若你习惯命令行,把下文的 `cbhelper.cmd` 换成 `node bin/cbhelper.js` 即可。 ```bash # 0) 先看环境是否就绪(11 项检查) cbhelper.cmd doctor # 1) 首次:初始化账号库(**默认免口令,直接回车即可**) cbhelper.cmd init # 想要口令保护?才需要显式传:init --password '<至少6位>'(或环境变量 CBHELPER_PASSWORD) # 说明:凭据快照本就是 Windows DPAPI 绑定当前用户的密文,异机解不开, # 因此免口令是默认形态;旧的"口令型"账号库会在首次使用时自动迁移为免口令。 # 2) 首次引导:确认/写入 CDP 端口(--write 实际执行) cbhelper.cmd guide # 预演:只报告计划 cbhelper.cmd guide --write # 执行:写 argv.json(保留注释);CDP 已可达时自动重启生效 # 3) 采集当前登录账号入库 cbhelper.cmd capture # 4) 查看账号库(含实时登录态对照与快照时效) cbhelper.cmd list # 5) 切换账号 cbhelper.cmd switch --dry-run # 预演:事务内校验后回滚,零副作用 cbhelper.cmd switch # 执行:写库 + 自重启 + 双证据校验 # 6) 回读校验(不写库、不重启) cbhelper.cmd verify --uid # 7) 迁移旧库(把历史明文账号库导入加密新库) # 注:原示例库 H:\MY\workdaddy\cbhelper-data 已于 2026-09-25 清理,此处仅示范形态 cbhelper.cmd migrate --from '<旧库目录>' --dry-run cbhelper.cmd migrate --from '<旧库目录>' # 8) 签到(**手动触发,无定时任务**;默认全关,需先同意) cbhelper.cmd checkin --status # 只读:各账号今日状态(标注来源:实时/本地记录/未知) cbhelper.cmd checkin --consent # 同意闸:启用(多账号轮换会重启客户端,会明示) cbhelper.cmd checkin # R1:签当前登录账号,**不重启客户端** cbhelper.cmd checkin --all --dry-run # R3 预演:只出计划,不切换、不重启 cbhelper.cmd checkin --all # R3:多账号切换式轮换(会重启客户端 N 次) cbhelper.cmd checkin --revoke-consent # 撤销同意 ``` ### 账号备份(导出 / 导入,面板与 CLI 同一套) ```bash # 导出账号清单(默认**免口令包**;加 --package-password 才加密,≥6 位) cbhelper.cmd export cbhelper.cmd export --out D:\bak\acc.cbhpkg --uids , --note "换机前备份" # 导入账号清单(免口令包无需口令;--policy skip|overwrite 决定已存在账号怎么办) cbhelper.cmd import --in D:\bak\acc.cbhpkg cbhelper.cmd import --in D:\bak\acc.cbhpkg --package-password '<口令>' --policy overwrite ``` 面板里同样有「**账号备份(导出 / 导入)**」区:填导出路径(留空自动用 `<数据目录>\exports\`) → 选范围/口令 → 「导出账号」;导入侧可直接填路径或点「浏览…」选文件 → 选冲突策略 → 「导入账号」。 > ⚠️ 快照是 **Windows DPAPI 绑定当前用户**的密文 → 导出包**只在本机同一 Windows 用户下有意义**, > 异机/异用户导入后解不开(这是技术限制,不是配置问题)。 > 免口令包里的账号**元数据**(昵称/手机号/uid)是明文,请勿放到共享或同步目录。 ### 会话共享 / 复制(面板「会话」页签怎么看) 切到别的账号时,当前账号的会话可以**共享**(同一份:谁都能打开、不额外占磁盘)、 **复制**(给每个账号各复制一份独立正文)、或**不处理**(各看各的)。面板里从上到下是四层信息: 1. **总开关**「切换账号时」:`关闭 / 全部共享(推荐)/ 全部复制` —— 默认行为; 2. **概况**:`会话库共 289 条 · 本账号 6 条(正文 18.6 MB)· 其它账号 89 条 · 未分配 194 条` —— 「未分配」= 没有归属账号的会话;**共享就是把归属置空**,于是所有账号都能看到它; 3. **汇总**:`切到别的账号时:共享 6 条 · 复制 0 条 · 不处理 0 条` + `涉及 3 个项目:…` —— 条数按**生效方式**统计,且**含你刚改、还没保存的选择**(可以先看效果再保存); 4. **逐条列表**(每行两行): ``` [跟随全局 ▾] WSQLAssist · 修复declare补全弹出无关下拉框 → 共享 已共享 5 分钟前 ``` - 左边下拉 = 这一条的设置(`跟随全局` = 用上面总开关的值); - **项目名**取 cwd 末段(悬停看完整路径)—— 一眼看清"这是哪个项目"; - `→ 共享` = **实际生效**的方式("跟随全局"也会被解析成具体值再显示); - `已共享 / 已复制` 徽标 = **库里当前的真实状态**(与"设置"是两件事,故分开显示)。 > **改完记得点「保存」**:只改下拉不保存 = 界面改了、切换账号时**不会**生效。 > 「预演(dry-run)」先看会写什么,「立即应用」当场执行(会改会话库;**改库后需重启客户端生效**)。 > **签到是「账号级」的**:在 CodeBuddy 里签一次,同一账号在 WorkBuddy 那边也算已签。 > > **R1 vs R3**:`checkin`(不带 `--all`)只签**当前登录账号**,**零重启**; > `--all` 走 R3 轮换 —— 换账号**必须重启客户端**(免重启四条路已实测证伪), > 所以 N 个待签账号 = N 次重启(外加回切原账号一次)。已签到的账号靠本地记录跳过。 > > ⚠️ **没有定时任务,只有按钮/命令**。签到不会自己发生 —— 你必须点面板按钮或敲命令。 > 原因:轮换要重启客户端,任何周期化都会变成"周期性重启客户端"(见 `AGENTS.md` 硬约束 #15)。 > > **面板账号下拉里会显示每个账号"今日是否已签到"**:当前账号是**实时**状态; > 其余账号来自**本地记录**(查它们的远端状态需要其 token,已被否决), > 从未观测到的显示**未知** —— 切到该账号一次即可补上记录。 全局选项:`--data ` `--port ` `--json` `--verbose` `--dry-run` `--password `(**仅"口令型"账号库需要**)|`--package-password `(导出/导入包口令,留空=免口令包) > `--json` 输出经统一脱敏(长凭据串、JWT、敏感键值一律替换为 ``;摘要与路径保留可读)。 > 想把这套工具交给别人用、或发布到 Gitee/GitHub?见 **`docs/发布与部署指南.md`**。 ### 2.1 面板(账号管理界面) 面板是**注入到 CodeBuddy 窗口右下角的圆形「账」按钮**(点开即账号管理界面), 由 daemon **运行时注入** —— 所以 **daemon 不在,面板就不在**(属预期行为,不是客户端故障)。 > **管理 daemon**:`cbhelper.exe panel --status`(看是否在跑)/ `cbhelper.exe panel --stop`(停掉)。 > 双击 `dist\cbhelper.exe` 启动的面板**没有窗口**(见 §2.2),所以这两个命令是它唯一的运行态入口; > `panel-start.cmd` / `cbhelper.exe panel` 是**前台**形态(保留窗口,便于看日志、Ctrl+C 停止)。 > 两种形态共用同一个 pidfile → **不会**同时跑两个 daemon(重复启动会被单实例保护挡下)。 **打开时的行为(自动展开 → 2 秒自动收缩)**:面板一注入(= 你打开 CodeBuddy,或用 `panel-start.cmd` / 双击 exe 启动工具)会**自动展开一次,2 秒后自动收缩**回右下角圆形「账」按钮 —— 面板浮在聊天区上方,不长时间占据空间。细节(都写在 `AGENTS.md` 硬约束 #21 与守卫里): - 2 秒内**鼠标移入 / 点击**面板 → 立刻撤销自动收缩(不会把你正在用的面板收走); - 面板正忙(采集 / 切换 / 签到 / 导入…)→ **顺延**,不把进行中的进度藏起来; - **不会反复弹**:同一 daemon 的心跳重连 / 窗口重载 / 热重注入都不再展开; 但**重新启动工具**(新 daemon 实例)会再展开一次 —— 否则你会以为"启动了却什么都没发生"; - 想让它一直开着:点「账」按钮手动展开即可(手动展开的**不会**被自动收起)。 ```bash # 一键启动(推荐;双击 panel-start.cmd 亦可) panel-start.cmd # 免口令(默认)→ 直接进入,无任何提示;账号库不存在时会自动初始化 # 仅当账号库是"旧口令型"时,才会提示一次「(一次性)请输入现有主口令,用于切换为免口令模式:」 # 或直接启动(常驻服务,Ctrl+C 退出) node bin/cbhelper.js panel --data --port 9224 ``` > ⚠ **面板是常驻服务,不是一次性命令** —— 别把它当前台命令跑(会一直不返回)。 > 请用独立窗口/后台方式启动,关掉即停止。 > > **这条已被强制**:`panel` 会检查 `process.stdout.isTTY`,**非交互环境(管道 / 重定向 / 脚本 / > 工具调用)直接拒绝启动并以退出码 3 结束**,并打印正确用法 —— 不会再静默挂住调用方。 > 确实需要在非交互环境常驻时加 `--allow-resident`(进程生命周期自行负责)。 > > ⚠ **面板归属建议交给 `panel-start.cmd`(会话外)托管**:若在代理会话里用后台任务启动, > 进程会被会话的 shell 包装进程挂住 → daemon 跨会话存活,**那个会话的后台任务永远不结束** > (看起来像"卡住")。用 `panel-start.cmd` 独立窗口启动就没这个问题。 > ⚠ **换 daemon / 换数据目录前,先清掉页面里的旧面板**:停止 daemon **不会**移除已注入的 DOM, > 窗口里会留下一个「看着活着、点了没反应」的**死面板**;而且新 daemon 会因 `PANEL_VERSION` > 相同而短路不重建(`docs/BUG.md` #25),死面板会一直挂着。清理命令: > > ```bash > node test/live-panel-dispose.js 9224 # 逐个窗口 dispose 旧面板 + 摘除 DOM > ``` **启动时会自动确保 CodeBuddy 在**(对齐 WorkDaddy 的 launcher 运行方式):`panel` 在注入前先做一次 启动编排,按**由轻到重**的顺序选路径 —— | 现场状态 | 动作 | |---|---| | CDP 端口已可达 | 什么都不做(不会去动正在正常运行的客户端) | | CDP 在**其它**端口存活 | 写 `argv.json` + 触发**应用自重启**(不结束进程,BUG.md #11) | | 客户端在跑但没带调试端口 | 结束进程(`taskkill /F`)→ 带 `--remote-debugging-port` 重启 | | 客户端没运行 | 直接带 `--remote-debugging-port` 启动 | 编排失败**不阻断**:面板仍会启动并持续重试注入,客户端稍后起来也能接上。 跳过这一步用 `--no-launch`;允许打开、但不许结束正在运行的实例用 `--no-restart`。 > ⚠ 「客户端在跑但没带调试端口」这一条会**结束客户端进程**(优雅关闭对该客户端无效,必须 > `taskkill /F`,见 BUG.md #10)→ **客户端里未保存的内容会丢**。该场景通常只在首次引导出现; > 若客户端里正在干活,请加 `--no-restart`(或先手动保存并重启一次)。 > > 只读地看它会选哪条路径(不会开关客户端):`node test/probe-ensure-plan.js 9224` **账号区(下拉选择框)**:面板里的账号是一行**下拉框**,**选中即切换** —— 账号再多也只占一行。 | 你在下拉里选… | 会发生什么 | |---|---| | **当前登录的账号**(标注「(当前)」) | 只提示,不动作 | | **没有密文快照的账号**(标注「(无快照)」) | 拒绝并回退 | | **其它账号** | 弹确认框 → 确认后切换(客户端会自动重启);取消则回退 | 下拉右侧的 `×` 删除**当前选中**账号(含本地加密快照,不影响官方客户端); 下方一行是选中账号的详情(短 uid · uin · 类型 · 来源 · 采集时间 · 快照大小)。 **多窗口**:CodeBuddy 开多个窗口时,daemon 会为**每个窗口**分别注入(每个窗口是一个独立的 workbench target);新开的窗口会在数秒内自动出现面板。 **验证面板是否在线**: ```bash node test/probe-multiwindow.js 9224 # 逐个窗口报告面板存活状态与 N/N 结论 node test/probe-account-select.js 9224 # 账号下拉渲染(选项数 / 选中项 / 旧卡片残留) node test/probe-account-select-interact.js 9224 # 账号下拉交互(桩 confirm,不会真的切换) node test/live-panel-dispose.js 9224 # 【写操作】清理 daemon 停止后残留的"死面板" ``` > 改了 `scripts/panel/inject.js` 的 UI/逻辑,**必须递增 `PANEL_VERSION`**,否则已注入的旧窗口 > 会一直停在旧界面(daemon 升级不会让页面自动重载)。见 `docs/BUG.md` #25。 **开机自启**(可选):把 `panel-start.cmd` 的快捷方式放进 `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`。 ### 2.2 单文件 exe(免装 Node,双击即用) 把整个工具打包成**一个 exe**(Node SEA),目标机器**不需要装 Node.js**, **双击就启动面板** —— 这是最省事的交付形态。 ```bash npm install # 只装**构建期**依赖(esbuild + postject);运行时仍零外部依赖 npm run build:exe # → dist/cbhelper.exe(约 83.5 MB) node test/probe-sea.js # 【必跑】拿真产物验证,而不是只跑源码单测 ``` > 💡 **也可以直接双击 `build-exe.cmd`** —— 它做上面第 2 步,并打印产物路径与体积; > 前置检查(真 Node + 构建依赖)失败时给出可照做的提示,**窗口不会一闪而过**。 > 它**不**跑单测、不检查 git、不打 zip —— 那些是 `release-exe.cmd` 的活。 - **产物**:`dist/cbhelper.exe`,**自包含**(内嵌 Node 运行时 + 面板资源)。已 gitignore,不入库。 - **★ exe 里必然带着 Node.js** —— 这不是"顺带打包",而是 SEA 的**机理**: 构建脚本第 3 步就是**复制一份 `node.exe` 当宿主**,第 4 步用 postject 把 blob 注进去。 实测(2026-09-27):宿主 `node.exe` 83.0 MB + blob 594 KB → 产物 83.6 MB, **Node 运行时占 99.31%**;产物自报 `runtime.kind = "sea-exe"`。 → 所以"单文件、双击即用"与"不带 Node"**不可兼得**;不想带 Node 就走**方案 A** (源码 + 借用客户端自带的 Node 运行时,见 `docs/发布与部署指南.md` §3),体积 ~300 KB。 → 也正因如此,**分发该 exe 必须随附 Node.js 的许可声明**(见 [`NOTICE.md`](NOTICE.md) §3.1)。 - **★ 想让 exe 变小?换 no-intl 宿主**(约 84 MB → 约 46 MB): 宿主体积 = 产物体积(实测占 **99.26%**),而官方 Windows Node 是 **full-icu**, 内嵌的 ICU 全量数据占 exe **45%(37.7 MiB)** —— 而 cbhelper **零 Intl 依赖**,这部分是白带的。 把 `--with-intl=none` 自编译的 `node.exe` 放进 `vendor/node-noi18n/`,构建时会**自动优先使用** (宿主选择链:`--node` → `CBHELPER_HOST_NODE` → `vendor/node-noi18n/node.exe` → 当前 node)。 获取方式见 [`vendor/node-noi18n/README.md`](vendor/node-noi18n/README.md); 取证与方案对比见 [`docs/exe体积瘦身方案分析.md`](docs/exe体积瘦身方案分析.md)。 构建日志**每次都会打印**宿主 ICU 形态与体积影响,所以体积不会悄悄退化; 发布流程可加 `--require-noi18n` 让它变成硬失败。 - **⚠️ 换了 Node 版本就会换宿主体积 —— 这是"exe 突然变大"的唯一原因**: 同一个项目,用 Node 22(`node.exe` 83.0 MiB)构建得到 **83.7 MB**; 换成 Node 24(`node.exe` 88.0 MiB)就得到 **88.6 MB** —— 差别**全在宿主**(业务代码只有 0.6 MB)。 所以构建日志里那行 `宿主体积 :xx MiB` 值得看一眼;要变小就换宿主(见上一条), 而不是去改业务代码(上限 0.74%)。 - **压缩:exe 本体默认不压;交付包会自动压**(2026-09-30 起): - **exe 本体**:UPX 压缩这条路保留但**默认关闭**(`--compress` 开启)。UPX 是 PE packer, 有杀软误报与启动变慢的代价 —— 而它压的是同一个二进制,**"让 exe 变小"的正道是换宿主**(见上)。 开启后会**先验证压缩后的 exe 还能跑**:SEA 负载靠 **PE 资源段**定位(`postject` 注入后 `.rsrc` 从 142 KB 涨到 662 KB),UPX 动了资源段会产出"看起来正常、启动即挂"的 exe, 而**这在 UPX 的退出码里看不出来**(它报 `Packed successfully`)。 任何一步不过关都保留未压缩版本(在副本上压,成功才原子替换,原文件不被触碰)。 - **交付包**:`release-exe.cmd` 在打 zip **之外**,若装了 7-Zip 会**额外**产出一个 `.7z` (实测同一目录 31.79 → **21.44 MiB**,省 33%)。7z 缺失或失败**都不阻断发布** —— zip 仍是通用格式,上传哪个由你决定。 - 查找顺序:UPX 走 `--upx` → `CBHELPER_UPX` → `tools/upx/upx.exe` → `PATH`; 7z 走 `--7z` → `CBHELPER_7Z` → `tools/7z/7z.exe` → `PATH`。 - **双击**该 exe = **无窗口后台**启动面板(并自动确保 CodeBuddy 已打开;没开就带调试端口把它拉起来): 本进程把面板交给一个隐藏窗口的后台副本后立即退出,**不留下 cmd 控制台窗口**。 ⚠️ 会**闪约 0.4 秒** —— Windows 为 console 子系统程序创建窗口的时机**早于代码运行**, 零依赖下无法消除(详见 `docs/BUG.md` #31;换来的是 exe 的 CLI 用法完全不受影响)。 **带参数**则按 CLI 语义执行,例如 `cbhelper.exe doctor`、`cbhelper.exe list`、`cbhelper.exe switch `; 想在前台看输出 / 排查:跑 `cbhelper.exe panel`,或先 `set CBHELPER_NO_DETACH=1` 再运行。 - **无窗口之后怎么管它**(面板没有窗口可关,所以给足三条入口): | 目的 | 命令 | |---|---| | 看它是否在跑(pid / 端口 / 启动时间 / 形态) | `cbhelper.exe panel --status` | | 停止它 | `cbhelper.exe panel --stop` | | 重复双击保护(不会起第二个 daemon) | 自动;`--force` 可绕过(不推荐) | 关掉 CodeBuddy 后,看门狗也会让面板自动退出。日志:`<数据目录>\logs\cbhelper.log`。 - **它怎么找到 CodeBuddy**(四路,按可靠性降序):运行中进程的 `ExecutablePath` → 环境变量 `CBHELPER_CLIENT_EXE` → 常见安装位置 → **注册表卸载表**。 ⚠️ 最后一路是**必需**的:客户端**没打开**时第一路必然为空,装在**非标准盘**时第三路也落空 —— 少了它就会出现「双击 exe 打不开客户端」(`docs/BUG.md` #30,`AGENTS.md` 硬约束 #19)。 若四路都落空,启动时会打印**现场**(客户端是否在跑 / exe 与来源 / 能否 spawn)+ **处理建议**; 也可自行指定:设 `CBHELPER_CLIENT_EXE=<客户端 exe 完整路径>`,或在 exe 同目录 / 其上一级 / 当前目录 放 `config.json` 写 `{"installRoots":["<安装根目录>"]}`。 自查(只读):`node test/probe-exe-discovery.js` —— 打印四路候选,并**模拟**"客户端未打开"看会走哪条路径。 - **改过 `scripts/panel/*` 或 `scripts/lib/*` 都必须重新构建**: 前者不然 exe 里是**旧面板**("面板起得来但一片空白 / 停在旧 UI",不报任何错); 后者不然 exe 里是**旧逻辑** —— ⚠️ `probe-sea.js` **抓不到 `lib/` 改动**,它只比对两个面板 asset 的字节数。 构建脚本与 `test/probe-sea.js` 都会拿**内嵌面板字节数与磁盘逐字节比对**,不一致即报错。 > ⚠ **绝不要前台裸跑该 exe**:不带参数 = 常驻面板,前台跑会一直不返回。 > 非交互环境现在会被**直接拒绝**(退出码 3,见 §2.1),但双击/独立窗口启动才是正确用法。 > > ⚠ 注入 SEA blob 后,宿主 `node.exe` 的 **Authenticode 签名失效**,构建日志会打 > `The signature seems corrupted!` —— 这是**预期现象**,不是构建失败。 ### 2.3 原生启动器(约 2.5 MB,推荐给"已经装了客户端"的用户) **如果你已经装了 CodeBuddy 客户端**(本工具本来就是这个前提),不需要那个 88 MB 的 exe —— 用**原生启动器**:一个约 **2.5 MB** 的 exe,内嵌全部 JS 负载,运行时**借用客户端自带的 Node**。 ```bash npm run build:launcher # → dist/cbhelper-lite.exe(约 2.5 MB) ``` > 💡 也可以直接**双击 `build-launcher.cmd`**(与 `build-exe.cmd` 对称:前置检查 → 构建 → 打印产物路径)。 > 它需要机器上有 **Go 1.24+**,查找顺序:`--go` → `CBHELPER_GO` → `PATH` → `~/.workdaddy-toolchains`。 **为什么能这么小**:它**不含 Node**。对照一下参考项目 WorkDaddy —— 它的 `WorkDaddyLauncher.exe` 只有 6.75 MB,正是因为那是 **Go 编译的启动器**, 只负责"找到 Node 并拉起它",而 83 MB 的 Node 运行时放在 exe **旁边**(不塞进去)。 Node SEA 则是把 84 MB 的 Node 内嵌进 exe,所以裸产物 88 MB。 两者的完整对照见 [`docs/exe体积瘦身方案分析.md`](docs/exe体积瘦身方案分析.md) §7。 | 形态 | 大小 | 需要客户端? | 说明 | |---|---|---|---| | `.cmd`(`cbhelper.cmd` / `panel-start.cmd`) | 约 0 | **是** | 双击会留一个控制台窗口 | | **原生启动器** `cbhelper-lite.exe` | **约 2.5 MB** | **是** | **双击无窗口**;Go 编译,内嵌负载 | | Node SEA `cbhelper.exe` | 约 88 MB | 否 | 自包含;双击会闪约 0.4 秒 | - **它怎么找 Node**:`PATH` → `CBHELPER_CLIENT_EXE` → `config.json` 的 `installRoots` → **注册表卸载表** (与硬约束 #13 / #19 同一套优先级)。找不到会给可照做的提示。 - **首次运行**:把内嵌负载解压到 `%LOCALAPPDATA%\cbhelper\runtime\<版本-时间戳>\`,之后复用。 - **双击** = 无窗口启动面板(面板是常驻服务,启动器会分离它然后自己退出); **带参数** = CLI 用法,输出回到当前终端(从 cmd / PowerShell 启动时会自动接回父控制台)。 - **排查**(⚠️ 双击时它**没有窗口、也没有输出**,出问题请按这三步查): 1. **看启动器日志**:`%LOCALAPPDATA%\cbhelper\launcher.log` —— 每一步都记在这里 (找到哪个运行时、负载解压到哪、子进程 pid、失败原因)。启动失败时会**弹一个错误框**, 框里写着日志路径。 2. **看 JS 侧日志**:`%APPDATA%\cbhelper\logs\cbhelper.log` —— 面板 / 注入相关的问题在这里。 3. **在终端里跑一次**:`cbhelper-lite.exe --dry-run` 只打印"它打算执行什么" (找哪个 Node、跑哪个脚本、什么参数),不会真启动;`cbhelper-lite.exe doctor` 跑完整自检。 调试时用 `npm run build:launcher -- --console` 编一个 console 子系统版本,能直接看到输出。 - ⚠️ **必须已装客户端**:启动器自己不带 Node,找不到就用不了。要"拿到就能跑"就用上面的 Node SEA 版。 ### 2.4 打包公开发布(`release-exe.cmd`) 要把 exe **公开发布**(GitHub / Gitee Release),别只跑 `npm run build:exe` —— 用一键脚本: ```bash release-exe.cmd # 构建 + 打包(七步,任一步不过即中止) release-exe.cmd --allow-dirty # 允许未提交改动(默认拒绝) release-exe.cmd --no-zip # 只组装目录,不压缩 ``` 产出 `release/cbhelper-v<版本>-win-x64.zip`(已 gitignore,不入库),内含: | 文件 | 为什么必须在包里 | |---|---| | `cbhelper.exe` | 主体 | | `LICENSE` | AGPL-3.0 全文 —— AGPL 要求向接收者**提供许可副本** | | `NOTICE.md` | 权利声明 / 第三方组件 / 商标 / 免责与使用风险 | | `README.md` | 用法 | | `SOURCE.txt` | 来源(仓库 / commit / 分支 / tag / 构建时间)—— AGPL 要求"对应源码"可达 | | `SHA256SUMS.txt` | 包内每个文件的 sha256 | | `THIRD-PARTY-NOTICES/nodejs-LICENSE.txt` | **exe 内嵌 Node.js 运行时(MIT)→ 必须随附其许可** | > **默认拒绝"工作区不干净"**:公开发布要求二进制与**已提交源码**对应(AGPL §6), > 所以有未提交改动时会直接失败,要发当前状态得显式 `--allow-dirty`。 > > ⚠ 本脚本**需要真正的 Node.js + npm**(它要跑 `npm install` 与 esbuild/postject)—— > 与 `cbhelper.cmd` / `panel-start.cmd` 不同,**不能**借用客户端自带的 Node 运行时。 > 这只是**发布者**的机器要求;最终用户仍然什么都不用装。 > > 这四样"随包许可/声明/来源/校验和"已用 `test/license.test.js` 的**发布守卫**钉住, > 漏掉任何一样 `npm test` 就会红。详见 `docs/发布与部署指南.md` §5.7。 **为什么必须 bundle**:SEA 下 `require()` 只能加载**内置**模块,相对路径 `require` 会报 `ERR_UNKNOWN_BUILTIN_MODULE`;同时 `__dirname` 在 SEA 下解析为 **cwd**(不是 exe 所在目录), 面板资源也不再是磁盘文件而是内嵌 **asset**。三处语义变化集中在 `scripts/lib/runtime.js` 适配。 --- ## 3. 数据目录 默认 `%APPDATA%\cbhelper`(可用 `--data` / 环境变量 `CBHELPER_DATA` / 项目 `config.json` 覆盖)。 ``` / ├── keyfile.json # PBKDF2 参数 + 口令校验器(不含口令本身) ├── meta.json # 账号索引:currentUid / order / bindings ├── accounts/.json # 账号记录(**绝不含明文 token**) ├── secrets/.secret.enc # AES-256-GCM 加密的 secret 密文快照 ├── backups/ # 整库备份 state.vscdb. + 回滚点 rollback-.json └── logs/cbhelper.log # 脱敏日志 ``` **账号记录示例**(无 token): ```jsonc { "uid": "11111111-…", "nickname": "13800000001", "phone": "*******0001", // 脱敏,仅后 4 位 "uin": "1000000001", "type": "personal", "capturedAt": "2026-09-25T10:58:00.000Z", "source": "official-login", // official-login | recapture | migrated "secretSnapshot": { "file": ".secret.enc", "keyName": "planning-genie.new.accessTokencn", "length": 32476, "sha256": "…", "chunks": [] }, "sessionPreview": { "domain": "www.codebuddy.cn", "expiresAt": …, "tokenType": "Bearer" }, "sessionContract": { "ok": true, "issues": [], "warnings": [] } } ``` --- ## 4. 工作原理(关键点) ``` CLI → actions(编排) ├── guide.js argv.json 定位/JSONC 解析/保留注释改写/CDP 就绪等待 ├── cdp-client.js C1 通道:/json/version、/json/list、Runtime.evaluate、ipcRenderer invoke/send ├── secret-store.js C3 通道:state.vscdb 的 secret 行定位/快照/还原/整库备份 ├── sqlite-secret.js node:sqlite 低层访问(事务 + 读回校验 + 回滚点) ├── launcher.js 写库 → 触发应用自重启 → 三级重启判据 → 双证据校验 → 重试 ├── account-store.js 账号库 CRUD ├── session.js session 契约校验/规范化/展示摘要 ├── vault.js 口令与主密钥(PBKDF2-SHA256) └── secure-transfer.js AES-256-GCM 快照加解密 ``` **切换流程(阶段 1 主路径)** ``` 1. 离开前重采:把当前账号的密文刷新一次(降低 refreshToken 轮换导致旧快照失效的风险) 2. 整库备份 state.vscdb 3. 写 secret(BEGIN IMMEDIATE + 事务内读回校验 + 落回滚点) 4. 经 CDP 触发应用自重启(codebuddy:reloadWindow{locale})—— 不需要 taskkill,也不需要用户启停 5. 三级重启判据:新端口就绪 → workbench target 出现 → 实例戳(日志会话目录)变化 6. 双证据生效校验:主进程 getSession.uid **且** 扩展宿主日志 Session restored uid 7. 不一致则重试(默认 3 次),并回读校验(幂等) ``` **零外部依赖**:仅用 Node 内置能力(`node:sqlite`、全局 `WebSocket`、`crypto`)。要求 **Node >= 22.5**。 --- ## 5. 安全设计 | 项 | 做法 | |---|---| | 快照等同凭据 | AES-256-GCM + PBKDF2-SHA256(600,000 迭代)+ 用户口令;`keyfile.json` 只存校验器,不存口令 | | 明文 token | **不落盘**。账号记录只有展示字段;`migrate` 时旧库的 `session.json` 明文 token 被丢弃 | | 日志 | 统一脱敏:长凭据串/JWT/敏感键值 → ``;md5/sha1/sha256 摘要与路径保留可读 | | 误改官方数据 | 每次切换前整库备份 + 回滚点;写入用"临时文件 + rename"原子替换;dry-run 走事务内校验后回滚 | | 跨机迁移 | **不支持**。secret 密文由 Windows DPAPI 绑定当前用户,复制到别的机器解不开(已在 BUG.md #3 记录) | | 环境变量抢占 | 启动器会清理 `CODEBUDDY_AUTH_TOKEN` / `ACC_PRODUCT_CONFIG_V3|V2`(它们会让认证存储不落盘) | --- ## 6. 已知约束 - **切换必须重启客户端**。官方把 `state.vscdb` 全量缓存在内存且不感知外部写入,"库外写 secret 后免重启"机制级不可达(BUG.md #12)。本工具改用**应用自重启**,用户无感。 - **首次引导已自动化**。`panel` 启动前会做一次启动编排:客户端没运行就打开它,在跑但没带调试端口就带端口重启(见 §2.1)。仅当「当前进程无法启动子进程」或「找不到客户端 exe」时才回退为人工启动。 - **代理沙箱内无法 spawn 子进程**(BUG.md #18)→ 进程表判据自动降级为 CDP/日志判据;真实运行环境不受此限。 - **无 GPU / 远程会话下客户端可能起不来**(BUG.md #5)。 - 本工具**不逆向官方加密**:secret 密文全程只做字节搬运,不解读。 --- ## 7. 渊源、致谢与权利声明 ### 7.1 渊源与致谢 cbhelper 的**立项动机与设计思路**来自 [WorkDaddy](https://gitee.com/babygoton/WorkDaddy) —— 一个同样用 CDP 向 Electron 系客户端渲染进程注入界面的开源客户端增强工具(AGPL-3.0-or-later)。 感谢 WorkDaddy 的作者与贡献者:它在 CDP 注入、启动编排、账号备份与还原、"自动化必须由用户显式同意"等方向上的公开实践与踩坑记录,为本项目**省下了大量试错成本**。本项目"零定时任务、只保留手动按钮"的设计取向,也直接受益于其把自动签到当作需用户明示同意的动作的既有做法。 ### 7.2 代码边界:未复制其代码 **cbhelper 未复制 WorkDaddy 的代码,也没有任何代码或运行时依赖。** 具体依据: | 检查项 | 结论 | |---|---| | 账号 / secret 引擎 | **独立实现** —— 目标客户端不同(CodeBuddy CN 是 `state.vscdb` 里的 Electron safeStorage 密文,WorkDaddy 面向 WorkBuddy 的登录文件),机制不可复用 | | CDP target 归属判定 | **独立实现** —— WorkDaddy 的实现面向"同机并存 4 个客户端 profile",本项目只服务单一客户端,按其自身许可建议重新编写 | | 面板 UI / 会话复制 / 签到 | 均为本项目自行设计与实现 | | `require` / `import` 指向 WorkDaddy | **0 处** | | 外部依赖 | `package.json` **没有 `dependencies` 字段**(零外部依赖) | **clone 本仓库即可独立构建与测试,不需要下载 WorkDaddy。** `reference/WorkDaddy` 是**独立的第三方副本**(自带独立 `.git`),**不在本仓库内**、也不被本仓库跟踪。 > **关于源码里出现的 "WorkDaddy" 字样**:那些是**注释中的事实性引用**,分三类 —— > ①说明"对齐其运行方式"、②说明"刻意不照抄其做法"、③记录其结论作为旁证。 > 均**不构成代码复用**。(此处刻意不写死具体处数:这类计数会随代码演进失效,属于典型的 > "断言基数腐化",一旦漂移就会让声明变成失实陈述。) ### 7.3 权利声明(摘要) | 项 | 内容 | |---|---| | 版权 | Copyright (C) 2026 cbhelper 作者 | | 联系方式 | **harrydeep@wardyan.top** | | 许可 | **AGPL-3.0-or-later**(`LICENSE` 为**未改动的官方全文**) | | 商标 | CodeBuddy / 腾讯 / WorkBuddy / WorkDaddy 等名称与标识归其各自权利人所有;本项目与腾讯**无隶属、无合作、无背书**关系 | | 第三方 | 单文件 exe **内嵌 Node.js 运行时(MIT)**;esbuild / postject / commander 仅构建期使用、不随产物分发 | **完整声明(第三方组件清单、商标、免责与使用风险、无担保)见 [`NOTICE.md`](NOTICE.md)。** **使用风险提示**:多账号管理可能违反官方服务条款;切换账号会重启客户端,**未保存内容可能丢失**; 本工具依赖客户端内部结构,官方更新后可能失效。是否使用及后果由使用者自行承担。 --- ## 8. 开发 ```bash node test/run-all.js # 单元测试(当前 338 个用例,零依赖、hermetic) node test/run-all.js store # 只跑某个文件 node test/live-smoke.js 9224 # 真实环境冒烟(只读) node test/live-secret.js # 真实环境 secret 通道(只读 + dry-run) node test/probe-*.js # 环境能力探测(spawn / sqlite / 扩展日志) node test/probe-ensure-plan.js 9224 # 启动编排决策链(只读:不开关客户端) node test/probe-exe-discovery.js # 【只读】客户端 exe 发现链自查(四路 + 模拟"客户端未打开") node test/probe-sea.js # 【exe 必跑】拿真产物验证 SEA 打包(只读) node test/probe-sea.js --build # 先构建再探针 node test/live-panel-dispose.js 9224 # 清理 daemon 停止后残留的"死面板" npm run build:exe # 打包单文件 exe → dist/cbhelper.exe ``` 单测全部使用 `os.tmpdir()` 临时目录,**不需要安装 CodeBuddy 客户端**,也不会碰真实账号库 —— 可直接用于 CI(已配 `.github/workflows/test.yml`:`windows-latest` × node 22.13.0 / 22.x / 24.x)。 > ⚠️ **单测跑的是源码,不能证明 exe 可用** —— 打包后的三处语义变化(`require` / `__dirname` / 面板 asset) > 只有拿**真产物**跑才作数,这就是 `test/probe-sea.js` 存在的理由。 **三类守卫**会把"静默失效"拦在提交前: | 守卫 | 拦住什么 | |---|---| | `test/base.test.js` 的「可共享性守卫」 | 往 `scripts/lib` 写本机盘符字面量(换机器就跑不起来) | | `test/packaging.test.js` 的「打包守卫」 | `.cmd` 非 ASCII / LF 行尾(cmd.exe 解析错位)、`.gitignore` 行尾注释(规则静默失效)、凭据类文件未声明忽略 | | `test/packaging.test.js` 的「exe 打包守卫」 | 内嵌 asset 清单与运行时读取清单不一致(exe 里面板空白)、构建依赖混进 `dependencies`、产物未 gitignore | | `test/packaging.test.js` 的「工具链不阻塞守卫」 | 同步进程调用缺 `timeout`(子进程不退出 → 调用方与整个测试运行器永久挂住) | 文档:`docs/CodeBuddy账号增强助手方案设计.md`(方案与实测回填)、`docs/BUG.md`(已确认的客户端约束)、`docs/CodeBuddy账号增强助手可行性分析.md`(前期可行性论证)、`docs/发布与部署指南.md`(打包部署与开源发布)。 --- ## 9. 可移植性(换机器 / 分享给别人) **源码内不含任何本机特有路径**,所有位置都由环境变量与配置推导 —— 代码可直接分享。 | 项 | 如何确定 | 可否覆盖 | |---|---|---| | 客户端数据目录 | `%APPDATA%\` | 换 profile(`--profile` / `profiles --use`) | | `argv.json` | `VSCODE_PORTABLE` 优先,否则 `%USERPROFILE%\\argv.json` | 同上 | | 客户端安装位置 | **优先读运行中进程的 `ExecutablePath`**;否则按候选列表探测 | 见下 | | 账号库目录 | `--data` > `CBHELPER_DATA` > `config.json:dataDir` > `%APPDATA%\cbhelper` | ✓ | **客户端装在非标准位置时**,用下列任一方式补充(**不用改代码**): ```bash # 方式一:环境变量(临时生效) set CBHELPER_INSTALL_ROOTS=D:\apps;E:\tools ``` ```jsonc // 方式二:项目 config.json(持久;该文件已在 .gitignore,不会随代码分发) { "installRoots": ["D:\\apps", "E:\\tools"] } ``` > 候选顺序:环境变量 → `config.json` → profile 自带;命中即用,全找不到时 `doctor` 会给出提示。 > 若客户端正在运行,`findInstallPath()` 直接从进程读 exe 路径,**与安装位置无关**。 **关于文档里的绝对路径**:`docs/` 下的方案与实测记录中出现的 `H:\MY\...`、`H:\TEMP\...` 是**当时的操作记录**(便于回溯证据),**不是运行依赖** —— 代码与配置均不引用它们。 `test/base.test.js` 内置「可共享性守卫」,会拦截往 `scripts/lib` 里写 `D:/E:` 盘符字面量的改动。 --- ## 10. 卸载 工具不改安装包、不写系统目录,卸载只需三步: ```bash # 1) 移除 CDP 端口配置(保留 argv.json 的注释与格式,自动备份到数据目录 backups/) cbhelper.cmd guide --revert --dry-run # 预演:只报告将改什么 cbhelper.cmd guide --revert # 执行 # 2) 停止面板:关掉 panel-start.cmd 的窗口即可(面板是 daemon 运行时注入的,随窗口结束消失) # 3) 删除数据目录(含加密快照与口令校验器) rmdir /s /q "%APPDATA%\cbhelper" ``` 重启 CodeBuddy 后即完全恢复原状(不再开本机回环调试端口)。 --- ## 11. 许可 **[AGPL-3.0-or-later](LICENSE)** —— `LICENSE` 为 GNU AGPL-3.0 **未改动的官方全文** (`SPDX-License-Identifier: AGPL-3.0-or-later`,与参考项目 WorkDaddy 保持同一许可)。 | 项 | 内容 | |---|---| | 版权 | Copyright (C) 2026 cbhelper 作者 | | 联系方式 | **harrydeep@wardyan.top** | | 许可 | AGPL-3.0-or-later | | 权利声明 / 致谢 / 第三方组件 | [`NOTICE.md`](NOTICE.md) | 本工具为**个人本机客户端增强**,与腾讯官方无隶属关系;不修改、不重签名、不重新打包官方安装包, 不逆向官方加密,不读取/修改/上传官方私有服务器协议。 > 分发 `dist/cbhelper.exe` 时请一并保留 Node.js 的许可声明 —— 该 exe 内嵌了 Node.js 运行时(MIT)。 > 详见 [`NOTICE.md`](NOTICE.md) §3.1。