Skip to content
dsh.fish
Bundle

dsh-plugin-directory

Browsable, searchable, stats-driven directory of GitHub DSH plugin repositories, rendered inside the DSH Web GUI settings.

Source
dingzhenyao
stars
2 stars
License
MIT
Updated
Updated 10 days ago

Readme

# dsh-plugin-directory

A plugin directory for the DeepSeek Harness (DSH) Web GUI. It renders a
**Plugin directory** tab inside **Settings → Plugins** that lets you browse,
search, filter, and compare GitHub repositories tagged with the
[`dsh-plugin`](https://github.com/topics/dsh-plugin) topic — including each
plugin's install command, function category, install form, language, license,
stars, and a quality score.

中文说明见 [`README.zh.md`](./README.zh.md)。

## Install

This plugin is itself a DSH plugin. Install it with the `dsh` CLI:

**npm**

```sh
dsh plugin add dsh-plugin-directory
```

**git** (when published under a GitHub org/user)

```sh
dsh plugin add github:dingzhenyao/dsh-plugin-directory
```

**manual / local checkout**

```sh
dsh plugin --profile <profile-name> add ./dsh-plugin-directory
```

After installation, open the DSH Web GUI and go to **Settings → Plugins → 插件目录
(Plugin directory)**.

### Compatibility

The plugin works with DeepSeek Harness `0.1.0-rc.5` (source checkout) and
`0.1.0-rc.6` (npm). Its client peer dependencies accept `>=0.1.0-rc.5 <0.2.0`,
so both a local `pnpm dsh` checkout and an npm-installed `dsh` satisfy them.

## Usage

The tab is a client-side directory over a snapshot of the `dsh-plugin` topic.
Repos are included when their README documents a `dsh plugin` command, they
carry the `dsh-plugin` topic, or their name/description mentions `dsh-plugin` —
the repo name itself does **not** need to contain "dsh". Candidates are ranked
by stars so real plugins outrank list/boilerplate repos:

- **Search** — filter by plugin name, owner, or description; the query also
  triggers a **live GitHub search** for the latest matching repos (see below).
- **Category multi-select** — combine any number of function categories
  (tool, skill, memory, vision, UI skin, MCP, orchestration, CLI/TUI, web,
  agent, other).
- **Install-form filter** — restrict to bundle / repository / client / unknown.
- **Group-by dimension** — group cards by function category, install form,
  language, or star bucket (default: no grouping).
- **Sort** — by quality score, stars, or most recent update (default: stars).
- **Statistics dashboard** — totals and breakdowns by category, install form,
  language, and star bucket.
- **One-click install** — a card's **Install** button copies the plugin's
  install command, shown **only when the plugin's own README documents a real
  `dsh` install command** (see the extraction policy below).
- **My plugins** — a personal management panel below the directory. Clicking
  **Install** records the plugin, and you can also add plugins manually (the
  source repository `owner/repo` is required) and remove or refresh existing
  entries. The list is stored in the browser's `localStorage` under
  `dsh-plugin-directory:installed` (see the note below).
- **Refresh** — a manual refresh button re-pulls the latest snapshot from the
  CDN.

## Install-method extraction policy

Every listed plugin carries an `install` field describing how to install it.
It is derived in priority order:

1. **Plugin's own README** — the first real install invocation found in the
   README becomes the command, with the source line kept as a snippet. The
   scanner accepts `dsh plugin add <target>`,
   `dsh plugin --profile <name> add <target>`,
   `pnpm dsh plugin [--profile <name>] add <target>`, and a bare
   `.dsh-plugin` reference (`github:<owner>/<repo>#<ref>&path:/.dsh-plugin`).
   The `--profile` flag is preserved when present. Prose mentions without a
   concrete target are ignored.
2. **`package.json` signals** — when the README has no install command but the
   plugin's manifest declares `dsh.client` or `dsh.bundle`, the npm package
   name is used: `dsh plugin add <package-name>`.
3. **`.dsh-plugin` directory** — a repository plugin (no client/bundle
   manifest) is installed from the repo: `dsh plugin add github:<owner>/<repo>&path:/.dsh-plugin`.
4. **Fallback** — when nothing above applies: `dsh plugin add github:<owner>/<repo>`.

The **Install** button is shown **only for step 1** — when the plugin's own
README actually documents a `dsh` install command. Derived commands (steps
2–4) are recorded on the entry for future use but never surface a button, so
a repository whose README does not explain how to install it is never given a
guessed install button.

## Data sync

- `data/plugins.json` and `data/meta.json` are regenerated by the
  `sync-data` GitHub Actions workflow every **6 hours**
  (cron `0 */6 * * *`), fetching repos whose README documents a `dsh plugin`
  command, that carry the `dsh-plugin` topic, or that mention `dsh-plugin` in
  name/description (a repo name need not contain "dsh"), ranked by stars, and
  committing the resulting snapshot back to the default branch.
- `pnpm sync` runs the same pipeline manually (deep-inspection results are
  cached in `data/.inspect-cache.json` and reused while a repo's `pushed_at`
  is unchanged).
- `data/overrides.json` lets humans correct the keyword-based category
  classification, keyed by `owner/repo`.
- The snapshot is bundled into the client as an offline fallback, and the tab
  refreshes it from a CDN at runtime (see below).

## Runtime data refresh & live search

- **CDN refresh** — on open, the tab fetches the latest snapshot from
  GitHub raw (`https://raw.githubusercontent.com/dingzhenyao/dsh-plugin-directory/main/data`);
  if it succeeds (and is no older than the bundled snapshot) it replaces the
  bundled snapshot and shows the last sync date, and if it fails (offline) it
  silently keeps the bundled snapshot. The owner (`dingzhenyao`) is the GitHub
  account hosting the `dsh-plugin-directory` repository — a single fixed value
  baked in at build time, shared by every installer (not per-installer).
  `raw.githubusercontent.com` follows the branch within minutes and serves
  files with `Access-Control-Allow-Origin: *`.
- **Live search** — typing a query also queries the GitHub Search API for
  `topic:dsh-plugin` repositories matching the query (debounced, 20 results),
  and appends them as a **Live results** section (deduped against the
  snapshot). Live results have no README inspection, so they show no install
  button. On rate-limit / failure the tab degrades to local results with a
  notice.

## My plugins (host-file management)

The **My plugins** panel records the plugins you manage through this
directory. It is backed by a **host-side data file** at
`$DSH_HOME/storages/dsh-plugin-directory/installed.json`, written through a
Typert Remote (`ctx.remote.pluginManager.*`) from the browser:

- Clicking a card's **Install** button adds the plugin (recorded with the
  `github:<owner>/<repo>` source and method `search`).
