Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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, --with-tool; the minimal skeleton omits the peerDependencies block entirely — it imports nothing):

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

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. Startup-fatal (SOP §5.6): the insert name is what the loader imports, so the whole profile dies with exit 1
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 Every bare import in index.js is declared in package.json A module specifier that is not relative, not absolute and not a built-in (node:-prefixed or bare builtin) appears in no dependencies / peerDependencies / optionalDependencies / devDependencies. Startup-fatal: the local (link:) route resolves imports from the plugin directory, so this is ERR_MODULE_NOT_FOUND → profile exit 1. This is the gate that keeps 0.1.1's blind spot closed
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 @deepseek-ai/dsh-tools / @deepseek-ai/dsh-system-prompt appears as a cordis.patch.yml insert again. Startup-fatal: dsh-base already provides those services, so startup aborts with service "tools" has been registered and the profile exits 1. A WARN (exit code 0) in 0.1.1 — a gate that calls an unstartable bundle green is worse than no gate
9 Cross-check against --name Only runs when --name was also passed
6c 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 the module is merely not installed it records a WARN and moves on — not installed is WARN (this check never installs anything), not declared is the FAIL on 6b
Declared for consumers, not only for local dev Never FAIL, only WARN: emits when 6b passed via devDependencies, which resolves locally but is absent for a consumer installing the package

A declaration in devDependencies only is therefore a WARN, not a FAIL: it is enough for route A (which does a local install) but not the shape you want to publish.

The print order is the table order above as it happens in a run; the numbering is the actual item count of that run (the denominator varies by mode: 6b/7/8/9 only appear when they apply). A run without --name and without a duplicate insert has 8 items.

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 6c honestly reports WARN ... SKIPPED (ERR_MODULE_NOT_FOUND) — that is not a failure (a static check never installs anything), but it is not "verified loadable" either. What 6b guarantees is the thing that actually broke: the import is declared.

Actual output (real runs, not examples)

Three runs, same generator, dated 2026-09-19 (DSH 0.1.6-alpha.1 global, Node v22.23.1). dsh-fix-tool is a --with-tool bundle from the current generator; dsh-old-tool is the same bundle from the shipped 0.1.1 generator, kept as the regression control. marks elided lines — the full text is in every run's own output.

