dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Skill manager UI for the DeepSeek Harness desktop shell — list, create, import, and open agent skills
English | 简体中文
A management UI for DSH skills in the official DeepSeek Harness (DSH) desktop shell: it lists the skill roots the host actually scans, marks each skill's origin and validity, creates new skills, imports them from a folder, and opens directories in the system file manager.
The plugin installs as a standalone bundle into a DSH profile. It does not take over the official skill scanning or hot-reload path.
The official desktop shell already ships a complete skill subsystem, but exposes no UI for it:
| Capability | Provided by | Shipped by default |
|---|---|---|
| Scan skill roots, parse frontmatter, hot-reload | @deepseek-ai/dsh-skill-filesystem | Yes |
Invoke skills with / | @deepseek-ai/dsh-client-ui-skill | Yes |
| List scanned roots, flag invalid files, create and import | —— | No |
When a frontmatter block is malformed, or a skill simply is not loaded, the only recourse is to inspect directories and logs by hand.
This plugin fills that gap. It does not participate in loading; it only performs file-level create / read / import operations, and reports the scanned roots and each skill's origin faithfully. It therefore coexists with the official plugin without conflict — one loads, the other manages files.
| Item | Requirement |
|---|---|
| Node.js | ^22.19.0 || >=24 |
| DeepSeek Harness | Official desktop build |
| Dependency | yaml (installed automatically) |
Installation has two steps: install into the profile, then register the package name in the bundle layer stack.
# 1. Enter the desktop profile directory (Windows default: %USERPROFILE%\.dsh\profiles\desktop)
cd "<DSH_HOME>/profiles/desktop"
# 2. Install into the profile using the pnpm bundled with the official desktop app
pnpm add "file:<absolute path to the plugin directory>"
# Or install straight from GitHub
pnpm add "github:VCPr0j3k7/dsh-skill-manager"
"the bundled pnpm" means <install dir>/resources/runtime/pnpm/bin/pnpm.cjs. If it is not on your
PATH, invoke it with the bundled node instead:
NODE="<install dir>/resources/runtime/primary-runtime/dependencies/node/bin/node.exe"
PNPM="<install dir>/resources/runtime/pnpm/bin/pnpm.cjs"
"$NODE" "$PNPM" add "file:<absolute path to the plugin directory>"
"$NODE" "$PNPM" add "github:VCPr0j3k7/dsh-skill-manager"
pnpm add only writes to dependencies. DSH loads nothing but the bundles listed in
dsh.profile.bundles, so the package name must be appended to the profile's package.json:
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-skill-manager" // ← add this entry
]
}
}
}
If this step is missed, the symptom is: the install reports no error, the dependency directory exists, and the plugin never loads — no entry in the UI, no record in the logs. This is the easiest step to get wrong.
dsh plugin --profile desktop add ...runsreconcilePlugins()after pnpm finishes, which adds the entry automatically. The official desktop build does not normally expose a directly callabledshCLI, however, so thepnpm addroute requires adding it by hand.
To confirm it was registered:
cd "<DSH_HOME>/profiles/desktop" && cat package.json
The desktop shell must be restarted after installation. The host assembles the plugin tree only at startup; a running instance does not hot-reload it.
cd "<DSH_HOME>/profiles/desktop"
"$NODE" "$PNPM" remove dsh-skill-manager
Then delete the "dsh-skill-manager" line from dsh.profile.bundles. A restart is required here
as well.
Uninstallation never deletes skill files: the plugin only writes into <DSH_HOME>/skills and
never cleans that directory.
The plugin consists of two halves that run independently:
| Half | Runs in | Form |
|---|---|---|
| Host half | DSH host process (Node.js) | Cordis plugin exporting name / inject / apply(ctx) |
| Client half | Desktop window (browser) | React page loaded via window.__ModuleLoader__.load({ id, factory }) |
The host half registers exactly one kind: 'prefix' route on ctx.webServer
(/dsh-skill-manager/api); dispatching within that prefix is handled by host/router.mjs.
This keeps the registration point in one place, and the framework tears it down on unload, leaving
no half-built route table behind.
Every response is shaped { ok: true, data } or { ok: false, error }, and business failures still
return HTTP 200 — status codes are reserved for transport-level facts such as "no such route".
The client half probes the host base URL on its first request. The page origin may be the host's
own HTTP address, the shell's custom scheme (dsh-app://app/), or an opaque origin
(location.origin === "null"). It therefore tries a relative path and the official virtual hostname
http://dsh.internal in order, and uses the data.plugin field returned by /info as the
acceptance test — a bare HTTP 200 is not enough, because the SPA fallback route answers unknown
paths with 200 + HTML. The resolved base is then cached.
Shared configuration: the plugin shares <DSH_HOME>/dsh-extras.json with its sibling plugins,
reading it via GET /config and deep-merging writes via POST /config. This plugin defines no
configuration of its own, but GET /info carries the file's appName field.
Logging: the host half writes to <DSH_HOME>/plugin-data/logs/dsh-skill-manager.log and keeps
the most recent 2000 lines in an in-memory ring buffer.
The "Skill 管理" entry in the sidebar opens the panel, which offers four operations:
| Operation | Behaviour |
|---|---|
| Create skill | The form writes <DSH_HOME>/skills/<name>/SKILL.md. The name must be kebab-case; description and body are required; an existing directory of the same name is rejected |
| Import from folder | Opens the system directory picker. If the chosen folder contains SKILL.md, the whole folder is copied into the user skill root under its own name; otherwise every flat .md file in it is copied. Existing targets are skipped, never overwritten |
| Open skill directory | Opens the user skill root in the system file manager; the per-skill "打开位置" button reveals that specific file |
| Restore bundled skills | Always reports "not applicable" in this plugin — see below |
The panel also shows:
The official desktop build does not ship bundled skills through plugins: bundled skills are
managed by the runtime itself. The host half therefore pins BUNDLED_DIR to null, and
POST /skills/restore-bundled answers with:
官方桌面版不随插件发行内置 skill(随包 skill 由运行时自行管理),此项不适用 (The official desktop build does not ship bundled skills through plugins — bundled skills are managed by the runtime — so this operation is not applicable.)
The button remains in the UI; clicking it shows exactly that message. The "bundled" tag in the skill list is likewise always empty here.
host/services/skills.mjs still contains a complete bundled-skill install and ledger implementation
(installBundledSkills / restoreBundledSkills / bundledSkillNames, ledger at
<DSH_HOME>/bundled-skills/.installed.json), but since bundledDir is always null in this plugin,
those functions install nothing.
The official implementation scans the following roots in rank order; for duplicate names the lower
rank wins. Each root is scanned exactly one level deep — nested SKILL.md files are not
supported:
| rank | Origin | Path | Writable |
|---|---|---|---|
| 100 | Current project (.dsh) | <project root>/.dsh/skills | Yes |
| 200 | Current project (.agents) | <project root>/.agents/skills | Yes |
| 400 | Desktop user directory | <DSH_HOME>/skills | Yes |
| 500 | Shared agent directory | $DSH_AGENTS_HOME/skills (default ~/.agents/skills) | Yes |
| 600 | Bundled | Official bundledSkillDir; not used by this plugin | No |
The "project root" is the nearest ancestor directory containing .git (at most 24 levels up),
falling back to the current working directory.
A skill is either:
<name>/SKILL.md, optionally alongside references/, scripts/,
assets/ and similar resources;<name>.md.Entries starting with . are skipped. SKILL.md must begin with a YAML frontmatter block delimited
by ---:
| Field | Required | Meaning |
|---|---|---|
name | Yes | Lowercase letters, digits and hyphens only (^[a-z0-9]+(?:-[a-z0-9]+)*$) |
description | Yes | Used by the model to decide whether to use the skill |
whenToUse | No | When to use it |
user-invocable | No | false disables manual invocation |
disable-model-invocation | No | true disables automatic invocation by the model |
Boolean fields are parsed strictly: true / false are accepted, as are the string forms
yes / no / on / off / 1 / 0. Any other value counts as unset (matching the official
behaviour).
Frontmatter is parsed with the yaml package — deliberately the same copy the runtime ships.
A different YAML library can reach different conclusions on edge-case syntax and would then
disagree with the host.
Skills are hot-effective: after creating or importing one, no host restart is needed; the official watcher refreshes the directory on the next model step.
All routes live under /dsh-skill-manager/api.
| Method | Path | Request body | Response |
|---|---|---|---|
GET | /skills/list | —— | { roots, skills, invalid, home, userRoot, bundledDir } |
POST | /skills/create | { input } | { path, name } |
POST | /skills/import | { sourceDir } | { imported, skipped, root } |
POST | /skills/restore-bundled | —— | Always a "not applicable" error |
POST | /skills/open-dir | { target? } | { opened }; defaults to the user skill root |
The following routes are provided by host/router.mjs (this plugin's page uses only a subset):
| Method | Path | Description |
|---|---|---|
GET | /info | Plugin id, host state, runtime directory, etc. The client uses data.plugin to accept a base URL |
GET | /config | Read <DSH_HOME>/dsh-extras.json |
POST | /config | Deep-merge and write that file |
POST | /open-external | Open an http/https URL with the system default handler; all other protocols are refused |
POST | /pick-directory | System directory picker; prefers the official ctx.directoryPicker, otherwise falls back to PowerShell on Windows |
GET | /restart-pending | Whether key profile files are newer than the host's ready timestamp |
POST | /restart-host | Always refuses: a plugin cannot restart the process that hosts it |
GET | /events | Incremental event fetch (?since=<seq>); the client polls every 300 ms |
dsh.profile.bundles array and the
profile has been reloaded (desktop shell restarted). There is no environment-variable switch.npm test
# equivalent to node test/check.mjs
test/check.mjs is a pure Node self-check (35 cases). It does not depend on the DSH runtime and does
not require the plugin to be installed into a profile. It uses a temporary DSH_HOME and a temporary
APPDATA, so it never touches the user's real directories. It covers:
package.json manifest contract: the files referenced by dsh.bundle.patch,
dsh.client.platform, exports and files all exist;name / inject / apply, and apply(ctx) neither throws
nor fails to register the prefix route;request("METHOD", "PATH") call in client.js (currently 10) is issued
against the real route table and must not fall through to a 404; the route prefix used by both
halves must also match;host/config.mjs defaults include githubToken and friends, and the config file path resolves
inside the current DSH_HOME.Route alignment is the core contract of this project: the client half is a build artefact, and if a route it calls does not exist on the host side, the only symptom is a button that fails silently (the host answers 404 while the page shows a generic error).
End-to-end verification: after installing and restarting the desktop shell, a "Skill 管理" entry
should appear in the sidebar, and the panel should list the skills under <DSH_HOME>/skills.
POST /restart-host always refuses; restart the desktop shell
manually when needed.<DSH_HOME>/skills does not exist
yet (a fresh install with no skills ever created), the operation reports a missing path. Create one
via "Create skill" or "Import from folder" first.SKILL.md files are never found.dsh-skill-filesystem. This plugin only performs file-level operations. The "valid / ignored"
verdicts shown in the UI replay the same rules; they are not a second source of truth.client.js is a build artefact. It is produced by slicing the desktop frontend sources, and it
contains several components this page does not use (install progress ring, README drawer, restart
notice, assorted icons — all from other panels in the same slice). They play no part in rendering
this plugin, and hand edits are overwritten by the next build.