DeepSeek Harness plugin

omdsh-plughub

The omdsh plugin hub: a Settings tab that installs and removes omdsh plugins from a configurable upstream, and configures every installed one from the settings schema it already registers

Jump to install

Source facts

Repository
omdsh-plugins/omdsh-plughub
Latest update
Aug 20, 2026
Category
Plugin Markets & Managers
GitHub stars
0
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/omdsh-plugins/omdsh-plughub
Plugin: omdsh-plughub
Author: omdsh-plugins

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer4 files
README.mdSource · read only
README language

omdsh-plughub

English | 中文

A plugin hub inside the DeepSeek Harness Settings: one more tab beside the shipped Plugins pages, listing what you can install from a configurable upstream, and configuring — or parking — everything already installed. The hub and the mode system stay on.

Installing a plugin used to be dsh plugin --profile web add <path> in a terminal, and configuring one used to be editing a profile's cordis.patch.yml by hand. This makes both a page.

What it adds

SurfaceWhere it comes from
A third tab in Settings → Plugins, Plugin hubAn entry in settings.plugins.tab, the seat ui-settings declares for inventory and configuration plugins
The merged catalog, and what each source reportedGET /api/plughub/catalog, resolved from the local, registry and github sources
Install or Update on Available; Update and Remove on InstalledPOST /api/plughub/install, /update and /uninstall, each shelling out to dsh plugin --profile <name>
Enable/Disable on InstalledPOST /api/plughub/enabled, rewriting dsh.profile.bundles and a parked list; the package stays in node_modules. The hub and the mode system can be updated, and cannot be disabled or uninstalled.
A configuration form for every installed pluginGET/POST /api/plughub/settings, carrying ctx.settings.describe({ redactSecrets: true }) and ctx.settings.mutate
Operation progress and the restart bannerGET /api/plughub/events, an event stream
The omdsh.plugin.card slotctx.slots, where a plugin whose control the generic form cannot draw registers its own face
Its own settings namespace, omdsh-plughubctx.settings.register, rendered by the same generic form every other plugin gets
omdsh-plughub on a terminalA bin, resolving the same catalog and running the same Installer the routes do

Two strings name this package on that page, one word apart, and the difference is a convention rather than an accident. The tab reads Plugin hub: it is settings chrome, so it takes the sentence case of the tab the harness ships beside it, Plugin list. Plugin Hub is this package's own dsh.plughub.displayName, which titles exactly one card in the installed list — the card for this plugin itself, drawn by the same code that titles every other card from the same field, and Title Case is what rule 5 asks of every displayName here. Nothing in the harness is modified: the tab is a published seat, and removing the row hands it straight back.

The idea

The hard part of a plugin hub is not the install button. It is the second half: once a plugin is installed, where does its configuration UI come from?

Two answers were possible. Every plugin could ship a card for this panel — but then a plugin published after this one shows up with nothing to click, and this package grows an entry per plugin forever. Or the panel could read what the plugin ALREADY declares.

The harness makes the second answer available, because it already has a user-settings seam. A plugin registers a namespace with a [schemastery] schema; ctx.settings.describe({ redactSecrets: true }) hands back that schema together with the current value, the composition base, the raw user layer, the redacted secret slots, and a revision. That is the complete input a configuration form needs.

So this package renders forms and installs packages, and knows nothing about any particular plugin. A plugin written next year gets a configuration page the day it is installed, having done nothing but follow the conventions.

  plugin (host half)          plughub (host half)        plughub (browser half)
  ──────────────────          ───────────────────        ──────────────────────
  ctx.settings.register(      describe(redact) ────────→ rehydrateSchema
    'omdsh-shortcuts',                                    ─→ plan ─→ controls
    Config,                   settings.mutate  ←──────── one path-addressed edit
    { base: entryConfig })

Nothing in the left column mentions this package; nothing in the right column mentions chords.

The middle column, and why it is there