$ 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 <name>` 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                     # <- the fixed blind spot: a bundle that kills the
                                        #    profile reported as green

The minimal skeleton (no --with-tool, so no import at all) — 8 items, all green, and 6c gets to import the real file:

$ node cli.mjs --verify /tmp/cdb-fix-e2e/gen-new/dsh-fix-min
[6/8] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/8] PASS every bare import in index.js is declared in package.json — no bare imports
[8/8] PASS index.js runtime namespace (best effort) — imported OK, no default export, apply() present, keys=[apply, name]
… ([1]-[5] PASS as above)

Summary: 8 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, marked startup-fatal) + 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 1 FAIL (item 8, startup-fatal) + 1 WARN 1
Remove peerDependencies from a --with-tool package.json (the import stays) 1 FAIL (item 6b, names the undeclared package) 1
Turn that import into any other undeclared bare import (import { Service } from 'cordis') 1 FAIL (item 6b) 1
Move the declaration into devDependencies only 0 FAIL / 2 WARN (item 6b PASSes via devDependencies, one WARN says prefer peerDependencies) 0
Verify the artifact the shipped 0.1.1 generator produces 1 FAIL (item 6b) — 0.1.1's own gate reported it as 7 PASS / 0 FAIL / 1 WARN 1 vs 0

The first seven rows were run against 0.1.1 and re-run against the current generator; the last four are the new gate. Raw evidence for the current round: evidence-2026-09-19-fix/ in the project knowledge base (files 23-, 25-, 06-).

One row changed in 0.1.2: the duplicate insert was 0 FAIL / 2 WARN, exit code 0, while the very same artifact was measured to abort startup (service "tools" has been registered, exit 1). It is a FAIL now, so that row shows the current gate's verdict.

Real output fragments (reproducible: each case is a copy under /tmp/rf-011/neg-*; the gate cases are 23-verify-neg-bare-cordis, 06-verify-newcli-on-old in the fix evidence):

$ node cli.mjs --verify /tmp/cdb-fix-e2e/neg/neg-bare-cordis    # undeclared bare import
[6/8] PASS index.js has no default export (function plugin) — static scan: no default-export form found
[7/8] FAIL every bare import in index.js is declared in package.json — undeclared bare import(s): cordis — in the local (link:) route these resolve from the plugin directory, so the import fails with ERR_MODULE_NOT_FOUND and `dsh --profile <name>` exits 1 before anything loads. Add them to dependencies/peerDependencies in package.json (the --with-tool skeleton declares '@deepseek-ai/dsh-tools' as a peerDependency for exactly this reason)
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] FAIL package.json name matches a patch insert name — package.json name="dsh-fix-tool" is not among patch insert names ["dsh-other-tool"] — startup-fatal (SOP §5.6): the insert name is what the loader imports, so `dsh --profile <name>` exits 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] 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/cdb-rel-neg-reinsert             # dsh-tools re-inserted
[10/10] FAIL no re-insert of a service package dsh-base already provides — @deepseek-ai/dsh-tools re-inserted — dsh-base already provides these; `dsh --profile <name>` exits 1 with `service "..." has been registered` (SOP §8.2)
Summary: 8 PASS / 1 FAIL / 1 WARN
verify exit code: 1

(neg-reinsert is the --with-tool artifact of the paragraph above (9 items) plus the duplicate insert, which makes item 8 appear — hence 10. The remaining examples use minimal-skeleton artifacts, which 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.

State the mode. pnpm dsh inside a deepseek-harness source check-out (the tsx launcher) and a global dsh (fallback links inside DSH_HOME) are two different resolution paths, and passing in the first says nothing about the second. Every run below is the mode that matters for an outside user: global dsh + a clean, isolated DSH_HOME + an install route the user actually has. Environment: dsh 0.1.6-alpha.1 (global), Node v22.23.1, pnpm 11.15.1.

Route B — the published package (dsh plugin add <name>): the recommended path

$ 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

Read out of that:

  1. The # == dsh-fix-tool block in --dump-config means the generated cordis.patch.yml was inserted as a layer (the profile's own patch is the empty []).
  2. The three log lines are "loaded + the tool really ran"; exit code 124 is normal — the plugin finished and the agent loop idled on agents: [], so timeout had to kill it.
  3. Route B needs no dependency work from the author: dsh plugin add <package> installs the package inside $DSH_HOME/profiles/<name>/, where the harness's fallback links for @deepseek-ai/* are reachable. The shipped 0.1.1 artifact (which declares nothing at all) boots this way too, which is exactly why the community plugins declare peerDependencies and not dependencies.

Route A — a local directory (link:): development only, one prerequisite

$ 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 picked up the declared peerDependencies (pnpm auto-installs missing peers) and put @deepseek-ai/dsh-tools into the plugin's own node_modules — which is the whole point: the link: target is outside DSH_HOME, so that directory has to resolve the import itself.

Remove that one command and the profile does not start. Same artifact, same add, only the prerequisite taken away:

$ 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

That exit code 1 is the entire incident: one unresolvable import in one layer, and nothing loads. It is why the generated README states the prerequisite instead of implying it, why --verify check 6b is a FAIL rather than a WARN, and why the rescue command (dsh plugin --profile <name> remove <package>) is documented in the generated README.

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.

Dependency declarations in the generated bundle (peerDependencies)

--with-tool writes this into the generated package.json:

"peerDependencies": {
  "@deepseek-ai/dsh-tools": "^0.1.6-alpha.1 || ^0.1.5-rc.2"
}
  • peerDependencies, not dependencies — matching the community plugins (dsh-airdrop, dsh-plugin-subscriptions): the harness provides the module at runtime, so the declaration says which generation the plugin was written against without dragging a second copy into every profile.

  • Route B does not actually need the declaration (the profile's fallback links resolve @deepseek-ai/*). It is the prerequisite for a local install (route A), and it is what --verify 6b checks.

  • The || is not decoration. Every published @deepseek-ai/dsh-tools version is a prerelease, and semver only lets a prerelease satisfy a range when some comparator carries a prerelease on the same major.minor.patch. Measured against the registry (mirror):

    range resolves to
    * 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@'<range>' version.) A "reasonable looking" caret range on a prerelease line silently resolves to a single old version — which is how a plugin ends up pointing at a generation nobody is running.

  • Refresh the range when the official line moves (npm view @deepseek-ai/dsh-tools dist-tags). --verify 6b only checks that the import is declared; keeping the range honest is the author's call.

  • The minimal skeleton imports nothing and therefore declares nothing.

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. A plugin that fails to load takes the whole profile down — exit code 1, nothing starts. There is no partial start: one unresolvable import or one bad layer and dsh --profile <name> terminates. The rescue command is dsh plugin --profile <name> remove <package>; run it from a shell, the profile does not need to boot first. This is why the generated README ships it and why --verify fails (not warns) on the two startup-fatal shapes it can see statically: an undeclared bare import (6b) and a patch insert name that is not the package name (4).
  4. The cordis.patch.yml insert name must equal your package name (SOP §5.6). The loader imports the insert name; a mismatch is failed to import loader entry … and startup exits 1 — the same fatal class as an unresolvable dependency, and equally invisible until you actually boot.
  5. --with-tool imports @deepseek-ai/dsh-tools: declare it. The generator writes it into peerDependencies; if you delete that block, --verify 6b FAILs by design. peerDependencies (not dependencies) matches the community plugins — see "Dependency declarations in the generated bundle".
  6. Know which resolution path you are on. A dependency resolves from where the file lives: with dsh plugin add <dir> (pnpm link:) the file is your source directory, which is usually outside DSH_HOME, so the import must be installed there (pnpm install, as the generated README says). With dsh plugin add <published-package> the files land inside $DSH_HOME/profiles/, where the harness's fallback links resolve @deepseek-ai/* for you. The old "must run pnpm dsh from a deepseek-harness check-out, never bare node" advice describes the check-out mode only (workspace packages live in the tsx launcher) — it is not a requirement for an installed global dsh, and it was never something an outside user could follow.
  7. dsh --profile demo does not exit on its own (the agent loop idles on agents: []); getting 124 from timeout is normal, not a failure.
  8. --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.
  9. link: installs need no re-add — edit index.js and re-run dsh --profile <name> (this holds for route A only; route B installs a copied package, so re-publish and re-add).
  10. Isolate the home: always export DSH_HOME=/tmp/xxx so tests do not pollute ~/.dsh.
  11. 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 --helpnpm 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, CHANGELOG.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

0.1.2 is prepared but not published — it carries the dependency gate, the peerDependencies declaration and the rewritten generated README described above (see CHANGELOG.md). Publishing is a separate, outward-facing step.

⚠️ 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

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages