Skip to content

CH 23 · Publish and Distribute

Word count~3,910 wordsTime~20 minPrereqCH 17, CH 22LevelReproducible

Chapter Goal

Walking through PART 04, you've learned to install plugins, and write plugins — but it's still sitting in your own workspace's folder, how do others get it? This chapter turns it into a "one command to install" distribution: first explain the underlying mechanism by which others install your plugin (bundle package), then give three distribution methods, and finally install it yourself before publishing to verify, confirm there's no problem before publishing.

First Clarify: How Do Others Install Your Plugin

dsh has no built-in plugin market; the official installation uniformly goes through dsh plugin --profile <name> add <source>. The sources are four kinds: npm package name / local directory / tarball / GitHub Release (covered systematically in CH 17). So what you need to do is turn your plugin into any one of these four sources.

And "a plugin that can be added by others" has an official name in dsh — bundle (package). Don't confuse the two concepts:

  • bundle is what you write and distribute. Its package.json declares dsh.bundle, answering "what does this package contribute" — a patch file that inserts or overrides plugin lines.
  • profile is what the user starts with dsh --profile <name>. Its package.json declares dsh.profile, answering "what bundles, in what order, make up this configuration".

Nothing is both. You distribute the bundle, the user installs it with a profile and runs it.

What Does a Publishable Plugin Look Like

In your finished plugin directory, the three most important things:

text
your-plugin/
├── package.json       # Declare dsh.bundle (and dsh.client if it has UI)
├── cordis.patch.yml   # The patch layer applied when installed
└── lib/               # Build artifacts — this is what others actually install

These fields in package.json, none can be sloppy:

FieldDescription
namePackage name, matches the plugin, reads well
versionSemantic version, major.minor.patch, start at 0.0.1
descriptionOne line explaining what the plugin does, shown in the install list
typemodule (ESM)
mainPoints to the build artifact, e.g. lib/index.js
filesOnly pack what you need to ship: lib, cordis.patch.yml, LICENSE, etc. Missing lib means others will fail to load after install
keywordsSearch keywords, ["deepseek","harness","dsh","dsh-plugin",...]dsh-plugin is the standard for being found
licenseOpen source must declare, e.g. MIT
dsh.bundle{"patch":"./cordis.patch.yml"} — declare this is a package, auto-mounted on install
dsh.clientPlugins with UI declare client injection (the set covered in CH 22)

Three Distribution Methods

From light to heavy. The first is purely local self-test, doesn't need any account; the latter two need an account — GitHub needs a GitHub account, npm needs an npm account.

Method One: Local Directory / tarball — Self-Test Before Publishing

  • Local directory: dsh plugin --profile demo add ./your-plugin, pnpm directly links that directory, suited for quick trials during development.
  • tarball: in the plugin directory pnpm pack to make a .tgz, then dsh plugin --profile demo add ./your-plugin-0.0.1.tgz.

Before publishing, always install with tarball yourself first — tarball is exactly the same as what you'll ship; if it can install and run, then you can consider publishing. Complete self-test in three steps:

powershell
# Pack in the plugin directory
pnpm pack

# Install into a clean temporary profile
dsh plugin --profile test add ./your-plugin-0.0.1.tgz

# See if it entered the config tree
dsh --profile test --dump-config

If you see your plugin's layer in the --dump-config output, the package loaded successfully. Then dsh --profile test to start, test whether the plugin's features work normally, all normal then publish.

Two details to remember:

  • After a bundle member changes, you must restart the profile to take effect — a running profile retains the bundle set it had at startup (the boundary in CH 17), install then restart before testing.
  • Loading order: profile's bundles list → profile's own cordis.patch.yml → $DSH_HOME/cordis.patch.yml--patch overlay, later-applied lines win. Your plugin belongs to the first layer.

You can also have dsh do it for you: send the following to it, and it will read the docs, pack, install a temporary profile, verify; you just verify.

text
Pack the plugin in my current workspace into a publishable form, and do a pre-publish self-test: first read the dsh official docs to understand how to pack and install plugins (bundle packages, the four sources of dsh plugin add), do it per the official spec; pack the plugin into a tarball, check whether the package.json metadata (version, description, files, keywords, license) is complete; install it into a temporary profile and verify it can load and the features work normally; finally tell me the verification result and the path of the packaged product, and explain what I still need to do to publish to npm.

Method Two: GitHub — Others Install with One URL