That middle step used to exist because the harness's own settings wire was gated by a hard-coded allowlist of namespace names, so no out-of-tree plugin could cross it. 0.1.0-rc.7 lifted that gate: the Host now serves every registered namespace, and a plugin that wants a card on the official Configurable tab registers one into settings.plugin.item.

This route is still here for a different reason. The official tab only renders namespaces that claimed that slot; this hub draws a generic form from the schema every omdsh plugin already registers, including ones that never wrote a card. And the boundary it draws is narrower than the Host's — a namespace is reachable only when an INSTALLED bundle declares it under dsh.plughub.settings, so shell and agent-loop are registered in the same process and unreachable here.

It carries transport and nothing else: validation, layering, redaction, revisions, and commits all stay in ctx.settings.

What the tab shows

Catalog sources — this plugin's own configuration, at the top, because these fields are the answer to "where does the list below come from". It is an ordinary settings namespace rendered by the same generic form every other plugin gets; it simply sits here instead of in the installed list, so nobody looking at an empty catalog has to hunt for the control that fixes it. It opens itself once when every source failed or nothing came back.

Available — the merged catalog, one button per card. Not installed is Install; installed with nothing to fetch is a grey Update; a newer version lights the same button. A card's title, summary, and documentation link are the plugin's own, read from its dsh.plughub manifest section and resolved for the active locale.

Installed — one row per plugin this profile has, composed or parked. Enable and Disable share one button: Disable takes a dependency off the layer stack without touching node_modules, so using it again is Enable rather than another install. Template bundles, the hub, and the mode system stay on. Expanding a row shows a form built from that plugin's settings schema; a plugin that registered no namespace says so, which is a real answer rather than an empty box.

A change to the profile puts a restart banner at the top. Plugin layers are composed at boot and only the user patch layers are watched, so a newly installed bundle genuinely cannot be hot-mounted — saying "restart" is the honest report, not a limitation being papered over.

Updates

The Update button is the only action on an installed Available card, and is grey until there is something to fetch. Which it is, is decided on the Host from two numbers it already holds — the version the winning catalog source advertises, and the version of the package on disk — compared by semver, not by string order (0.10.0 is newer than 0.9.0, and 1.0.0 is newer than 1.0.0-rc.2).

StateThe card showsThe button
available0.1.0 → 0.2.0Highlighted
currentthe one versionGrey: up to date
linkedthe one versionGrey: installed from a directory on this machine, so its files already ARE the source
unknownwhatever version is knownGrey: this source publishes no version to compare against

An update runs the same dsh plugin add an install does, against a specifier that names where it is going. For a git specifier that is the specifier unchanged, since re-resolving the ref is the whole of what it does. For a registry one the advertised version is appended — pnpm add @scope/name@0.2.0 — because a bare pnpm add <name> on a dependency the manifest already satisfies prints Already up to date, changes nothing, and exits zero: the operation would report success while the card went on offering the same update.

Naming the version buys two things past correctness. The button installs the version printed above it rather than whatever latest means at the moment it is pressed; and an explicit version is exempt from pnpm's minimumReleaseAge, which hides a release for its first day and would otherwise make the first press after a publish that same silent no-op. The profile then records the exact version rather than a range, which is the honest thing for a manifest whose updates are button presses.

It is its own route because the preconditions are opposite: an install refuses a package the profile has, an update requires it.

Afterwards the restart banner appears. An update changes no bundle LIST — the same package name, different code behind it — so the usual comparison would miss the one operation that swaps running code out from under the process; the runtime latches it instead. Conservative on purpose: an update that fetched the same version costs a needless restart, and the other way costs somebody running code they believe they replaced.

linked is what a checkout install looks like, and dsh plugin add <path> records link:. So a profile assembled from local directories will show every Update button grey, correctly — editing the checkout is already editing the plugin.

Where the catalog comes from

Three sources, merged on the package name, highest precedence first:

SourceWhat it isWhy it exists
localDirectories of plugin checkoutsThe copy you are editing beats the copy somebody published
registryOne curated JSON manifestOne request, full metadata, and the upstream's chance to say what it recommends
githubRepository enumerationZero maintenance: push a plugin repo to the account and it appears

