DSH HUB
首页插件商店插件包社区排行榜资源发布指南
插件源码
返回插件目录

RubyCcll /

RubyCcll/create-dsh-bundle

仅 Topic 仓库

这个插件还没有填写简介。

★ 0 Stars0 Forks0 IssuesN/A 社区评分0 已确认安装
查看 GitHub
README来源: main@9a348ede

create-dsh-bundle

中文 | English

A zero-dependency, non-interactive scaffolder and verifier for DeepSeek Harness (DSH) plugin bundles — for people writing DSH plugins.

npx create-dsh-bundle --name dsh-my-plugin --with-tool

One command generates a plugin bundle that dsh plugin add accepts (package.json / index.js / cordis.patch.yml / README.md / .gitignore); the built-in --verify then re-reads those files and reports on them. Zero npm dependencies, no network access, no LLM calls, no API key.

  • The generator itself is not a DSH plugin and does not declare dsh.bundle (see "Why this package does not declare dsh.bundle" below).
  • Non-interactive is a hard requirement: everything is parameter-driven (Node's built-in util.parseArgs), with no readline/inquirer prompts, so it runs in headless environments, cron jobs and subagents without waiting for an answer.
  • Package/binary name create-dsh-bundle: create-dsh-plugin is already taken by a third party, so it is not used here.

Install

npx create-dsh-bundle --help          # run without installing
npm i -g create-dsh-bundle            # or install globally

Requires Node ^22.19 || >=24 (same engines as DSH itself). No dependencies, no postinstall.

Usage

Two modes: passing only --verify selects verification mode; when --verify is present nothing is generated (--name may still be passed, in which case it is used only for check 9's cross-check and does not trigger generation).

# generate
create-dsh-bundle --name dsh-my-plugin --desc "What it does" --with-tool --out ./dsh-my-plugin

# verify an already generated bundle (read-only, writes nothing)
create-dsh-bundle --verify ./dsh-my-plugin

Full parameter list

Option Mode Required Description
--name <pkg> generate yes Package name, dsh-<what-it-does>; lowercase letters/digits/hyphens only, first character must be a letter. Anything outside ^[a-z][a-z0-9-]*$ is rejected with exit code 1
--desc <text> generate no package.json description; a one-line default is used when omitted
--with-tool generate no Generate the greet tool example (defineTool + inject = ['tools'] + one real tool call). Omit it for a minimal skeleton
--out <dir> generate no Output directory, defaults to ./<name>. An existing directory is refused (exit 1); there is no force flag
--verify <dir> verify yes Verify the generated files in that directory; read-only, exit code 0 = all PASS, 1 = any FAIL
-h, --help — no Print usage

Options are parsed with Node's built-in util.parseArgs using strict: true + allowPositionals: false: unknown options and stray positional arguments both fail with exit code 1 instead of being silently ignored.

Name derivation rules

Follows the three-part convention of the official dsh-hello template (example with --name dsh-approval-gate):

Name Rule Result
package name as given dsh-approval-gate
export name (name in index.js) drop the dsh- prefix approval-gate
cordis id (id in cordis.patch.yml) export name, drop a -plugin suffix approval-gate

Generated file structure

dsh-my-plugin/
├── package.json         # declares dsh.bundle: {"dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}
├── index.js             # plugin entry: name + apply(ctx) (adds inject = ['tools'] with --with-tool)
├── cordis.patch.yml     # bundle config layer: inserts only its own layer
├── .gitignore           # .dsh-home/ + node_modules/
└── README.md            # install / verify / pitfalls notes shipped with the bundle

The generated cordis.patch.yml (--name dsh-demo-x, byte for byte):

- insert:
    - id: demo-x
      name: dsh-demo-x

The generated package.json (byte for byte; nothing that could be omitted was omitted):

{
  "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"
    }
  }
}

The generated index.js skeleton (--with-tool, function-plugin form, no default export):

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', … }) */ })()
}

Without --with-tool the minimal skeleton is generated instead: only export const name and export function apply(ctx), importing no @deepseek-ai/dsh-* package and registering no tool.

What --verify checks

create-dsh-bundle --verify <dir> re-reads the generated files without writing anything and without installing anything. It prints PASS / FAIL / WARN per item:

