Nexus-Aethra
DSH-plugin-switch
DSH Plugin Switch is a marketplace for DeepSeek Harness plugins and skills. It lets users browse, search, and install community projects from GitHub, automatically detecting whether a repository is a DSH plugin or a DSH skill and installing it to the correct location.
- Stars
- 1
- Language
- TypeScript
- Created
- Aug 13, 2026
- Updated
- Aug 13, 2026
Introduction
dsh-plugin-switch
A marketplace for community DeepSeek Harness plugins. The plugin adds a Marketplace tab inside Settings → Plugins, browses the official dsh-plugin GitHub topic, and installs chosen repositories through dsh plugin add.
Before installing, Marketplace reads the repository's Git tree. Repositories with cordis.patch.yml are installed as profile plugins. Repositories with a root SKILL.md, .dsh/skills/<name>/SKILL.md, or .agents/skills/<name>/SKILL.md are installed as DSH skills under ~/.dsh/skills/<name>/; no package lifecycle scripts run for this path.
Status
This iteration ships both halves:
- Host (
src/index.ts) — talks to GitHub REST, caches the search for ten minutes, reads each repo's optionalplugin.json, and spawnsdsh plugin add <spec>as a child process for installs. Exposesctx.marketplacefor the client. - Client (
src/client/) — registers a tab into the officialsettings.plugins.tabslot , renders the search box + repo list + install buttons, and calls the host service through the cordis DI proxy.
pnpm install && pnpm build succeeds end-to-end on this checkout:
[CJS] lib/client.js 9.91 kB │ gzip: 3.30 kB
[ESM] lib/index.js 5.13 kB │ gzip: 2.18 kB
[ESM] lib/invariant.js 0.73 kB │ gzip: 0.43 kB
Architecture
DSH-plugin-switch/
├── package.json # npm package + dsh.bundle + dsh.client manifest
├── pnpm-workspace.yaml # own workspace; allowBuilds for the prepare hook
├── cordis.patch.yml # the layer applied when a profile lists this bundle
├── tsconfig.json # ESM + JSX, no monorepo paths
├── tsdown.config.ts # host (ESM Node) + client (CJS browser) builds
├── src/
│ ├── index.ts # host half: GitHub fetch, cache, ctx.provide('marketplace')
│ ├── invariant.ts # companion; no runtime invariant (see file JSDoc)
│ └── client/
│ ├── index.tsx # client half: registers settings.plugins.tab
│ ├── MarketplaceTab.tsx # UI body (search + list + install)
│ ├── MarketplaceTab.module.css
│ └── css-modules.d.ts
└── README.md
Why host + client
DSH plugins are roles in capability seams . This plugin needs both roles:
- Host — uses the
webcapability (ctx.web.fetch) to call the GitHub REST API and usesnode:child_processto spawndsh plugin add. No UI. - Client — uses the
slotsservice to register a tab into the official settings section. No network.
They communicate through ctx.provide('marketplace', service) on the host side and ctx.marketplace on the client side — the cordis DI proxy bridges host and browser across the JSON-RPC seam.
src/invariant.ts — no runtime invariant
The upstream convention "Every package owns ./invariant" requires every package to ship a runtime-invariant companion. This plugin does not own any session log events, registries, or services whose entries could outlive the apply call — it only fetches GitHub, caches in process memory, and spawns child processes for installs. So the invariant companion is a documented no-op with a real reason. A later change that adds an event subscription or a registry contribution must replace the body with the corresponding register call.
Prerequisites
The plugin needs ctx.web.fetch to reach GitHub. Two pieces must be in the composed tree:
-
@deepseek-ai/dsh-tool-webwithfetch: true. The base bundle composes this withfetch: falseby default. This bundle'scordis.patch.ymloverrides that row to flip the boolean on. -
A registered
WebFetchProvider. The base bundle does not mount one. Add the row in your profile'scordis.patch.yml:- insert: - id: web-fetch-http name: '@deepseek-ai/dsh-web-fetch-http'
Install a GitHub Release tarball
Build a release artifact with pnpm run pack, upload dist/dsh-plugin-switch-<version>.tgz to a GitHub Release, then install that asset URL directly:
download tarball from our release
dsh plugin --profile web add ./dsh-plugin-switch-<version>.tgz
For local verification, install the generated artifact with dsh plugin --profile demo add ./dist/dsh-plugin-switch-<version>.tgz.
pnpm ≥10 blocks the prepare script the first time; pnpm-workspace.yaml allows it for this package, so the add completes in one shot.
Configuration
- id: dsh-plugin-switch
config:
githubToken: ghp_xxx # optional; raises the rate limit to 5000/h
The token is optional. Without it, anonymous requests run at 60/h per IP — fine for a single browsing session but the ten-minute host-side cache is what keeps the limit under control.
How the install action works
The host's install(spec) spawns:
dsh plugin --profile <active-profile> add <spec>
as a child process and streams the exit code back to the client. stdout/stderr become the inline status message under the install button. A successful install requires a profile restart — the button surfaces a "restart DSH" hint when the host reports success.
Build-script approval
Most plugins install immediately. If pnpm rejects a Git-hosted plugin because it needs a lifecycle build script, the queue item changes to Approval needed instead of failing permanently. The queue displays the exact package-and-revision key pnpm requested. Selecting Allow build and retry writes only that key to the active profile's pnpm-workspace.yaml allowBuilds map and retries the original install. Approve this only for a repository and pinned revision you trust.
Known limitations
- No
plugin.jsonmanifest enforcement. Repos can ship any shape they like; the UI shows the manifest badge when present and falls back to the GitHub description otherwise. A later iteration may define a convention upstream. - No allowlist for installed-plugin configuration cards. Each plugin's namespace still needs an entry in the host apiproxy allowlist to expose a configuration card in the existing Plugins page. The Marketplace tab itself does not need the allowlist; this only matters for the post-install configuration UX.
- Anonymous GitHub API rate limit. 60 calls/h per IP. The host caches the search for ten minutes; users with many sessions should set
githubToken.
License
MIT.