dsh-skill-station
Skill library, importer, and public skill market for DeepSeek Harness.
English | 中文
Skill Station gives DeepSeek Harness one place to manage the skills an agent can use. It lists every skill under the DSH home, the shared agents directory, and each workspace; it imports skills that already exist in Claude Code, Codex CLI, Cursor, and Gemini CLI libraries; and it installs new skills from dropped folders, ZIP archives, local paths, or a public Agent Skills market. The market needs no account or token, and it lives in the browser UI; the Host keeps every credential, network call, and filesystem write.
Contents
Screenshots
Skill market
The Skill market tab listing skills.sh results with install, target-root, conflict, and version controls
Browse the skills.sh catalog or search by keyword, choose a destination root and a conflict policy, then install one skill with the version selector.
Install result
A market skill installed with its local path and SHA-256 shown
After an install, the panel reports the local skill directory and the SHA-256 of the canonical archive. The same digest is written next to the skill in .dsh-skillhub.json.
Library
The Skill library tab with two installed skills, toggles, and per-skill actions
The library groups skills by root. Toggle model invocation, open details, assign scene tags, export a ZIP, or move a skill to the trash. Built-in host skills are listed read-only.
The screenshots come from a local harness that drives the real Host API; the market rows are live skills.sh data. The interface ships in English and Chinese, and these captures use the Chinese locale.
Features
Skill library
- Lists skills from every writable root, grouped by scope:
$DSH_HOME/skills (usually ~/.dsh/skills), ~/.agents/skills, and <workspace>/.dsh/skills plus <workspace>/.agents/skills for each workspace. Skills bundled with the Host are shown in a separate read-only group.
- Search by name and description; open a detail view with parsed frontmatter, the full
SKILL.md body, and a file tree for directory bundles.
- Edit text files in place, including
SKILL.md; saving returns the freshly parsed metadata so the list stays correct.
- Enable or disable a skill by rewriting its frontmatter invocation flags (
disable-model-invocation and user-invocable). Directory bundles and flat <name>.md skills are both supported.
- Export any skill as a ZIP named after the skill; re-importing that archive reproduces the folder layout.
- Run discovery diagnostics that mirror the Host loader's strict rules — invalid YAML, missing
name or description, non-kebab names, and legacy camelCase invocation keys — and repair sloppy frontmatter in place.
- Create a new skill with a guarded wizard that writes a valid kebab-named
SKILL.md.
- Group skills with scene tags (for example "engineer" or "design"); assignments are stored in
~/.dsh/skill-station/scenes.json.
- Delete to a restorable trash instead of deleting permanently; restore an entry or empty the trash explicitly.
Import from other agents
-
Read-only scans of the libraries used by Claude Code, Codex CLI, Cursor, and Gemini CLI. Source directories are never modified.
-
Default scan locations:
| Agent | User libraries | Workspace libraries |
|---|
| Claude Code | ~/.claude/skills | .claude/skills |
| Codex CLI | ~/.codex/skills | .codex/skills |
| Cursor | ~/.cursor/skills | .cursor/skills |
| Gemini CLI | ~/.gemini/antigravity/skills, ~/.gemini/skills | .gemini/skills |
-
Import into the global roots or into the selected workspace's project roots.
-
Resolve name conflicts with skip, rename, or replace. A rename rewrites the frontmatter name so the imported copy does not shadow the original; a replace moves the old version into the trash first.
-
The Host's skill watcher notices the new files, so no DSH restart is needed after an import.
Install from anywhere
- Drag a skill folder into the panel, pick a folder with the file picker, drop or choose a ZIP archive, or paste the absolute path of a folder that already exists on the Host.
- ZIP archives are decompressed on the Host; symlink entries are rejected and every path is checked before anything is written.
- Browser installs are validated, staged inside the destination root, and moved into place with an atomic rename. If the staging step or the final rename fails, the staging directory is removed and a replaced skill is restored from the trash.
- Local-path installs are streamed disk-to-disk and are the right choice for large trees with vendored dependencies.
Public skill market
- Works with skills.sh out of the box: catalog discovery through the published sitemaps, keyword search through the public search API, and installation from the GitHub repository archive.
- Market identities use the source's own uniqueness rule and become the installed skill name:
owner--repo--skillId on skills.sh (for example anthropics--skills--pdf, installed as anthropics-skills-pdf).
- Optional backends are configuration only — the OpenClaw registry, a static JSON catalog, or a deployment-provided enterprise Skill Hub. The UI, install pipeline, conflict policy, and source record are identical for all of them.
- Downloaded skills support skip and replace conflict handling; rename is rejected with
409 because the archive defines its own name.
- Every installed market skill carries a source record: provider, upstream identity, version, archive SHA-256, and installation time.
Requirements
- A DSH Web profile with plugin support (the 0.2.x line). The package peers on
@deepseek-ai/cordis ^4.0.5 and the 0.2.1 client shell packages.
- Node.js 22.19 or newer on the 22.x line, or Node.js 24.
- Any operating system DSH already supports. Paths follow DSH conventions and honor
$DSH_HOME.
Install
Add the bundle to the profile you run, then restart that profile:
dsh plugin --profile web add dsh-skill-station
# restart the profile (for example dsh web, or reopen Desktop)
To remove it again:
dsh plugin --profile web remove dsh-skill-station
The package is a normal DSH bundle. cordis.patch.yml inserts the Host row that mounts the API and the client row that registers the sidebar button and the Settings section; the profile keeps the package installed until you remove it.
DSH Desktop manages plugins per profile as well. If Skill Station is not already part of the profile you start, install the same bundle into it with --profile desktop (or whichever profile name your Desktop build uses). Builds that already ship the bundle need no installation step.
Once installed, open the station from the Skill Station button at the bottom of the sidebar, or from Settings → Skill Station.
Using Skill Station
The panel has five tabs and a workspace selector. The selector controls which project roots are managed; the global roots are always available.
| Tab | What it does |
|---|
| Library | Browse, search, enable or disable, inspect, edit, export, diagnose, and tag installed skills; create a new skill. |
| Skill market | Browse the configured market, search by keyword, choose a target root and a version, and install one skill. |
| Import | Scan the external agent libraries, select candidates, and import them with a conflict policy. |
| Install | Drop a folder, pick one, upload a ZIP, or paste a local absolute path. |
| Trash | Restore a deleted skill or empty the trash. |
Writable roots, in the order the library shows them:
| Scope | Root |
|---|
| Global (user) | $DSH_HOME/skills — ~/.dsh/skills by default |
| Shared (user) | ~/.agents/skills |
| Workspace | <workspace>/.dsh/skills |
| Workspace (shared) | <workspace>/.agents/skills |
A skill is either a directory bundle (<name>/SKILL.md plus assets) or a single Markdown file (<name>.md) directly under a root. Removing a skill always moves it to ~/.dsh/skill-station/trash first.
Skill market
skills.sh (default)
skills.sh is a public Agent Skills directory. It publishes no browse-everything JSON, so the Host uses the three public surfaces it does expose:
| Leg | Request | Notes |
|---|
| Discovery | GET /sitemap.xml → GET /sitemap-skills-N.xml | Up to 40,000 skill pages, parsed on first browse and cached for 10 minutes |
| Search | GET /api/search?q=... | Keyword search; rows carry the repository, skill id, and install count |
| Archive | GET https://codeload.github.com/<owner>/<repo>/zip/HEAD | The skill payload is the repository's own archive |
A skill id is only unique inside one repository, so skills.sh identities keep all three coordinates: owner--repo--skillId. SKILL.md names must be kebab-case, so the identity is projected onto that grammar and the skill is installed as anthropics-skills-pdf rather than anthropics--skills--pdf. The install request always names one exact identity; the Host verifies that the downloaded archive really publishes that skill and rejects a well-formed but unpublished identity with 404 NOT_FOUND.
Other backends
| Configuration | Backend | Discovery | Download | Identity |
|---|
market.mode: skills-sh (default) | skills.sh | Sitemap shards or search | GitHub repository archive | owner--repo--skillId |
market.mode: registry | clawhub.ai | Cursor catalog or search | Registry ZIP endpoint | owner--slug |
market.mode: catalog | Static JSON document | One catalog document | GitHub, npm, or direct archive URL | Entry id |
market.provider: htsc | Deployment-provided Skill Hub | Deployment API | Immutable version ZIP | Deployment skill name |
The static catalog defaults to https://awesome-dsh-plugin.com/plugins.json. Switching backends is a configuration change; the panel, install pipeline, and security checks are shared.
Enterprise Skill Hub
A deployment that runs its own Skill Hub can serve an organization-scoped catalog by setting market.provider: htsc and filling the Host-only skillHub block. This build ships no default hub host: the base URL, bearer token, and environment must come from the deployment. Without a valid hub configuration the market reports an actionable unavailable state, while the library, imports, and local installations keep working.
Conflicts and records
- Skip leaves an existing skill untouched; replace moves it to the trash and installs the new archive.
- Rename is not available for downloads (
409): a market archive defines its own identity and a renamed copy would no longer match the source.
- Each install writes
.dsh-skillhub.json next to the skill: schema version, provider, market identity, version, archive SHA-256, and installation timestamp. Replacing a downloaded skill requires a matching source record; a local skill or one from another provider is never overwritten.
Configuration
The market works with no configuration at all. Everything below is optional and lives under the plugin's row in the profile configuration:
plugins:
dsh-skill-station:
# Replace the built-in external-agent scan list (optional).
sources:
- id: claude
label: Claude Code
userDirs: ['~/.claude/skills']
projectDirs: ['.claude/skills']
# Maximum request body accepted from the browser, in bytes (64 MiB).
maxBodyBytes: 67108864
market:
# external (default) or htsc for a deployment-provided Skill Hub.
provider: external
# skills-sh (default), registry, or catalog.
mode: skills-sh
baseUrl: https://skills.sh
catalogUrl: https://awesome-dsh-plugin.com/plugins.json
npmRegistry: https://registry.npmjs.org
maxPages: 8
timeoutMs: 15000
maxAttempts: 3
catalogCacheTtlMs: 600000
# Read by the Host only, never sent to the renderer. Needed only for htsc.
skillHub:
baseUrl: https://skillhub.example.com/ai_skillhub_service
apiToken: <deployment token>
userId: <deployment user id>
environment: uat
| Key | Default | Meaning |
|---|
maxBodyBytes | 67108864 | Largest request body accepted from the browser, in bytes. |
sources | Claude Code, Codex CLI, Cursor, Gemini CLI | Read-only external libraries to scan. The list replaces the defaults rather than extending them. |
market.provider | external | external for a public market, htsc for a deployment Skill Hub. |
market.mode | skills-sh | Public market flavor: skills-sh, registry, or catalog. |
market.baseUrl | Mode-dependent | Market root; https://skills.sh for skills-sh and https://clawhub.ai for registry. |
market.catalogUrl | https://awesome-dsh-plugin.com/plugins.json | Catalog document used by catalog mode. |
market.npmRegistry | https://registry.npmjs.org | npm registry used to resolve npm package entries in a catalog. |
market.maxPages | 8 | Registry catalog pages walked per browse request. |
market.timeoutMs | 15000 | Per-attempt network timeout in milliseconds. |
market.maxAttempts | 3 | Retry attempts for retryable catalog requests. |
market.catalogCacheTtlMs | 600000 | Catalog cache lifetime (10 minutes). |
skillHub.baseUrl | — | Skill Hub service root supplied by the deployment. |
skillHub.apiToken | — | Bearer token for the hub. Host-only secret. |
skillHub.userId | — | Optional explicit user identity when the deployment does not inject one. |
skillHub.environment |
A hub deployment can also provide SKILLHUB_BASE_URL, SKILLHUB_API_TOKEN, SKILLHUB_ENV, and SKILLHUB_USER_ID through the environment instead of the configuration row. Configuration and environment values are read by the Host process; the renderer only receives a status object saying whether the market is configured.
How it works
Host and client
The package has two halves, mounted by the same bundle patch:
- Host (
src/): a Cordis function plugin named dsh-skill-station that injects webServer and registers one prefix route, /skill-station/api, inside ctx.effect() so it unloads with the plugin. It resolves the writable roots, enumerates skills from disk, validates every write against its root, and performs all network access.
- Client (
src/client/): one browser bundle in the form the DSH web shell loads, window.__ModuleLoader__.load({ id, factory }), where factory is a CommonJS closure and react, react-dom, and @deepseek-ai/* resolve from the shell's module table. It registers two slots — sidebar.footer.action (a button that opens a drawer) and settings.section (the same panel embedded in Settings) — plus English and Chinese dictionaries.
The renderer talks to the Host through the local API only. It never receives a market archive, an upstream response body, a filesystem path outside the skill roots, or a Skill Hub token.
HTTP API
All routes live under /skill-station/api and answer JSON. Mutating requests must be same-origin, and POST bodies must declare application/json.
| Method | Route | Purpose |
|---|
| GET | /roots | Workspaces and configured external sources |
| GET | /skills | Skills grouped by root, plus the Host registry view |
| GET | /skill | One skill's metadata, body, and file tree |
| GET | /file | One text file inside a skill |
| GET | /export | ZIP download for one skill |
| GET | /diagnose | Skills the Host loader would ignore, with reasons |
| GET | /scenes | Scene tag assignments |
| GET | /trash | Current trash entries |
| GET | /market/status | Market provider and configuration state |
| GET | /market/skills | Market catalog or keyword search |
| POST | /scan | Scan external agent libraries |
| POST | /import | Import selected external skills |
| POST | /upload | Install a dropped or picked file set |
| POST | /upload-zip | Install an uploaded ZIP archive |
| POST | /market/install | Install one market skill |
| POST | /toggle | Enable or disable a skill |
| POST | /delete | Move a skill to the trash |
| POST | /trash-restore | Restore a trashed skill |
| POST | /trash-empty | Permanently empty the trash |
| POST | /scenes/assign, /scenes/unassign | Edit scene tags |
| POST | /save-file | Save an edited file |
|
Install pipeline
Every installation, import, or market download goes through the same path:
- Validate. Parse the incoming
SKILL.md; require a kebab-case name and a non-empty description; repair common real-world frontmatter damage (for example unquoted colons) before the strict Host loader ever sees it.
- Resolve the name. Apply the conflict policy: skip, rename (imports only), or replace through the trash.
- Stage. Write the complete file set into a hidden
.staging-<uuid> directory inside the destination root, checking containment, per-file size, and total size. Symbolic links are skipped on copy and rejected in archives.
- Verify. Re-read the staged
SKILL.md and confirm the metadata is valid before the destination changes.
- Commit. Rename the staging directory onto the target path in one atomic operation.
- Roll back. If anything fails after a replaced skill was trashed, the trash entry is restored and the staging directory is removed.
Archive canonicalization
Market backends return different shapes: GitHub repository archives, registry ZIPs, npm tarballs, or direct archives. Before an archive reaches the installer, the Host rebuilds it into the canonical form:
- Decode a ZIP or a gzip-compressed tarball, dropping entries with absolute paths,
.. segments, control characters, or Windows device names.
- Strip a single shared top-level folder and search for the skill directory:
skills/<skillId>/SKILL.md, <skillId>/SKILL.md, then the shallowest SKILL.md. In skills.sh mode the identity must match, so an archive that publishes no such skill is rejected instead of installing an unrelated template.
- Keep only files under that skill root, re-emit one ZIP rooted at the skill directory, and rewrite the root
SKILL.md frontmatter name when it does not match the market identity.
- Bound the result (5,000 entries, 32 MiB per file, 64 MiB total) and hand the ZIP to the normal install pipeline.
Security model
- Same-origin only. Every mutation and every
/market/* request must be same-origin or a non-browser caller; a cross-origin Origin is rejected with 403 before any work happens.
- JSON only. POST requests that do not declare
application/json are rejected with 415, which blocks simple cross-site form submissions.
- Containment. Every write target is normalized and checked to stay inside its resolved root. Upload paths reject absolute paths,
.., backslashes, control bytes, and Windows device names; file views and saves are confined to the skill directory.
- No symlink following. Directory copies, exports, and archive decoding skip or reject symbolic links, and a skill root must be a real directory.
- Bounded work. The default request body cap is 64 MiB. Browser ZIP uploads decompress under 10,000 entries and 256 MiB total; installed files are capped at 64 MiB each and 256 MiB total. Market archives are stricter: 5,000 entries, 32 MiB per file, 64 MiB total. A file view is truncated at 512 KiB and an in-place edit is capped at 2 MiB.
- Token containment. The Skill Hub token is read from Host configuration or environment and is never serialized into the client bundle or a renderer response. Market errors are mapped to stable, redacted codes.
- Identity checks. A market archive must actually publish the requested skill identity; the installed source record is also checked on replacement so a different provider cannot overwrite an existing skill.
- Honest trust boundary. Public markets are open to anyone, and moderation upstream is automated only. The recorded SHA-256 is a local integrity check of the rebuilt archive, not a signature and not a trust decision. Review third-party skill instructions before enabling them. DSH skills are executable instructions and assets.
Verified evidence
The 0.2.1 Host API was exercised against a real HTTP server and the real network (skills.sh search and codeload.github.com archives), with $HOME isolated to a temporary directory:
| Check | Result |
|---|
| Host API end-to-end suite | 22 / 22 cases pass, 70 assertions, 0 failures (Node.js 22.22.3, darwin-arm64) |
| Live keyword search | GET /market/skills?keyword=pdf returned 6 rows in 1.7 s; anthropics--skills--pdf was first (222,304 installs at test time) |
| Live install | anthropics/skills pdf archive (4,317,426 B, 543 entries) installed in 8.0 s: 13 files written (12 skill files plus the source record), SKILL.md 8,101 B |
| Content provenance | 11 of 12 payload files byte-identical to the GitHub archive; SKILL.md differed only in the frontmatter name line, and matched upstream after normalizing that line; no extra files on disk |
| Lifecycle | toggle off, toggle on, delete, trash, and restore all returned 200; after restore the SKILL.md SHA-256 was unchanged and the trash was empty |
| Rejections | unknown skill 404, cross-origin request 403, non-JSON content type 415, invalid page or parameters 400, unknown root 400, rename-on-download 409 |
| Identity enforcement | a well-formed but unpublished skills.sh identity is rejected with 404 NOT_FOUND |
| Isolation | the real ~/.dsh tree was byte-identical before and after the run |
The test harness rebuilt the Host API from source, drove it through real node:http requests, and verified the installed files against a separately downloaded upstream archive. Network timings vary by machine and route; the counts and digests above are from that run.
Repository layout
.
├── src/
│ ├── index.ts # Host plugin entry: mounts the HTTP route
│ ├── api.ts # Route dispatch, validation, install orchestration
│ ├── external-market.ts # skills.sh / registry / catalog clients, archive canonicalization
│ ├── skillhub.ts # Deployment Skill Hub client (Host-only token)
│ ├── roots.ts # Writable roots, containment, disk enumeration
│ ├── scanner.ts # Read-only external agent library scanner
│ ├── importer.ts # Conflict policy, staged installs, rollback
│ ├── zip.ts # Uploaded ZIP decoding and limits
│ ├── frontmatter.ts # YAML frontmatter parse, repair, flags, rename
│ ├── diagnose.ts # Discovery diagnostics against Host loader rules
│ ├── export.ts # ZIP export
│ ├── trash.ts # Trash manifests, restore, empty
│ ├── scenes.ts # Scene tag persistence
│ └── client/ # Browser bundle: entry, App, MarketTab, api, locales, styles
├── docs/screenshots/ # market.png, install.png, library.png
├── scripts/ # build-client.mjs, verify-client.mjs, smoke.mjs
├── tests/ # Vitest specs
├── cordis.patch.yml # Bundle patch that activates the plugin row
├── MARKET.md # Market provider reference
├── DESIGN.md # Architecture notes
└── .github/workflows/ci.yml # Node 22/24 build, test, smoke
Development
npm install
npm run build # Host TypeScript, client bundle, and bundle-shape verification
npm test # Vitest unit and composition specs
npm run smoke # Boots the built API and exercises every endpoint
The repository has no lockfile, so CI uses npm install rather than npm ci. The build writes lib/, which is git-ignored; the client build is wrapped in window.__ModuleLoader__.load and gated by scripts/verify-client.mjs (banner, plugin id, externalized shell modules, and a sandboxed factory run). npm run typecheck runs the compiler without emitting.
CI runs on a Node.js 22/24 matrix: install, build, unit tests, and the built-artifact smoke test.
Known limitations
- Public markets do not publish upstream signatures. The local digest detects a changed archive but does not establish who built it; review third-party instructions before enabling a skill.
- Downloaded skills cannot be renamed during install (
409); uninstall or rename the existing skill first if you need a different name.
- Browser uploads have to cross the JSON request body, so very large skills should be installed from a local path instead.
- The enterprise Skill Hub requires deployment-supplied configuration; without it the market surface reports itself unavailable, but the local library and import features still work.
- Market backends are third-party services and can change or rate-limit; a
429 is surfaced as an error and catalogs are cached for ten minutes.
- The recorded SHA-256 covers the rebuilt archive. Archives are rebuilt on install, so the digest is an integrity record for that installation rather than a reproducible upstream digest.
License
MIT
Acknowledgements
- skills.sh — the public Agent Skills directory used as the default market.
- clawhub.ai — the OpenClaw registry backend.
- awesome-dsh-plugin.com — the default static catalog.
- The DSH team for the Host skills registry, web server, slots, and locale APIs this plugin builds on.