# Check FAILs when
1 All five generated files present (SOP §3) Any of package.json / index.js / cordis.patch.yml / README.md / .gitignore is missing (the output names the missing file)
2 package.json parses File missing / JSON.parse throws / root is not an object
3 cordis.patch.yml exists and is well formed Missing; or the built-in YAML subset parser fails (flow style [{...}], wrong indentation, extra lines all count)
4 package.json name equals the patch insert name No name found in the insert, or it differs from the package name
5 dsh.bundle.patch points at an existing file dsh.bundle.patch is not declared, or it is declared but the file does not exist
6 index.js has no default export export default / export { x as default } / module.exports = / exports.default = found
6b index.js runtime namespace (best effort) It imports and the namespace has a default key; or the import fails with a hard error such as a syntax error. When dependencies cannot be resolved it records a WARN and moves on
7 In tool mode inject contains 'tools' Tool code is detected (--with-tool, or ctx.tools.register / dsh-tools in the source) but inject has no tools
8 No re-insert of a service package dsh-base already provides Never FAIL, only WARN (re-inserting @deepseek-ai/dsh-tools / @deepseek-ai/dsh-system-prompt makes startup fail)
9 Cross-check against --name Only runs when --name was also passed

The print order is the table order; the numbering is the actual item count of that run (the denominator varies by mode: item 7 appears only in tool mode, item 8 only when there really is a duplicate insert, item 9 only when --name was passed).

Exit code: any FAIL → 1; PASS/WARN only → 0. WARN does not affect the exit code.

⚠️ Scope of the claim: --verify is a static check of the generated files (plus one best-effort local import); it is not the same as "installed into a DSH profile and loaded successfully". Install/load verification is a separate thing — see "Install and load verification" below. The @deepseek-ai/dsh-* imports in index.js cannot resolve from a standalone directory, so item 6b honestly reports WARN ... SKIPPED (ERR_MODULE_NOT_FOUND) — that is not a failure, but it is not "verified loadable" either.

Actual output (real runs, not examples)

With --name (tool mode, 9 items):

$ node cli.mjs --name dsh-demo-x --verify /tmp/rf-011/dsh-demo-x
create-dsh-bundle --verify /tmp/rf-011/dsh-demo-x

[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-demo-x 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-demo-x
[5/9] PASS dsh.bundle.patch points at an existing file — ./cordis.patch.yml -> /tmp/rf-011/dsh-demo-x/cordis.patch.yml
[6/9] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/9] WARN index.js runtime namespace (best effort) — SKIPPED (ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/dsh-tools' imported from /private/tmp/rf-011/dsh-demo-x/index.js) — static scan only, nothing was executed
[8/9] PASS inject contains 'tools' (tool example) — inject=[tools]
[9/9] PASS cross-check against --name — --name dsh-demo-x matches package.json

Summary: 8 PASS / 0 FAIL / 1 WARN
Note: this is a static check of the generated files (plus one best-effort local import) — it is NOT "installed into a DSH profile and loaded successfully".
      For install/load verification use the pnpm dsh sequence in the README (isolated DSH_HOME).
verify exit code: 0
$ echo $?
0

The same directory without --name has only 8 items (item 9 does not run, everything else is word for word identical):

$ node cli.mjs --verify /tmp/rf-011/dsh-demo-x
create-dsh-bundle --verify /tmp/rf-011/dsh-demo-x

[1/8] PASS All five generated files present (SOP §3) — all 5 present: package.json, index.js, cordis.patch.yml, README.md, .gitignore
[2/8] PASS package.json parses — name=dsh-demo-x version=0.1.0
[3/8] PASS cordis.patch.yml exists and is well formed — block sequence, 1 insert(s)
[4/8] PASS package.json name matches a patch insert name — dsh-demo-x
[5/8] PASS dsh.bundle.patch points at an existing file — ./cordis.patch.yml -> /tmp/rf-011/dsh-demo-x/cordis.patch.yml
[6/8] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/8] WARN index.js runtime namespace (best effort) — SKIPPED (ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/dsh-tools' imported from /private/tmp/rf-011/dsh-demo-x/index.js) — static scan only, nothing was executed
[8/8] PASS inject contains 'tools' (tool example) — inject=[tools]

Summary: 7 PASS / 0 FAIL / 1 WARN
verify exit code: 0

The minimal skeleton (no --with-tool) drops item 7 and does import successfully, so item 6b reports a real namespace (7 items):

$ node cli.mjs --verify /tmp/rf-011/dsh-demo-min
create-dsh-bundle --verify /tmp/rf-011/dsh-demo-min

[1/7] PASS All five generated files present (SOP §3) — all 5 present: package.json, index.js, cordis.patch.yml, README.md, .gitignore
[2/7] PASS package.json parses — name=dsh-demo-min version=0.1.0
[3/7] PASS cordis.patch.yml exists and is well formed — block sequence, 1 insert(s)
[4/7] PASS package.json name matches a patch insert name — dsh-demo-min
[5/7] PASS dsh.bundle.patch points at an existing file — ./cordis.patch.yml -> /tmp/rf-011/dsh-demo-min/cordis.patch.yml
[6/7] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/7] PASS index.js runtime namespace (best effort) — imported OK, no default export, apply() present, keys=[apply, name]

