← 返回列表
未验证
一个零依赖、非交互式的 DeepSeek Harness DSH 插件包脚手架与验证工具——面向编写 DSH 插件的人。
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/19 · 已提供中文文档
综合分
29.5
GitHub 分
29.5
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add RubyCcll/create-dsh-bundle该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
信任档位:已验证本站已于 1 天前真实安装成功(L4 · 真实安装)
- 是什么
- dsh 原生插件 · other
- 装得上吗
- 本站已真实安装成功(L4 · 真实安装,非静态推断)
- 安全吗
- 本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
- 还在维护吗
- 活跃:最近一次提交在 6 天前
档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →
🟢实装验证通过· 2026/9/25
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/24(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
由 DeepSeek 最新模型翻译生成create-dsh-bundle
一个零依赖、非交互式的 DeepSeek Harness (DSH) 插件包脚手架与验证工具——面向编写 DSH 插件的人。
npx create-dsh-bundle --name dsh-my-plugin --with-tool
一条命令即可生成 dsh plugin add 可接受的插件包(package.json / index.js / cordis.patch.yml / README.md / .gitignore);内置的 --verify 随后会重新读取这些文件并给出报告。零 npm 依赖、无网络访问、无 LLM 调用、无需 API key。
- 生成器本身不是 DSH 插件,也不声明 dsh.bundle(见下文“为什么本包不声明 dsh.bundle”)。
- 非交互式是硬性要求:一切由参数驱动(Node 内置的 util.parseArgs),没有 readline/inquirer 提示,因此可在无头环境、cron 任务和子代理中运行,而无需等待回答。
- 包/二进制名 create-dsh-bundle:create-dsh-plugin 已被第三方占用,因此这里不使用该名称。
安装
npx create-dsh-bundle --help # 无需安装即可运行
npm i -g create-dsh-bundle # 或全局安装
需要 Node ^22.19 || >=24(与 DSH 本身相同的 engines)。无依赖,无 postinstall。
用法
两种模式:仅传入 --verify 即选择验证模式;当 --verify 存在时不生成任何内容(仍可传入 --name,此时它仅用于检查 9 的交叉核对,不会触发生成)。
生成
create-dsh-bundle --name dsh-my-plugin --desc "What it does" --with-tool --out ./dsh-my-plugin
验证已生成的包(只读,不写入任何内容)
create-dsh-bundle --verify ./dsh-my-plugin
完整参数列表
| 选项 | 模式 | 必填 | 描述 |
|---|---|---|---|
| --name | 生成 | 是 | 包名,dsh-;仅允许小写字母/数字/连字符,首字符必须是字母。任何不符合 ^[a-z][a-z0-9-]$ 的内容都会以退出码 1 被拒绝 |
| --desc | 生成 | 否 | package.json 的 description;省略时使用单行默认值 |
| --with-tool | 生成 | 否 | 生成 greet 工具示例(defineTool + inject = ['tools'] + 一次真实工具调用)。省略则为最小骨架 |
| --out | 生成 | 否 | 输出目录,默认为 ./。已存在的目录会被拒绝(退出码 1);没有强制标志 |
| --verify | 验证 | 是 | 验证该目录中生成的文件;只读,退出码 0 = 全部 PASS,1 = 任一 FAIL |
| -h, --help | — | 否 | 打印用法 |
选项使用 Node 内置的 util.parseArgs 解析,并设置 strict: true + allowPositionals: false:未知选项和多余的位置参数都会以退出码 1 失败,而不是被静默忽略。
名称派生规则
遵循官方 dsh-hello 模板的三部分约定(以 --name dsh-approval-gate 为例):
| 名称 | 规则 | 结果 |
|---|---|---|
| 包名 | 按给定值 | dsh-approval-gate |
| 导出名(index.js 中的 name) | 去掉 dsh- 前缀 | approval-gate |
| cordis id(cordis.patch.yml 中的 id) | 导出名去掉 -plugin 后缀 | approval-gate |
生成的文件结构
dsh-my-plugin/
├── package.json # 声明 dsh.bundle: {"dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}
├── index.js # 插件入口:name + apply(ctx)(使用 --with-tool 时添加 inject = ['tools'])
├── cordis.patch.yml # bundle 配置层:仅插入自己的层
├── .gitignore # .dsh-home/ + node_modules/
└── README.md # 随 bundle 一起提供的安装 / 验证 / 注意事项
生成的 cordis.patch.yml(--name dsh-demo-x,逐字节一致):
- insert:
- id: demo-x
name: dsh-demo-x
生成的 package.json(逐字节一致,--with-tool;最小骨架完全省略
peerDependencies 块——它不导入任何内容):
{
"name": "dsh-demo-x",
"version": "0.1.0",
"description": "demo",
"type": "module",
"main": "index.js",
"files": [
"index.js",
"cordis.patch.yml"
],
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
},
"peerDependencies": {
"@deepseek-ai/dsh-tools": "^0.1.6-alpha.1 || ^0.1.5-rc.2"
}
}
生成的 index.js 骨架(--with-tool,函数插件形式,无默认导出):
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'demo-x'
export const inject = ['tools']
export function apply(ctx) {
console.log('[demo-x] plugin loaded!')
ctx.tools.register(defineTool({ name: 'greet', / parameters / output.render / execute / }))
// then drive one real tool call (as if the model issued it; no key needed)
void (async () => { / ctx.tools.execute({ callId: 'demo-1', name: 'greet', … }) / })()
}
不带 --with-tool 时会生成最小骨架:只有 export const name 和 export function apply(ctx),不导入任何 @deepseek-ai/dsh- 包,也不注册任何工具。
--verify 检查的内容
create-dsh-bundle --verify 会重新读取生成的文件,不写入任何内容,也不安装任何内容。它按项打印 PASS / FAIL / WARN:
| # | 检查项 | 何时 FAIL |
|---|---|---|
| 1 | 五个生成的文件全部存在(SOP §3) | package.json / index.js / cordis.patch.yml / README.md / .gitignore 中任意一个缺失(输出会指明缺失的文件) |
| 2 | package.json 可解析 | 文件缺失 / JSON.parse 抛出异常 / 根不是对象 |
| 3 | cordis.patch.yml 存在且格式良好 | 缺失;或内置的 YAML 子集解析器失败(流式风格 [{...}]、缩进错误、多余行都算) |
| 4 | package.json 的 name 等于补丁插入的 name | 插入内容中未找到 name,或它与包名不同。启动致命(SOP §5.6):插入的 name 正是加载器所导入的内容,因此整个 profile 会以退出码 1 终止 |
| 5 | dsh.bundle.patch 指向一个存在的文件 | dsh.bundle.patch 未声明,或者已声明但文件不存在 |
| 6 | index.js 没有默认导出 | 发现 export default / export { x as default } / module.exports = / exports.default = |
| 6b | index.js 中的每个裸导入都在 package.json 中声明 | 一个既非相对路径、也非绝对路径、也非内置模块(以 node: 为前缀或裸内置模块)的模块说明符,未出现在任何 dependencies / peerDependencies / optionalDependencies / devDependencies 中。启动致命:本地(link:)路由从插件目录解析导入,因此这会导致 ERR_MODULE_NOT_FOUND → profile 退出码 1。这道关卡正是堵住 0.1.1 盲区的那道门 |
| 7 | 在工具模式下 inject 包含 'tools' | 检测到工具代码(--with-tool,或源码中的 ctx.tools.register / dsh-tools),但 inject 中没有 tools |
| 8 | 不重复插入 dsh-base 已提供的服务包 | @deepseek-ai/dsh-tools / @deepseek-ai/dsh-system-prompt 再次作为 cordis.patch.yml 插入出现。启动致命:dsh-base 已提供这些服务,因此启动会以 service "tools" has been registered 中止,profile 退出码为 1。在 0.1.1 中这是 WARN(退出码 0)——一个把无法启动的 bundle 判为绿色的关卡,比没有关卡更糟 |
| 9 | 与 --name 交叉核对 | 仅在同时传入 --name 时运行 |
| 6c | index.js 运行时命名空间(尽力而为) | 它能导入,且命名空间有 default 键;或者导入因语法错误之类的硬错误而失败。当模块只是未安装时,它记录一条 WARN 并继续——未安装 是 WARN(此检查从不安装任何东西),未声明 是 6b 的 FAIL |
| — | 为消费者声明,而不仅为本地开发声明 | 从不 FAIL,只 WARN:当 6b 通过 devDependencies 通过时发出,因为这在本地能解析,但对于安装该包的消费者来说并不存在 |
因此,仅在 devDependencies 中声明是 WARN,而非 FAIL:这对路由 A(执行本地安装)来说足够了,但并不是你想要发布的形态。
打印顺序就是上表的顺序,按运行中实际发生的顺序排列;编号是该次运行的实际条目数(分母随模式变化:6b/7/8/9 仅在适用时出现)。一次不带 --name 且没有重复插入的运行有 8 个条目。
退出码:任何 FAIL → 1;仅 PASS/WARN → 0。WARN 不影响退出码。
⚠️ 声明范围:--verify 是对生成文件的静态检查(外加一次尽力而为的本地 import);它并不等同于“安装到 DSH profile 并成功加载”。安装/加载验证是另一回事——参见“安装和加载
验证”如下。index.js 中的 @deepseek-ai/dsh- 导入无法从独立目录解析,因此 6c 如实报告 WARN ... SKIPPED (ERR_MODULE_NOT_FOUND) —— 这不是失败(静态检查从不安装任何东西),但也不是“已验证可加载”。6b 保证的是真正出问题的那件事:该导入已被声明。
实际输出(真实运行,而非示例)
三次运行,同一生成器,日期为 2026-09-19(DSH 0.1.6-alpha.1 全局,Node v22.23.1)。
dsh-fix-tool 是来自当前生成器的 --with-tool 包;dsh-old-tool 是来自已发布的 0.1.1 生成器的同一个包,保留作为回归对照。
… 标记被省略的行 —— 完整文本在每个运行各自的输出中。
$ node cli.mjs --verify /tmp/cdb-fix-e2e/gen-new/dsh-fix-tool # current generator
create-dsh-bundle --verify /tmp/cdb-fix-e2e/gen-new/dsh-fix-tool
[1/9] PASS All five generated files present (SOP §3) — all 5 present: package.json, index.js, cordis.patch.yml, README.md, .gitignore
[2/9] PASS package.json parses — name=dsh-fix-tool version=0.1.0
[3/9] PASS cordis.patch.yml exists and is well formed — block sequence, 1 insert(s)
[4/9] PASS package.json name matches a patch insert name — dsh-fix-tool
[5/9] PASS dsh.bundle.patch points at an existing file — ./cordis.patch.yml -> …/cordis.patch.yml
[6/9] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/9] PASS every bare import in index.js is declared in package.json — 1 bare import(s), all declared: @deepseek-ai/dsh-tools -> peerDependencies
[8/9] WARN index.js runtime namespace (best effort) — SKIPPED (ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/dsh-tools' imported from …/index.js) — static scan only, nothing was executed. …
[9/9] PASS inject contains 'tools' (tool example) — inject=[tools]
Summary: 8 PASS / 0 FAIL / 1 WARN
verify exit code: 0
$ node cli.mjs --verify /tmp/cdb-fix-e2e/gen-old/dsh-old-tool # the 0.1.1 artifact
[6/9] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/9] FAIL every bare import in index.js is declared in package.json — undeclared bare import(s): @deepseek-ai/dsh-tools — in the local (link:) route these resolve from the plugin directory, so the import fails with ERR_MODULE_NOT_FOUND and dsh --profile exits 1 before anything loads. …
… (8/9 WARN runtime namespace, 9/9 PASS inject, [1]-[5] PASS)
Summary: 7 PASS / 1 FAIL / 1 WARN
verify exit code: 1
$ node /tmp/cdb-fix-e2e/old-cli.mjs --verify /tmp/cdb-fix-e2e/gen-old/dsh-old-tool # 0.1.1's own gate
Summary: 7 PASS / 0 FAIL / 1 WARN
verify exit code: 0 # 会在任何内容加载之前以 1 退出。将它们添加到 package.json 中的 dependencies/peerDependencies(--with-tool 骨架正是出于这个原因将 '@deepseek-ai/dsh-tools' 声明为 peerDependency)
Summary: 6 PASS / 1 FAIL / 1 WARN
verify exit code: 1
$ node cli.mjs --verify /tmp/cdb-fix-e2e/neg/neg-name-mismatch # insert name changed
[4/9] 失败 package.json name 与某个 patch insert name 匹配 — package.json name="dsh-fix-tool" 不在 patch insert names ["dsh-other-tool"] 中 — 启动致命(SOP §5.6):insert name 正是加载器导入的名称,因此 dsh --profile 会以 1 退出
Summary: 7 PASS / 1 FAIL / 1 WARN
verify exit code: 1
$ node cli.mjs --verify /tmp/rf-011/neg-default-export # export default appended
[6/8] 失败 index.js 没有默认导出(函数插件)— 发现 export default — Loader 会丢弃命名空间(postmortem 0001)
Summary: 6 PASS / 1 FAIL / 1 WARN
verify exit code: 1
$ node cli.mjs --verify /tmp/rf-011/neg-stray-line # patch line 4 has broken indentation
[3/8] 失败 cordis.patch.yml 存在且格式良好 — 第 4 行:在 "stray: 1" 附近出现意外缩进
[4/8] 失败 package.json name 与某个 patch insert name 匹配 — 无法比较:package.json 或 cordis.patch.yml 不可读
Summary: 5 PASS / 2 FAIL / 1 WARN
verify exit code: 1
$ node cli.mjs --verify /tmp/rf-011/neg-missing-file # cordis.patch.yml deleted (item 1 catches the missing file first)
[1/8] 失败 所有五个生成的文件均存在(SOP §3)— 缺少 1/5:cordis.patch.yml — 期望全部为 [package.json, index.js, cordis.patch.yml, README.md, .gitignore]
Summary: 3 PASS / 4 FAIL / 1 WARN
verify exit code: 1
$ node cli.mjs --verify /tmp/cdb-rel-neg-reinsert # dsh-tools re-inserted
[10/10] 失败 不重新插入 dsh-base 已提供的服务包 — @deepseek-ai/dsh-tools 被重新插入 — dsh-base 已提供这些;dsh --profile 会以 1 退出,并显示 service "..." has been registered(SOP §8.2)
Summary: 8 PASS / 1 FAIL / 1 WARN
verify exit code: 1
(neg-reinsert 是上一段的 --with-tool 产物(9 项)加上重复插入,这使得第 8 项出现 — 因此为 10。其余示例使用最小骨架产物,其项数为 8。)
另外:在 --verify 前后比较目标目录的 md5 快照 → 完全没有变化(只读)。
生成侧失败路径(真实输出;这些都不允许静默成功)
$ node cli.mjs --out /tmp/never-gen
Error: --name is required.
$ echo $?
1
$ node cli.mjs --name Dsh-Bad --out /tmp/never-gen
Error: --name "Dsh-Bad" is not a valid package name (lowercase letters, digits, hyphens; must start with a letter).
$ echo $?
1
$ node cli.mjs --name dsh-demo-x --desc "demo" --with-tool --out /tmp/dsh-demo-x
Error: output directory already exists: /tmp/dsh-demo-x
refusing to overwrite — pass a different --out, or remove that path first.
$ echo $?
1
$ node cli.mjs --bogus
Error: Unknown option '--bogus'
(prints USAGE)
$ echo $?
1
--name 1bad 和 --name dsh_bad 也会以退出码 1 退出。已存在的目录一律拒绝;没有 --force:要么另选一个 --out,要么自己先删除该路径。
安装与加载验证(端到端,实际运行)
本节是证明该 bundle 确实能安装进 DSH 并加载的证据;这与上面的静态 --verify 是两回事。
说明模式。 在 deepseek-harness 源码检出目录中运行 pnpm dsh(tsx 启动器)与全局 dsh(DSH_HOME 内的回退链接)是两条不同的解析路径,传入前者并不能说明后者的情况。下面每次运行采用的都是对外部用户有意义的模式:全局 dsh + 干净、隔离的 DSH_HOME + 用户实际拥有的安装途径。环境:dsh 0.1.6-alpha.1(全局),Node v22.23.1,pnpm 11.15.1。
途径 B — 已发布的包(dsh plugin add ):推荐路径
$ cd dsh-fix-tool && npm pack --pack-destination /tmp/cdb-fix-e2e/routeB # stands in for the
published tarball
$ export DSH_HOME=/tmp/cdb-fix-e2e/home-B # isolated home, ~/.dsh never touched
$ dsh plugin --profile demoB add /tmp/cdb-fix-e2e/routeB/dsh-fix-tool-0.1.0.tgz
$ echo $?
0
$ dsh --profile demoB --dump-config | grep -A3 dsh-fix-tool
== dsh-fix-tool
- id: fix-tool
name: dsh-fix-tool
$ timeout 20 dsh --profile demoB
[fix-tool] plugin loaded!
[fix-tool] hello from my first plugin
[fix-tool] greet replied: [{"type":"text","text":"Hello, Cordis!"}]
$ echo $?
124
从中可以读出:
1. --dump-config 中的 # == dsh-fix-tool 块意味着生成的 cordis.patch.yml 被作为一层插入(该 profile 自身的 patch 是空的 [])。
2. 这三行日志是“已加载 + 工具确实运行了”;退出码 124 是正常的 —— 插件已完成,agent 循环在 agents: [] 上空转,因此 timeout 不得不将其终止。
3. 途径 B 不需要作者做任何依赖处理:dsh plugin add 会把包安装到 $DSH_HOME/profiles// 内部,在那里 harness 针对 @deepseek-ai/ 的回退链接是可访问的。随附的 0.1.1 产物(它完全没有声明任何依赖)也能以这种方式启动,这正是社区插件声明 peerDependencies 而非 dependencies 的原因。
途径 A — 本地目录(link:):仅用于开发,有一个前提条件
$ cd /tmp/cdb-fix-e2e/routeA/dsh-fix-tool && pnpm install
dependencies:
+ @deepseek-ai/dsh-tools 0.1.6-alpha.2
Done in 657ms using pnpm v11.15.1
$ export DSH_HOME=/tmp/cdb-fix-e2e/home-A
$ dsh plugin --profile demoA add "$PWD"
$ timeout 20 dsh --profile demoA
[fix-tool] plugin loaded!
[fix-tool] hello from my first plugin
[fix-tool] greet replied: [{"type":"text","text":"Hello, Cordis!"}]
$ echo $?
124
pnpm install 拾取了声明的 peerDependencies(pnpm 会自动安装缺失的 peer),并将
@deepseek-ai/dsh-tools 放入插件自己的 node_modules —— 这正是关键所在:
link: 目标位于 DSH_HOME 之外,因此该目录必须自行解析这个导入。
移除那一条命令,profile 就无法启动。相同的产物、相同的 add,只是去掉了前提条件:
$ dsh plugin --profile demoA0 add /tmp/cdb-fix-e2e/routeA0/dsh-fix-tool
$ timeout 20 dsh --profile demoA0
Error: dsh: plugin tree failed to load: failed to apply loader entry include (cordis:include):
failed to import loader entry fix-tool (dsh-fix-tool): Cannot find package
'@deepseek-ai/dsh-tools' imported from /private/tmp/cdb-fix-e2e/routeA0/dsh-fix-tool/index.js
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@deepseek-ai/dsh-tools' imported from …
$ echo $?
1
那个退出码 1 就是整个事故的全貌:某一层中有一个无法解析的导入,于是什么都加载不了。这就是为什么生成的 README 会明确陈述前提条件而不是暗示它,为什么 --verify 检查 6b 是 FAIL 而不是 WARN,以及为什么救援命令(dsh plugin --profile remove )被记录在生成的 README 中。
与官方发布指南的关系
官方的打包/安装教程位于 DSH 源码树中,而不在本包中:
- docs/user/develop/basic/publish.md(中文版:publish.zh.md)—— “打包与安装插件”:涵盖两个概念(bundle / profile)、dsh.bundle 清单、dsh plugin add 以及层顺序。其中有一句话值得在此重复:“bundle 是你编写并分发的东西;profile 是用户用 dsh --profile 启动的东西。没有任何东西同时是两者。”(译自中文版——请在你的检出中核对原文措辞)。
- docs/user/develop/basic/first-plugin.md / tool.md / config.md —— 最小插件骨架、defineTool、配置层。
- docs/cordis-tutorial/(7 章)—— 底层的 Cordis 概念。
生成的 bundle 中的依赖声明(peerDependencies)
--with-tool 会将以下内容写入生成的 package.json:
"peerDependencies": {
"@deepseek-ai/dsh-tools": "^0.1.6-alpha.1 || ^0.1.5-rc.2"
}
- 是 peerDependencies,不是 dependencies —— 与社区插件(dsh-airdrop、dsh-plugin-subscriptions)保持一致:harness 在运行时提供该模块,因此该声明说明了插件是针对哪一代编写的,而不会把第二份副本拖入每个 profile。
- 路线 B 实际上并不需要该声明(profile 的回退链接会解析
@deepseek-ai/)。它是本地安装(路线 A)的前提条件,也是 --verify 6b 所检查的内容。
- || 不是装饰。 每一个已发布的 @deepseek-ai/dsh-tools 版本都是预发布版本,而 semver 只有在某个比较符携带相同 major.minor.patch 上的预发布标识时,才允许预发布版本满足一个范围。对照注册表(镜像)实测:
| range | 解析为 |
|---|---|
| | 0.0.1-rc.1 |
| >=0.1.1-rc.2 | 0.1.1-rc.2 |
| ^0.1.5-rc.2 | 0.1.5-rc.2 |
| ^0.1.6-alpha.1 \|\| ^0.1.5-rc.2 | 0.1.5-rc.2、0.1.6-alpha.1、0.1.6-alpha.2 |
(npm view @deepseek-ai/dsh-tools@'' version。)在预发布线上一个“看起来合理”的脱字符范围会静默解析为某个单一的旧版本——这正是插件最终指向一个没人在跑的代际的原因。
- 当官方线移动时刷新范围(npm view @deepseek-ai/dsh-tools dist-tags)。--verify 6b 只检查该导入是否被声明;保持范围诚实是作者的责任。
- 最小骨架不导入任何东西,因此也不声明任何东西。
为什么这个包不声明 dsh.bundle
create-dsh-bundle 是一个脚手架 + 验证器,而不是一个 Cordis 插件:它没有 index.js 插件入口,不提供任何服务或工具,也没有 cordis.patch.yml。按照官方定义,dsh.bundle 是“这个包贡献哪个配置层”的声明;我们不贡献任何层。强行塞入一个 dsh.bundle.patch 意味着用户的 dsh plugin add create-dsh-bundle 会试图加载一个不存在或无意义的插件层——那是把一份损坏的配置写进 profile。所以这个包声明 bin,并且不声明 dsh.bundle。
陷阱
1. 绝不要把 default export 混入函数插件。 当具名导出 name / inject / apply 存在时,添加 export default 会让 Loader 丢弃整个命名空间(官方事后分析 0001 中的真实事故)。--verify 的检查 6 正是为此而存在。
2. 不要重新插入 @deepseek-ai/dsh-tools / @deepseek-ai/dsh-system-prompt。 dsh-base 已经提供了 tools 服务;再次插入会得到 service "tools" has been registered。要使用工具,只需在你的插件中写 export const inject = ['tools']。
3. 一个加载失败的插件会拖垮整个 profile——退出码 1,什么都启动不了。 不存在部分启动:一个无法解析的导入或一个坏层,dsh --profile 就会终止。救援命令是 dsh plugin --profile remove ;从 shell 运行它,profile 不需要先启动。这就是为什么生成的 README 会附带它,以及为什么 --verify 对它能在静态上看到的两种启动致命形态会失败(而不是警告):未声明的裸导入(6b)和 patch insert 的 name 不是包名(4)。
4. cordis.patch.yml 中插入的 name 必须等于你的包 name(SOP §5.6)。加载器导入的是插入名称;不匹配会导致 failed to import loader entry … 并且启动以退出码 1 结束——这与无法解析的依赖属于同一类致命错误,而且同样要等到你真正启动时才会暴露。
5. --with-tool 会导入 @deepseek-ai/dsh-tools:请声明它。 生成器会把它写入 peerDependencies;如果你删掉那个块,--verify 6b 会按设计 FAIL。peerDependencies(而不是 dependencies)与社区插件保持一致——参见“生成包中的依赖声明”。
6. 弄清楚你处于哪条解析路径上。 依赖从文件所在的位置解析:使用 dsh plugin add (pnpm link:)时,文件位于你的源码目录,而该目录通常在 DSH_HOME 之外,因此导入必须安装在那里(pnpm install,正如生成的 README 所说)。使用 dsh plugin add 时,文件会落到 $DSH_HOME/profiles/ 内,harness 的回退链接会为你解析 @deepseek-ai/。旧的“必须从 deepseek-harness 检出目录运行 pnpm dsh,绝不要用裸 node”的建议只描述了检出模式(工作区包位于 tsx 启动器中)——这不是已安装的全局 dsh 的要求,而且外部用户从来也无法遵循它。
7. dsh --profile demo 不会自行退出(agent 循环在 agents: [] 上空转);从 timeout 得到 124 是正常的,不是失败。
8. --dump-config 输出很长(它包含整个 dsh-base 层);使用 grep -A3 '' 只查看你自己的层。
9. link: 安装无需重新添加——编辑 index.js 并重新运行 dsh --profile (这仅适用于路线 A;路线 B 安装的是复制后的包,因此需要重新发布并重新添加)。
10. 隔离 home 目录:始终 export DSH_HOME=/tmp/xxx,这样测试不会污染 ~/.dsh。
11. 保持非交互:此工具(以及你基于它编写的任何生成器)不得引入 readline/inquirer——在无头运行中没有人来回答,它会挂起。
打包与发布就绪验证
此包已经过完整的打包验证(npm pack → 将 tarball 安装到隔离目录 → npx create-dsh-bundle --help → npm publish --dry-run);每个步骤的原始输出都在仓库中的 VERIFICATION.md 里(该文件不属于发布产物——files 只列出 cli.mjs、README.md、CHANGELOG.md 和 docs/zh-CN.md,并且中文翻译有意放在根 README 命名空间之外)。create-dsh-bundle@0.1.0 已发布到 npm(仓库已推送到 GitHub);发布状态以 npm 页面显示为准:https://www.npmjs.com/package/create-dsh-bundle
0.1.2 已准备好但尚未发布——它包含上文所述的依赖门禁、peerDependencies 声明以及重写后的生成 README(参见 CHANGELOG.md)。发布是
一个独立的、面向外部的步骤。
⚠️ 从这台机器发布时的硬性前提条件(其默认 registry 是镜像——实测 npm config get registry = https://registry.npmmirror.com):始终显式传入 registry,否则你会发布到中国镜像:npm login --registry=https://registry.npmjs.org,然后 npm publish --registry=https://registry.npmjs.org。命令和实测输出:VERIFICATION.md §9.1。
开发
sh test/smoke.sh # 或 npm test:生成 → 断言文件列表(对每个文件执行 test -f)→ 验证 → 反例(默认导出)→ 三条失败路径 → 最小骨架
node cli.mjs --help
Zero dependencies, a single cli.mjs (containing the built-in YAML subset parser and the verification logic). After changing cli.mjs, run test/smoke.sh: it asserts not only exit codes but the generated file list file by file (a missing output file fails the run instead of staying silently green).
License
MIT