dsh-portable
Verifieddsh-portable · v0.1.10 · MIT · Web UI
Offline and online export/import for DeepSeek Harness: pack a whole working DSH home (profile, plugins with node_modules, skills, hooks, AGENTS.md, task boards) into one .dshpkg, restore it on another machine over a file, a URL, or a one-shot LAN share —
Install
dsh plugin add dsh-portable Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-portable
给 DeepSeek Harness 用的离线 / 在线导入导出插件:把一台机器上正在工作的 DSH 主目录打包成单个 .dshpkg,在另一台机器上还原 —— 不需要联网、不需要 pnpm install、不需要手工改路径。
English · CHANGELOG · 发布流程 · 接力文档
为什么要有它
把 DSH 环境搬到另一台机器,麻烦有三层:
- 一个能用的 profile 不止一个文件。 它是
profiles/<name>/加上带node_modules的插件、技能、hooks、任务板、存储、AGENTS.md、已下载运行时、会话日志。 - 插件是靠 pnpm 联网装的。 内网机、被锁的办公电脑、代理坏掉的机器根本跑不了
dsh plugin add。 - 配置里写死了绝对路径(
C:\\Users\\me\\.dsh、/home/me/.dsh)和 profile 名。直接复制目录树,换台机器启动就是一堆坏掉的插件路径。
dsh-portable 三件事一起解决:把白名单内的根打成单个自描述归档;任何时刻都不走出白名单;导入时只重写真正含本机路径的那六个配置文件。
包里装什么
| 根 | 路径 | 默认 |
|---|---|---|
| profile | profiles/<name>(配置和插件的 node_modules) |
总是 |
| skills | skills |
总是 |
| skills-disabled | skills-disabled |
总是 |
| hooks | hooks |
总是 |
| storages | storages |
总是 |
| 任务板 | task-board / todo-board 存储 | 总是 |
| 内置浏览器宿主 | dsh-builtin-browser-host | 总是 |
| 智能体指令 | AGENTS.md |
总是 |
| 会话日志 | sessions | 可选(includeSessions) |
| 运行时 | 已下载的运行时 | 可选(includeRuntimes) |
| 凭据 | .credentials.yaml |
可选,默认关闭 |
| source map | 包内 *.map |
默认包含(includeSourceMaps) |
| 备份文件 | 配置旁 *.bak-* |
默认不含(includeBackups) |
表外的路径永远不会被采集 —— 代码里不存在"把整个主目录递归复制一遍"的步骤。
安装
dsh plugin --profile desktop add dsh-portable
插件必须登记在 profile 自己的 package.json 里 —— dependencies 和 dsh.profile.bundles 两处都要。把包目录手工拷进 profiles/<profile>/node_modules 看起来能跑,但该 profile 下次执行 npm install 时,npm 会把没声明的包当 extraneous 删掉;那之后面板照常渲染卡片,但每个请求都回 404。dsh plugin add(或在 profile 里 npm install dsh-portable)会把这两处一起写好。
然后打开 DSH 设置 → 便携迁移。设置页区块注册在 settings.section、顺序 70,中英文随宿主语言自动切换。
不按按钮就什么都不会写进 DSH 主目录。
面板怎么用
六个卡片区:
- 导出 —— 勾选可选根、给文件起名、点导出。打包跑在 worker 线程里;进度条实时显示条目数,20 MB / 14 000 条目的包约 3 秒。
- 包列表 ——
<DSH_HOME>/dsh-portable/exports下的所有.dshpkg,显示大小、修改时间,按钮为 选择 / 下载 / 分享 / 删除(删除需二次确认)。选择把包标记给下面的导入卡片;下载把包直接流给浏览器存盘,方便用 U 盘或文件传输拷到另一台机器——局域网分享只在两台机器同网时可用。 - 上传 —— 选一个
.dshpkg,流式落盘(先写.part再改名),带百分比进度,2 GB 的包也不会进内存。 - 导入 —— 选 merge 或 replace(当前选中项高亮),先预览(零写入),再点「执行导入」,此时会先弹出内联确认块,确认后才真正写入。结果以键值表格呈现:落地项、被重写的路径数、备份目录与 warning,并额外显示插件清单合并结果(本机保留的 bundle、包需要但本机缺失的 bundle)。导入完成后需要重启 DSH,写入的配置才会生效。
- 局域网分享 —— 把包发布成一次性下载链接(
http://<内网IP>:<端口>/d/<token>)。默认 1 次下载、60 分钟,支持 Range/HEAD,过期或下完返回 410。复制会把链接写进剪贴板,且只有真的写成功才显示「已复制」(纯 HTTP 来源下退回 textarea 方案)。无中继、无云端、无账号。 - 备份 —— 导入前自动生成的备份(
<DSH_HOME>/portable-backups/<stamp>),保留最近 5 份。每份都有恢复按钮:把该备份里的文件写回主目录,写回前会先把当前版本另存到<stamp>-pre-restore。恢复后同样需要重启 DSH。
界面截图
截自 DSH 设置面板;图中的主目录用户名与局域网分享链接均为示例值。


