dsh 自建插件市场实操:索引怎么写、版本怎么筛、装不上看哪里
本文完全由 WorkBuddy 独立收集资料、调试代码并完成文章写作。
我是 Alex Xiang,前百度/微博工程师,现在专注于 AI 工程与工具产品。更多文章欢迎关注微信公众号「字与码」。
一份 JSON 文件,128 MiB 上限,扔在任意静态主机上,就能变成 dsh 里的一个插件市场。
10 月 17 日的 0.2.1-rc.1 把这件事做实了:插件页多出一个「发现」标签,可以从你指定的索引源里发现和安装插件。到 10 月 23 日的 rc.3,这条路径没再改动。
容易被读成「又开了个第三方应用商店」。不是。dsh 自己不内置任何索引源,索引里写什么,装的就是什么——包括一条指向内网 Git 仓库、钉死在某个 commit 上的安装 spec。对要把内部工具分发给同事的团队,这是第一次不用把包发上公网 npm。
真正的门槛不在写 JSON,在版本筛选。索引里每个插件可以列几十个版本,dsh 只挑「与当前版本兼容、且不是预发布」的那个装;筛错一次,用户点安装就失败一次。

这套机制到底长什么样
四样东西,缺一不可:
- 插件包:一个 npm 包,
package.json里声明dsh.bundle。 - 索引文件:一个 JSON,列出包名、展示信息、每个版本的来源和兼容范围。
- 静态主机:能返回 2xx、不重定向、走 https 的地方。GitHub Pages、对象存储、内网 nginx 都行。
- 索引源:在插件页「发现」里加的一条 URL,属于当前 profile。

name@version,Git 源拼成带 commit 的 pnpm spec。图:本文绘制。打开方式按官方文档是:插件 → 实验性插件旁的「更多」→ 自定义插件市场,打开开关后「已安装」旁边出现「发现」标签,Creator 模式同时获得一个只读的 plugin_market 工具。关掉这个组合包,两样一起消失。
DSH 对索引源只挑两件事:protocol 必须是 http: 或 https:,URL 里不能带用户名、密码或 # 片段。它不校验你是谁,也不校验索引里那些标题和作者是谁写的——文档里写得很直白:市场是第三方内容,DSH 检查文件格式,不验证这些声明。
第一步:把包做成 bundle
索引只列声明了 dsh.bundle、且 patch 是一个文件路径或路径列表的版本。没有这个字段,包再正常也不会被生成器收进去。
社区插件 dsh-plugin-model-proxy 的声明长这样,可以直接照抄结构:
{
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": {
"inject": ["@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-settings"],
"platform": "web"
}
}
}
patch 指向的那份 Cordis patch 决定插件往 profile 里加哪些层;client 那段声明它要不要往 Web 前端注入东西。前者是后端能力,后者是界面扩展,两件事分开写。
第二步:生成索引
仓库里有一个参考生成器,读一份纯文本清单,吐一个 index.json:
pnpm --filter @deepseek-ai/dsh-plugin-market-generator start \
--input plugins.txt --output site/index.json --title "Team plugins"
plugins.txt 一行一个来源,空行和 # 开头的行会被忽略:
# npm 包:列出所有未弃用的 bundle 版本
@team/dsh-deploy
# Git 仓库:列出发布 tag 和默认分支
https://gitlab.example.com/team/dsh-plugins
几个容易忽略的行为,生成器的 README 里写了,值得先记下:
- 它只下载、只读取,从不安装,也不运行被索引包的代码。npm tarball 在内存里读,字节要对得上注册表给的完整性哈希;Git 对象以深度 1 拉进一个临时裸仓库按 blob 读,没有工作树,hooks 和 LFS 都不生效。
- 退出码分三档:0 全部成功,1 有来源读不到,2 参数或清单本身不合法。CI 里可以直接拿它当门禁。
- 结果有缓存,放在清单旁边的
.plugin-market-cache,不在发布目录里。上游没变化时重跑,输出字节不变。 - 读不到的来源保留上一次的成功结果,不会因为一次网络抖动就把插件从市场里抹掉。
生成器不处理 .npmrc 里的令牌,只支持公开或免认证的注册表;私有仓库得靠凭据助手或 ssh agent。
第三步:发布,然后加一条 URL
发布就是把 index.json 和它的 icons/ 目录放到静态主机上。三个细节:
图标走相对路径最省事。索引在 https://market.example.com/dsh/index.json,"icon": "icons/foo.svg" 就会解析到同目录下的 icons/foo.svg,换主机不用改文件。跨源的绝对图标 URL 必须 https:,指向 http: 的他源会被整份拒绝——我实测过,报错是 must resolve to https or the index origin。
内联图标别贪大。data: URL 内联的上限是 32 KiB,生成器默认只内联 16 KiB 以下的,超出的自动落到 icons/ 目录。
索引本身没有大小开关。默认上限 128 MiB,超了就是拒绝;正常团队索引离这个量级很远,除非你把每个版本的 README 都塞进去。
加索引源时输入那个 JSON 的 URL,然后 DSH 会做一次拉取和校验,失败原因直接显示在「管理索引源…」里,连违规值的 JSON 路径一起给你。
我拿三个真插件试了一遍
光看文档不足以判断这条路是否可用,所以我把它跑了一遍:本地装 @deepseek-ai/dsh-plugin-market-index@0.2.1-rc.3,从 npm 注册表拉三个社区插件的元数据,拼成一份 format 1 的索引,再用官方的 parseMarketIndex 校验,最后让 marketInstallTarget 把安装 spec 打出来。
结果先说兼容面:

