Plugin Installation Spec

August 22, 2026 · View on GitHub

This document defines how dsh-ezcommit-plugin is installed, updated, removed, and released. Goal: every user can install and upgrade it through the standard DSH CLI.

1. Background: DSH plugin model and this package

A DSH profile (as of @deepseek-ai/dsh@0.1.1-rc.2) is assembled from bundle patch layers (cordis.patch.yml lists loaded by the Loader). A bundle package can be dual-face: the package's dsh.client declaration plus exports "./client" lets dsh-client-modules compose the client bundle into the web boot graph, and the browser mounts it as a client-side Cordis plugin via the window.__ModuleLoader__ factory format — the same mechanism used by third-party skin packages such as dsh-skin-market and maid-atelier.

This package is a static dual-face bundle:

  • Host half (main / exports "."src/index.js): loads in the Node process, registers the /ezcommit/api prefix routes on webServer (git collection, model arbitration, batched commits), and prints an install notice plus a self-check at startup.
  • Client half (exports "./client"src/client.js): a browser module factory exporting apply(ctx) that registers the session-header button and confirmation dialog, talking to the Host half over same-origin HTTP.

It takes effect after install and a profile restart: the session header shows [branch] [One-Click Commit] automatically. No cordis preset and no cordis_define / cordis_run are needed.

2. Package structure spec (DSH bundle contract)

dsh plugin add is a thin pnpm forwarder: it runs pnpm add <pkg> inside the profile directory, then scans the dependencies — a dependency whose resolved package declares dsh.bundle.patch in package.json joins the profile's bundle layer list; otherwise it is installed as a plain dependency with a warning. This package declares accordingly:

// package.json (excerpt)
{
  "name": "dsh-ezcommit-plugin",
  "main": "./src/index.js",          // Host half entry: default export must be a Cordis Plugin
  "exports": {
    ".": "./src/index.js",
    "./client": "./src/client.js",   // Client half: browser module factory (consumed by dsh.client)
    "./package.json": "./package.json"
  },
  "dsh": {
    "engines": { "dsh": ">=0.1.1-rc.1" },
    "bundle": { "patch": "./cordis.patch.yml" },   // the key field: the bundle layer patch
    "client": { "inject": [], "platform": "web" }  // the key field: static client-half declaration
  }
}
# cordis.patch.yml (package root)
- insert:
    - id: ezcommit
      name: 'dsh-ezcommit-plugin'   # module specifier, resolved from the profile's node_modules

Contract essentials (verified against the official in-box bundles and dsh-skin-market / maid-atelier):

  • dsh.bundle.patch points to a loader patch list (YAML array): each item {id, name, config?} inserts/overrides a composition row; name is a module specifier resolved from two anchors (DSH install anchor, then the profile directory);
  • The entry module's default export must be a Cordis Plugin (apply function or an object with apply);
  • dsh.client (platform: "web" plus an inject array) declares a static client half; exports["./client"] must point at the client bundle (module-factory format, factory id = package name);
  • cordis.patch.yml and src/** must ship inside the npm package (explicitly listed in files), otherwise the bundle layer fails to resolve after install;
  • Bundle rows should stay fault-tolerant: this package's Host half checks optional services (webServer / shell / llm, …) and can never break profile boot.

3. Installation

dsh plugin --profile web add dsh-ezcommit-plugin

3.2 From the git repository

dsh plugin --profile web add git+https://github.com/PenguinAndy/dsh-ezcommit-plugin.git

Packages with a prepare build script are blocked by pnpm until allowed in the profile's pnpm-workspace.yaml; this package is dependency-free with no build script, so that prompt never appears.

dsh plugin --profile web add file:/path/to/dsh-ezcommit-plugin
# or the link: form (changes visible immediately)
dsh plugin --profile web add link:/path/to/dsh-ezcommit-plugin

Relative paths (file:., link:../dsh-ezcommit-plugin) are anchored to the invoking directory by the CLI, equivalent to absolute paths.

3.4 Verifying the install

# the composed profile should contain the ezcommit row and this package's bundle layer:
dsh --profile web --dump-config | grep -A2 ezcommit
# the profile manifest should list the bundle and the dependency:
cat "$DSH_HOME/profiles/web/package.json"

After restarting the profile (dsh web):

  • the startup log prints [dsh-ezcommit-plugin] v<x.y.z> installed (static dual-face…) and mounted /ezcommit/api/* routes;
  • after a browser refresh, every web session header shows [branch] [One-Click Commit] (grayed out outside a git repo or with no changes);
  • the client bundle is served at /plugins/dsh-ezcommit-plugin/client.js.

4. Update and removal

dsh plugin --profile web update dsh-ezcommit-plugin   # upgrade to latest
dsh plugin --profile web remove dsh-ezcommit-plugin   # uninstall (bundle layer removed automatically)

Updates and removals also require a profile restart to take effect in the running process.

5. Versioning and releases

  • Versions follow SemVer; package.json.version is the single source of truth.
  • Release flow: pushing a v<x.y.z> tag triggers the GitHub Action .github/workflows/release.yml:
    1. assert the tag matches package.json.version;
    2. pnpm verify (package contract + HTTP-route integration on real git + client factory smoke);
    3. npm pack produces the tarball;
    4. create the GitHub Release (auto-generated release notes) and attach the tarball;
    5. when the NPM_TOKEN secret is configured, publish to npm (npm publish --access public).
  • Local release commands: npm version <patch|minor|major>git push --tags (or tag manually and push).

6. Troubleshooting

SymptomCause and fix
No button in the UI after installMake sure the profile was restarted (a running process does not hot-load bundles); then check the startup log for the [dsh-ezcommit-plugin] notice and mounted /ezcommit/api/* routes
Startup log shows "route mount failed"The profile is not web-shaped (no webServer service) or the route prefix collides; confirm the composed profile has a webserver row and no other package claims /ezcommit/api
Button appears but stays disabledThe workspace is not a git repo or has no changes (by design); point the session workspace at a dirty git repo
"Start analysis" reports NO_MODEL / SERVICE_MISSINGThe session has no routed model yet, or the profile lacks llm/agents; start a normal model turn in the session first
Browser console reports a client-bundle errorConfirm the profile restarted and the client bundle is reachable (/plugins/dsh-ezcommit-plugin/client.js should return JS); hard-refresh the page if needed
dsh: cannot resolve profile bundle ...The package is not installed in the profile (interrupted/offline install); re-run dsh plugin --profile <name> add <pkg>
warning: <pkg> declares no dsh.bundle — installed as a plain dependencyA same-named package without dsh.bundle.patch, or an old version; upgrade to a bundle-declaring version
pnpm blocks build scripts ... allowBuildsA git-source install triggered a prepare script; follow the prompt to add allowBuilds in the profile's pnpm-workspace.yaml (not applicable to this package)
Startup log shows "self-check found N problems"The installed src/client.js is corrupt or missing; reinstall or switch to the git source
npm publish 404 / E403First publish needs --access public and npm account permissions; make sure NPM_TOKEN is an Automation/CI token