dsh-portable
Export a whole working DSH home — profile, installed plugins with their node_modules, skills, hooks and task boards — into one .dshpkg tar and import it on another machine without network or pnpm; also streams over HTTP or a one-shot LAN link.
Install
dsh plugin --profile web add dsh-portable
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-portable
给 DeepSeek Harness 用的离线 / 在线导入导出插件:把一台机器上正在工作的 DSH 主目录打包成单个 .dshpkg,在另一台机器上还原 —— 不需要联网、不需要 pnpm install、不需要手工改路径。
[English](README.md) · [CHANGELOG](CHANGELOG.md) · [发布流程](PUBLISHING.md) · [接力文档](HANDOFF.md)
为什么要有它
把 DSH 环境搬到另一台机器,麻烦有三层:
1. 一个能用的 profile 不止一个文件。 它是 profiles/<name>/ 加上带 node_modules 的插件、技能、hooks、任务板、存储、AGENTS.md、已下载运行时、会话日志。
2. 插件是靠 pnpm 联网装的。 内网机、被锁的办公电脑、代理坏掉的机器根本跑不了 dsh plugin add。
3. 配置里写死了绝对路径(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 主目录。
面板怎么用
六个卡片区:
1. 导出 —— 勾选可选根、给文件起名、点导出。打包跑在 worker 线程里;进度条实时显示条目数,20 MB / 14 000 条目的包约 3 秒。
2. 包列表 —— <DSH_HOME>/dsh-portable/exports 下的所有 .dshpkg,显示大小、修改时间,按钮为 选择 / 下载 / 分享 / 删除(删除需二次确认)。选择把包标记给下面的导入卡片;下载把包直接流给浏览器存盘,方便用 U 盘或文件传输拷到另一台机器——局域网分享只在两台机器同网时可用。
3. 上传 —— 选一个 .dshpkg,流式落盘(先写 .part 再改名),带百分比进度,2 GB 的包也不会进内存。
4. 导入 —— 选 merge 或 replace(当前选中项高亮),先预览(零写入),再点「执行导入」,此时会先弹出内联确认块,确认后才真正写入。结果以键值表格呈现:落地项、被重写的路径数、备份目录与 warning,并额外显示插件清单合并结果(本机保留的 bundle、包需要但本机缺失的 bundle)。导入完成后需要重启 DSH,写入的配置才会生效。
5. 局域网分享 —— 把包发布成一次性下载链接(http://<内网IP>:<端口>/d/<token>)。默认 1 次下载、60 分钟,支持 Range/HEAD,过期或下完返回 410。复制会把链接写进剪贴板,且只有真的写成功才显示「已复制」(纯 HTTP 来源下退回 textarea 方案)。无中继、无云端、无账号。
6. 备份 —— 导入前自动生成的备份(<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,不认路径。 见上。
- **导入必
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::12,最后更新 2026-10-20