Back to home@dujar

dsh-community-plugins

DeepSeek Harness web-GUI plugin: discover and install community plugins from the dsh-plugin topic — searchable SQLite catalog, fork browser, local-plugin installs

Stars
0
Language
JavaScript
Created
Aug 25, 2026
Updated
Aug 25, 2026
GitHub repo

Introduction

dsh-community-plugins

English | 简体中文

A DeepSeek Harness (DSH) web-GUI plugin that adds a Community plugins tab to Settings → Plugins. It discovers the plugins published under GitHub's dsh-plugin topic, keeps a local SQLite catalog of their metadata, refreshes it in the background, and lets you search, browse, aggregate by tag, and install/uninstall with one click.

The tab sits alongside the built-in Plugin configuration and Plugin list tabs. The built-in Plugin list only shows what is already installed; this tab makes the rest of the community discoverable.

Demo: browsing, filtering, and installing a plugin from the Community plugins tab

Features

  • Two viewsList (rich cards) and Grid (compact cards), with a view switcher in the toolbar; the choice persists per browser.
  • Categorized tags — topic tags are grouped into labeled categories (DSH & DeepSeek, AI & Agents, Languages, Web & UI, Data & Storage, Tooling, Trading, Security, and Other) and shown in a compact, scrollable area with its own filter box; click a tag to drill into that category, click All (or the active-tag ×) to clear it. The umbrella dsh-plugin and deepseek-harness tags are omitted.
  • Local SQLite catalog — plugin metadata is cached in $DSH_HOME/dsh-community-plugins/catalog.db (node:sqlite). Browsing, searching, sorting and tag aggregation all read this local file, so they are instant and offline.
  • Stable results — the result list never reloads on its own. It is re-read only when you change a filter (search text, sort, tag, status filter) or press Refresh, and the filters plus the last result set survive switching settings tabs and reloading the page. Clear filters in the status row resets search, sort, tags and the status filter in one click.
  • Status filterAll / Installed / Local only chips, each with a count. Installed narrows the community list to plugins that are in this profile (by resolved GitHub repo or package name); Local only lists the installed plugins that are not in the community catalog at all — private repos, local checkouts, registry packages outside the dsh-plugin topic — each with its description, version and author read from the installed package's manifest (file:/link: specs resolve relative to the profile directory, anything else through the profile's node_modules), its install spec, an Uninstall button that removes by package name, and — when the package ships one — its README rendered inline (a safe minimal markdown subset: headings, bullets, fenced code, bold/italic, http(s) links; everything else is escaped, truncated at 12k chars). Multiple README variants (README.md, README.zh-CN.md, …) become language tabs on the card, so each reader can pick their preferred language. A local plugin whose repo exists on GitHub additionally renders with the same rich card as catalog rows (avatar, stars, forks + fork browser, topics, remove/open actions) — metadata is fetched once per repo and cached in SQLite.
  • Background refresh — the catalog seeds itself at dsh web startup (top repos by stars and by recent update) and refreshes again in the background when you change the search term, so the cache grows with what you look for. A Refresh button forces an immediate update; the status row shows Updating… while a fetch is in flight, and the list picks up the new rows once that fetch lands.
  • Search by name, owner, description, or topic, with sort by stars / forks / recently created / recently updated / name. Recently updated uses GitHub's real updated_at (any repo change), not the push time the cards display.
  • Fork browser — each card shows the repo's fork count; click it to list the forks (stars, last push, description, archived state) in a dialog. Every fork has a Compare with upstream link (github.com/<upstream>/compare/<branch>...<forkOwner>:<branch>) so you can see what it carries that upstream has not merged, and an Install button that installs that fork instead of the original. When a fork would displace an already-installed plugin of the same name, the row says replaces <owner/name>. Listings are cached per repo (10 min, DSH_COMMUNITY_FORKS_TTL_MS); Refresh in the dialog forces a fresh fetch.
  • Install — one click runs the real dsh plugin --profile <profile> add github:owner/name on the host (pnpm under the hood) and reconciles dsh.profile.bundles. A pre-flight check fetches the repo's package.json and rejects repos that do not declare a dsh.bundle manifest, so repos that merely carry the dsh-plugin topic (the harness itself, apps, demos) fail fast with a clear reason instead of a confusing pnpm error or a hang.
  • Add local plugins — at the top of the tab is an Add local plugin panel. Click Browse… to walk the filesystem in a folder picker, or paste a path directly (e.g. /home/user/my-plugin), then click Validate. The host reads that directory's package.json and checks for a dsh.bundle.patch declaration — if the manifest is missing, invalid JSON, or lacks the DSH bundle key, the UI shows a clear error and the install button stays disabled. Once validated, click Dry run to confirm the metadata (name, version, author, description) before committing; the dry run returns the same info without touching pnpm. Only after a successful dry run does Install local plugin become active, and clicking it runs the real dsh plugin add <path> on the host.
  • Folder pickerBrowse… opens a dialog that lists one directory at a time, walked by the host over /community-plugins/browse. A browser folder chooser cannot return an absolute path without routing the selection through a file-upload prompt, which is the wrong shape when only a path is wanted, so the disk is read server-side instead — nothing is uploaded. Folders that already hold a dsh.bundle manifest are badged plugin and can be taken with Select without opening them; Select this folder takes whatever directory is currently listed. Hidden directories are off by default and the toggle persists while the tab is open.
  • Already-installed plugins are marked — the tab reads the active profile's package.json, so anything you installed earlier (here or with dsh plugin add) shows a green Installed badge and a Remove button instead of Install. A plugin that is installed but missing from dsh.profile.bundles — installed, not loaded — additionally shows a Not enabled badge. Matching is by resolved GitHub repo, falling back to package name, so a plugin installed from the npm registry under a name that differs from its repo is not detected.
  • Enable / disable — every installed plugin (community, local, or in the fork browser) gets an Enable or Disable button that adds or removes its name from dsh.profile.bundles in the profile manifest. The badge and button flip immediately; the harness itself loads or unloads the plugin after you restart dsh web. Only names that are actually installed can be toggled, so the manifest can't be polluted with unknown entries. Every notice that needs one carries a Restart dsh web button: it respawns the server with the exact command line it was launched with, and the page reloads itself once the new instance answers.
  • Uninstall — one click runs dsh plugin --profile <profile> remove <package>, resolving the actual package name from the profile manifest.
  • Copy install command — for terminal users, every list card offers a copy-to-clipboard button with the exact command.
  • Internationalization — English and Simplified Chinese, following the DSH language setting and switching live when it changes.
  • Theme-aware — all styling rides the DSH design tokens (--dsw-alias-*), so it follows the light/dark theme automatically.

