dsh-skill-catalog
A DeepSeek Harness (DSH) plugin (not a skill): scan every skill your DSH can
load, merge them into one browsable catalog, and put it in a sidebar panel —
solving "too many skills, no idea which command comes from which source".
DSH natively supports / invocation, so this plugin only does cataloging and
browsing — it registers no slash commands of its own.
English | 中文
Features
- Single entry point: a button at the bottom of the DSH left sidebar (next to Settings) opens a narrow panel on the right that sits beside the conversation instead of covering it.
- Scans exactly what DSH can load: every direct child of
~/.dsh/skillsand~/.agents/skillsbecomes a collection (single-skill directory,skills/skill pack, or loose.md). Inclusion rules mirror DSH's own source and skill-pack provider loading rules — if DSH won't load it, it is not listed. - Refresh: the ↻ button re-scans
SKILL.mdfrom disk. - Click to insert: clicking a command card writes
/commandstraight into the current conversation input (via DSH'suiSession.current.value.props.inputActions, preferringsetDraft(text)); falls back to copying to the clipboard when the composer is unavailable. - Right-click for details: right-click a command card for an info card explaining what it does (your custom note takes precedence — see below). The card flips away from viewport edges; it closes on left-click elsewhere, Esc, list scroll, the ✕ button, or right-clicking the same card again.
- Native tool
dsh_catalog: ask in chat "what is /estimate" or "which skills do I have" and get the three-level catalog (collection › category › command) plus the panel address. - Custom categories (saved, persistent): paste "category + commands" in the panel's ⚙ Categories editor — saved once, effective forever. See Custom categories.
- Custom command notes (saved, persistent): paste a Markdown table in the panel's ✎ Notes editor; right-clicking a command then shows your note (the skill's own description is kept as an "original description" footnote), and noted commands carry a blue marker and a "noted" badge. See Custom command notes.
Scan sources
| Source | Skill location (scan roots) | Parser |
|---|---|---|
| DeepSeek Harness | ~/.dsh/skills, ~/.agents/skills (each direct child = one collection) |
skill_md |
Three-level structure: collection › category › command. Core rules (mirroring "how DSH reads skills"):
- Collection = the file name one level below a skills root. DSH reads
skills from
~/.dsh/skillsand~/.agents/skills(project-level<project>/.dsh/skillsand<project>/.agents/skillsexist too; this plugin does not scan those yet). Each direct child is a collection:<root>/<name>/SKILL.md— single-skill collection<root>/<name>/skills/<s>/SKILL.md— skill-pack collection, one command per skill inside the pack<root>/<name>.md— loose skill collection (name = file stem)- pure resource directories (neither layout) are skipped entirely
- Category has three sources, highest priority first:
- Custom categories saved in the panel (see below)
- the skill frontmatter's
category - Auto-categorization — keyword extraction from name + description (weighted, stop-worded), grouped by "same primary topic". Zero config, zero rule tables. The category dropdown is linked to the collection: pick a collection first, then its categories appear.
- Command = the skill name (
/name). Inclusion rules mirror DSH's source (isPotentialSkillPath, max 2 path segments below the root) plus the skill-pack provider's loading rules.
Custom categories
Auto-categorization is the zero-config fallback, but if you have your own taxonomy for a skill library (e.g. 74 commands split into Onboarding / Game Design / QA & Testing), paste it once in the panel and it sticks.
How to
- Click ⚙ Categories at the top right of the panel;
- pick the collection the rules belong to (defaults to the current filter; "All collections" matches by command name across collections);
- paste lines of the form category name followed by its commands:
Onboarding & Navigation /start /help /project-stage-detect /adopt /settings
Game Design /brainstorm /map-systems /design-system /quick-design
QA & Testing /qa-plan /smoke-check /soak-test /regression-suite
- Click Save & apply. The panel reports how many categories/commands were parsed and how many matched the current catalog.
Parsing is lenient: tokens starting with / are commands, everything else
merges into the category name (so names with spaces and & like
Onboarding & Navigation work as-is); leading -, 1., # and backticks
are stripped; commands dedupe within a category; order is preserved —
what you paste is what the editor shows when you reopen it.
Why refresh / plugin installs & removals never lose it
| Scenario | Mechanism |
|---|---|
| Click ↻ Refresh | Refresh only re-scans SKILL.md; every catalog build re-reads and re-applies your rules |
| Reopen the client / restart DSH | Rules live on disk, independent of any process |
| Uninstall/update this plugin | Rules live in ~/.dsh-catalog/, not in the plugin directory and not in the assets/ runtime artifact |
| Another plugin is removed | Rules are stored by command name and only match, never filter: a missing command keeps its rule and recovers automatically when the plugin returns |
| Install a new plugin | Commands not in any rule go to (Uncategorized) in strict mode, otherwise auto-categorization |
Storage location and format
~/.dsh-catalog/categories.json (created on first save):
{
"version": 1,
"updated_at": "2026-01-01T00:00:00",
"rules": [
{
"collection": "my-game-skills",
"category": "Game Design",
"commands": ["brainstorm", "map-systems", "design-system"]
}
],
"strict_collections": ["my-game-skills"]
}
collectionempty = global rule (matches by command name, any collection).strict_collections: commands in these collections that no rule lists all go to(Uncategorized), dropping auto-categorization (the checkbox in the editor). Uncheck to let new skills keep auto-categorizing.- If the file is corrupted it is backed up as
*.corrupt-<timestamp>.jsonand scanning continues with empty rules.
HTTP API
The panel talks to these endpoints (the server listens on 127.0.0.1 only):
| Method | Path | Description |
|---|---|---|
| GET | /api/custom?collection=X |
Read saved rules; also returns an editable paste-format text |
| POST | /api/custom |
Save. body: { text | rules, collection, strict, mode }, mode = replace (default) or merge |
| POST | /api/custom/reset |
Clear all custom categories |
Custom command notes
A skill's own description is often one English sentence that doesn't tell you how to use it. This feature lets you write your own note, shown on right-click.
How to
- Click ✎ Notes at the top right of the panel;
- pick a collection and paste a table like this (the most common shape of a skill list — copy it straight in):
| Command | Note |
|---|---|
| **ask-matt** | Skill router. Ask it when unsure which skill/flow to use. |
| **code-review** | Dual-axis code review: repo standards + requirement spec, parallel subagents, side-by-side report. |
- Click Save & apply; right-clicking a command card now shows your note.
Parsing: split on |, the first cell is the command name (**, backticks
and a leading / are stripped), the rest merges into the note; header rows
(| Command | Note |) and |---|---| separator rows are skipped
automatically; name: note and name␣␣note (two or more spaces) also
work. Order is preserved and round-trips into the same table format.
Display rules
| Case | Right-click info card shows |
|---|---|
| Custom note exists | Your note (body); the skill's description as an "original description" footnote |
| No custom note | The skill's own description (as before) |
The info card is position: fixed, follows the cursor, and flips back inside
the viewport near edges; long notes scroll inside the card. Right-click the
same card again to close (toggle); right-clicking a different card switches
content. Other ways to close: left-click elsewhere, Esc, scroll the list, or
the ✕ on the card.
Noted commands carry a blue left marker and a "noted" badge, and the search box matches note text too.
Persistence
Same mechanism as custom categories: stored in ~/.dsh-catalog/notes.json
(not in the plugin directory, not in assets/), re-read on every
catalog build — refresh, reopen, reinstall, and other plugins' installs or
removals don't affect it. Notes are stored by command name and kept while
a command is missing.
{
"version": 1,
"updated_at": "2026-01-01T00:00:00",
"notes": [
{ "collection": "dsh-mattpocock-skills", "command": "ask-matt", "note": "Skill router…" }
]
}
collection empty = global (matches by command name, any collection).
HTTP API
| Method | Path | Description |
|---|---|---|
| GET | /api/notes?collection=X |
Read saved notes; also returns an editable table text |
| POST | /api/notes |
Save. body: { text | notes, collection, mode } |
| POST | /api/notes/reset |
Clear all custom notes |
Install (DSH plugin)
# From npm (once published):
dsh plugin --profile web add dsh-skill-catalog
dsh plugin --profile desktop add dsh-skill-catalog
# Or from a local checkout (any profile; web = dsh web, desktop = DSH desktop client):
dsh plugin --profile web add "link:/absolute/path/to/dsh-skill-catalog"
dsh plugin --profile desktop add "link:/absolute/path/to/dsh-skill-catalog"
# Verify registration
dsh web --dump-config | findstr dsh-skill-catalog
dsh web # restart the harness
Uninstall: dsh plugin --profile web remove dsh-skill-catalog
The panel UI needs the right-sidebar services
(@deepseek-ai/dsh-client-ui-sidebar-right). Profiles without them still get
the tool and the HTTP service — only the panel stays hidden.
Configuration
| Env var | Default | Description |
|---|---|---|
DSH_CATALOG_PORT |
8765 |
Preferred port for the panel server (falls back 8765→8769 on conflict). The sidebar panel auto-discovers ports in the 8765–8769 range only; moving the server outside that range (e.g. 9000) breaks panel discovery, though the API stays reachable at that port directly |
Custom data locations are documented under
Custom categories (~/.dsh-catalog/categories.json,
edited in the panel, not by hand).
Including skill libraries from elsewhere
Scan roots are fixed to DSH's own two skills roots: ~/.dsh/skills and
~/.agents/skills. To bring a library from elsewhere (e.g. D:\path\to\skills)
into the catalog, link it in — no code changes needed:
# Recommended shape: real parent directory, skills inside points at the target
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills\MyLib"
New-Item -ItemType Junction -Path "$env:USERPROFILE\.agents\skills\MyLib\skills" `
-Target "D:\path\to\skills"
All three link shapes are recognized (the scanner detects the layout, so a link can no longer make a whole entry vanish):
| Shape | Structure | Result |
|---|---|---|
| Skill pack | <name>/skills/<s>/SKILL.md |
one command per skill in the pack |
| Folded link | <name>/<s>/SKILL.md (link points directly at skills/) |
same, validated by directory name |
| Single skill | <name>/SKILL.md |
one command |
Using it in DSH
- Sidebar: click the book icon at the bottom of the DSH left sidebar to open the visual catalog.
- Chat: call
dsh_catalog(acceptsquery/collection/refresh). - Click-to-insert: clicking a command card writes
/commandinto the current conversation input (or copies it); press Enter to invoke.
Privacy
- The panel server listens on
127.0.0.1loopback only, never exposed. - The scan artifact
assets/command-catalog.jsoncontains local absolute paths; it is a runtime artifact and excluded via.gitignore. The repository contains no hardcoded local paths or personal data. - Personalization lives outside the repository: custom categories in
~/.dsh-catalog/categories.json(local collection and command names, not distributed with the repo). - Auto-categorization is driven entirely by each skill's own name/description; nothing is uploaded and no external service is contacted.
Structure
dsh-catalog/
package.json Plugin manifest (dsh.bundle.patch / dsh.client)
cordis.patch.yml Plugin registration (bundle insert)
lib/index.js Cordis host entry: registers the tool + starts the panel server
lib/scan.js DSH skill scanner (no third-party deps, Node port):
direct children of skills roots = collections
(single skill / skill pack / loose), auto-categorization
+ custom category application
lib/customCategories.js Custom categories: persistence
(~/.dsh-catalog/categories.json), paste parsing,
override application
lib/customNotes.js Custom command notes: persistence
(~/.dsh-catalog/notes.json), table parsing,
right-click info card display
lib/store.js User-data directory + legacy-location migration
lib/server.js Panel data HTTP service (/api/catalog, /api/refresh,
/api/mode, /api/custom R/W, /api/custom/reset,
/api/notes R/W, /api/notes/reset)
client.js DSH browser entry: registers via window.__ModuleLoader__.load
sidebar icon (sidebar.footer.action) + right panel
(sidebarRight), native React components, data via
/api/catalog
assets/icon.svg Plugin/sidebar icon
assets/command-catalog.json Scan artifact (runtime-generated, gitignored)
Extending: add a scan source
Append one record to the SOURCES list in lib/scan.js; no parsing logic
changes needed. collection is usually omitted — collection names are
derived from the directories the roots actually hit:
{
client: "My Source",
kind: "skill_md", // DSH skills-root source
roots: ["~/.mysource/skills"],
// collection: "explicit name", // only for non-directory sources
}
No comments yet. Be the first to write one.