A losing source still contributes its repo when the winner has none — a local checkout rarely knows where it is published, and the card's link is nicer for it.

Out of the box the catalog is the curated manifest this collection publishes, fetched from a CDN that caches GitHub:

https://cdn.jsdmirror.com/gh/omdsh-plugins/registry/registry.json

GitHub enumeration is off — upstream is empty — because that one file already lists every plugin, and asking GitHub for the account (then for each repository's package.json) is what made a fresh tab slow. Point upstream at an account to enumerate it as well, or empty registryUrl so the hub derives https://raw.githubusercontent.com/<account>/registry/HEAD/registry.json from the account instead. Empty both to run on localSources alone.

A local source is scanned exactly one directory deep, so a monorepo whose installable half sits in packages/ is not offered. That is usually right — what a monorepo here holds is a bundle for a DIFFERENT surface, and a profile composes one surface. Point localSources at the inner directory when you do want one listed.

A source that fails is REPORTED rather than hidden. "No plugins here" and "GitHub rate-limited this account" look identical on an empty list, and only one of them resolves itself.

With one exception, in the other direction: a 404 on a derived manifest URL is absence, not failure. Publishing no curated manifest is the ordinary state of an upstream account, enumeration is what covers it, and a red row under every default install — naming a file nobody ever promised — would only teach people to ignore the place failures are reported. A URL somebody typed into registryUrl is the opposite case: they meant a manifest to be there, so a 404 on it is reported like any other.

The registry manifest is { "plugins": [...] } (or a bare array):

{
  "plugins": [
    {
      "name": "@omdsh-plugins/omdsh-shortcuts",
      "repo": "omdsh-plugins/omdsh-shortcuts",
      "version": "0.1.0",
      "plughub": { "displayName": { "": "Shortcuts", "zh": "快捷键" }, "order": 10 }
    }
  ]
}

spec may be given explicitly; omitted, it is github:<repo>.

The manifest this account publishes lives in omdsh-plugins/registry, generated from the plugins' own package.json files rather than kept by hand.

The routes it holds

RouteMethodWhat it does
/api/plughub/catalogGETThe merged catalog. ?refresh=1 consults every source again
/api/plughub/installedGETThis profile's plugins, which of them can be removed, and which are composed
/api/plughub/installPOST{ id } — install one catalog entry
/api/plughub/updatePOST{ name } — reinstall one installed plugin from what the catalog offers now
/api/plughub/uninstallPOST{ name } — remove one dependency-managed bundle
/api/plughub/enabledPOST{ name, enabled } — compose or park a dependency-managed plugin
/api/plughub/eventsGETOperation progress, the restart flag, and settings invalidations, as an event stream
/api/plughub/settingsGETEvery namespace an installed plugin owns, redacted
/api/plughub/settingsPOST{ ns, ops, expectedRevision } — one path-addressed edit

Reach

The read routes carry the same fence /api carries: a Host header naming us — loopback, or an authority this deployment was told to serve — plus same-origin browser markers. They are exactly as reachable as the settings panel that renders them.

The write routes are loopback only, whatever --trusted-host says. Each of them changes this machine: an install runs that package's prepare script, and a settings write persists to the Host document. "The deployment published /api to the LAN" is not consent to either. Someone who genuinely wants to install over a published dsh web still can, from a terminal, where the decision is visibly theirs.

And a write names something the Host already resolved. An install names a catalog ENTRY, never a package specifier — the Host looks the specifier up in the catalog it resolved itself, so no request can reach a package the configured upstreams did not offer, and there is no request shape that can carry a specifier at all. A settings write names a namespace an INSTALLED plugin declares it owns. Both allowlists are structural rather than checks somebody has to remember to write.

How an install actually runs

It shells out to dsh plugin --profile <name> add <spec>.

pnpm add is only half of an install; the other half is reconciling dsh.profile.bundles against what is now on disk, and that reconciliation belongs to the launcher this runtime was started by. Reimplementing it here would mean carrying a copy that has to track a program the user upgrades independently, and getting it wrong means a profile that boots without the plugin it just "installed".

One thing this package does have to know: pnpm ≥10 refuses to run a dependency's install scripts until they are allowlisted, and a git-hosted dsh plugin BUILDS ITSELF in prepare — its published tree has no lib/. So a git install that is not allowlisted succeeds, writes the dependency, reconciles the bundle list, and then the next boot dies on Cannot find module .../lib/index.js. The allowBuilds entry is written into the profile's pnpm-workspace.yaml BEFORE the install, because the failure arrives one restart later than the mistake.

The package NAME is the right entry for a registry dependency and not enough for a git one. pnpm keys a git-hosted package by the tarball it resolved — @scope/name@https://codeload.github.com/owner/repo/tar.gz/<sha> — and refuses an allowlist naming anything else, so the entry written ahead of the install is correct in form and inert in fact. That commit is not knowable beforehand without re-implementing pnpm's resolution, and it changes on every push.

So the name goes in first, and if pnpm refuses anyway it is asked. Its refusal prints the exact key it wants; that key is read back, written, and the install runs again.

It runs again as many times as it keeps learning something, because pnpm reports the refusals it REACHED rather than the ones it would reach next. A plugin with a native dependency is blocked on that dependency first and on its own prepare only once the dependency is allowed — omdsh-remdev takes three passes for exactly that reason. Progress is the loop's condition, not a count: an entry already set to true is nothing new to write, so the loop ends the moment an attempt teaches it nothing, and a bound of four is a backstop rather than the thing that stops a healthy install.

One thing pnpm does that has to be answered rather than read: it writes the blocked package into that file ITSELF, valued set this to true or false. That is a question, and the person who pressed Install already answered it, so the value is replaced rather than treated as an entry that already exists.

Operations run one at a time: two pnpm runs in one directory race over the same lockfile, and the loser's diagnostic describes the race rather than anything the person did.

The same installs, on a terminal

This package ships a bin. It is the tab's install path with argv where the route was:

omdsh-plughub list                     # what the catalog offers, and what is installed
omdsh-plughub add omdsh-status         # install one
omdsh-plughub update omdsh-status      # move it to the version the catalog offers
omdsh-plughub remove omdsh-status      # and remove it

It exists because of the paragraph above. Two packages in the collection install from npm — this one, and omdsh-basemode. Every other plugin installs from GitHub, and a git install has no working dsh plugin add line — the allowlist key pnpm demands carries the commit it resolved, so it can be copied out of a failure and never written down in advance. This package has always known how to answer that, and until now it answered only to a button.

Nothing here is a second implementation. The command resolves the same catalog, takes the same specifier out of it, and hands it to the same Installer — so a plugin installed from a terminal and one installed from the tab are the same dependency, the same bundle row, and the same restart.

A name, or a specifier

Which of the two an argument is decides whether the catalog is consulted at all, and it is what lets one account be named without moving the catalog:

What you typeWhat it installs
omdsh-statusthe catalog entry whose name ends in that segment; two matches is reported rather than guessed
@omdsh-plugins/omdsh-statusthat entry, named exactly
github:someone/omdsh-statusthat repository, as written, without consulting the catalog at all
@omdsh-plugins/omdsh-status@0.1.2the same, pinned to a version
/checkouts/omdsh-statusthe same, from a checkout — admissible here and refused in a route, which is what isInstallableSpec's allowPath has always been for: a path typed at a keyboard is not a path arriving inside somebody's manifest

--upstream <account> moves the whole catalog to another account for one run. That is the other half of the same question — an argument says WHAT to take, the upstream says WHERE the catalog looks — and it is why a bare name does not try to carry an account of its own.

It does not read this plugin's stored settings. A namespace is resolved by the harness's settings service inside a running tree, and this program is not one, so --upstream, --github-token, --registry-url and the timeouts are flags with the same defaults the schema declares. --help lists them.

Which controls the form draws

Schema nodeControl
stringText field
string with role('secret')Write-only field, masked once a value is stored; the Host reports only whether one is
numberN