WorkBuddy U 盘便携版制作工具
概述
把本机安装的 WorkBuddy(Electron 桌面版)与 .workbuddy 数据目录拼成一个自包含文件夹,用户手动拷到 U 盘,插任意 Windows 电脑双击启动器即用。核心难点是「路径搬家」——数据目录里存在写死的绝对路径,换盘符必失效。WorkBuddy 没有内置便携模式开关,但留了官方环境变量/命令行钩子,配合启动前自愈脚本即可达成。日常更新走增量同步(只传变化文件,几分钟搞定),不必每次全量重拷。
触发场景:制作 WorkBuddy 便携版 / U 盘便携 / 随身 WorkBuddy / 把 WorkBuddy 装进 U 盘 / 便携打包 / 重新打包便携包 / 便携版打不开 / 增量同步到U盘 / U 盘更新。
快速开始(四步行云流水)
# 1) 一键打包(自动:定位安装与数据目录 → 校验钩子 → robocopy → 生成启动器/放入自愈脚本/README)
powershell -ExecutionPolicy Bypass -File scripts\build_portable.ps1
# 2) 合成自检(不碰真实数据,43 项断言全绿才算过关)
powershell -ExecutionPolicy Bypass -File scripts\selftest.ps1
# 3) 交付:把输出文件夹的「内容」拷到 U 盘根目录,保持三者同级:
# <U盘>\WorkBuddy\ <U盘>\WorkBuddyData\ <U盘>\WorkBuddy-Portable-Launcher.bat
# 4) 日常增量更新(插上 U 盘后从 PC 推变化文件上去,几分钟而非半小时)
powershell -ExecutionPolicy Bypass -File scripts\sync_portable.ps1
核心原理(取证结论,来自 resources/app.asar)
| 钩子 | 作用 | 代码实证 |
|---|---|---|
WORKBUDDY_CONFIG_DIR 环境变量 |
取代 ~/.workbuddy;技能、MCP、模型、密钥全走这里 |
process.env.WORKBUDDY_CONFIG_DIR?.trim() → 否则 WORKBUDDY_DATA_FOLDER_NAME(默认 .workbuddy)→ 否则 join(homedir(), t) |
| Electron userData | Chromium 档案 / 登录态 / 会话,默认嵌套在 <CONFIG_DIR>\app |
getWorkbuddyUserDataDir(){ return process.env.WORKBUDDY_USER_DATA_DIR?.trim() \|\| path.join(getWorkbuddyConfigDir(),'app') } |
--user-data-dir=<path> 命令行参数 |
Chromium 级 userData,作双保险 | process.argv.filter(e=>e.indexOf('--user-data-dir=')===0) |
结论:只需设 WORKBUDDY_CONFIG_DIR 一个变量,技能与 Chromium 档案即全部落到 U 盘。 另有别名 CODEBUDDY_CONFIG_DIR 亦被识别。详细取证过程(含代码片段与版本基线)见 references/FINDINGS.md。
「插上 U 盘从账号自动更新」为何不成立(实证结论)
用户常设想:插上 U 、登录账号、一切自动从云端恢复。对 app.asar 全量取证后结论是否定的,三层证据:
| 资产 | 是否跟账号走 | 证据 |
|---|---|---|
| 登录态(Chromium profile) | ✅ 跟 U 盘走(首次登录后留 WorkBuddyData\app) |
userData 嵌套在配置目录内 |
| 市场安装的技能 | ⚠️ 可手动从市场重装,非自动 | installSkill×31、downloadSkill×23、api.skillhub.cn、COS 扩展桶 |
| 专家包(experts) | ⚠️ 有云同步通道,但仅限专家包且需手动触发 | cloudSync×67 全部形如 wb.experts.cloudSync.list/upload/download/deleteCloud |
.secrets.env 密钥 |
❌ 纯本地文件,与账号无关 | 密钥回退链:系统环境变量 → .secrets.env → 报错 |
binaries(Python venv/运行时 2.5G) |
❌ 本地下载缓存 | 位于配置目录内 |
mcp.json 连接器配置 |
❌ 本地文件 | connectors\default\mcp.json |
| 工作区数据(workspace/projects) | ❌ 本地 | 见 defaultWorkspacePath |
关键否定证据:settingsSync / configSync / accountSync / syncSettings / userSettingsSync 在 asar 中全部 0 次命中;200+ 个 WORKBUDDY_* 环境变量中无任何云同步类变量。
因此正确姿势:首次手工打包一次,之后用 sync_portable.ps1 做增量更新(见下节)。市场技能虽可重装,但自定义技能、密钥、运行时、工作区仍必须随 U 盘同步。
打包后必须改写的绝对路径(完整清单)
| # | 位置 | 问题 | 处理 |
|---|---|---|---|
| 1 | binaries\python\envs\*\pyvenv.cfg 与 Scripts\activate* |
写死基座解释器路径;venv 靠 home 找 python,错则启动失败 |
启动器运行时正则改写 |
| 2 | app\app-config.json 的 defaultWorkspacePath |
设置-通用-「默认工作空间存储路径」 | 启动器运行时改写为 <U盘>\WB 并建目录 |
| — | connectors\default\mcp.json |
已确认干净:全远程 url / npx,密钥走 ${ENV_VAR} |
无需处理 |
| — | .secrets.env、settings.json、workbuddy.db |
已确认干净:无绝对路径 | 无需处理 |
关键教训:venv 的前缀形态不唯一。 default venv 用 C:\Users\<用户>\.workbuddy\binaries\python\...(junction 逻辑路径),而 sanzijing 等 venv 用 E:\...\.workbuddy\binaries\python\...(物理路径)。因此禁止用固定旧前缀做字符串替换,必须用正则匹配「任意盘符 + 任意中间路径 + \.workbuddy\binaries\python\」再替换为目标路径,否则漏改一半。
工作流
步骤 1:取证(升级 WorkBuddy 后必须重做)
ASAR="D:/Softwares/WorkBuddy/resources/app.asar"
cat "D:/Softwares/WorkBuddy/version"
grep -a -o '.\{60\}WORKBUDDY_CONFIG_DIR.\{200\}' "$ASAR" | head -3
grep -a -o '.\{80\}--user-data-dir=.\{80\}' "$ASAR" | head -3
grep -a -o '.\{100\}defaultWorkspacePath.\{100\}' "$ASAR" | head -3
若 WORKBUDDY_CONFIG_DIR 或 defaultWorkspacePath 已改名,先更新本技能再继续。
步骤 2:打包
运行 scripts/build_portable.ps1(自动完成:定位安装目录与数据目录 → 校验钩子 → robocopy 应用本体 → robocopy 数据(排除 logs、file-history)→ 生成启动器(New-LauncherBat.ps1,非资产拷贝)→ 放入自愈脚本/同步脚本/README)。
- 钩子校验默认是硬门禁:
app.asar里找不到任一钩子即终止打包(防止产出「不搬家」的无效包);确认过风险后可用-Force跳过。 DATA_EXIT=8/9属预期:少量app\session\Network\Cookies被运行中的 WorkBuddy 锁住,浏览器 cookie 可再生,首次启动重新登录即可。
步骤 3:启动前自愈
启动器调用 portable-prep.ps1 <U盘根目录>,完成三件事:改写 venv 路径、改写 defaultWorkspacePath、创建 WB 工作区目录。幂等(重复运行 fixed=0)。退出码语义:0=成功(单项软失败只记日志不阻塞启动),1=包结构坏(启动器会停下报错)。
步骤 4:交付
把整个文件夹交给用户手动拷到 U 盘。启动器用 %~dp0 取真实路径,换盘符、换文件夹层级均自适应;传给 prep 的参数去掉尾反斜杠(%WB:~0,-1%),避开 Windows 命令行 \" 转义陷阱。
步骤 5:验证(必做,不可跳过)
- 合成自检:运行
scripts/selftest.ps1——在临时目录搭合成迷你包(不碰真实数据),覆盖 venv 两种前缀改写、JSON 有效性与嵌套保留、幂等、畸形参数、硬错误退出码、启动器生成器契约(ASCII/CRLF/关键行/幂等)、增量同步双向/排除/镜像/干跑等 43 项断言。
- ⚠️ 绝不可对正式暂存包运行自愈脚本:那会把暂存路径烤进 venv,之后 U 盘上再跑时正则匹配不到旧前缀,导致漏改。 - 真机抽检:用户拷到 U 盘后在另一台 Windows 上双击启动器,确认能进界面、技能在、工作区指向
<U盘>\WB。
增量同步(日常更新,替代全量重拷)
场景正是「经常插 U 盘到电脑进行更新」:PC 上新装了技能、改了配置、换了密钥,插上 U 盘跑一次同步,只传变化的文件(通常 1–3 分钟),不必再全量拷 9.6 GB / 35 万文件(半小时+)。
# PC → U盘(默认方向):把 PC 上的新技能/新配置/新密钥推到 U 盘
powershell -ExecutionPolicy Bypass -File scripts\sync_portable.ps1
# U盘 → PC:出差归来,把 U 盘上新建的工作区/对话带回 PC
powershell -ExecutionPolicy Bypass -File scripts\sync_portable.ps1 -Direction USBtoPC
# 干跑预览:只看会传什么,不落一个字节
powershell -ExecutionPolicy Bypass -File scripts\sync_portable.ps1 -DryRun
# 镜像模式(危险):让目标与源完全一致,删除目标独有文件
powershell -ExecutionPolicy Bypass -File scripts\sync_portable.ps1 -Mirror
| 设计 | 说明 |
|---|---|
默认 /XO |
只复制源比目标新的文件;目标独有文件保留(不删任何东西) |
| 排除项 | logs、file-history、__pycache__、.pytest_cache、*.log、*.tmp、Thumbs.db、desktop.ini |
| junction 解析 | PC 侧 .workbuddy 若是 junction(如指向 E:\WorkBuddyData),自动解析真实路径再同步 |
| 自我同步防护 | PC 数据目录若位于 U 盘包内(正在跑 U 盘便携版时),直接报错退出,绝不自己同步自己 |
| 锁文件 | 运行中的 WorkBuddy 锁住 Cookies 等 → robocopy 重试 1 次后跳过,软失败,同步照常完成 |
| 退出码 | 0=成功(含「无变化」);1=参数/包结构错;2=robocopy 真失败(≥8) |
| 日志 | 每次同步追加到 <U盘>\sync-portable.log |
能力边界(明确不做什么)
| 边界 | 说明 |
|---|---|
| 仅 Windows | 脚本依赖 PowerShell 5.1 + robocopy + cmd;macOS/Linux 不在范围内 |
| 单机构建 | 打包依赖本机安装目录 D:\Softwares\WorkBuddy(可用 -AppSource 改) |
| 不含账号云同步 | 已实证:asar 无 settings/config/account sync;cloudSync 仅专家包专用。「插上登录即恢复」不成立 |
| 增量同步非实时 | 手动触发;不做文件监听/后台常驻;不做跨设备网络同步 |
| 不含旧工作区迁移 | 便携版用全新 WB 工作区;搬旧工作区需另行拷贝 workspace\、projects\(可用 sync 之后手动搬) |
| 不含自动更新托管 | 自动更新会写向 U 盘;不干预、不禁止 |
| 不含「联机自动更新」 | 已评估并否决:WB 小程序/账号同步均无此能力(见 FINDINGS 第 9 节);manifest+静态托管方案仅设计未实施 |
| 不改宿主机 | 便携模式只写 U 盘;secret_store 检测 WORKBUDDY_CONFIG_DIR 后禁用 setx,不污染别人电脑 |
安全须知
- 密钥随包走:
.secrets.env(可能含多个 API Key)会原样进入 U 盘。U 盘遗失 = 全部密钥泄露,README 中已向终端用户明示;建议定期轮换密钥。 -ExecutionPolicy Bypass的必要性:U 盘脚本默认不受信任,不带此参首次启动会被执行策略拦截。脚本全部明文可审,无混淆、无外部通信、无远程下载。- 写操作范围:prep 只写
<U盘>\WorkBuddyData\内的 venv 配置、app-config.json、日志与WB目录;sync 只按 robocopy 语义写源→目标。均不碰宿主机注册表、不改系统设置。 - 无网络行为:全链路(build / prep / sync / launcher)不发起任何网络请求。
-Mirror风险:会删除目标独有文件。务必先-DryRun -Mirror预览。
插到别人电脑上的安全模型(实测 + 合成测试背书)
| 担忧 | 结论 | 机制 |
|---|---|---|
| 别人的 WorkBuddy 数据会被读进我的 U 盘吗 | 不会 | 启动器先 set "WORKBUDDY_CONFIG_DIR=<U盘>" 再 start,覆盖式劫持;别人机器上的 .workbuddy 一个字节都不读 |
| 别人的环境变量(同名 API Key)会劫持我的 U 盘吗 | 不会 | secret_store 便携模式(检测 WORKBUDDY_CONFIG_DIR 自动激活):U 盘 .secrets.env 优先于继承 env;env 仅在 U 盘缺该键时兜底 |
| 在别人机器上写密钥会污染它的系统吗 | 不会 | 便携模式下 put_secret 只写 U 盘文件,绝不 setx |
| 我会不会登成别人的账号 | 不会 | 登录态(Chromium profile)在 U 盘上,插到哪都用我的身份 |
| 我会泄漏什么到别人电脑 | 少量痕迹 | AppData\Local\WorkBuddy\logs、%TEMP%\workbuddy-*(日志/临时文件,非密钥) |
| 更新会同步别人的东西进来吗 | 不会 | 更新源是只读 manifest(我发布的内容);无任何上传/回报逻辑 |
> ⚠️ 反向风险自己心里有数:U 盘 = 你的完整身份(登录态 + 密钥)。谁拿到 U 盘谁就能当你。物理保管 + 建议 BitLocker 加密 U 盘。
脚本与资产
| 文件 | 作用 |
|---|---|
scripts/build_portable.ps1 |
一键打包(robocopy,钩子硬门禁 + -Force,排除可再生垃圾) |
scripts/portable-prep.ps1 |
启动前路径自愈(幂等、带日志、畸形参数免疫) |
scripts/sync_portable.ps1 |
PC↔U 盘增量同步(/XO 默认不删、双向、-DryRun、-Mirror opt-in) |
scripts/fix_portable_paths.ps1 |
把技能脚本里的 Path.home() / ".workbuddy" 改写成便携感知(幂等、自愈误伤、from __future__ 安全) |
scripts/test_portable_safety.ps1 |
便携安全合成测试(11 项:防 env 劫持/不污染宿主/非便携回归/bootstrap 双模式/真技能编译) |
scripts/selftest.ps1 |
合成自检(43 项断言,exit 0 = 全绿) |
scripts/New-LauncherBat.ps1 |
启动器生成器:打包时产出 WorkBuddy-Portable-Launcher.bat(纯 ASCII + CRLF + 无 BOM,写后自校验)。不直接ship .bat 是因为 SkillHub 发布包白名单拒收 .bat(HTTP 400,与 _icon.png 同形态),生成器方案让单一事实源留在可 diff 的 PowerShell 源码里 |
assets/README.txt |
终端用户中文说明 |
references/FINDINGS.md |
取证笔记:钩子实证、路径审计、体积基线、PowerShell/命令行坑 |
依赖声明:仅 Windows PowerShell 5.1+ 与系统内置 robocopy/cmd,零第三方依赖。
故障排查
| 现象 | 原因 | 处置 |
|---|---|---|
启动器报 WorkBuddy\WorkBuddy.exe not found |
文件夹结构被改动,三者不同级 | 保持 WorkBuddy\、WorkBuddyData\、启动器同级 |
启动器报 portable-prep failed |
包结构坏 / 参数畸形 | 看 WorkBuddyData\portable-prep.log;确认 prep 与启动器同级 |
| 技能报 python 找不到 / venv 崩 | venv pyvenv.cfg 未改写 |
检查日志中 venv fixed 行;正则必须含反斜杠:[A-Za-z]:\\[\w.\\-]*?\\\.workbuddy\\binaries\\python\\ |
| 工作区路径没变 | app-config.json 键名变更 |
重新取证 defaultWorkspacePath;脚本已支持键缺失时自动注入 |
| 工作区为空 / 会话丢失 | 预期行为:U 盘上是全新 WB 工作区 |
需要旧工作区就单独拷贝 workspace\、projects\ |
| 首次启动要重新登录 | 打包时 Cookies 被运行中的 WorkBuddy 锁住 | 预期行为;登录态之后留在 U 盘 |
同步报 cannot locate this PC's .workbuddy |
homedir 下无 .workbuddy 且未设 WORKBUDDY_CONFIG_DIR |
显式传 -Source <路径> |
同步报 USB stick cannot sync onto itself |
正在跑 U 盘便携版,PC 侧解析到了 U 盘路径 | 关掉 U 盘上的 WorkBuddy 再同步,或显式传 -Source |
| 同步后 U 盘文件没变 | 源文件 mtime 不新于目标(/XO 语义) |
先 -DryRun 看清单;确认方向不是反的 |
| 打包中途报 hook MISS 并终止 | WorkBuddy 升级改了钩子名 | 重跑步骤 1 取证;确认无风险后 -Force |
| 打不开 / 白屏 | 宿主机器缺 VC++ 运行库或 GPU 兼容问题 | Electron 自带运行库;尝试加 --disable-gpu |
| 打包中途报 launcher generation failed | New-LauncherBat.ps1 丢失或自校验不过(非 ASCII/混用 LF) |
确认生成器与 build 脚本同级;手动跑一次生成器看报错 |
FAQ 与反模式
Q:U 盘要多大? 完整包 9–10 GB(应用 1.2 GB + 数据 8–9 GB,35 万文件)。64GB 以上、USB 3.2/固态型;也可只挑必要技能砍体积。
Q:插上 U 盘能自动从账号更新吗? 不能,已实证(见上节)。登录态随 U 盘走;市场技能可手动重装;密钥/运行时/工作区必须靠 sync_portable.ps1 同步。
Q:插到别人的电脑上,启动器会把别人的东西同步进我的 U 盘/账号吗? 不会,且反向也做了防护:① 启动器覆盖式设置 WORKBUDDY_CONFIG_DIR,别人机器上的 WorkBuddy 数据一个字节都不读;② secret_store 便携模式让 U 盘 .secrets.env 优先于继承来的环境变量——别人机器上同名的 API Key 劫持不了你的 U 盘身份;③ 便携模式下 put_secret 绝不 setx,不污染别人电脑;④ 登录态在 U 盘上,插到哪都是你的号。你自己泄漏出去的只有 logs/%TEMP% 痕迹。详见「安全须知 → 插到别人电脑上的安全模型」表。
Q:增量同步必须双向吗? 默认 PCtoUSB 就是「插上 U 盘更新」场景。USBtoPC 仅在「出差期间在 U 盘上新建了工作区/对话、想带回 PC」时才用。
Q:以后 WorkBuddy 升级了怎么办? 只需重跑步骤 1 取证确认钩子未变;钩子没变就只换 U 盘上的启动器 + prep 两个小文件,9.6 GB 大包不用动。
Q:为什么不用 ConvertFrom/ConvertTo-Json 改 app-config.json? ConvertTo-Json 默认序列化深度只有 2 层,app-config.json 的嵌套对象(如 personalization)会被静默截断损坏。正则只替换值,不动结构,更安全。
Q:prep / sync 失败了会怎样? prep 软失败(单文件被锁)只记日志、照常启动;硬失败(包结构坏)启动器停下并显示日志路径。sync 失败按退出码区分(0/1/2),永不静默吞错。
Q:同步时 U 盘上的 WorkBuddy 正在运行可以吗? 可以,但运行中的 Cookies 等锁文件会被跳过(软失败)。追求完全一致就先退出 U 盘上的 WorkBuddy。
反模式清单(每条都是实测踩过的坑):
1. ❌ 对暂存包跑 prep —— 会把暂存路径烤进 venv,U 盘上改不回来
2. ❌ 固定旧前缀字符串替换 venv 路径 —— 前缀形态不止一种,必漏
3. ❌ 正则字符类漏反斜杠 —— [\w.-] 匹配不了 C:\Users\...,实测 fixed=1 只改到 app-config
4. ❌ %~dp0 原样传参 —— 尾反斜杠 + 引号触发 Windows 命令行转义,参数到 prep 手里会变形
5. ❌ 信 grep -c $'\r' 判行尾 —— Git Bash 文本模式会吞 \r 误报,必须字节级检测(od / python)
6. ❌ 跑 U 盘便携版时执行 USBtoPC 同步 —— PC 侧会解析到 U 盘自己,脚本虽已防护报错,但说明方向搞反了
7. ❌ -Mirror 不先 -DryRun —— 会删目标独有文件(如 U 盘上新建的工作区)
8. ❌ 自检里复用早期创建的文件断言 /MIR 删除 —— 前序 USBtoPC 步骤已把它搬到对侧,「目标独有」前提被污染、断言假失败(本轮实测踩坑);mirror 断言必须用全新文件,robocopy /MIR 行为已手工复证(目标独有标 *extra 并删除,exit 3)
9. ❌ PowerShell -like '[FAIL]*' 匹配失败行 —— 方括号是通配符字符类,恒不匹配字面量;失败检测失灵、FAIL 被放跑,必须 -match '^\[FAIL\]'
10. ❌ 便携包复用主力机的 lib/secret_store.py 旧版 —— 旧版在别人电脑上要么用别人的 env 密钥(串号)、要么读别人 home 的回退文件、要么直接缺密钥;必须升级到带便携模式的版本并跑 fix_portable_paths.ps1
11. ❌ bootstrap 块插在 from __future__ 之前 —— SyntaxError: from __future__ imports must occur at the beginning of the file;插入点必须跳过 __future__ 行(本轮实测 3 个文件坏掉)
12. ❌ 替换正则不加边界 —— Path\.home\(\) 会命中 _pb_Path.home() 的尾部子串,把 bootstrap 块误伤成 _pb__wbroot()(_wbroot() 内部无限递归);必须 (?<![A-Za-z0-9_]) 负向断言 + 误伤自愈
13. ❌ PowerShell 函数内 $results += ... 收集结果 —— 数组是值类型,函数内追加只改局部副本,调用方永远拿到空(本轮实测 8 个 PASS 全丢);必须 return 行数组由调用方追加
14. ❌ Start-Process 在 WorkBuddy 宿主环境跑 —— 宿主进程 env 同时含 Path 与 PATH 键,PS 5.1 抛「字典中的关键字 Path」ArgumentException;子进程重定向用 cmd /c "... > out 2>&1"
15. ❌ Out-File -Encoding ascii 生成 python 源码 —— PS 5.1 会加 BOM,python 第一行就 SyntaxError;用 [IO.File]::WriteAllText(p, s, (New-Object Text.ASCIIEncoding))
16. ❌ PS here-string 插值后的 Windows 路径直接进 python 字符串字面量 —— \U 触发 unicodeescape 报错;必须 r'...' raw 前缀;单引号字符串也不能跨行(用 chr(10).join)
17. ❌ 发布包里直接放 .bat 文件 —— SkillHub 白名单拒收(HTTP 400,与 _icon.png 同形态,--dry-run 却照过);正解是 ship 生成器(New-LauncherBat.ps1)、打包时产出 .bat,单一事实源留在可 diff 的源码里
体积与性能
- 完整包约 9–10 GB(应用 1.2 GB + 数据 8–9 GB,35 万文件);全量拷贝慢的主因是海量小文件而非体积
- 增量同步:
/XO只传较新文件,日常更新通常 1–3 分钟(取决于变化量) - 建议 U 盘 64 GB 以上、USB 3.2 / 固态型;机械 U 盘体验差
- 排除
logs\(约 1.4 GB)与file-history\(可再生)可显著减负