Back to home

nonmean

dsh-plugin-explorer

DSH client plugin: browse GitHub repos tagged dsh-plugin (name, README, stats) with sync and search

Stars
1
Language
TypeScript
Created
Aug 14, 2026
Updated
Aug 14, 2026

Introduction

dsh-plugin-explorer

A local tool for the DeepSeek Harness web GUI that browses the GitHub dsh-plugin topic: every repo's name, README, and statistics (stars, forks, open issues, language, license, last update, topics), with a Sync button to refresh the data and an in-memory search over the cached snapshot.

It is a client plugin (dsh.client dual-face package) that mounts one entry into the sidebar's footer action list (sidebar.footer.action): a Plugin Explorer trigger beside Settings that opens the tool's modal.

Using the tool

  1. Open the web GUI (http://127.0.0.1:3080) and refresh the page.
  2. Click Plugin Explorer in the sidebar footer (bottom-left, next to Settings).
  3. Press Sync to fetch the topic's repos from the GitHub search API (sorted by stars, up to 1000 repos — GitHub's search cap).
  4. Search filters the cached snapshot by repo name, description, topics, language, license, and README text (readmes the tool already fetched).
  5. Expand a repo's README to load and render it (fetched lazily on first expand, then cached).

Data persists in localStorage under dsh-plugin-explorer:repos:v1 and dsh-plugin-explorer:readmes:v1, so the list survives restarts and works offline. The readme cache is size-bounded (oldest entries evicted).

GitHub API notes

  • Unauthenticated REST rate limits apply (60 req/h core, 10 req/min search, per IP). One full sync costs up to 10 search requests; READMEs cost one request each and are cached. Rate-limit hits abort gracefully with a message and keep the previous data (a partial sync is preserved).
  • The tool talks to api.github.com straight from the browser (CORS is permissive); no token is required for public data. If you hit limits often, set a GITHUB_TOKEN? No — the browser cannot read your env; the tool is intentionally anonymous.

Architecture

src/
  index.ts              node half: empty apply() (mounts the loader row)
  client/
    index.ts            browser half: inject ['slots'], slots.inject(...)
                        into 'sidebar.footer.action', register RepoExplorer
    RepoExplorer.tsx    trigger + modal: sync, search, list, README expand
    RepoExplorer.module.css
    github.ts           GitHub REST client + localStorage cache + helpers

The browser bundle (lib/client.js) is emitted by tsdown in the shell's closure-factory format: window.__ModuleLoader__.load({ id, factory }) with the frozen platform module table as externals (react, @deepseek-ai/cordis, @deepseek-ai/dsh-client-ui-primitives, ...), CSS Modules compiled by lightningcss and injected as <style data-plugin> tags.

Mounting into the GUI

The tool is mounted through the web profile's user patch layer (no changes to the DeepSeek Harness checkout):

# ~/.dsh/profiles/web/cordis.patch.yml
- insert:
    - id: dsh-plugin-explorer
      name: dsh-plugin-explorer

plus a symlink so the profile can resolve the package:

ln -s /Users/boxiao/Documents/dev/dhs-plugin-explorer \
      /Users/boxiao/.dsh/profiles/node_modules/dsh-plugin-explorer

New rows are read at GUI boot (config hot-reload is off in the web profile), so a restart of the dsh web process was required once. After that, rebuilds of lib/client.js hot-reload through the client-plugin HMR chain (the host polls the bundle and broadcasts rebuilt over SSE; no refresh needed).

Development loop

pnpm install          # registry dev deps only; dsh types resolve through a
                      # node_modules/@deepseek-ai -> ~/.dsh/profiles/node_modules/@deepseek-ai
                      # symlink (the exact package versions the GUI runs)
pnpm run typecheck    # tsc --noEmit against the real dsh .d.ts artifacts
pnpm run build        # tsdown: lib/index.js + lib/client.js (+ .map)
pnpm run watch        # tsdown --watch; bundle rewrites trigger client HMR

Known limitations

  • GitHub search caps topic results at 1000 of ~2370 repos; the status line reports the topic total and the capped list length.
  • The list renders the first 300 matches at once (scroll container); the status line notes when more matches exist.
  • Unauthenticated rate limits can interrupt a sync; partial results are kept and the UI explains the reset time.