DSH Quality
Beginner
4 min read

Write Your First DeepSeek Harness Plugin

Write Your First DeepSeek Harness Plugin — Learn how to build beginner dsh plugins.

Last updated: 2026-08-19

One sentence: everything is a plugin

DeepSeek Harness (dsh) is built around a single idea: the CLI, the web UI, tools, commands, skills, MCP servers, LLM adapters and cron jobs are all plugins. A plugin is just a JS/TS module that exports apply(ctx, config). Once you internalize this, every extension point in dsh becomes the same skill with a different registration call.

The minimal skeleton that actually loads

The first trap for beginners: a package that installs but never activates. Only packages that declare dsh.bundle (specifically dsh.bundle.patch) become an active profile layer. Here is the smallest package.json that works:

json
{
  "name": "my-first-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.ts",
  "dsh": { "bundle": { "patch": ["index.ts"] } }
}

And the plugin itself:

ts
export function apply(ctx, config) {
  ctx.log('my-first-plugin activated');
  ctx.commands.register('hello', {
    description: 'Say hello from your first plugin',
    action: () => `Hello from ${config.name ?? 'my-first-plugin'}`,
  });
}

apply(ctx, config) is the contract: ctx is how your plugin registers capabilities, config is the user-provided options. No other lifecycle hooks are required.

Install it into your web profile

dsh keeps plugin bundles isolated per profile under $DSH_HOME/profiles/ (default ~/.dsh). Point your local plugin at the web profile and restart:

bash
dsh plugin --profile web add ./
npx @deepseek-ai/dsh web

Open http://127.0.0.1:3080 and you should see the activation log plus a /hello command ready to run.

Verify, then compare against the Hub

If nothing happened, the usual suspect is a missing dsh.bundle declaration. Confirm the profile list shows your plugin: dsh plugin --profile web list.

Once it runs, browse Top Rated plugins on this site and open one you admire — every one of them is the same shape: a module exporting apply(ctx, config). The only difference is what they register on ctx.