DSH Plugin Scout
English | 中文
· MIT
Discover and verify DeepSeek Harness plugins published under the GitHub topic dsh-plugin, from inside DSH. Plugins are grouped by purpose, ranked by trust score, and checked against the official peer-compatibility rule. Supports DSH 0.2.x (>=0.2.0-rc.2 <0.3.0); tested on desktop 0.2.0-rc.2.
The in-app UI is currently Chinese only. The plugin's title and description in the official Plugins page are localized (locale/en.json, locale/zh.json).
Install
Open the official Plugins page → Add plugin, and enter one of:
| Spec | Notes |
|---|
dsh-plugin-scout | Recommended. Prebuilt npm package with provenance; no install scripts, no build approval needed |
Absolute path of a prebuilt dsh-plugin-scout-<version>.tgz, if supplied with a release | Bypasses the npm registry; the current tag workflow creates release notes only and does not attach a tarball |
From the CLI: dsh plugin --profile <profile> add dsh-plugin-scout. Enable it on the Plugins page; a 发现插件 (Discover plugins) entry appears in the sidebar.
Do not install from github:fegnze/dsh-plugin-scout: a git install fetches source without lib/, and this repository deliberately ships no prepare script.
One-click install, through the official installer
The Install button in the details drawer takes the same path as the official Add plugin: inspect first, then installBundle(spec, {enabled: false, requestId, registry}). Installed plugins start disabled; Enable is a separate click (setBundleEnabled).
- Plugins with install hooks, or whose DSH peers do not cover the running version, need a second click to install.
- When the official installer blocks dependency build scripts (
pendingBuilds), they are listed by name; a double click on Allow and reinstall retries with approvedBuilds.
- Cancel install calls
cancelInstall with the same requestId; a lost reply is recovered with waitForInstall. On failure the official installer restores package.json and the lockfile.
- Uninstalling and version exemptions stay on the official Plugins page. Scout never calls
removeBundle; the tests fail if it does. Without the official pluginManager, the Install button is hidden and the drawer falls back to "copy pinned spec + open the Plugins page".
Data sources (first-party only)
| Source | Used for |
|---|
GitHub GraphQL search(topic:dsh-plugin) | Repository metadata, root package.json, cordis.patch.yml text (never evaluated) |
registry.npmjs.org/<name>/latest | The manifest that would actually be installed: peers, install hooks, repository, provenance |
Official pluginManager.listBundles / inspect | Installed state; the official pre-install check (resolves only, runs no code) |
Official pluginManager.installBundle / waitForInstall / cancelInstall / setBundleEnabled | One-click install (disabled by default), cancel, separate enable; progress from plugin-manager/install-state and install-log events |
No third-party marketplace or aggregator is used.
Crawling, measured (2026-10): the topic holds about 19,000 repositories, 12,700 of them created in August, many of them same-name copies. GitHub search returns at most 1,000 results per query, so Scout bisects time windows adaptively (≤900 results per window, 60 s minimum window). Pages are 50 items: 100-item pages return 502 in dense windows, so on 502 the page size steps down to 25, then 10, resuming from the same cursor. Later runs crawl incrementally by pushed.
Refresh is streaming and pays only for what changed:
- Newest first. Windows are seeded geometrically (last 6 h / 1 d / 3 d / 1 w / 1 mo / 3 mo / older) and paged from the newest. Window counting and paging overlap (3 workers).
- Publish while crawling. Each page is merged into the record set immediately and published every 1.5 s; the UI reloads the list when
status.rev changes. The previous index stays usable throughout. A checkpoint is written every 30 s.
- Pay for change. With an existing index, a light query fetches metadata only (100 per page, no blobs). Repositories whose
pushedAt is unchanged keep their facts and only refresh stars and similar metadata; changed ones fetch package.json and the patch by node id in batches of 50.
- npm in parallel. npm lookups run alongside the GitHub crawl (8 workers), only for repositories with new pushes, missing entries, or entries older than one day. Entries older than 7 days are refreshed oldest first, at most 800 per run.
- Activation does not wait for the index. The ~30 MB index loads in the background; the UI first sees
loaded: false and polls.
Measured on 2026-10-14 over a real network:
| Scenario | First results | Total | Notes |
|---|
| Incremental, 1,082 repos pushed | 4.5 s | 38 s | 125 refetched blobs, 963 metadata-only, 70 npm lookups |
| Cold full crawl | 6.7 s | — | ~1,000 plugins browsable within 60 s; the rest fill in the background |
Checks and trust score
| Check | Rule | Points |
|---|
| V1 | Root package.json declares dsh.bundle / dsh.client (otherwise not a plugin) | 20 |
| V2 | Official peer rule: only @deepseek-ai/dsh and @deepseek-ai/dsh-*, semver.satisfies(runtime, range, {includePrerelease}), workspace:* means the running version. Inside the App, Scout calls dsh-app-boot.evaluatePluginCompatibility directly | covered 30 · no peers 10 · not covered 5 |
| V3 | Install hooks: preinstall / install / postinstall / prepare | none +15 · present −15 |
| V4 | Source chain: the same-name npm package's repository points back to this repo | match +15 · mismatch −20 · unpublished 0 |
| V5 | npm provenance | +10 |
| V6 | Activity: archived, pushed within 90 days, version drift (V6b) | +5 / −10 |
| V9 | Uses the @deepseek-ai/ scope but the repo is not owned by deepseek-ai | −30 |
| V10 | Fork | note only |
| — | Stars: min(5, round(log10(1+s)·2)) | ≤5 |
The total is clamped to 0–100. Any ✕ marks the plugin as risky: copying its spec or installing it takes two clicks, and its badge shows ✕. Repositories sharing a package name collapse under one representative, chosen by: matching source chain > score > stars > earliest creation. The rest are listed as copies, hidden by default.
Ten purpose categories: UI, session, memory, agent, tools, models, security, development, bridges, domain. A category is scored from the official packages a plugin depends on (highest weight), topics/keywords (3), and text (2); a plugin is categorized only with a score of at least 2. On 41 hand-labelled plugins, the top-1 category is correct for at least 35.
UI and integration points
- Discover page: sidebar 发现插件 →
main panel with search, filters (compatibility, risk, kind, installed state, copies), category chips, sorting and paging.
- Details drawer: checklist, score breakdown, category evidence, source chain and copies, surface (cordis inserts,
client.inject, peers), and the official pre-install check. Footer:
- Install / Enable / Cancel install, as described above.
- Copy pinned spec: copies
name@npmVersion, or github:owner/repo when unpublished.
- Open in Plugins page / Add on Plugins page: installed plugins open their official details via
pluginNavigation.openBundle; others switch to the official Plugins page.
- Official Plugins page:
plugins.detail.badge (trust score) and plugins.detail.section (checklist plus a link back to Scout). Packages missing from the index render null, taking no space.
Development
npm install --ignore-scripts
npm test # builds lib/, then runs the core / host / client / manifest suites
Local debugging: add this directory's absolute path on the Plugins page (a link install; do not move the directory). Host changes need a full App restart; client-only changes need a window reload.
GitHub token, in order: DSH_SCOUT_GITHUB_TOKEN / GITHUB_TOKEN / GH_TOKEN, or gh auth token run through the DSH subprocess service. The token stays in process memory: never written to disk, never sent to the browser. Without one, the page asks you to run gh auth login.
Data and configuration
- Index:
~/.dsh/dsh-plugin-scout/index.json stores facts only (repo metadata, slimmed manifests, npm cache) and is re-judged against the current runtime on every load, so compatibility verdicts follow DSH upgrades automatically.
- Auto refresh: an in-process timer checks 45 s after start and runs an incremental crawl when the index is older than
autoRefreshHours (default 24; 0 disables). No LaunchAgent or external scheduler.
- Optional config:
dataDir, topic, runtimeVersion (overrides runtime detection), autoRefreshHours.
Security boundaries
- Routes live on DSH's own webServer (
/dsh-plugin-scout/api/*). Every request must carry the custom header x-dsh-plugin-scout: 1 and pass DSH connection.admit (Host/Origin fence plus browser auth). Without the connection service, requests are refused (fail closed).
cordis.patch.yml is scanned for name: with a regex; YAML tags are never parsed. Crawled code is never executed and tarballs are never downloaded.
- Style tags carry
data-plugin / data-plugin-css before entering the DOM. Only --dsw-alias-* tokens are used; class names avoid substrings that host skins target; the drawer does not use role="dialog".
Layout
src/core/: classify.mjs (categories), verify.mjs (checks and score), crawl.mjs (GraphQL, windows, npm), store.mjs (fact index, queries).
src/host.mjs: runtime and token detection, refresh state machine, same-origin routes, in-process timer.
src/client/: Discover page, drawer, installer, official-page badge/section, styles.
scripts/build.mjs: esbuild bundling; both artifacts are executed after writing, as the gate.
test/: core (real GraphQL page, 41 labelled samples), host (fake-fetch end-to-end refresh, route auth), client (Happy DOM loading the built artifact), manifest (official packaging conventions).
After a DSH upgrade, re-check the official Plugins page slot names, the pluginManager methods, and the dsh-app-boot exports before re-accepting.
Official conventions followed
Based on the official repository's docs/user/develop/basic/publish, docs/cookbook/adding-a-package §5, and the @deepseek-ai/dsh-package-manifest types; test/manifest.test.mjs asserts each one.
| Convention | This package |
|---|
Bundle: dsh.bundle.patch; patch rows reference the package by name | ./cordis.patch.yml → name: 'dsh-plugin-scout' |
Only public author fields under dsh | manifestVersion: 1, bundle, client (platform: web) |
Display metadata: locale/en.json is the entry, other languages use the same fields; export ./locale/*.json and ./package.json, and ship them | meta.title / meta.description in en.json and zh.json |
| Icon: relative path inside the package, ≤256 KiB, self-contained | ./icon.svg |
Packages sharing host instances are peers; the version gate reads only @deepseek-ai/dsh* peers | @deepseek-ai/cordis ~4.0.4; @deepseek-ai/dsh-app-boot >=0.2.0-rc.2 <0.3.0 (optional, declares the range for the official gate) |
engines.dsh is declarative | >=0.2.0-rc.2 <0.3.0 |
| Ship prebuilt artifacts; never ask users to approve builds | Published to npm with --provenance; no prepare or other install hooks |
Releasing
- Prepare the intended
version in package.json and commit the release changes to main.
- Manually dispatch
publish.yml from main with expected_sha set to the full approved commit SHA. Keep dry_run: true (the default) for validation first: the workflow checks the SHA, installs dependencies without lifecycle scripts, runs tests (including the build), and previews the package with npm pack --dry-run --ignore-scripts. A dry run does not publish. For an approved release, dispatch the same SHA with dry_run: false to run npm publish --access public --provenance --ignore-scripts.
- npm authenticates through Trusted Publishing (OIDC), so no npm token is stored in the repository; provenance is attached automatically. Configure npmjs.com → package Settings → Trusted Publisher for this GitHub repository and workflow
publish.yml, or use npx npm@latest trust github dsh-plugin-scout --file publish.yml --repo fegnze/dsh-plugin-scout.
- Separately,
git tag v<version> && git push origin v<version> triggers release.yml. It checks that the tag matches the package version and creates a GitHub Release with generated notes only; it neither publishes to npm nor builds or attaches a tgz. Supply a prebuilt archive separately if offering tarball installation.
- Keep the repository public with the
dsh-plugin topic so it appears on https://github.com/topics/dsh-plugin and in Scout itself.