dsh-plugin-updater
English | 简体中文
Package-level plugin manager with update detection for
DeepSeek Harness (DSH).
It adds a Settings → 插件包 page to the DSH web UI that lists every plugin
package installed in the current profile, shows the installed version and
the latest available one, and can install, update, and remove packages —
dsh plugin (a thin pnpm forwarder) with a browser UI.
This is a community plugin, not an official DeepSeek package. It manages the
install state of a profile (npm packages and the dsh.profile.bundles layer
list); for runtime enable/disable of loader entries see
dsh-plugin-manager
— the two are complementary.
Features
- Lists every
dependency of the current profile with its installed version,
source (npm / Git / local / built-in), and whether it is an active bundle
layer (dsh.profile.bundles).
- Update detection per source:
- npm — installed version vs
pnpm view <name> dist-tags (runs inside
the profile directory, so the configured registry and credentials apply).
- Git (
github:owner/repo, git+…, …#ref) — the installed commit
(from the profile lockfile) vs git ls-remote <url> <ref>; the newest
semver tag is displayed as the latest version when the repo has tags.
- Local (
link: / file:) — direction-aware check against origin:
the checkout's HEAD is compared with git ls-remote origin HEAD, and
when they differ a fetch + rev-list --count decides who moved — origin
ahead → 可更新; local ahead (unpushed work) → no update offered; diverged
→ flagged for manual handling.
- Built-in
@deepseek-ai/* bundles — version shown, updated together
with the dsh installation.
- One-click install (any pnpm spec), update (
<name>@latest for npm,
re-fetch of the recorded spec for Git), and remove, each running
pnpm add/remove in the profile directory followed by the same bundle
reconciliation dsh plugin performs (a dependency resolving to a package
that declares dsh.bundle.patch joins the layer list; a removed one leaves).
- The page is split into two tabs: 商店 (the marketplace below) and
已安装 (installed packages, the manual install input, and the operation
log); both stay mounted so search results survive tab switches. The page
opens on 已安装, and the marketplace only queries GitHub the first time its
tab is activated (unauthenticated search allows 10 requests/min).
- 插件市场 (marketplace) — searches GitHub for repositories carrying the
dsh-plugin topic (forks excluded),
sortable by relevance / stars / last push, with pagination, and installs any
result directly as a github:owner/repo dependency. Repos already present
in the profile are badged 已安装. Results are cached ~5 min; set
DSH_GITHUB_TOKEN (or GITHUB_TOKEN / GH_TOKEN) to raise GitHub's
unauthenticated search quota.
- Build-script gate — pnpm ≥ 10 refuses packages whose install scripts
are not allowlisted (and fails hard when a profile carries previously
ignored builds). When an install/update is blocked this way, the page
offers an explicit 允许构建脚本并重试 button: it writes the exact
allowBuilds entry pnpm suggests into the profile's pnpm-workspace.yaml
and re-runs the install. Allowing means consenting to run that package's
install scripts — do it only for repos you trust.
- Live operation log and an explicit "restart dsh to apply" banner — profile
layers are composed at boot, so changes take effect after a restart.
- Check results are cached (~10 min) in
~/.dsh/plugin-updater.json; no
background polling of registries.
Install
dsh plugin --profile web add github:hyqhyq3/dsh-plugin-updater
dsh --profile web # restart, then open Settings → 插件包
Or from a local checkout:
dsh plugin --profile web add link:/path/to/dsh-plugin-updater
How it works
- Host half
lib/index.js — discovers the profile it is installed in
(the profile whose node_modules/dsh-plugin-updater realpaths to its own
package directory; override with DSH_PLUGIN_UPDATER_PROFILE), reads the
install state (package.json, node_modules/*/package.json,
pnpm-lock.yaml), runs update checks (pnpm view, git ls-remote), and
exposes a same-origin JSON API at /plugin-updater/api/* on the GUI
webserver. Mutations spawn pnpm in the profile directory and re-apply the
bundle reconciliation.
- Client half
lib/client.js — a hand-written module factory registering
the Settings section; polls /api/state every 3 s. The UI follows DSH’s
Settings → General → Language through the updater locale namespace
(zh / en); the plugin has no language setting of its own. Server-generated
messages follow the same locale, while external tool output stays verbatim.
API requests carry ?lang=zh or ?lang=en (absent or invalid values default
to zh). Legacy cached check results are discarded once on upgrade.
HTTP API:
| Method | Path | Purpose |
|---|
| GET | /plugin-updater/api/ping | liveness + discovered profile |
| GET | /plugin-updater/api/state | plugins, versions, check results, op status |
| GET | /plugin-updater/api/search | GitHub marketplace (?q=&sort=best|stars|updated&page=&per_page=) |
| POST | /plugin-updater/api/check | re-check updates ({name?}) |
| POST | /plugin-updater/api/install | pnpm add <spec> + reconcile |
| POST | /plugin-updater/api/update | pnpm add <name>@latest / re-add spec |
| POST | /plugin-updater/api/remove | pnpm remove <name> + reconcile |
Plain ESM JavaScript, zero dependencies (Node built-ins only; Node
^22.19 || >=24, pnpm on PATH, git for Git update checks). No build step —
lib/*.js ships exactly as written.
Limitations
- Changes require a dsh restart to take effect (this page shows the banner);
the running process is never reloaded implicitly.
- The manager refuses to remove itself from the page — use
dsh plugin --profile <name> remove dsh-plugin-updater.
- Update detection for Git packages pins to the spec's ref (
#ref or the
default branch HEAD); a moved tag is not treated as an update.
- A
link: install without a .git target is listed but not checked.
- Marketplace search uses GitHub's public search API: without a token it is
limited to 10 requests/min (results are cached ~5 min to stretch that);
DSH_GITHUB_TOKEN raises the quota.
- Only
dependencies of the profile manifest are managed; runtime loader
rows (enable/disable) are out of scope.
Model Experience
None. This plugin adds no model-facing surfaces: it registers no tools and
injects no prompt context; it only serves the Settings page and its JSON API.
License
MIT