照着插件市场写第一个 DSH plugin:目录结构、plugin 清单与本地加载的完整流程
写第一个 DSH plugin,最快的路径是「反推」:从插件市场挑一个结构最小的插件,看它怎么组织,再写自己的最小版本。 规范负责兜底,现成插件负责让你看到真实形态。
为什么从市场插件反推
规范告诉你「必须有什么」,现成插件告诉你「实际怎么写」,两者对照比只看规范快。 市场里的插件是已跑通的样本,目录结构、package.json 字段、入口写法、配置声明都能直接看,你不需要从零猜哪些字段是必需的(来源)。
反推的目标是结构,不是代码,所以先选小样本再看复杂样本。 抄目录与字段,不抄业务逻辑;业务逻辑要自己写,否则改一处就崩。一上来读几千行的插件,会被业务细节淹没;先找只做一件事的插件。
第一步:挑一个结构最小的插件
不是所有市场插件都适合当模板。 三个筛选条件,能过滤掉大部分干扰。
- 功能单一: 只做一件事,例如一个命令、一个面板或一个工具;功能越多,结构越难拆。
- 有公开源码: 能从 Hub 详情页跳到仓库;只有安装包的插件看不到结构。
- 最近有更新: 说明它跟得上当前版本,字段与写法没有过时。
优先选「只注册一个命令」的插件,避开带构建链的插件。 前者通常只有一个入口文件、一个 package.json 和一份 README,结构最清楚;后者目录里会多出打包、转译、样式编译配置,第一次读容易被误导。
第二步:只看四个文件
一个插件的信息密度集中在四个文件里。 别急着读全部源码。
- package.json: 看包名、
type、main/exports、依赖与dsh.bundle声明;它决定插件怎么被加载。 - 入口文件: 找
apply函数,看它注册了什么:命令、工具、服务还是界面。 - 配置声明: 看插件接受哪些配置项、默认值是什么;没有配置声明的插件,通常也不能在设置页调整。
- README: 看安装命令、兼容版本与已知限制;它比源码更快告诉你「这个插件解决什么问题」。
四个文件读完,再决定要不要看其他源码;dsh.bundle 是最容易被忽略的字段。 大多数第一次开发只需要理解这四层。声明了 dsh.bundle,本地安装时会自动追加到 profile 的 bundles 列表;没有声明,插件可能装上了但不会进入组合(来源)。
第三步:抄结构,写最小 apply
先把「能被加载」跑通,再谈功能。 最小插件只需要一个入口与一个注册动作。
// 最小插件:加载时打一条日志,注册一个命令
export const apply = (ctx) => {
ctx.logger.info('plugin loaded');
ctx.command('hello', 'say hello').action(() => 'Hello, DSH!');
};
这段代码只验证两件事:插件被加载、命令被注册;配置声明按需加。 两件事都成立,说明目录、包名与入口都对,再往里加业务逻辑。需要用户填参数时,再加配置 schema;一开始就堆配置,会把「加载失败」和「配置错误」混在一起。包名与目录名不必一致,但要有规律:用 dsh- 前缀方便识别,也避免与核心包重名,重名会导致解析冲突,排查很费时间。
第四步:本地装进 profile
本地调试不要发布 npm,直接链接目录。 一条命令,改完重启即可。
- 确认目录是包根目录:里面有
package.json。 - 在仓库所在目录执行
dsh plugin --profile web add ./你的插件目录。 - 首次运行会初始化 profile,pnpm 把目录以链接方式装进去。
- 用
dsh plugin --profile web list确认包在列表里。 - 重启并新开会话,验证命令或入口是否出现。
add 的参数是目录,不是文件,装错 profile 是最常见的「没生效」。 指到 src 或 dist 都会失败;必须是含 package.json 的那一层。你在桌面端测试,却装进了 web,换到对应 profile 再装一次即可。
改了依赖要重新 add。 只改源码,重启即可;改了 package.json 或依赖,重新执行 add 让 pnpm 重新链接。
第五步:调试三招
本地插件调试,先看日志,再看配置,最后才怀疑代码。
- 日志:
apply里的ctx.logger.info是最直接的证据;没有这条日志,说明插件没被加载。 - 配置:
dsh --dump-config看插件注册段是否出现;出现但行为不对,再看注册逻辑。 - 重启: 热重载不生效时完整退出再启动;不要只刷新页面。
「没日志」与「日志有但功能不对」是两类问题,每次只改一处。 前者查加载与包名,后者查注册与参数;方向完全不同。一次改入口、配置与包名,出问题无法归因;保留改动记录,能快速回退。
第六步:发布前检查
本地跑通不等于可以发布。 发布前过一遍清单,能避免大部分返工。
- 包名唯一: 不与核心包、已有插件重名;重名会在安装时报解析冲突。
- 声明
dsh.bundle: 让 profile 能识别它是组合包。 - 写清兼容版本: README 与 package.json 都要有,用户才能判断能不能装。
- 配置有默认值: 没有默认值的配置项,用户第一次运行就会失败。
- README 有安装与卸载: 两条命令写清楚,减少重复提问。
- 本地目录与 npm 包都测过: 两种安装方式行为一致,再提交收录。
发布前用干净 profile 测一次。 你本地能跑,可能是有缓存或旧依赖;干净环境才能暴露缺依赖的问题。
四个常见坑
第一次写插件,四个坑出现频率最高。
- 目录层级错。
add指向了子目录,pnpm 找不到package.json;列表里没有包。 - 忘了
dsh.bundle。 包装上了但没进入组合,表现为「列表有、功能没有」。 - 包名冲突。 与核心包或已有插件重名,加载时报解析失败;换一个带前缀的名字。
- 配置无默认值。 首次运行缺参数直接失败;给每个配置项一个安全默认值。
四个坑的共同点是「结构问题」,不是业务逻辑。 先把结构跑通,再写功能,返工最少。
下一步
先跑通最小插件,再补功能与发布。 想系统读开发规范,看《DSH plugin 怎么开发》;想了解本地目录与 GitHub 源码安装的差别,看《dsh plugin add 本地目录怎么装》;想提交到市场,看《把插件发布到 DSH Plugin Hub》。
来源:DeepSeek Harness 官方文档 - 打包与安装插件、DeepSeek Harness 官方文档 - 第一个插件、dsh CLI README、DSH Plugin Hub 插件页
常见问题
写第一个 DSH plugin,先看一个结构最小的市场插件。规范告诉你必须有什么,现成插件告诉你实际长什么样;两者对照,理解最快,看完再回规范补字段与边界。
一个能跑起来的 DSH plugin 最小目录,至少需要 package.json、入口文件与配置声明。package.json 声明包名、依赖与 dsh.bundle,入口导出 apply 函数,配置声明决定插件能接受哪些设置。
本地 DSH plugin 改完代码通常不需要重装:本地目录安装是链接方式,重启即可生效。如果改了 package.json 或依赖,重新执行一次 add;热重载不生效时,完整重启最稳。
本地 DSH plugin 装进 profile 后列表没有,先看 add 的目录是否是包根目录(含 package.json),再看 profile 是否装对,最后看包名与 dsh.bundle 声明。三者任一不对,列表里都不会出现。
相关术语
- apply
- apply 是 DSH plugin 的入口函数,框架加载插件时调用并传入 ctx,插件在函数内注册命令、工具、界面或服务。— DeepSeek Harness 官方文档 - 第一个插件
- dsh.bundle
- dsh.bundle 是 package.json 里的声明,标记该包是一个插件组合包;声明后本地安装时会自动追加到 profile 的 bundles 列表。— DeepSeek Harness 官方文档 - 打包与安装插件
- 本地目录安装
- 本地目录安装是把插件源码目录以链接方式装进 profile 的方式,适合改源码调试,改完重启即可生效。— DeepSeek Harness 官方文档 - 打包与安装插件
- profile
- profile 是插件安装与运行的目标环境,本地插件要装进实际使用的 profile 才会被加载。— dsh CLI README
来源
- DeepSeek Harness 官方文档 - 打包与安装插件· deepseek-ai
- DeepSeek Harness 官方文档 - 第一个插件· deepseek-harness
- dsh CLI README· deepseek-ai
- DSH Plugin Hub 插件页· dsh-plugin.org