DSH Plugin Store
Back to home

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
Skills
GitHub repo

Introduction

dsh-plugin-switch

image

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 optional plugin.json, and spawns dsh plugin add <spec> as a child process for installs. Exposes ctx.marketplace for the client.
  • Client (src/client/) — registers a tab into the official settings.plugins.tab slot , 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 web capability (ctx.web.fetch) to call the GitHub REST API and uses node:child_process to spawn dsh plugin add. No UI.
  • Client — uses the slots service 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:

  1. @deepseek-ai/dsh-tool-web with fetch: true. The base bundle composes this with fetch: false by default. This bundle's cordis.patch.yml overrides that row to flip the boolean on.

  2. A registered WebFetchProvider. The base bundle does not mount one. Add the row in your profile's cordis.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.json manifest 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.