前言¶
DSH 的理念是「一切皆插件」。实际用过几个 profile 之后,问题会慢慢变成第二类问题:插件装在哪台机器上就跟着哪台机器走。换新机器要重新找仓库、重新 clone、重新填一份配置;从本地目录(link: / file:)装的插件甚至连「重新找」这一步都没有,因为本来就没有 registry 版本可重装。
dsh-plugin-sync 给出的办法是:把插件清单、版本、启用状态和配置写进你自己的 GitHub 私有仓库,不经过任何第三方云。插件由 Jfreey 维护,在社区目录里归在「联网工具」分类下,仓库目前星标仍是 0,属于早期项目。
下面介绍它做什么、怎么装、怎么用、以及有哪些必须知道的边界。
这是什么¶
一句话定位:它用你自己的私有 GitHub 仓库当 DSH 插件的清单和源码备份,跨机器同步靠「推送 / 拉取」两端自己确认。
它不替代插件管理器。安装、卸载、升级仍然在 DSH 的插件管理器里做;本插件只负责把「当前装了什么、什么版本、哪些配置、来源在哪」记录下来,推送到仓库,再在别的机器上按相同的记录装回来。
维护者是 Jfreey,许可证为 MIT。源码托管在 GitHub 的 Jfreey/dsh-plugin-sync,目录页是 skillhub.cn 上的插件索引页(社区目录,与 DeepSeek / 幻方无官方从属关系)。
已核实的核心能力¶
按 README 的「做什么」一节整理,下面这些能力都能在仓库里核对。
清单:记录所有非安装自带的 bundle,包括名称、版本、启用状态、安装来源、声明的 row id。
来源追溯:每个插件从哪来,优先级是本地 checkout 的 .git remote、其次包自身的 repository 字段,monorepo 会一直显示到子目录;查不到就标「来源未知」,不猜。
配置备份:备份 profile 里这些 row 的 override 层。离开本机前,令牌形态的值统一替换成 <redacted>;apiKeyEnv 这类「指向密钥的环境变量名」原样保留,因为它本身不是密钥。
源码备份:link: / file: 安装的插件没有 registry 版本,源码会以真实文件提交到同一个私有仓库,一次推送一个提交,可以在 GitHub 上直接读、看 diff、用仓库搜索。还原时下载、校验 SHA-256、写盘,再 link 安装。
还原:缺失安装、版本替换、写入配置、切换启用状态,分组勾选。README 明确写了「你没点,它不动」,每条结果回报宿主真正做了什么。
更新检查:来源发布了比本机新的版本时给提示;npm 装的能一键更新,本地目录的留给你自己处理。
清理:每行一个「同步」开关(只影响这台机器);「清理备份」能删掉清单条目,也能连带删掉仓库里的源码文件。
推送与拉取:推送在插件变更后自动触发(30 秒防抖)加手动;拉取是定时加手动,只算差异,不会自己动手。
安装与启用¶
README 给出的安装方式是把插件管理器指向仓库目录,本仓库本身就是可安装 bundle(package.json 声明了 dsh.bundle):
dsh plugin --profile desktop add /绝对路径/plugin-sync
注意命令里的路径是本地 checkout 或提取出来的目录,不是 github: 形式。装完刷新页面,设置里会出现「插件同步」入口。
要求 DeepSeek Harness 0.2.x,依赖 settings.section、ctx.locale、ctx.pluginManager、ctx.configEditor、ctx.credentials 这几个契约。低于这个版本不会可用。
绑定仓库与登录¶
插件需要一个私有 GitHub 仓库来承载数据。默认数据仓库名是 dsh-plugin-sync-data,dsh-plugin-sync 这个名字留给插件自己的公开仓库,两者不撞名。
1、建私有仓库:打开 github.com/new?name=dsh-plugin-sync-data&visibility=private,点击 Create。README 特别提示要建空仓库,不要勾 README。
2、生成细粒度令牌:页面提供了预填链接,令牌的名称、描述、90 天有效期、Contents: write 都已设置好。Repository access 选 Only select repositories,勾上刚建的仓库,Generate,复制 github_pat_…。
3、登录:把令牌粘进第一个输入框,点「从我的仓库里选」——这一步令牌只用于列仓库,不落盘——挑一个仓库,再点「校验并保存」,这时才真正登录并存令牌。
README 里有两条容易被忽略的行为。第一,登录后先拉取、不会把本机状态推上去,所以新机器用空清单或旧版本覆盖共享 manifest 的风险被规避了;要让仓库第一次有内容,登录后点一次「推送」。第二,仓库必须是私有的,配置会离开本机,公开仓库等于公布;公开仓库在登录时和每次推送前都会被拒,除非显式勾选允许公开仓库的确认项。
选到一个不是同步仓库的仓库会被拦住:非空且没有 manifest 的仓库是别人的项目,页面会列出其中几个文件名,你确认后才写;同名文件但格式不对则直接拒绝。经典令牌(repo scope)也能用,但权限更宽,README 不推荐。插件不读 git 凭据助手,也不读钥匙串。
仓库里实际有什么¶
根目录是一个 dsh-plugin-sync.json,记录设备和插件条目。结构如下,字段名和示例都来自 README:
{
"schema": 1,
"updatedAt": "2026-11-05T05:10:00.000Z",
"devices": {
"3f2a91c0d4e8": { "name": "workstation", "updatedAt": "…" }
},
"plugins": {
"dsh-searxng-search": {
"version": "0.5.0",
"enabled": true,
"source": "local:dsh-searxng-search",
"repository": "https://github.com/octocat/dsh-searxng-search",
"repositoryFrom": "git-remote",
"config": { "searxng-search": { "proxy": "http://127.0.0.1:7890" } },
"snapshot": {
"format": "tree",
"path": "snapshots/dsh-searxng-search/0.5.0",
"commit": "9c33e2c…",
"files": 18,
"bytes": 292546,
"sha256": "…"
},
"redacted": 1,
"updatedAt": "…",
"by": "3f2a91c0d4e8"
}
}
}
README 的截图用的也是 octocat / workstation 这类虚构示例数据,不含真实账号或机器信息。
源码备份放在 snapshots/<名字>/<版本>/ 下,通过 Git Data API 提交,所以一次推送就是一个提交,仓库保持可读。文件按内容寻址,没变的文件不重复上传;被删掉的文件在同一个提交里删除;可执行位保留。收哪些文件看 package.json 的 files 白名单(没写就全收),外加 package.json、cordis.patch.yml、locale/、assets/、scripts/、图标和 README。node_modules、.git、.env*、日志、软链永远不收。单文件超过 1 MiB,或整份快照超过 maxSnapshotBytes,整份拒绝,不会上传半份。
两台机器写同一个文件时,每次推送先读当前版本、把本机条目合进去、再用读到的版本号写回;返回 409 说明别人先写了,就重读再合并,最多三次。本机没有的远端条目原样保留,文件是并集,空机器推上去不会删掉任何东西。
主要配置项¶
全部字段和默认值都写在 README 的配置表里,下面挑出最常用的几项:
| 字段 | 默认值 | 含义 |
|---|---|---|
repo |
空 | 私有仓库的 owner/name。 |
apiBase |
https://api.github.com |
GitHub Enterprise 时修改。 |
manifestPath |
dsh-plugin-sync.json |
清单文件的位置。 |
snapshotsPath |
snapshots |
源码备份的位置。 |
snapshotFormat |
tree |
tree 存真实文件;json 退回单文件 base64 快照。 |
deviceName |
主机名 | 写进清单的设备名。 |
deviceId |
自动派生 | 稳定身份,为空时按主机、平台、home 派生。 |
autoPush |
true |
插件变更后自动推送,30 秒防抖。 |
autoPull |
true |
定时拉取清单。 |
intervalMinutes |
15 |
定时周期,限制在 1–1440。 |
includeSnapshots |
true |
上传本地插件的源码。 |
maxSnapshotBytes |
4194304 |
单个插件的源码总量上限。 |
maxManifestBytes |
2097152 |
清单上限,超过直接拒绝。 |
excludedRows |
[] |
这台机器永不发布的 row id。 |
excludedPlugins |
[] |
这台机器不推送、也不参与差异的插件。 |
allowPublicRepo |
false |
允许公开的同步仓库。 |
页面里改的设置存在 ~/.dsh-plugin-sync/settings.json,在 profile 之外,不重写任何 profile 文件;cordis.patch.yml 里的 row 配置提供默认值。
安全边界¶
README 有一节「安全说明」,有四条值得在动手前读完。
一是监听范围:插件只接受回环浏览器的请求,/state 和 /api 要求自定义头,且要求对端是 127.0.0.1 或 ::1。README 直接提示「请让 Harness 只监听回环」。
二是令牌绑定主机:登录时调 GET /user,并把 base 和令牌一起写进凭据记录;之后所有请求都用这个 base,改设置不可能把令牌送去别处,换 API 主机必须重贴令牌。
三是脱敏分两层:敏感形态的键名和敏感形态的值(令牌形态、私钥等)都变成 <redacted>,apiKeyEnv 这类引用保留。源码里出现令牌形态的值,整份拒绝并点名文件。清单不含本机绝对路径,本地插件的路径被替换成 local:<name>,还原靠源码备份。
四是令牌存储:存在凭据记录 dsh-plugin-sync/github(存储文件权限 0600),插件自己的设置文件同样是 0600,没有路由会返回令牌。
典型流程¶
结合上面的能力,换机器可以按这个顺序走。
1、新机器上装好本插件,按上面的命令指向 checkout 目录,刷新页面,进入设置 → 插件同步。
2、登录:建好或选好私有仓库,粘贴细粒度令牌,选仓库,校验并保存。这一步只有拉取,不会把新机器的空状态推出去。
3、点「推送」给仓库播种;之后这台机器上的插件变更会自动推送(30 秒防抖),也可以随时手动推。
4、回到原来的机器或任意新机器,插件同步页会显示差异,勾选要还原的条目(缺失安装、版本替换、配置写入、启用状态),点一次应用。每条结果都回报宿主实际做了什么。
5、从本地目录装的插件会连带还原源码:下载、校验 SHA-256、写盘、link 安装。不需要手动再 clone 一次。
适用场景与注意¶
适合这样的场景:DSH 有多个 profile 或多台机器,插件主要从 GitHub 仓库装、希望自己掌控备份位置,并且能接受「私有仓库 + 手动确认」这层约束。两个独立存储等于两个 DSH profile,适合 profile 级隔离。
需要注意的地方也明确:插件以当前 dsh 进程的权限运行,能读写凭据记录和本地文件,安装前应检查源码与许可证(本项目为 MIT)。它要求 Harness 0.2.x 的几项契约,版本不符就用不了。同步仓库必须保持私有,配置会离开本机。另外它是个人备份工具,不是发布通道——作者的调研结论里写明,发布分发是下一版要做的事,市场 PR 仍需手动。
结尾¶
dsh-plugin-sync 解决的是一个很具体的问题:把「DSH 装了哪些插件、什么版本、什么配置、源码在哪」放进你自己的私有 GitHub 仓库,换机器时不用重新找一遍。清单、版本、配置、源码备份、还原、更新提示、清理都在一个设置页里完成,密钥不外发,公开仓库和不可识别的 manifest 都会被拒绝。
目录页:https://www.skillhub.cn/plugins/Jfreey/dsh-plugin-sync
GitHub:https://github.com/Jfreey/dsh-plugin-sync