Install

Requires Node.js 22.19+ and pnpm (dsh plugin installs through pnpm under the hood).

# local checkout (development)
dsh plugin --profile web add /path/to/dsh-community-plugins

# from GitHub
dsh plugin --profile web add github:dujar/dsh-community-plugins

Then restart dsh web and refresh the browser page. The install adds dsh-community-plugins to the profile's dsh.profile.bundles automatically; if it is not added, append "dsh-community-plugins" to that array in $DSH_HOME/profiles/web/package.json and restart.

Usage

  1. Open Settings → Plugins → Community plugins.
  2. Browse the top plugins (by stars), switch between List / Grid, and use the category tags to drill into a topic.
  3. Type to search — results come from the local cache instantly, and the host refreshes that query from GitHub in the background. Results then stay put until you edit a filter again; Clear filters puts you back at the unfiltered list.
  4. Click a card's fork count to browse that repo's forks, compare one against upstream on GitHub, and install from it if it carries changes you want.
  5. Click Install on a plugin. When it finishes, restart dsh web and refresh the page (the note under the result explains this). Newly installed plugins appear in the built-in Plugin list tab after the restart.

How the cache works

  • The host fetches the topic with the GitHub search API and upserts each repo (name, owner, description, stars, language, topics, archived/fork flags, last push) into the SQLite catalog.
  • At boot it fetches the top 100 by stars and the top 100 by updated; each search fetches the top 50 for that query. Fetching is coalesced and rate-limited (one fetch per 6s by default) so it stays under GitHub's unauthenticated search limit (10/min). On a 403/429 the worker backs off until the reset time GitHub reports.
  • GitHub's search API caps unauthenticated results at 1000 per query, so the local cache is a curated, accumulating subset of the full topic (popular + recently active + whatever you search), not a complete mirror of all ~5000 tagged repos. Searching is the way to pull more of the topic into the cache.

Structure

dsh-community-plugins/
  package.json         # manifest + dsh.bundle.patch / dsh.client declarations
  cordis.patch.yml     # host-half mount line (applied by the profile bundle mechanism)
  lib/
    index.js           # host half: SQLite catalog + background refresh + install/uninstall routes
    client.js          # browser half: list/grid/table views + tag aggregation (React, zero-build, i18n)
  test/
    helpers.test.mjs   # repo/spec parsing helpers
    catalog.test.mjs   # SQLite upsert / query / tag aggregation
    host-smoke.test.mjs# route registration
    client-smoke.test.mjs # slot wiring
    install-route.test.mjs # install pre-flight + responses
    folder-picker.test.mjs # browse route + local validate/install flow
    forks.test.mjs     # fork listing cache, forced refetch, rate-limit fallback
    installed-state.test.mjs # profile manifest -> installed / enabled reporting
    toggle-route.test.mjs  # enable/disable edits dsh.profile.bundles, guarded
    restart-route.test.mjs # restart responds, schedules restarter, exits; untrusted rejected
    mini-react.mjs     # React stub + fake clock shared by the client tests
    persistence.test.mjs # cached results / filter-driven refetch / clear filters
    forks-ui.test.mjs  # fork count -> fork browser -> install from a fork
  LICENSE
  README.md           # English (this file)
  README.zh-CN.md     # Simplified Chinese

