# dsh-plugin-manager

Manage DeepSeek Harness plugins **without editing config files**.

The shipped workflow is: drop a package into the profile, hand-edit
`cordis.patch.yml`, restart, repeat. This plugin gives the web GUI a **插件管理
(Manage) tab** — right inside **Settings → Plugins** — where you can:

- **install** a plugin by npm package name (with optional JSON config),
- **enable / disable** any loader entry with one click (applied **live**, no restart),
- **uninstall** plugins again,
- see the whole composed Loader tree, the profile's dependencies and bundles,
  and which rows the manager itself installed.

It writes your profile's `cordis.patch.yml` for you (a delimited auto-managed
block; your hand-written patches are preserved byte-for-byte) and the running
server's user-patch watcher (HMR) applies the change immediately.

It also ships a CLI (`dsh-plugin-manager`) and a chat command (`/plugin`).

## Components

| Piece | Role |
|---|---|
| `lib/index.js` | Host half: JSON API under `/plugin-manager/api` + the `/plugin` chat command |
| `lib/client.js` | Browser half: the 插件管理 tab in Settings → Plugins |
| `lib/core.mjs` | Shared logic: managed-block editing of `cordis.patch.yml`, pnpm runner, bundle reconcile |
| `bin/dsh-plugin-manager.mjs` | Standalone CLI (works even while the server is stopped) |

## Install

npm: `@osskn4w7/dsh-plugin-manager`

```sh
# Option A — standard dsh bundle install:
dsh plugin --profile web add @osskn4w7/dsh-plugin-manager
# then restart dsh web (bundle patch layers load at boot)

# Option B — live install without a restart (from this checkout):
dsh-plugin-manager bootstrap --profile web
# or by hand: pnpm add file:… in the profile dir, then enable the entry,
# then refresh the browser (Settings → Plugins → 插件管理)
```

Use one option, not both (the row would be inserted twice). `bootstrap`
installs this package into `$DSH_HOME/profiles/web` via pnpm and wires its
Loader entry into `cordis.patch.yml`. Because the profile patch file is
watched live, the host half activates immediately; refresh the browser once
so the client bundle is picked up.

To use the CLI standalone (outside a profile checkout):

```sh
npm i -g @osskn4w7/dsh-plugin-manager
dsh-plugin-manager list --profile web
```

Requirements: `pnpm` on PATH, the `web` profile (or any profile with the web
bundle), and a loopback-bound server (the default).

## Publishing (maintainers)

The package lives on npm as `@osskn4w7/dsh-plugin-manager` and on GitHub as
[OSSKn4w7/dsh-plugin-manager](https://github.com/OSSKn4w7/dsh-plugin-manager).

```sh
npm version patch          # or minor / major — bumps version + git tag
npm publish --registry=https://registry.npmjs.org/ --access public
git push && git push --tags
```

Notes:
- The default npm registry on this machine is an npmmirror mirror — always
  pass `--registry=https://registry.npmjs.org/` (mirrors are read-only).
- The account has 2FA enabled (`auth-and-writes`): publish either with
  `--otp <code>` or with a granular access token that has **bypass 2FA**
  enabled (create it at npmjs.com settings; its direct-publish ability is
  scheduled to end January 2027 in favor of a 2FA-approval flow).
- The `bin` entry (`dsh-plugin-manager`) is included in the published tarball
  via the package's `files` whitelist.

## How enabling/disabling works

- **Plain plugin** (no `dsh.bundle` in its manifest): `install` runs
  `pnpm add` and inserts a live Loader entry into the managed block —
  enabled immediately.
- **Bundle plugin** (declares `dsh.bundle`): `install` runs `pnpm add` and
  reconciles the name into `dsh.profile.bundles` in `package.json` (same rule
  as `dsh plugin --profile … add`). Bundle patch layers are read at boot, so
  it reports `needsRestart`; after restart its entries appear and can be
  toggled.
- **Enable/disable**: adds/removes an id-targeted `disabled` patch in the
  managed block. The watcher recomposes the tree live.

## CLI

```sh
dsh-plugin-manager list [--json] [--profile web]
dsh-plugin-manager enable <entry-id>   # or: disable
dsh-plugin-manager install <pkg> [--config '{"a":1}']
dsh-plugin-manager remove <pkg>
dsh-plugin-manager bootstrap
```

## Chat command

In the web chat, `/plugin list`, `/plugin enable <id>`, `/plugin disable <id>`,
`/plugin install <pkg>`, `/plugin remove <pkg>`.

## Notes

- The managed block in `cordis.patch.yml` is delimited by
  `# >>> dsh-plugin-manager … >>>` / `# <<< dsh-plugin-manager <<<`; edits
  inside it are overwritten. Everything outside is yours.
- The HTTP API is loopback-only (matching the server's default bind) and
  carries no auth — do not expose the web server to a network you do not
  trust.
- A disabled row whose id no longer exists in the composed tree logs a benign
  loader warning; the manager drops such patches for packages it uninstalls.

## License

MIT
