Back to home@dennisrongo

dsh-plugins

Dennis Rongo's plugin collection for DeepSeek Harness (dsh)

Stars
0
Language
JavaScript
Created
Aug 23, 2026
Updated
Aug 28, 2026

Introduction

dsh-plugins

Dennis Rongo's plugin collection for DeepSeek Harness (dsh) — an MIT agent harness where every capability is a plugin.

Everything here is built on the harness's public seams: cordis service keys, the Typert host/client bridge, shell.* slots, the system-prompt section registry, and ctx.cmdlineArgs. No forks, no patched launcher, no vendored harness code.

Both surfaces are supported. Every plugin runs unchanged under the dsh CLI and under DSH Desktop — a community desktop wrapper that ships the harness pre-packaged with multi-provider model support ("Any model, zero setup"), for Windows and macOS. It is an independent project, not affiliated with DeepSeek.

The desktop keeps its own DSH_HOME (%APPDATA%\dsh-desktop\harness on Windows) with its own profiles, so you install a plugin once per profile on whichever surface you use. Nothing here is CLI-specific: the same package, the same cordis.patch.yml row, and the same /api endpoints serve both.

Status: developed against dsh 0.1.1-rc.2. dsh is a fast-moving 0.1.x dev preview that promises breaking changes — re-verify against your installed version.

Platforms: the plugins are plain Node and platform-agnostic, and DSH Desktop ships for Windows and macOS, so they should run on either. Most tooling is portable Node — verify.mjs, anchor.mjs, link-superpowers-skills.mjs. Only dev-link.ps1's profile-junction half is Windows-only. Everything here has been exercised on Windows against both the CLI and DSH Desktop; macOS and Linux are untested.

Plugins

PackageAddsHalvesEndpoints
dsh-todoper-workspace sprint/roadmap task list, plus a dsh-todo CLIhost + client + CLIdshTodo/list, replace
dsh-gitsource-control "Changes" tabhost + clientdshGit/status, diff, stage, commit, init, sync, suggestMessage
dsh-weatherweather bar in the shell overlayclient only
dsh-headless-plus--model / --resume / --continue for the headless appCLI app
dsh-superpowersSuperpowers methodology as a system-prompt sectionhost
dsh-mission-controlfleet dashboard overlay — sessions, swarm tree, token burn, permission inboxclient only

dsh-todo

A task tab scoped to the workspace you're working in, so each project keeps its own backlog instead of one global pile. It is a sprint/roadmap list, not a checklist: each task carries a status, a priority, and optional release and sprint labels.