engines.dsh 判断,没声明时按 @deepseek-ai/dsh* 的 peer 范围推导。图:本文实测。@goodandready/dsh-context-lens 列了 35 个版本,能在 0.2.1-rc.3 上装的只有 9 个——它的 peer 范围是 ^0.1.7-rc.2 || ^0.2.0-rc.1,把 0.1.x 那一大批全挡在了外面。另外两个插件就没这个问题:dsh-plugin-model-proxy 的 6 个版本全兼容(>=0.1.0-rc.6),dsh-plugin-studio 的 5 个版本也全兼容,因为它的 peer 全是 *。
三个插件的推荐版本分别是 0.1.6、0.1.35、0.3.1,marketInstallTarget 打出来的 spec 就是 dsh-plugin-model-proxy@0.1.6 这种形状。官方 CLI 的 dsh plugin --profile web add <spec> 会把后面的参数原样转给 pnpm,所以市场这一层新增的东西只有两件:元数据,和筛选。
有一个口径要交代:我算兼容性时给 semver 开了 includePrerelease,因为当前版本本身就是 0.2.1-rc.3 这种预发布;不带这个选项,^0.2.0-rc.1 会把 0.2.1-rc.3 判成不满足。DSH 安装时还会再查一遍,两边不一致的情况要按它的结果走。
九条规则,哪几条最容易踩
格式校验是「整份拒绝」,任何一条不过,整个索引不加载。我把常见错误都造了一遍:
| 写错的地方 | 校验结果 |
|---|---|
format 写成 2 | $.format → must be 1 |
| 图标指向 http 的他源 | $.plugins[0].icon → must resolve to https or the index origin |
| Git 源的 commit 写分支名或短 sha | $.plugins[0].versions[0].source.commit → must be a lowercase 40-hex-digit commit SHA |
Git 源用 http:// | $.plugins[0].versions[0].source.url → must use https: |
版本号写成 v1.0.0 | $.plugins[0].versions[1].version → must be an exact SemVer version |
| 插件引用了没声明的分类 | $.plugins[0].metadata.category → references undeclared id "internal" |
| 市场标题只有中文 | $.title → must contain en |
engines.dsh 里塞 workspace:* | $.plugins[0].versions[1].engines.dsh → must not be a workspace: range |
| 图标写相对路径 | 通过,解析成索引同目录的绝对 URL |
我自己还踩了一个表格外的坑:构造索引时把 author 赋成了 undefined,字段还在,值没了,校验器直接拒——$.plugins[0].author must be a string with a non-whitespace character。它的判定标准是「字段存在就必须合法」,不是「值为空就当没写」。拼索引的脚本必须在序列化前把 undefined 的键整个删掉,不能指望 JSON.stringify 帮你兜。
git 源那条 40 位 sha 的规则值得单独说:它堵死了「用分支名当版本」的写法。索引必须钉在某个不可变的提交上,否则同一个 index.json 明天指向的代码可能就不是今天这份了。生成器那边也是一样,默认分支只贡献「当前版本」这一条记录,其余版本全靠 tag。
我的判断
这不是一个应用商店,是一份插件分发协议。
它做的事情可以一句话说完:把「装一个插件」降级成「拼一条 pnpm spec」,市场负责给这条 spec 配上元数据、兼容范围和筛选规则。dsh 自己的插件系统早就以 pnpm 和 Cordis patch 为基础,索引这层只是把那套东西暴露成一个可读的 JSON。
所以对团队来说,真正的变化不是「有自己的商店了」,是内部工具的分发从口头传命令变成了一条 URL。以前是「你 pnpm add 一下这个包,哦对,得先加 scope,还要 @ 某个 commit」;现在是同事在「发现」里点一下,确认框里显示来源、commit 和版本,装错了还有版本列表可以退回去。
判断能被推翻的地方在于版本数量:如果内部只有三五个插件、半年才发一次版,写一份手维护的 index.json 比跑生成器更划算——格式就那些字段,几十行 JSON 的事。
泼一盆冷水
一,它还是预发布。0.2.1 从 10 月 3 日的 alpha.1 到 23 日的 rc.3,20 天发了 8 个预发布版本,索引格式现在是 format: 1。正式版之前字段还可能动,索引文件和客户端要一起升级。
二,装 rc.3 得指定版本。我查 npm 的时候,@deepseek-ai/dsh 的 latest 标签还停在 0.2.1-rc.2,rc.3 在 next 上;校验器包 @deepseek-ai/dsh-plugin-market-index 的 latest 更是停在 alpha.4。不写标签直接装,拿到的不是最新那份,写进文章的结论也就跟着错一版。
三,市场不校验声明。标题、描述、图标、作者、来源都由索引决定,DSH 只查文件格式。也就是说,谁能改那个 JSON,谁就能决定你的同事装什么。索引放 http 就等于把安装 spec 交给中间人,文档明确要求这类场景用 https。
四,装完就是 Node 扩展,没有额外隔离。索引里的兼容性检查、注册表检查、确认框,拦的是「装错版本」,不是「装了坏东西」。第三方插件的代码权限和你手写的插件一样大。
怎么用
一,先看现成的,别急着自建。社区已经有 dsh-plugin-studio、dsh-plugin-shop-catalog 这类现成市场,先装一个看看插件页的交互是不是你要的,再决定是自建索引还是给它提交条目。
二,索引跟 tag 走,别跟默认分支。默认分支那条记录会随提交漂移,而用户装的是一个具体 commit。版本记录要么来自 vX.Y.Z,要么来自 <name>@X.Y.Z,别让两套命名混在一个仓库里。
三,每个版本都写 engines.dsh。不写就得靠 peer 依赖反推,反推出来的范围经常比你以为的窄——^0.2.0-rc-1 || ^0.1.7-rc.2 这种写法会把整条 0.1.x 线挡在外面。写清楚,用户看到的推荐版本才对。
四,索引和 icons/ 放同一个目录,用相对路径引用。换主机、换域名、加路径前缀都不用改文件,反过来一旦写成绝对 URL,迁移就得重新校验一遍。
参考资料
- dsh v0.2.1-rc.1 发布说明(自定义索引源、Claude Code Mods 兼容层):https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-rc.1
- dsh v0.2.1-rc.3 发布说明:https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-rc.3
- 插件市场用户文档:https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/user/guide/plugin-market.zh.md
- 索引生成器 README:https://github.com/deepseek-ai/deepseek-harness/blob/main/apps/plugin-market-generator/README.zh.md
- 索引格式校验器(npm):https://www.npmjs.com/package/@deepseek-ai/dsh-plugin-market-index
微信公众号
欢迎关注「字与码」
如果这篇文章对你有用,也欢迎在微信里继续关注后续更新。
X / Twitter
关注 @ax2_zicode
更即时的技术观察、新文章提醒和一些短想法会发在 X 上。