Write Your First DeepSeek Harness Plugin
Write Your First DeepSeek Harness Plugin — Learn how to build beginner dsh plugins.
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:
{
"name": "my-first-plugin",
"version": "0.1.0",
"type": "module",
"main": "index.ts",
"dsh": { "bundle": { "patch": ["index.ts"] } }
}And the plugin itself:
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:
dsh plugin --profile web add ./
npx @deepseek-ai/dsh webOpen 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.