Skip to content

dsh-portable

Verified

dsh-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 环境搬到另一台机器,麻烦有三层:

  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,不认路径。 见上。
  • 导入必须确认。 缺 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。