Host routes

RouteMethodDescription
/community-plugins/catalogGETLocal catalog query. Params q, tag, sort (stars/forks/created/updated/name), filter (all/installed/local), limit, offset. filter=local returns the profile's installed plugins with no catalog row (flagged local: true). Returns { items, total, allCount, tags, counts, filter, refreshing, refreshedAt }.
/community-plugins/refreshPOSTBody { q }; schedules a background GitHub fetch.
/community-plugins/forksGETParams repo (owner/name), force (1 to bypass the cache). Returns { ok, items, fetchedAt, cached, stale?, rateLimited? } — one page of forks (up to 50, by stars), cached in SQLite.
/community-plugins/stateGETReturns { profile, plugins: [{ name, spec, repo, enabled }] } — the profile name and installed out-of-tree plugins. enabled is true when the package is listed in dsh.profile.bundles (i.e. actually mounted).
/community-plugins/installPOSTBody { repo: "owner/name" } for GitHub installs, or { path: "/local/path", dryRun?: bool } for local filesystem installs. A dryRun: true returns metadata without running pnpm. Runs dsh plugin --profile <p> add <spec> on the host.
/community-plugins/uninstallPOSTBody { repo: "owner/name" }, or { name } for a local plugin with no GitHub repo (the name must be actually installed). Runs dsh plugin --profile <p> remove <name>.
/community-plugins/browseGET?path= (empty means the user's home) and ?hidden=1; returns { ok, path, parent, entries: [{ name, path, plugin }], self, home, truncated, reason }. Subdirectories only, symlinked directories followed, capped at 400 entries.
/community-plugins/validatePOSTBody { path: "/local/path" }; reads the local package.json, checks for dsh.bundle.patch, returns { ok, installable, name, version, description, author, reason }.
/community-plugins/pluginPOSTBody { name, enabled }; adds or removes the name from dsh.profile.bundles in the profile manifest. Guarded to installed plugin names.
/community-plugins/restartPOSTRespawns this dsh web process (detached restarter re-execs the original command line after a 1.5s port-release wait), responds { ok, restarting }, then exits. DSH_COMMUNITY_RESTART_CMD overrides the re-exec command for supervised setups.

All routes are guarded by the same fail-closed same-origin/localhost trust check as dsh-trader: a cross-origin or malformed Origin/Referer rejects, a CORS-simple content type rejects, and only then does a localhost host count as trusted.

Configuration

Environment variables (all optional):

VariableDefaultPurpose
DSH_COMMUNITY_PROFILEauto-detectedThe profile to install into. Falls back to DSH_PROFILE, then auto-detection, then web.
DSH_PROFILEFallback profile name when DSH_COMMUNITY_PROFILE is unset.
DSH_BINdsh on PATHFull path to the dsh executable used for install/uninstall.
DSH_COMMUNITY_INCLUDE_FORKSunsetAny non-empty value includes GitHub forks in the catalog (forks are excluded by default).
DSH_COMMUNITY_MIN_FETCH_INTERVAL_MS6000Minimum gap between GitHub fetches, to stay under the search rate limit.
DSH_COMMUNITY_FORKS_TTL_MS600000How long cached fork listings and GitHub repo metadata are served before GitHub is asked again. The REST endpoints use GitHub's core limit (60/hr unauthenticated), separate from search.
DSH_COMMUNITY_GITHUB_TOKENOptional bearer token (falls back to GITHUB_TOKEN) sent with GitHub API requests — lets the fork browser and local-plugin metadata reach private repos you have access to.
DSH_COMMUNITY_RESTART_CMDOverrides the restart command (e.g. systemctl restart dsh-web). Default: re-exec this process's own command line.

Profile auto-detection finds the profile whose dsh.profile.bundles includes this plugin, so a custom profile hosting the web GUI works without any configuration.

Development

node --check lib/index.js
node --check lib/client.js
npm test
npm pack --dry-run   # package validation before publishing

docs/record.mjs regenerates the docs/demo.gif at the top of this README (it drives a running DSH page with Playwright; playwright is a dev dependency only).

License

MIT