Skip to main content
All posts
Development

How to Publish a DeepSeek-Harness Plugin (npm vs GitHub)

What to check before publishing a dsh plugin to npm or GitHub, why prebuilt npm packages skip allowBuilds, and how the dsh-plugin topic drives discovery.

Publishing a DeepSeek-Harness (dsh) plugin means making it installable via dsh plugin --profile <name> add <specifier> — either as a prebuilt npm package or as a GitHub repository — and then making it findable, since dsh doesn't run a marketplace or a submission form. This post covers both halves: what to ship, and how it gets found.

npm vs. GitHub: the real difference is who runs the build

Both are officially supported install sources, and the mechanics are identical either way — dsh plugin add just forwards to pnpm. The difference that actually matters to your users is where the build happens:

  • Publish to npm and you build once, at publish time, and ship compiled output. Users installing from npm never run your build.
  • Publish only to GitHub (the more common path right now, since this is a young, fast-moving ecosystem) and pnpm pulls source, not output — your prepare script has to run on every install, which is exactly the trigger for pnpm 10+'s allowBuilds security prompt.

Neither is "wrong," but npm is the officially recommended path specifically because it removes a security decision from your users entirely.

Publishing to npm

Before npm publish / pnpm publish, make sure your package.json's files array actually includes everything dsh needs at runtime — not just your compiled entry point, but cordis.patch.yml too:

{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "lib/index.js",
  "files": ["lib", "cordis.patch.yml"],
  "dsh": {
    "bundle": { "patch": "./cordis.patch.yml" }
  }
}

Leaving cordis.patch.yml out of files is a genuinely common mistake — the package installs fine, dsh.bundle.patch still resolves locally in your source tree, but the file simply isn't in the published tarball, so it 404s for anyone who actually installs from the registry. Test this before publishing by running pnpm pack and inspecting the resulting .tgz — it should contain both your built code and the patch file, with nothing your Config schema or plugin logic depends on missing.

Because a package published this way ships built output, you generally don't need a prepare script — that's specifically for source distributed via GitHub, where pnpm needs to compile it after checkout. If you publish both source and prebuilt output to npm anyway, keeping prepare doesn't hurt, but it isn't what makes the npm path skip the build-permission prompt; shipping compiled lib/ in files is.

Publishing via GitHub

If you're not ready to manage npm releases yet, a tagged GitHub repository works as an install source on its own — dsh plugin --profile <name> add github:owner/repo. The tradeoffs:

  • Users pull source, not output, so your repo needs a working prepare script that compiles src/ to lib/ (or wherever your main field points) automatically after install.
  • The first time someone installs it, pnpm 10+ blocks that prepare script by default and asks the user to explicitly add allowBuilds: { your-package: true } to their profile's pnpm-workspace.yaml — a real friction point, and one the dsh docs frame as a genuine security decision, not boilerplate: it's "permission to execute the package's code on your machine at install time, outside any sandbox the agent runs under." See Installing DeepSeek-Harness Plugins from GitHub for the user-facing side of that prompt.
  • Encourage users to pin a commit (github:owner/repo#<sha>) rather than tracking a branch — without a pin, a later push to your default branch silently changes what code runs the next time someone reinstalls or updates.

Neither path is inherently more or less "legitimate" — plenty of real, actively used plugins across the ecosystem are GitHub-only. But if build friction or trust concerns come up from your users, publishing prebuilt output to npm is the direct fix.

Versioning: there's no separate dsh version system

Plugin versioning is ordinary pnpm/npm semver — dsh doesn't layer its own release or compatibility system on top. dsh plugin --profile <name> update runs pnpm update semantics against your profile's dependencies; there's no dsh-specific changelog format or version-pinning mechanism beyond what npm and Git already provide (a version range in package.json, or a commit SHA for GitHub installs).

README conventions that actually help adoption

There's no schema-enforced README format, but the convention that's emerged across the ecosystem — and what most crawlers and directory sites, including FindHarness, expect to find — is a copy-pasteable install command near the top, in the exact form users need:

dsh plugin --profile web add your-package-name
# or, for a GitHub-only release:
dsh plugin --profile web add github:owner/repo

Skip placeholder profile names like <profile> in that snippet if you can help it — web is the one profile every user already has.

How discovery actually works

dsh doesn't have an official marketplace, submission form, or registry beyond npm and GitHub themselves, so "getting discovered" comes down to two concrete, low-effort steps:

  1. Tag your repository with the GitHub topic dsh-plugin. This is the one discovery mechanism the official README names explicitly — it's what topic-search-based tooling looks for first. See How to Find DeepSeek-Harness Plugins for how noisy that topic actually is in practice and why the tag alone isn't sufficient signal for a tool to trust you're a real plugin.
  2. Get listed in awesome-dsh-plugin/awesome-dsh-plugin, the most actively maintained community index — it accepts pull requests adding new entries in its established - [owner/repo](url) - one-line description format.

Why the dsh.bundle.patch field is what actually verifies you

A GitHub topic is trivially self-applied by anyone — it doesn't prove a repository is a real, working dsh plugin rather than a project that merely mentions dsh in its README. The one field that's an actual functional signal is the same dsh.bundle.patch field covered throughout this series: a directory that wants to separate real plugins from noise can check whether a candidate repository's package.json (for npm-published packages) actually declares it. That's the verification approach FindHarness's own /plugins catalog uses on top of the community-curated awesome-dsh-plugin list — it isn't a submission form so much as a consequence of shipping the field correctly and being indexed in the sources that get checked. See How to Vet a DeepSeek-Harness Plugin Before You Install It for the installer's side of that same verification logic.

FAQ

Do I need to publish to npm to be listed anywhere?

No — GitHub-only plugins with the dsh-plugin topic and a working install command are commonly listed in community indexes and directories, npm publication just removes the allowBuilds friction for your users.

What's the minimum I need in package.json to publish?

name, version, an entry point (main), a files array that includes your compiled code and cordis.patch.yml, and the dsh.bundle.patch field pointing at that patch file. Everything else (description, keywords, repository URL) helps discoverability but isn't functionally required by dsh.

Should I add dsh-plugin as an npm keyword too?

The GitHub topic is the mechanism the official README names explicitly for repository-level discovery; adding a matching npm keyword is a reasonable, low-cost step for npm-based search tooling, though it isn't something the official docs specifically require.

How do I know if my published package actually works from a clean install?

Run pnpm pack, inspect the resulting tarball's contents, then dsh plugin --profile <a throwaway profile> add ./your-package-0.1.0.tgz to simulate exactly what a real user's install will pull down — this catches the missing-cordis.patch.yml-in-files mistake before a real user does.

Next steps

Walk through the whole build-to-publish path from an empty folder in Build a DeepSeek-Harness Plugin from Scratch, see the installer-side experience your users will have in How to Install DeepSeek-Harness Plugins and Installing DeepSeek-Harness Plugins from GitHub, and check your plugin against the same trust signals installers use in How to Vet a DeepSeek-Harness Plugin Before You Install It.