Summary: 7 PASS / 0 FAIL / 0 WARN
verify exit code: 0

Negative-case matrix (all of these were actually run: break a generated file on purpose, see whether it FAILs)

What was broken Result Exit code
Deleted README.md (or .gitignore) 1 FAIL (item 1, names the missing file) 1
Truncated package.json into invalid JSON 4 FAIL (2/4/5/6b) 1
Replace the insert name in the patch with another package name 1 FAIL (item 4) + 1 WARN 1
Append export default { … } to index.js 1 FAIL (item 6) + 1 WARN 1
Write the patch in flow style - insert: [{id: x, name: y}] 2 FAIL (items 3, 4) 1
Add a stray indented line 4 stray: 1 to the patch 2 FAIL (items 3, 4) 1
Re-insert @deepseek-ai/dsh-tools 0 FAIL / 2 WARN (exit code is still 0) 0

Real output fragments (reproducible: each case is a copy under /tmp/rf-011/neg-*):

$ node cli.mjs --verify /tmp/rf-011/neg-default-export        # export default appended
[6/8] FAIL index.js has no default export (function plugin) — found export default — the Loader drops the namespace (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] FAIL cordis.patch.yml exists and is well formed — line 4: unexpected indentation near "stray: 1"
[4/8] FAIL package.json name matches a patch insert name — cannot compare: package.json or cordis.patch.yml unreadable
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] FAIL All five generated files present (SOP §3) — missing 1/5: cordis.patch.yml — expected all of [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/rf-011/neg-reinsert              # dsh-tools re-inserted
[9/9] WARN no re-insert of a service package dsh-base already provides — @deepseek-ai/dsh-tools re-inserted — dsh-base already provides these; startup reports `service "..." has been registered` (SOP §8.2)
Summary: 7 PASS / 0 FAIL / 2 WARN
verify exit code: 0

(neg-reinsert has 9 items because the patch contains a duplicate insert, which makes item 8 appear; the other examples, having no --name, have 8.)

Also: comparing md5 snapshots of the target directory before and after --verify → no change at all (read-only).

Generation-side failure paths (real output; none of these is allowed to succeed silently)

$ 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 and --name dsh_bad exit 1 as well. An existing directory is always refused; there is no --force: either pick another --out or delete the path yourself first.

Install and load verification (end-to-end, actually run)

This section is the evidence that the bundle really installs into DSH and loads; it is a different thing from the static --verify above.

$ export DSH_HOME=/tmp/dsh-bundle-test-home2        # isolated home, leaves ~/.dsh alone
$ cd /path/to/deepseek-harness                     # must be the repo root
$ pnpm dsh plugin --profile demo add /tmp/dsh-demo-x
dsh: initialized profile demo at /tmp/dsh-bundle-test-home2/profiles/demo

dependencies:
+ dsh-demo-x link:/tmp/dsh-demo-x

Already up to date
Done in 227ms using pnpm v11.7.0
$ echo $?
0

$ pnpm dsh --profile demo --dump-config | grep -A3 'dsh-demo-x'
# == dsh-demo-x
- id: demo-x
  name: dsh-demo-x

$ timeout 25 pnpm dsh --profile demo
$ node --import tsx/esm apps/cli/src/bin.ts --profile demo
[demo-x] plugin loaded!
[demo-x] hello from my first plugin
[demo-x] greet replied: [{"type":"text","text":"Hello, Cordis!"}]
$ echo $?
124

Three things to read out of that:

  1. dsh plugin add is a link: symlink (+ dsh-demo-x link:/tmp/dsh-demo-x), not a packed install; after editing index.js you do not add it again — just re-run pnpm dsh.
  2. The # == dsh-demo-x block in --dump-config means the generated cordis.patch.yml was inserted as a layer. (The profile's own cordis.patch.yml is the empty []; this layer comes entirely from the bundle.)
  3. Exit code 124 from timeout 25 is normal: the plugin loaded and the tool call completed, but the agent loop idles on agents: [] and the process never exits by itself. The three log lines above are the evidence of "loaded + tool really ran".

Relation to the official publish guide

The official packaging/installation tutorial lives in the DSH source tree, not in this package:

  • docs/user/develop/basic/publish.md (Chinese: publish.zh.md) — "Packaging and installing plugins": covers the two concepts (bundle / profile), the dsh.bundle manifest, dsh plugin add, and layer order. One line from it is worth repeating here: "A bundle is what you write and distribute; a profile is what the user starts with dsh --profile <name>. Nothing is both at the same time." (translated from the Chinese edition — check the original wording in your checkout).
  • docs/user/develop/basic/first-plugin.md / tool.md / config.md — minimal plugin skeleton, defineTool, config layers.
  • docs/cordis-tutorial/ (7 chapters) — the underlying Cordis concepts.

Declaring dependencies once the bundle is published to npm (read before publishing)

In source-checkout mode (pnpm dsh + link:) the generated bundle needs no dependency declarations: @deepseek-ai/dsh-* resolves through the tsx launcher inside the DSH repo. But once you publish it to npm and a user installs it into their own profile with dsh plugin add <published-name>, the imported @deepseek-ai/dsh-* packages must be declared by you as dependencies in the generated package.json — resolution happens in the profile's node_modules, not in the DSH installation directory. The --with-tool skeleton therefore leaves this to you: add dependencies yourself before publishing (something like {"@deepseek-ai/dsh-tools": "0.1.5-rc.2"}, using whatever version is current at that time). This tool deliberately does not write that field: it is not required in checkout mode, and pinning an rc version would go stale immediately.

Why this package does not declare dsh.bundle

create-dsh-bundle is a scaffolder + verifier, not a Cordis plugin: it has no index.js plugin entry, provides no service or tool, and has no cordis.patch.yml. By the official definition, dsh.bundle is the declaration of "which config layer this package contributes"; we contribute no layer. Forcing a dsh.bundle.patch in would mean a user's dsh plugin add create-dsh-bundle tries to load a nonexistent or meaningless plugin layer — that is writing a broken config into a profile. So this package declares bin and no dsh.bundle.

Pitfalls

  1. Never mix a default export into a function plugin. With named exports name / inject / apply present, adding export default makes the Loader drop the whole namespace (the real incident in the official postmortem 0001). Check 6 of --verify exists for exactly this.
  2. Do not re-insert @deepseek-ai/dsh-tools / @deepseek-ai/dsh-system-prompt. dsh-base already provides the tools service; inserting it again gives service "tools" has been registered. To use tools, just write export const inject = ['tools'] in your plugin.
  3. Always run pnpm dsh, never bare node; run it from the repo root and pass an absolute path to the plugin. Workspace packages are not in the top-level node_modules, so only the tsx launcher can resolve them.
  4. dsh --profile demo does not exit on its own (the agent loop idles on agents: []); getting 124 from timeout is normal.
  5. --dump-config output is long (it includes the whole dsh-base layer); use grep -A3 '<your-package-name>' to look at your own layer only.
  6. dsh plugin add is a link: — edit index.js and re-run pnpm dsh, no re-add needed.
  7. Isolate the home: always export DSH_HOME=/tmp/xxx so tests do not pollute ~/.dsh.
  8. Stay non-interactive: this tool (and any generator you write from it) must not introduce readline/inquirer — in a headless run nobody is there to answer and it will hang.

Packaging and release-readiness verification

This package has been through full packaging verification (npm pack → install the tarball into an isolated directory → npx create-dsh-bundle --help → npm publish --dry-run); the raw output for each step is in VERIFICATION.md in the repository (that file is not part of the published artifact — files lists only cli.mjs, README.md and docs/zh-CN.md, and the Chinese translation deliberately lives outside the root README* namespace). create-dsh-bundle@0.1.0 is published on npm (repository pushed to GitHub); the release status is whatever the npm page says: https://www.npmjs.com/package/create-dsh-bundle

⚠️ Hard prerequisite when publishing from this machine (its default registry is a mirror — measured npm config get registry = https://registry.npmmirror.com): always pass the registry explicitly, otherwise you publish to the Chinese mirror instead: npm login --registry=https://registry.npmjs.org followed by npm publish --registry=https://registry.npmjs.org. Commands and measured output: VERIFICATION.md §9.1.

Development

sh test/smoke.sh     # or npm test: generate → assert the file list (test -f per file) → verify → negative case (default export) → three failure paths → minimal skeleton
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

—/ 5

暂无评分

需要先验证清单

Commit 9a348ede068b

社区评论

还没有评论,来写第一条。

DSH HUB

社区维护的 DSH 插件索引。不是 GitHub 或 DeepSeek AI 的官方产品。

社区资源API关于