The most common in the community: build the artifact, upload it to a GitHub Release, others install with a single URL. The DSH Skill & MCP Panel we installed in CH 12 was distributed this way.

Three steps:

  1. Pack: in the plugin directory pnpm pack, get your-plugin-0.0.1.tgz.
  2. Upload: push the repo to GitHub, create a Release on the repo page (write a version number v0.0.1), upload the .tgz.
  3. Give the install address: others install with this address:
powershell
dsh plugin --profile web add https://github.com/your-username/repo-name/releases/download/v0.0.1/your-plugin-0.0.1.tgz

What's installed is the build artifact, no need for allowBuilds authorization, just as easy as installing from npm. To fix an "always points to latest" address, replace download/v0.0.1/... with releases/latest/download/your-plugin.tgz (don't include version number in the filename).

Note: plugins going this route, lib/ generally isn't in git — the repo only has source code, build locally before publishing, pnpm pack to tarball, then upload to the Release.

Advanced: GitHub Actions auto-build and publish

Manual pack, create Release and upload files once is fine, but with many versions it gets annoying. Have GitHub Actions do it for you: push a v0.0.1 tag, Actions auto-runs build → pnpm pack → create Release → upload the tarball. From then on, just push a new tag for a new version, everything else is automatic.

A typical workflow goes in the repo's .github/workflows/release.yml: trigger is push: tags: ['v*'], steps in order: checkout code, install Node, pnpm install, pnpm build, pnpm pack, use softprops/action-gh-release to create a Release and upload the generated .tgz as an attachment. Set it up once, and you don't need to manually upload files for each version.

You can also have dsh do the publishing for you

Packaging, pushing the repo, creating the Release — these repetitive tasks, just hand them to dsh. Send this in the Web UI's input box:

text
Publish the plugin in my current workspace to GitHub, release v0.0.1, use GitHub Actions for auto-build and publish per the dsh official spec, and finally tell me what address others can use to install.

It will go read the docs itself, run commands itself, handle git and Release itself. You just need to confirm according to the permission policy when it does boundary-crossing operations like pushing the repo and creating the Release, and finally verify the install address is correct.

Method Three: Publish to npm — Others Just Type the Package Name

Publish the plugin to npm, and it's the most concise for others to install: dsh plugin --profile web add your-plugin.

Four steps:

  1. Register and log in to npm: register an account on the npm website (free), then npm login (will ask for username, password, and email).
  2. Build: ensure lib/ is the build artifact. Source-type plugins need to pnpm build before publishing — npm installs pre-built code, publish without building, and others get an empty package.
  3. Publish: run pnpm publish in the plugin directory.
  4. Verify: switch to a clean profile, dsh plugin --profile test add your-plugin, confirm it can install and run.

After Publishing: Tag the dsh-plugin Topic

Regardless of the method, add dsh-plugin to the GitHub repo's Topics — the official team explicitly recommends doing this, so others can more easily find your plugin. Remember to also include dsh-plugin in the npm package's keywords, double-pronged.

Common Pitfalls

ProblemWhat's happeningHow to handle
Installed but no effectBundle member change didn't restartRestart the profile after install then test
Others fail to load after installfiles missed lib/, only source shippedAdd the build artifact directory to files, repack
Version number duplicatesSame version publish is permanently rejected by npmChange to a different version then publish
Package name takenAlready exists on npm with the same namePick an unrelated package name
Installed but not activatedPackage didn't declare dsh.bundleAdd "dsh":{"bundle":{...}}, otherwise it's just a regular dependency and not mounted

What you learned in this chapter

You pass if you can complete the items below:

  • [ ] Know that a bundle is "what you distribute", a profile is "what the user starts", and their manifests are different
  • [ ] Write a correct package.json for a publishable plugin: name / version / files / keywords / license / dsh.bundle, and dsh.client if it has UI
  • [ ] Know the appropriate scenarios for the three distribution methods: local/tarball self-test, GitHub Release tarball (mainstream one-click install, supports Actions auto-publish), npm publish
  • [ ] Self-test before publishing: pnpm pack → temporary profile add--dump-config confirm → restart to verify
  • [ ] Know that you can have dsh do the packaging, self-test, and publishing to GitHub, no need to do it step by step manually
  • [ ] Know that after publishing, tag the GitHub repo with the dsh-plugin topic, and the npm package keywords include dsh-plugin
  • [ ] Can avoid common pitfalls: files missing lib/, bundle changes not restarted, package didn't declare dsh.bundle

Open Source · MIT · Community Driven