HTTP API
前缀:/dsh-portable/api。除下载路由与上传流式路由外,响应都是 JSON。鉴权复用宿主的 connection.requestRejection(cookie + 可信来源),该服务不存在时降级为仅回环地址可访问。
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /status |
宿主信息、包数量、当前任务、分享服务状态 |
| GET | /profiles |
本机 DSH 主目录里的 profile |
| GET | /jobs |
最近的任务(导出 / 预览 / 导入) |
| GET | /jobs/detail?id= |
单个任务的实时进度 |
| POST | /jobs/cancel |
取消任务 |
| POST | /export |
开始导出 |
| GET | /artifacts |
可导入的包列表 |
| GET | /artifacts/meta?id= |
单个包的 manifest |
| GET | /artifacts/download?id= |
以附件形式把包流给浏览器(存到本机;HEAD 只回头部) |
| POST | /artifacts/delete |
删除包(UI 里二次确认) |
| PUT | /upload/<name> |
上传包,流式落盘 |
| POST | /import/preview |
两阶段安装第一阶段(零写入) |
| POST | /import |
两阶段安装第二阶段(必须 confirm:true) |
| GET | /backups |
自动备份列表 |
| POST | /backups/restore |
把某份备份写回主目录(写回前先另存当前文件) |
| POST | /share |
生成一次性局域网下载链接 |
| GET | /share |
列出有效链接 |
| POST | /share/remove |
撤销链接 |
所有涉及"哪个包"的参数都只接受 artifactId(如 exports:machine-20261018.dshpkg),绝不接受文件路径。 /、\\、.. 与控制字符一律拒绝,所以模型或网页都没法借这个插件读写任意文件。
模型工具
注册到宿主的 tools 服务(服务不存在时静默跳过):
| 工具 | 行为 |
|---|---|
portable_export |
打包当前 profile,返回 id、大小、条目数、各根统计 |
portable_inspect |
只读 manifest,不导入 |
portable_import |
不带 confirm:true 就是 dry-run;带上才真正两阶段安装 |
portable_share |
生成一次性局域网链接(1 次下载、60 分钟) |
无宿主引导安装(目标机没装 DSH)
# Windows
powershell -ExecutionPolicy Bypass -File bootstrap\install.ps1 preview .\machine.dshpkg
powershell -ExecutionPolicy Bypass -File bootstrap\install.ps1 install .\machine.dshpkg --yes
# macOS / Linux
sh bootstrap/install.sh preview ./machine.dshpkg
sh bootstrap/install.sh install ./machine.dshpkg --yes
参数:--home <目录>(默认 $DSH_HOME 或 ~/.dsh)、--profile <名>、--mode merge|replace、--keep <n>、--yes。退出码:0 成功、1 失败、2 用法错误;非 TTY 且没有 --yes 时拒绝写入。
包格式
.dshpkg 就是 gzip 压缩的 tar,manifest 是第一条目:
.dsh-portable/manifest.json
profiles/desktop/...
skills/...
hooks/hooks.json
...
{
"format": "dsh-portable/1",
"createdAt": "2026-10-18T02:11:03.000Z",
"tool": { "name": "dsh-portable", "version": "0.1.0" },
"source": {
"hostname": "WORKSTATION",
"platform": "win32",
"arch": "x64",
"node": "v22.23.2",
"dshHome": "C:\\Users\\me\\.dsh",
"profile": "desktop"
},
"options": { "includeNodeModules": true, "includeSessions": false },
"stats": { "entries": 13774, "files": 13482, "dirs": 292, "links": 0, "bytes": 20664511 },
"roots": [{ "id": "skillsDisabled", "logical": "skills-disabled", "files": 13193, "bytes": 44615569 }]
}
因为就是普通 tar,tar -xzf machine.dshpkg 在任何机器上都能直接看包里的内容 —— 哪怕那台机器从没听说过 DSH。长路径走 PAX 扩展头,读取端是增量解析,PAX 正文跨 chunk 也能正确处理。
导入流水线
预览: 读 manifest ──► 每个顶层项的文件数/字节数/目标机是否存在 ──► 报告(零写入)
安装: 解包到 <home>/portable-staging/<stamp>
──► 目标 profile 名不同则重命名目录
──► 重写 6 个配置文件里的本机路径
──► 这 6 个文件备份到 <home>/portable-backups/<stamp>
──► 逐顶层项落地(merge:只增不减;replace:先移走再落)
──► 清理旧备份 ──► 删除暂存区
路径重写刻意收窄:只碰 profiles/<p>/cordis.patch.yml、cordis.yml、package.json、pnpm-workspace.yaml、hooks/hooks.json、hooks/settings.json。插件包内部不重写,第三方完整性校验与 source map 不受影响。
安全模型
- 白名单采集。 代码里不存在递归遍历整个主目录的逻辑。
- 只认 artifactId,不认路径。 见上。
- 导入必须确认。 缺
confirm:true的路由返回400 CONFIRM_REQUIRED;模型工具默认 dry-run。 - 解包防护。 200 000 条目上限、单文件 2 GB、总解压 8 GB、压缩比 200(输出超 64 MB 后才生效)、
wx不覆盖、safeJoin挡..、符号链接为绝对路径或越界时拒绝。 - 写之前先备份。 六个配置文件先复制到备份目录;replace 模式把整个目录移进
portable-backups/<stamp>/replaced/,不是删除。 - 凭据默认不进包。
.credentials.yaml只有显式勾选才打包,面板上的勾选框直接标注风险。导出是纯本地操作,不上传任何数据。 - 分享链接范围极小。 一次下载、60 分钟、单文件、token 门禁,之后 410。没有目录列举,没有中继。
开发
npm test # 60 项:tar、archive、installer、jobs、routes、share、inspect
npm run check # 语法检查 + import 冒烟
npm run sync # 把本包拷进某个 profile 的 node_modules 做真机联调
npm run verify # 发布门禁:版本一致、文件清单、测试
没有构建步骤:index.js(宿主端)、lib/client.js(浏览器端 bundle,纯 React.createElement)、lib/*.mjs 都是直接发布的源码。这是刻意约束 —— 插件必须能在包版本未知的宿主上加载,所以客户端半边只依赖 react 和 --dsw-alias-* CSS 变量。
兼容性
- DSH
>= 0.1.7-rc.2 - Node
^22.15.0 || >= 24.0.0 - Windows / macOS / Linux(Windows 长路径走
\\?\前缀)
已知限制
- 会话日志默认不进包,因为里面有对话内容。
- 包是 tar,不加密也不签名;含凭据时请放在加密介质上。
- 导入只能按顶层根整体 merge 或 replace,v0.1.x 没有单文件选择器。
- 指向 DSH 主目录之外的符号链接会被打进包,但在 Windows 上没有开发者模式时无法重建 —— 这种情况记 warning,不中断导入。
许可
MIT,见 LICENSE。