- **Add** accepts a manual source repository — `owner/repo` or
  `github:owner/repo` — and rejects anything that is not a valid
  `owner/repo` (method `manual`). The display name is the repo basename.
- **Update** refreshes an entry's timestamp (reinstall); **Delete** removes
  it.
- **Sync status** reads the harness's **real Loader inventory** (the built-in
  read-only `pluginInventory` Remote) and shows a per-entry badge: **installed**
  (with `enabled`/`failed`/`loading` refinements), **disabled**, **not
  installed**, or **unknown** (inventory unavailable). Matching is by the
  Loader `moduleName` against the ledger id/source, with a unique-basename
  fallback so an npm install of the same project still lines up with a
  recorded git source. The inventory is re-read automatically every 30 seconds
  (and on demand via the **Sync status** button).

The ledger is bookkeeping only: it records which plugins you triggered an
install for (plus manual additions), and it **does not** actually run
`dsh plugin add`/remove or read your exact install state back into the file —
the real install state is shown live via the inventory sync, not persisted.

### How the Remote works

The host exports `PluginManagerGateway` (a `TypertRemoteService` subclass with
`@Remote('list' | 'add' | 'delete' | 'update')` methods). The host gateway
discovers it through its SRC fallback (`resolveSrcDescriptor`) — no generated
`./typert` manifest is required. The client half hand-writes the matching
`TYPERT_REMOTE` contribution (`src/client/remote.ts`) with **strict** zod
codecs (the client gateway rejects `src-json`), mounts it via
`ctx.remote.$mount(...)`, and the tab calls `ctx.remote.pluginManager.*`.
The real install status comes from the harness's own read-only
`ctx.remote.pluginInventory` Remote (mounted by `@deepseek-ai/dsh-api-remotes`).

The browser bundle needs `@deepseek-ai/dsh-api-remotes` injected (it provides
`ctx.remote`), and the host build lowers the standard `@Remote` decorator via a
tsdown transform (`ts.transpileModule`) so the method markers register at
runtime.

The validation helpers (`isSourceRepo` / `normalizeSource`) are shared between
the host (`src/data/installed.ts`) and client (`src/data/installed-types.ts`).

## Development

```sh
pnpm install
pnpm build       # tsdown → lib/index.js (host half) + lib/client.js (browser half)
pnpm test        # vitest
pnpm sync        # refresh data/ from the GitHub dsh-plugin topic
pnpm typecheck   # tsc --noEmit
```

In the DSH file sandbox, vite's Windows realpath probe spawns a suppressed
child process, so run tests through the shim:

```sh
NODE_OPTIONS=--require=./scripts/vitest-sandbox.cjs pnpm test
```

## Known limitations

- **Category label language** — fixed copy (tab name, buttons, placeholders)
  follows the active UI language immediately, but category/install-form label
  dictionaries are read when the tab page opens: after switching the UI
  language, reopen the tab for the updated labels.
- **"My plugins" is bookkeeping, status is live** — the panel stores which
  plugins you triggered an install for (and manual additions) in the host data
  file, but does not write the harness's real install state back; the
  per-entry installed/disabled/failed badge is read live from the Loader
  inventory via **Sync status**, so an install performed outside this
  directory only shows up if its `moduleName` matches a recorded entry.
- **CDN cache delay** — `raw.githubusercontent.com` serves the branch with a
  ~5-minute cache, so a fresh CI commit reaches the CDN refresh within minutes
  (and a stale edge cache is rejected in favor of the bundled snapshot); live
  search covers the gap for the very newest repos.
- **Unauthenticated search quota** — the live-search feature queries GitHub's
  search API from the browser (unauthenticated, 10 requests/minute); it is
  debounced and degrades to local results on rate-limit. The sync pipeline
  uses `GITHUB_TOKEN` in CI (see `.github/workflows/sync.yml`).
- **CDN requires the repo to be published** — the CDN refresh and git-install
  line point at `github.com/dingzhenyao/dsh-plugin-directory`; until that
  repository exists (pushed to GitHub), the tab silently falls back to the
  bundled snapshot. Change `src/client/data.ts` (`CDN_BASE`) if the repo moves.

## License

MIT

Install

dsh plugin --profile web add github:dingzhenyao/dsh-plugin-directory#1c06d79dd54c0bb1d5bd94af2e6b9eb0ee14f02c

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
Source