@nanmicoder/dsh-skills-hub 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +7 -3
- package/README.md +24 -12
- package/README_EN.md +24 -12
- package/README_ZH.md +1 -1
- package/assets/readme/marketplace.png +0 -0
- package/compatibility.json +6 -6
- package/docs/CATALOG.md +39 -0
- package/docs/COMPATIBILITY.md +31 -1
- package/docs/CONTRACT.md +15 -10
- package/docs/RELEASE.md +4 -4
- package/lib/catalog/catalog.js +89 -0
- package/lib/catalog/skills.json +8673 -0
- package/lib/client.js +2549 -1819
- package/lib/client.js.map +1 -1
- package/lib/market/clawhub-provider.js +139 -22
- package/lib/market/install-service.js +19 -8
- package/lib/market/market-service.js +88 -9
- package/lib/market/skillhub-provider.js +6 -2
- package/lib/skills/installed.js +5 -0
- package/lib/types/catalog/catalog.d.ts +73 -0
- package/lib/types/client/api.d.ts +12 -3
- package/lib/types/client/components/CategoryBar.d.ts +13 -0
- package/lib/types/client/components/FilterBar.d.ts +4 -0
- package/lib/types/client/components/InstallConfirmDialog.d.ts +14 -11
- package/lib/types/client/components/InstalledSkillDetail.d.ts +1 -1
- package/lib/types/client/components/InstalledSkills.d.ts +1 -1
- package/lib/types/client/components/MarketSkillDetail.d.ts +34 -0
- package/lib/types/client/components/SkillDetailShell.d.ts +66 -43
- package/lib/types/client/locales.d.ts +64 -0
- package/lib/types/client/skill-insights.d.ts +30 -0
- package/lib/types/client/state.d.ts +23 -3
- package/lib/types/market/clawhub-provider.d.ts +15 -0
- package/lib/types/market/install-service.d.ts +1 -0
- package/lib/types/market/market-service.d.ts +25 -3
- package/lib/types/market/types.d.ts +29 -0
- package/lib/types/skills/installed.d.ts +4 -0
- package/lib/types/web-routes.d.ts +4 -13
- package/lib/web-routes.js +48 -19
- package/package.json +26 -27
- package/release-notes/v0.0.3.md +12 -0
- package/release-notes/v0.0.4.md +11 -0
- package/lib/types/client/components/InstallStateBadge.d.ts +0 -11
- package/lib/types/client/components/SecurityBadge.d.ts +0 -16
- package/lib/types/client/components/SkillDetailView.d.ts +0 -19
package/lib/web-routes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skills Hub HTTP surface.
|
|
3
3
|
*
|
|
4
|
-
* One `prefix` route (`/api/skills-hub`) owns the
|
|
4
|
+
* One `prefix` route (`/api/skills-hub`) owns the endpoints the panel
|
|
5
5
|
* needs; every response is JSON with `Cache-Control: no-store`, because the
|
|
6
6
|
* panel polls and every payload is either live upstream data or live disk state.
|
|
7
7
|
*
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* otherwise be reachable by a cross-site request or a DNS-rebound page.
|
|
17
17
|
*/
|
|
18
18
|
import { installMarketSkill, MarketInstallError, uninstallMarketSkill, } from "./market/install-service.js";
|
|
19
|
-
import { getMarketFileContent, getMarketSkillDetail, getMarketStatus, isValidMarketFilePath, listMarketSkills, } from "./market/market-service.js";
|
|
19
|
+
import { getMarketFileContent, getMarketSkillDetail, getMarketStatus, isValidMarketFilePath, listMarketCategories, listMarketSkills, } from "./market/market-service.js";
|
|
20
20
|
import { getMarketStats } from "./market/cache.js";
|
|
21
|
+
import { CLAWHUB_OWNER_PATTERN } from "./market/clawhub-provider.js";
|
|
21
22
|
import { readInstalledFile, readInstalledSkill, removeInstalledSkill } from "./skills/management.js";
|
|
22
23
|
import { scanInstalledSkills } from "./skills/installed.js";
|
|
23
24
|
import { resolveSkillsScanRoots } from "./skills/root.js";
|
|
@@ -116,19 +117,9 @@ function sendFailure(res, error) {
|
|
|
116
117
|
const failure = toHttpFailure(error);
|
|
117
118
|
sendJson(res, failure.status, errorPayload(failure.code, failure.message), failure.allow === undefined ? undefined : { allow: failure.allow });
|
|
118
119
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* The frozen gate type says `boolean`, the real service answers `401 | 403 |
|
|
123
|
-
* undefined`; both are accepted so the envelope can report the true status when
|
|
124
|
-
* the plugin is wired to the real Connection service, while a plain predicate
|
|
125
|
-
* still works.
|
|
126
|
-
*/
|
|
127
|
-
function evaluateGate(gate, req, res) {
|
|
128
|
-
const outcome = gate.requestRejection(req, res);
|
|
129
|
-
if (outcome === undefined || outcome === false || outcome === null)
|
|
130
|
-
return undefined;
|
|
131
|
-
return typeof outcome === 'number' ? outcome : 403;
|
|
120
|
+
/** Preserve the host's authentication (401) and origin (403) rejections. */
|
|
121
|
+
function evaluateGate(gate, req) {
|
|
122
|
+
return gate.requestRejection(req);
|
|
132
123
|
}
|
|
133
124
|
/**
|
|
134
125
|
* Read a bounded JSON object body.
|
|
@@ -248,6 +239,30 @@ function enumQuery(url, name, allowed, fallback) {
|
|
|
248
239
|
return raw;
|
|
249
240
|
throw new RouteError(400, 'BAD_REQUEST', `Invalid ${name} filter: ${raw}`);
|
|
250
241
|
}
|
|
242
|
+
/** Shape of a catalogue category key. */
|
|
243
|
+
const CATEGORY_KEY_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
244
|
+
/** `category` query parameter: a catalogue category key, shape-checked only. */
|
|
245
|
+
function parseCategory(url) {
|
|
246
|
+
const raw = url.searchParams.get('category');
|
|
247
|
+
if (raw === null || raw === '' || raw === 'all')
|
|
248
|
+
return undefined;
|
|
249
|
+
if (!CATEGORY_KEY_PATTERN.test(raw))
|
|
250
|
+
throw new RouteError(400, 'BAD_REQUEST', `Invalid category: ${raw}`);
|
|
251
|
+
return raw;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Optional registry owner. ClawHub slugs are shared across authors, so the
|
|
255
|
+
* panel names the author of the card it opened; a malformed owner is refused
|
|
256
|
+
* rather than silently dropped (that would fall back to guessing).
|
|
257
|
+
*/
|
|
258
|
+
function parseOwner(raw) {
|
|
259
|
+
if (raw === undefined || raw === null || raw === '')
|
|
260
|
+
return undefined;
|
|
261
|
+
if (typeof raw !== 'string' || !CLAWHUB_OWNER_PATTERN.test(raw)) {
|
|
262
|
+
throw new RouteError(400, 'BAD_REQUEST', `Invalid owner: ${String(raw)}`);
|
|
263
|
+
}
|
|
264
|
+
return raw;
|
|
265
|
+
}
|
|
251
266
|
/** `limit` query parameter, clamped into the documented range. */
|
|
252
267
|
function parseLimit(url) {
|
|
253
268
|
const raw = url.searchParams.get('limit');
|
|
@@ -293,6 +308,8 @@ async function handleStats(res) {
|
|
|
293
308
|
async function handleList(url, res) {
|
|
294
309
|
const params = {
|
|
295
310
|
q: optionalQuery(url, 'q', MAX_QUERY_LENGTH),
|
|
311
|
+
scope: enumQuery(url, 'scope', ['catalog', 'market'], 'catalog'),
|
|
312
|
+
category: parseCategory(url),
|
|
296
313
|
source: enumQuery(url, 'source', ['all', ...MARKET_SOURCES], 'all'),
|
|
297
314
|
security: enumQuery(url, 'security', ['all', ...SECURITY_FILTERS], 'all'),
|
|
298
315
|
installed: enumQuery(url, 'installed', INSTALLED_FILTERS, 'all'),
|
|
@@ -303,15 +320,20 @@ async function handleList(url, res) {
|
|
|
303
320
|
const result = await listMarketSkills(params);
|
|
304
321
|
sendJson(res, 200, result);
|
|
305
322
|
}
|
|
323
|
+
async function handleCategories(res) {
|
|
324
|
+
sendJson(res, 200, listMarketCategories());
|
|
325
|
+
}
|
|
306
326
|
async function handleDetail(source, slug, url, res) {
|
|
307
|
-
|
|
327
|
+
const owner = parseOwner(url.searchParams.get('owner'));
|
|
328
|
+
sendJson(res, 200, await getMarketSkillDetail(source, slug, { force: parseRefresh(url), owner }));
|
|
308
329
|
}
|
|
309
330
|
async function handleFile(source, slug, url, res) {
|
|
310
331
|
const filePath = url.searchParams.get('path') ?? '';
|
|
311
332
|
if (!isValidMarketFilePath(filePath)) {
|
|
312
333
|
throw new RouteError(400, 'BAD_REQUEST', `Invalid file path: ${filePath}`);
|
|
313
334
|
}
|
|
314
|
-
|
|
335
|
+
const owner = parseOwner(url.searchParams.get('owner'));
|
|
336
|
+
sendJson(res, 200, { file: await getMarketFileContent(source, slug, filePath, owner) });
|
|
315
337
|
}
|
|
316
338
|
/**
|
|
317
339
|
* List installed skills from disk.
|
|
@@ -326,10 +348,12 @@ async function handleInstalled(deps, res) {
|
|
|
326
348
|
sendJson(res, 200, { items: (await scanInstalledSkills(roots)).map(item => ({ ...item, removable: deps.allowUninstall() })) });
|
|
327
349
|
}
|
|
328
350
|
async function handleInstall(req, deps, res) {
|
|
329
|
-
const
|
|
351
|
+
const body = await readJsonBody(req);
|
|
352
|
+
const { source, slug } = parseBodyId(body);
|
|
330
353
|
const result = await installMarketSkill(source, slug, {
|
|
331
354
|
skillsRoot: await deps.skillsRoot(),
|
|
332
355
|
allowUninstall: deps.allowUninstall(),
|
|
356
|
+
owner: parseOwner(body['owner']),
|
|
333
357
|
});
|
|
334
358
|
// Refresh before answering, so the next /skills or /installed call is correct.
|
|
335
359
|
await deps.rescan().catch(() => undefined);
|
|
@@ -352,7 +376,7 @@ async function handle(req, res, gate, deps) {
|
|
|
352
376
|
// leaving the whole marketplace readable and installable from any web page.
|
|
353
377
|
const resolvedGate = gate();
|
|
354
378
|
if (resolvedGate !== undefined) {
|
|
355
|
-
const rejection = evaluateGate(resolvedGate, req
|
|
379
|
+
const rejection = evaluateGate(resolvedGate, req);
|
|
356
380
|
if (rejection !== undefined) {
|
|
357
381
|
sendJson(res, rejection, errorPayload(rejection === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN', rejection === 401 ? 'unauthorized' : 'forbidden'));
|
|
358
382
|
return;
|
|
@@ -368,6 +392,11 @@ async function handle(req, res, gate, deps) {
|
|
|
368
392
|
await handleStatus(res);
|
|
369
393
|
return;
|
|
370
394
|
}
|
|
395
|
+
if (head === 'categories' && segments.length === 1) {
|
|
396
|
+
requireMethod(method, 'GET');
|
|
397
|
+
await handleCategories(res);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
371
400
|
if (head === 'stats' && segments.length === 1) {
|
|
372
401
|
requireMethod(method, 'GET');
|
|
373
402
|
await handleStats(res);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanmicoder/dsh-skills-hub",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Skills Hub for DeepSeek Harness: browse, preview, install and remove third-party skills from ClawHub and SkillHub inside the DSH Web GUI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
"pack:check": "pnpm check && node scripts/pack-check.mjs"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@deepseek-ai/cordis": "
|
|
66
|
-
"@deepseek-ai/dsh-client-connection": "0.2.
|
|
67
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.
|
|
69
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.
|
|
70
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
71
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
72
|
-
"@deepseek-ai/schemastery": "
|
|
65
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
66
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.1",
|
|
72
|
+
"@deepseek-ai/schemastery": "~3.18.5",
|
|
73
73
|
"react": "^18.2.0"
|
|
74
74
|
},
|
|
75
75
|
"peerDependenciesMeta": {
|
|
@@ -102,24 +102,23 @@
|
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
|
-
"@deepseek-ai/cordis": "4.0.
|
|
106
|
-
"@deepseek-ai/dsh-client-connection": "0.2.
|
|
107
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
108
|
-
"@deepseek-ai/dsh-client-store": "0.2.
|
|
109
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.
|
|
110
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.
|
|
111
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
112
|
-
"@deepseek-ai/dsh-fs": "0.2.
|
|
113
|
-
"@deepseek-ai/dsh-home-paths": "0.2.
|
|
114
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
115
|
-
"@deepseek-ai/dsh-
|
|
116
|
-
"@deepseek-ai/dsh-
|
|
117
|
-
"@deepseek-ai/dsh-
|
|
118
|
-
"@deepseek-ai/dsh-
|
|
119
|
-
"@deepseek-ai/dsh-
|
|
120
|
-
"@deepseek-ai/dsh-skill": "0.2.
|
|
121
|
-
"@deepseek-ai/
|
|
122
|
-
"@deepseek-ai/schemastery": "3.18.4",
|
|
105
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
106
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.1",
|
|
107
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.1",
|
|
108
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-rc.1",
|
|
109
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.1",
|
|
110
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.1",
|
|
111
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.1",
|
|
112
|
+
"@deepseek-ai/dsh-fs": "0.2.1-rc.1",
|
|
113
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.1",
|
|
114
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.1",
|
|
115
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.1",
|
|
116
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.1",
|
|
117
|
+
"@deepseek-ai/dsh-scope": "0.2.1-rc.1",
|
|
118
|
+
"@deepseek-ai/dsh-session": "0.2.1-rc.1",
|
|
119
|
+
"@deepseek-ai/dsh-skill": "0.2.1-rc.1",
|
|
120
|
+
"@deepseek-ai/dsh-skill-filesystem": "0.2.1-rc.1",
|
|
121
|
+
"@deepseek-ai/schemastery": "3.18.5",
|
|
123
122
|
"@types/node": "^24.13.3",
|
|
124
123
|
"@types/react": "~18.3.1",
|
|
125
124
|
"@types/react-dom": "^18.3.7",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Skills Hub 0.0.3
|
|
2
|
+
|
|
3
|
+
`@nanmicoder/dsh-skills-hub` for DeepSeek Harness **0.2.0-rc.2**.
|
|
4
|
+
|
|
5
|
+
- **Curated catalogue.** The home page now opens on 398 hand-picked skills from ClawHub and SkillHub, grouped into 13 categories (agents, research, development, office, productivity, content, media, data, marketing, finance, professional, life, security) with editor's picks first. The list ships with the plugin, so it renders instantly and offline; details, files and installs are still read live from upstream and cached. Searching switches to live results from both marketplaces.
|
|
6
|
+
- **Redesigned detail page.** Header with downloads, installs, stars and file count; Overview, Files, Security and Changelog tabs; a rule-based "what this skill will do" panel (shell commands, hooks, secrets, network hosts, workspace writes, required binaries) and "when it triggers" list, both derived from the skill's own files; per-scanner security reports from upstream.
|
|
7
|
+
- **Install confirmation.** The dialog lists what the skill will be able to do, where it will be installed, and requires an explicit acknowledgement for skills that are unscanned or flagged by upstream scanners.
|
|
8
|
+
- **Same-name skills resolved by author.** ClawHub slugs can be shared by several authors. Details, file previews and installs are now pinned to the author shown on the card, live search shows one entry per slug, and a mirror is only folded into an original by the same author. Installed skills record their author.
|
|
9
|
+
- **Unified look.** Marketplace home, detail pages and Installed share one visual system, and fit the docked 720px panel in light and dark themes.
|
|
10
|
+
- Copy now states plainly that Skills Hub is a community plugin, not a DeepSeek product, and that the curated list is a recommendation rather than a security guarantee.
|
|
11
|
+
|
|
12
|
+
Install in **DeepSeek Harness desktop → Plugins → Add plugin** using `@nanmicoder/dsh-skills-hub@0.0.3`, then enable the plugin. Existing skills and cache settings remain in place. Catalogue policy is documented in [CATALOG.md](../docs/CATALOG.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Skills Hub 0.0.4
|
|
2
|
+
|
|
3
|
+
`@nanmicoder/dsh-skills-hub` for DeepSeek Harness **0.2.1-rc.1**.
|
|
4
|
+
|
|
5
|
+
- Move the exact Harness dependency and peer cohort to `0.2.1-rc.1`, with Cordis 4.0.5 and Schemastery 3.18.5. Remove the retired `dsh-invariants` development dependency.
|
|
6
|
+
- Type the HTTP trust gate against the host connection service, preserving authentication (401) and origin (403) rejection statuses without an unchecked boolean adapter.
|
|
7
|
+
- Retain the existing sidebar, curated catalogue, Markdown previews, persistent cache and skill installation/removal flows.
|
|
8
|
+
|
|
9
|
+
This release targets the exact candidate host version; it does not claim compatibility with an unreleased stable 0.2.1. See [COMPATIBILITY.md](../docs/COMPATIBILITY.md) for validation evidence and limitations.
|
|
10
|
+
|
|
11
|
+
After publication, install `@nanmicoder/dsh-skills-hub@0.0.4` from the host plugin manager. Release preparation does not publish the package.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { InstallState } from '../../market/types.ts';
|
|
2
|
-
/**
|
|
3
|
-
* Local install state chip.
|
|
4
|
-
*
|
|
5
|
-
* The three labels come from dictionary keys the contract already fixes —
|
|
6
|
-
* `installed`, `installed.installable` and `notInstallable` — so this badge
|
|
7
|
-
* adds no vocabulary of its own.
|
|
8
|
-
*/
|
|
9
|
-
export declare function InstallStateBadge(props: {
|
|
10
|
-
state: InstallState;
|
|
11
|
-
}): JSX.Element;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { SecurityReport, SecurityStatus } from '../../market/types.ts';
|
|
2
|
-
/**
|
|
3
|
-
* Audit verdict chip.
|
|
4
|
-
*
|
|
5
|
-
* Shows a shield for every verdict except `flagged`, which uses the warning
|
|
6
|
-
* triangle: a scan that found something must not look like a scan that merely
|
|
7
|
-
* ran, and shape reads faster than color alone.
|
|
8
|
-
*
|
|
9
|
-
* `reports` supplies the upstream status line for the hover title, so the chip
|
|
10
|
-
* can stay a two-word summary without hiding what the vendor actually said.
|
|
11
|
-
*/
|
|
12
|
-
export declare function SecurityBadge(props: {
|
|
13
|
-
status: SecurityStatus;
|
|
14
|
-
reports?: SecurityReport[];
|
|
15
|
-
compact?: boolean;
|
|
16
|
-
}): JSX.Element;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { NormalizedSkillDetail } from '../../market/types.ts';
|
|
2
|
-
import type { MarketController, MarketState } from '../state.ts';
|
|
3
|
-
/**
|
|
4
|
-
* Market skill detail page.
|
|
5
|
-
*
|
|
6
|
-
* Like the home page this component owns no data: the tab, the selected file
|
|
7
|
-
* and the file contents all live in `MarketState`, so switching tabs or
|
|
8
|
-
* re-opening a skill does not lose its place. The reading surface itself — back
|
|
9
|
-
* affordance, header, tabs, facts rail — is `SkillDetailShell`, which the local
|
|
10
|
-
* installed page uses too.
|
|
11
|
-
*
|
|
12
|
-
* The bundled GFM renderer handles catalogue Markdown without relying on a
|
|
13
|
-
* host renderer delegate. Raw HTML is disabled and URLs use its safe default.
|
|
14
|
-
*/
|
|
15
|
-
export declare function SkillDetailView(props: {
|
|
16
|
-
detail: NormalizedSkillDetail;
|
|
17
|
-
state: MarketState;
|
|
18
|
-
controller: MarketController;
|
|
19
|
-
}): JSX.Element;
|