What you get. A tab beside Chat and Trajectory. Tasks move through real workflow states — backlog · todo · in-progress · blocked · done — changed from a pill on the row, because a boolean can't express the two things a standup actually asks: what is moving, and what is stuck. Priority is P0P3 as a chip, with only P0/P1 coloured so the list flags what's urgent instead of turning into a rainbow. Release (v1.2.0, what ships together) and sprint (Sprint 24, when it's worked) are deliberately separate free-text axes — a task can be in both, and grouping works with no releases table to administer. Group by None · Status · Release · Sprint · Priority gives collapsible sections with their own progress bars; grouping by status is a kanban board without drag-and-drop. Clicking a title opens a task detail modal (description, status, priority, labels, due date); the chevron still gives a cheap in-row peek, and double-click renames inline. Due dates are stored as calendar days, so "due the 14th" reads as the 14th in every timezone; overdue is flagged red, due-today amber. Completed work is archived, not deleted — an item carries an optional archivedAt stamp whose presence is the archived state, so there's one source of truth and no way to store an archived item without a date. Every irreversible action goes through a confirmation dialog that quotes the task by name.

CLI. The package ships a dsh-todo binary so an AI agent can shell out and manage the same list you see in the tab — no profile, no session, no running server:

npx dsh-todo list --open --json
npx dsh-todo add "Fix token refresh" --priority p0 --release v1.2.0 --due 2026-03-14
npx dsh-todo update t1a2 --status in-progress --sprint "Sprint 24"
npx dsh-todo done t1a2 && npx dsh-todo archive

It targets a workspace directory (--workspace, default cwd) and talks to the same SQLite file the host uses, which is safe by construction: SQLite's file lock refuses a writer that lands inside another process's transaction, and the CLI sets a busy_timeout so it waits for the harness to commit rather than failing. Verified live — the CLI wrote while a running server held its handle and the API returned the new task with no restart. Ids accept any unambiguous prefix, an empty value clears a field (--release ""), invalid values are refused rather than dropped, and --json prints structured output on the error path too. Exit codes are distinct: 0 ok, 2 usage, 3 not found.

How it works. TodoService extends TypertRemoteService registers under the cordis key dshTodo and owns one SQLite database per project at <workspace>/.dsh/todo.db, resolved through workspaceRegistry. Writes use optimistic concurrency: every replace states the revision it observed, and the host rejects a stale write with ok:false, code:'revision-conflict' and the current list, rather than silently clobbering. A legacy central ~/.dsh/storages/dsh_todo.json is migrated on first read.

Endpoints. POST /api/dshTodo/list{ list: { items, revision, updatedAt } }; POST /api/dshTodo/replace → the new list or a revision conflict. Both take a single parameter named request.

The task shape is { id, title, description?, status, priority, release?, sprint?, dueDate?, createdAt, completedAt?, archivedAt? }. status is the source of truth — there is no separate done flag to fall out of sync — and only the status transition writes completedAt. Absent optional fields are absent keys, never ''. Existing v1 databases are migrated in place on first open: CREATE TABLE IF NOT EXISTS doesn't add columns to a table that already exists, so each new column is added with ALTER TABLE after consulting PRAGMA table_info, then title is backfilled from the old text and done = 1 becomes status = 'done'. The v1 columns are still written on every insert, so a downgrade still reads a sane list. src/db.ts is shared by the host and the CLI on purpose — a second copy of the migration is the one duplication that could genuinely corrupt a database.

Requires. workspaceRegistry from dsh-base, which @deepseek-ai/dsh-web-app composes by default. The CLI requires none of it.


dsh-git

Source control for the workspace, without leaving the harness.

What you get. A Changes tab showing branch, upstream ahead/behind, and a file list split into staged and unstaged, with per-row stage/unstage/discard and a diff pane. An ✦AI message button drafts a commit message from the staged diff through the harness's own llm service, then Commit all commits. Recent history is listed underneath. A directory that isn't a repository reports repo: false and offers Initialize repository rather than erroring.

The layout is responsive to the tab, not the window. The diff sits beside the file list when there's room and below it when there isn't, switched by a container query (@container dshgit (min-width: 720px)) — the tab is resized by the shell's own sidebar and panels independently of the viewport, so the width that matters is its own. Opening a diff never moves a row: the list's column width is reserved in both states, so the first click can't reflow and re-truncate every filename under the pointer. While a patch is in flight the pane shows a skeleton shaped like a diff — shimmering meta/hunk/add/del bands sized off the real 18px diff line — rather than a spinner, which would blank a large surface; the shimmer animates background-position, never a transform or a box dimension, so it can't shift layout, and prefers-reduced-motion flattens it. Clicking down a list starts overlapping requests, so each is stamped with a monotonic sequence and a stale reply is discarded rather than painted under the wrong filename. Icons are inline 16px SVGs on a matching 0 0 16 16 viewBox in 20px buttons, which keeps file rows at 32px.

How it works. GitService extends TypertRemoteService under the cordis key dshGit, shelling out to git in the workspace directory. Writes are serialised per repository root through an internal queue, so two tabs can't interleave a stage and a commit. Paths from the client go through assertSafePath, which refuses absolute paths and .. escapes. Untracked files have no diff for git to produce, so their contents are synthesized into a /dev/null patch — otherwise clicking a new file would show a blank pane and look broken. push without a remote fails as data ({ ok: false, output }), not an exception.

Endpoints. status, diff, stage, commit, init, sync, suggestMessage under POST /api/dshGit/<method>, each taking one parameter named request.

Requires. workspaceRegistry and llm (both composed by dsh-base) and agentDefaultModel for message drafting.


dsh-weather

Current conditions pinned to the bottom of the web UI: temperature, condition, location, a short hourly outlook, humidity and wind.

How it works. A pure-consumer client plugin registering into the additive shell.overlay slot — no host service, no endpoints, no API key. It fetches Open-Meteo directly from the browser (CORS-enabled). Readings are always fetched in Celsius and converted at render time, so the °F/°C toggle needs no refetch; the choice persists in localStorage["dsh-weather:unit"] and defaults to °F. Location resolves from localStorage["dsh-weather:location"], then a geo-provider chain, then a hard fallback to New York; every network path degrades to a visible error state rather than throwing into the shell.

Accessibility is deliberate: the unit toggle is a real <button> with an aria-label, the bar is aria-live="polite", focus is styled via :focus-visible, and prefers-reduced-motion disables the refresh spinner.


dsh-headless-plus

The stock headless app answers one task and exits, with no way to choose a model or continue a conversation. This replaces it.

Stock headlessheadless-plus
dsh --profile headless "task"unchanged
--model provider/model — per-invocation model override
--resume <session-id> — continue a persisted session
--resume latest / --continue / -c — continue this workspace's most recent session
--session-info — print the new session id on stderr at exit
dsh --profile headless-plus --model anthropic/claude-sonnet-4-6 "refactor the auth module"
dsh --profile headless-plus --continue "now add tests"
dsh --profile headless-plus --resume session-6f2ca6dc-… "pick up where we left off"

How it works. Two rows replace the two stock ones: a startup row that owns the flag family via ctx.cmdlineArgs (each app owns its own flags, so this doesn't collide with the launcher), and a runner row that receives the parsed task by injection. Resuming goes through the public ctx.agents.resume() with ResumeAgentOptions; --resume latest maps the workspace to its session directory using the same slug rule as dsh-session-persistence-jsonl. Model overrides go through ctx.agentDefaultModel. Note the folder is plugins/dsh-headless-plus but the package is @dennisrongo/dsh-headless-plus, and a mount row takes the package name.


dsh-superpowers

Makes the Superpowers methodology mandatory-first for every agent in a profile, rather than a skill the model may or may not reach for.

How it works. Upstream delivers its bootstrap through a SessionStart hook that must re-fire on startup|clear|compact. dsh has no hook shell, but its system prompt is a layered, ordered section registry that is reassembled after compaction — so one registered section covers all three upstream trigger points for the life of the session, with no gap where the bootstrap can fall out. It sits at order -50, just before persona.

Nothing is vendored. The section body is read from your own clone of the upstream repo at profile start, located via superpowersRoot, then SUPERPOWERS_ROOT, then a probe of common clone paths under $HOME. So git pull + a profile restart is the entire update path. To have the clone's skills catalog follow a pull too, instead of drifting as copies:

node scripts/link-superpowers-skills.mjs     # --dry-run to preview, --restore to undo

dsh-mission-control

One glass panel over the whole agent fleet, floating above the stock web UI.

What you get. A shell.overlay dashboard, docked as a right rail, with a Fleet list of every session (root and subagents) grouped by workspace and showing running / waiting / done, a swarm tree of coordinator → worker lineages, a stats strip (session count, running, subagents, waiting-on-you), estimated token burn broken down by model, and a permission inbox surfacing sessions blocked on approval / question / plan-review.

Stage is a full-screen takeover — press it and the rail swaps for a live grid of tiles, one per session that is running, waiting on you, or was touched inside the activity window (30m / 2h toggle), busiest first. A tile carries the session's live conversation and answers a pending permission in place, so you never lose the tab you came from; Esc or × returns to the panel. Because Stage covers the whole viewport it also spans DSH Desktop's 36px window-drag strip, which swallows clicks before hit-testing — its bar clears that band and every control opts out with data-dsh-no-drag.

A settings drawer persists to localStorage (bad shapes fall back to defaults, and a storage failure degrades to in-memory rather than throwing): sessions listed per workspace group, fleet sort order, and an optional pomodoro timer in the footer with configurable work / short-break / long-break lengths and a desktop notification on phase change.

How it works. A pure consumer on public faces only — ctx.sessions.list and ctx.workspaces.list as ObservableSnapshots bridged into React, sessionStats projections (turns / steps / llmMs / decodeTokens), and PendingInteraction off the session summaries. No services, no tools, no presets and no host half; it floats over the stock UI without touching it. CSS is namespaced dshmc-, and control metrics are CSS custom properties so the 400px rail's compact sizing and Stage's full-screen sizing derive from one set of tokens rather than diverging.


Every package carries an AGENTS.md with its endpoints, mount row, dev loop and a verification recipe. See AGENTS.md for the repo as a whole.

Install a plugin

All six are on npm, and each declares dsh.bundle — so one command installs and mounts it. dsh plugin forwards to pnpm inside the profile directory:

# web/desktop UI plugins
dsh plugin --profile web add @dennisrongo/dsh-todo
dsh plugin --profile web add @dennisrongo/dsh-git
dsh plugin --profile web add @dennisrongo/dsh-weather
dsh plugin --profile web add @dennisrongo/dsh-mission-control

# CLI-app and prompt plugins, in a headless-style profile
dsh plugin --profile headless add @dennisrongo/dsh-headless-plus
dsh plugin --profile headless add @dennisrongo/dsh-superpowers

Several at once is fine. Restart the profile and it's live — there is no cordis.patch.yml row to write; see step 4 if you're upgrading from a version that needed one.

Prefer the git source — to track main, or to pick up a change before it's released?

dsh plugin --profile web add "github:dennisrongo/dsh-plugins#path:/plugins/dsh-todo"
dsh plugin --profile web add "github:dennisrongo/dsh-plugins#main&path:/plugins/dsh-todo"   # pin a ref

Quote the argument — # and & are shell metacharacters. All six ship their built lib/, so a git install works even though it runs no build step.

The dsh-todo command

dsh-todo needs no profile and no running server, so it can also be installed on its own — useful for CI, or to let an agent manage a project's tasks without the web UI:

npx @dennisrongo/dsh-todo list --open     # no install at all
pnpm add -g @dennisrongo/dsh-todo         # then: dsh-todo list --open

Installing it into a profile already puts the binary on that profile's node_modules/.bin. Full command and flag reference: plugins/dsh-todo.

Installing from a clone

Clone if you want to edit the plugins or run the tests.

Prerequisites

node --version     # 22+
pnpm --version     # 11+
npm i -g @deepseek-ai/dsh
dsh --version

These plugins declare their @deepseek-ai/* packages as peers and deliberately don't install their own copies — they resolve to the ones inside your global dsh install. Step 5 is what wires that up.

Using DSH Desktop only? You don't need the CLI to install or run the plugins — steps 3, 4 and 6 work against the desktop's own profiles. You only need a copy of the harness packages for this repo's tooling (tests, typecheck, live editing), and you can point at the ones the desktop already bundles instead of installing the CLI:

$env:DSH_HOST_DEPS = "$env:LOCALAPPDATA\Programs\DSH Desktop\resources\app\node_modules\@deepseek-ai"

Verified working: anchoring resolves to the desktop bundle rather than the npm host. Keep it consistent — don't anchor against the bundle while a CLI profile serves the same plugin, or the two surfaces load different physical copies of the harness packages.

You also need the harness itself configured with a model provider before any of this is useful — that's dsh's own setup (~/.dsh/settings.yaml and credentials), not something these plugins touch.

1. Clone and build

git clone https://github.com/dennisrongo/dsh-plugins.git
cd dsh-plugins
pnpm install
pnpm run build      # rebuild lib/ from src/

pnpm run test and pnpm run typecheck need step 5's anchoring first — dsh-todo's smoke test imports @deepseek-ai/cordis directly, and on a bare clone that fails with ERR_MODULE_NOT_FOUND. Build works without it (the harness packages are marked external). If you want the tests before touching a profile, run just the anchoring half now:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-link.ps1 -IdentityOnly
pnpm run test

2. Pick or create a profile

A profile is a directory under $DSH_HOME/profiles/<name> holding a package.json and a cordis.patch.yml. The CLI uses ~/.dsh; DSH Desktop keeps its own DSH_HOME — on Windows that's %APPDATA%\dsh-desktop\harness, and the desktop logs the path it booted with at startup if you need to confirm it.

web and headless have built-in templates, so installing into them creates the directory, its manifest, an empty cordis.patch.yml, and the pnpm settings below. Any other name you must scaffold yourself — two files:

// <profile>/package.json
{
  "name": "dsh-profile-my-web",
  "private": true,
  "dsh": {
    "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] }
  }
}
# <profile>/pnpm-workspace.yaml — do not skip this
packages:
  - .

nodeLinker: hoisted
autoInstallPeers: false

That second file is what dsh writes for its own template profiles, and autoInstallPeers: false matters: these plugins declare the harness packages as peers, and with auto-install on, pnpm fetches its own copies from npm — at versions that don't even match the declared ranges, since the published @deepseek-ai/* releases lag the version bundled with dsh. You end up with duplicate harness packages in the profile and a latent module-identity problem.

Use @deepseek-ai/dsh-headless instead of dsh-web-app for a headless profile. Don't edit cordis.yml — it's generated; cordis.patch.yml is your layer.

3. Install the plugins

dsh plugin forwards to pnpm inside the profile directory:

dsh plugin --profile web add \
  "file:C:/absolute/path/to/dsh-plugins/plugins/dsh-todo" \
  "file:C:/absolute/path/to/dsh-plugins/plugins/dsh-git" \
  "file:C:/absolute/path/to/dsh-plugins/plugins/dsh-weather"

On Windows use a native absolute path with forward slashes — the MSYS /c/... form fails with LINKED_PKG_DIR_NOT_FOUND.

Each package declares dsh.bundle, so dsh plugin registers it as a profile layer and prints the package it added. (If you see declares no dsh.bundle — installed as a plain dependency, you're on an older version of that plugin and will need a manual row.)

DSH Desktop: same command with the desktop's profile. If that profile's node_modules was created by a different pnpm major you'll get ERR_PNPM_UNEXPECTED_STORE; edit the profile's package.json by hand instead and let step 5 supply the live module.

4. Nothing to mount — but read this if you're upgrading

Each package declares dsh.bundle, so dsh plugin add appends it to the profile's dsh.profile.bundles and it self-mounts. One entry brings up both halves of a plugin: the host service and the browser tab. Restart the profile and it's live.

Breaking change from earlier versions of these plugins. They used to require a hand-written insert: row in your profile's cordis.patch.yml. That row is now a duplicate of the one the bundle provides, and the harness treats that as fatal:

Error: dsh: plugin tree failed to load: failed to apply loader entry include
(cordis:include): duplicate loader entry id: dsh-weather

If you have rows for dsh-weather, dsh-todo, dsh-git, superpowers, headless-plus-startup or headless-plus-runner — or the headless-startup / headless-runner disables, which dsh-headless-plus now carries itself — delete them. Check with dsh --profile <name> --dump-config, which labels each row with the layer it came from; you want exactly one per plugin.

You still edit cordis.patch.yml to configure a row, which is what a bare id: is for:

# pin the superpowers clone instead of letting the plugin probe for it
- id: superpowers
  config:
    superpowersRoot: /absolute/path/to/superpowers

dsh-superpowers needs a clone of obra/superpowers on disk. To keep its skills catalog current on a git pull rather than drifting as copies, also run node scripts/link-superpowers-skills.mjs (cross-platform).

dsh-headless-plus replaces the stock headless app, so its bundle disables the two stock rows for you — it needs a profile built on @deepseek-ai/dsh-base + @deepseek-ai/dsh-headless.

5. Optional: link for live editing (Windows)

Installing is done — the plugins work at this point. A plain install resolves the harness packages through the profile, so you can skip straight to step 6. This step is for editing this repo and seeing the change without reinstalling, plus it's what makes this repo's own pnpm run test and pnpm run typecheck resolve.

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-link.ps1 -Profiles web -DesktopProfiles web
node scripts/anchor.mjs        # the portable half on its own, any platform

Two jobs, and both are undone by any pnpm install, so re-run afterwards:

  • Profile junctions. Points each profile's installed plugin at this repo so a rebuild self-deploys; pnpm otherwise materialises file: deps as copies frozen at install time.
  • Dependency anchoring. Points each package's node_modules\@deepseek-ai\* at your global dsh install. Once a plugin is junctioned it resolves through its real path, so Node looks for dependencies here rather than in the profile — without anchoring a junctioned plugin dies at boot with ERR_MODULE_NOT_FOUND. -IdentityOnly does this half alone.

It only touches plugins a profile actually declares, follows the package name (so unscoped packages work), and prints what it skipped.

macOS / Linux: only the profile-junction half is Windows-only. The anchoring half is portable — run node scripts/anchor.mjs, which is what makes pnpm run test and typecheck resolve. Install and run (steps 1–4, 6) work fine anywhere; verified that a plain install boots and serves /api with no linking at all. What you lose is the live-edit loop (re-run step 3 after a build, or symlink by hand).

6. Verify

node scripts/verify.mjs                     # static checks, no server needed
dsh --profile web --port 38111 --no-open    # then, in another shell:
node scripts/verify.mjs --port=38111        # adds the live /api probes

Cross-platform, exits non-zero on failure. It reports the dsh version on each surface, then for every package resolves each @deepseek-ai specifier its built output imports and asserts it lands in the dsh CLI's own copy (never a .pnpm store path), checks the other runtime dependencies resolve, imports each host entry point, and confirms each client bundle still carries its window.__ModuleLoader__.load wrapper. With --port it POSTs each host endpoint and requires 200 with "ok":true.

404 on a probe means the package's ./typert export wasn't registered — see the plugin's AGENTS.md. Then open the UI and confirm the tabs render; Promise.allSettled in the client swallows failures, so a tab can render while every call fails.

Run this after upgrading dsh. A harness upgrade doesn't break pnpm test — that only proves this repo is self-consistent. What it breaks is resolution and the wire contract, which is what this checks.

When something is wrong at the harness level rather than the plugin level — a session that won't load, a workspace showing no history, a hand-edited registry that reverts — see TROUBLESHOOTING.md. Most of those failures are silent.

Publishing

.github/workflows/publish.yml publishes every package that isn't already on npm at its current version, so a re-run after a partial failure is safe. It installs the harness and runs scripts/anchor.mjs first (the tests can't resolve the peers otherwise), then build, test and verify.mjs, and publishes with npm provenance.

Trigger it by publishing a GitHub release — the tag must match the package versions — or manually via workflow_dispatch, which also takes a dist-tag and a dry-run toggle.

It needs an NPM_TOKEN secret on this repository: Actions secrets are write-only and cannot be shared between user-account repos, so a token set elsewhere isn't visible here.

Development

pnpm install          # all packages
pnpm run build        # pnpm -r --if-present run build
pnpm run test         # pnpm -r --if-present run test

Client-half edits deploy on a browser refresh; host-half edits need a profile restart. Registering a new ./typert export needs a full restart either way — the loader caches its per-package verdict for the process lifetime.

Workspace configuration lives in pnpm-workspace.yaml: pnpm 11 ignores pnpm blocks in package.json, build permissions are allowBuilds (a map, not the older onlyBuiltDependencies list), and autoInstallPeers is off because the @deepseek-ai/* peers are dev-preview and partly unpublished.

One trap worth knowing: DSH Desktop runs a profile-repair install on startup that prunes this repo's per-package node_modules — which takes zod with it and makes the harness refuse to boot (Cannot find package 'zod'). Recovery is pnpm install at the root, then scripts\dev-link.ps1.

Repository layout

plugins/     one self-contained package each (pnpm workspace members)
scripts/     verify.mjs                    — check the plugins against your installed dsh
             anchor.mjs                    — point each package's @deepseek-ai at that dsh
             dev-link.ps1                  — anchor + junction into profiles (Windows)
             link-superpowers-skills.mjs   — link an upstream superpowers clone's skills

AGENTS.md            repo conventions and the rules that are not obvious
TROUBLESHOOTING.md   harness-level failure modes, mostly silent ones

dsh-todo, dsh-git and dsh-weather were consolidated here from standalone repos.

License

MIT