dsh-skill-importer 0.1.0
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/LICENSE +21 -0
- package/README.md +98 -0
- package/lib/client.js +1235 -0
- package/lib/index.js +463 -0
- package/lib/types/client/SkillImporterSection.d.ts +16 -0
- package/lib/types/client/SkillsPicker.d.ts +16 -0
- package/lib/types/client/index.d.ts +39 -0
- package/lib/types/client/locales.d.ts +117 -0
- package/lib/types/client/name.d.ts +5 -0
- package/lib/types/frontmatter.d.ts +39 -0
- package/lib/types/index.d.ts +34 -0
- package/lib/types/server.d.ts +83 -0
- package/lib/types/types.d.ts +64 -0
- package/package.json +90 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Markdown frontmatter parsing and validation (pure, browser-safe).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the subset of `dsh-skill-filesystem`'s frontmatter contract the
|
|
5
|
+
* import UI needs for preview and pre-flight validation. The authoritative
|
|
6
|
+
* parse remains the host provider's; this parser only previews and catches
|
|
7
|
+
* obvious mistakes before the import instruction is sent.
|
|
8
|
+
*/
|
|
9
|
+
/** Frontmatter fields the import UI understands. */
|
|
10
|
+
export interface SkillFrontmatter {
|
|
11
|
+
/** Kebab-case skill name (required for a valid skill). */
|
|
12
|
+
name?: string;
|
|
13
|
+
/** One-line routing description (required for a valid skill). */
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Optional routing guidance. */
|
|
16
|
+
whenToUse?: string;
|
|
17
|
+
/** `disable-model-invocation: true` keeps the skill out of model catalogs. */
|
|
18
|
+
disableModelInvocation?: boolean;
|
|
19
|
+
/** `user-invocable: false` keeps the skill out of the `/` menu. */
|
|
20
|
+
userInvocable?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Parsed Markdown file: frontmatter plus the body after it. */
|
|
23
|
+
export interface ParsedSkillFile {
|
|
24
|
+
/** Parsed frontmatter values (empty when the file has no frontmatter block). */
|
|
25
|
+
readonly frontmatter: SkillFrontmatter;
|
|
26
|
+
/** Markdown body after the closing `---` (empty when absent). */
|
|
27
|
+
readonly body: string;
|
|
28
|
+
}
|
|
29
|
+
/** True when the name satisfies the harness skill-name rule. */
|
|
30
|
+
export declare function isValidSkillName(name: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Parse a skill Markdown file's frontmatter block.
|
|
33
|
+
* @param text - full file text.
|
|
34
|
+
* @returns parsed frontmatter and body; a file without a leading `---` block
|
|
35
|
+
* yields an empty frontmatter with the whole text as body.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseSkillFile(text: string): ParsedSkillFile;
|
|
38
|
+
/** Why a parsed file cannot be imported as a skill (or undefined when it can). */
|
|
39
|
+
export declare function validateSkillFile(text: string): string | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host (Node) half of the dsh-skill-importer plugin.
|
|
3
|
+
*
|
|
4
|
+
* Registers four `/skill-importer/*` HTTP routes on the harness's own web
|
|
5
|
+
* server (`ctx.webServer` — the official plugin route registry, served on
|
|
6
|
+
* the same origin as the Web UI):
|
|
7
|
+
*
|
|
8
|
+
* - GET /skill-importer/health — liveness probe
|
|
9
|
+
* - GET /skill-importer/list — installed skills across all roots
|
|
10
|
+
* - POST /skill-importer/import — write one skill file from its text
|
|
11
|
+
* - POST /skill-importer/import-url — fetch a URL and write the skill file
|
|
12
|
+
* - POST /skill-importer/delete — remove one installed skill copy
|
|
13
|
+
*
|
|
14
|
+
* The host process owns the filesystem (no agent sandbox, no approval), so
|
|
15
|
+
* an import lands the file immediately; the skill-filesystem provider's
|
|
16
|
+
* watcher then discovers it and hot-refreshes the catalog. No session, no
|
|
17
|
+
* model, no agent involved in imports.
|
|
18
|
+
*/
|
|
19
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
20
|
+
/** Route paths this plugin owns (exact matches; the client fetches the same literals). */
|
|
21
|
+
export declare const ROUTES: {
|
|
22
|
+
readonly health: "/skill-importer/health";
|
|
23
|
+
readonly list: "/skill-importer/list";
|
|
24
|
+
readonly import: "/skill-importer/import";
|
|
25
|
+
readonly importUrl: "/skill-importer/import-url";
|
|
26
|
+
readonly delete: "/skill-importer/delete";
|
|
27
|
+
};
|
|
28
|
+
/** Required services: the harness web server's route registry and the workspace registry. */
|
|
29
|
+
export declare const inject: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Host plugin body: register the importer routes.
|
|
32
|
+
* @param ctx - host plugin context (provides `ctx.webServer`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side skill importer logic: direct filesystem writes, skill-root
|
|
3
|
+
* scanning, and URL fetching. Pure Node — no Cordis imports — so the route
|
|
4
|
+
* handlers stay unit-testable and the plugin body is only wiring.
|
|
5
|
+
*
|
|
6
|
+
* This is the "direct write" path: the host process owns the filesystem
|
|
7
|
+
* (no agent sandbox, no approval), so an import lands the file immediately
|
|
8
|
+
* and the skill-filesystem watcher discovers it in place.
|
|
9
|
+
*/
|
|
10
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
11
|
+
import type { ImportRequest, ImportTarget, ImportUrlRequest, SkillListEntry } from './types.ts';
|
|
12
|
+
/** Hard cap for one imported skill body (matches the client preview limit). */
|
|
13
|
+
export declare const MAX_CONTENT_BYTES: number;
|
|
14
|
+
/** Cap for one request body read (JSON overhead above the content cap). */
|
|
15
|
+
export declare const MAX_BODY_BYTES: number;
|
|
16
|
+
/** The harness home (`$DSH_HOME`, defaulting to `~/.dsh`). */
|
|
17
|
+
export declare function dshHomeDir(): string;
|
|
18
|
+
/** Absolute skill root for one target under one workspace. */
|
|
19
|
+
export declare function skillRoot(target: ImportTarget, workspacePath: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Rebuild a skill file's frontmatter in the canonical, strictly-YAML-valid
|
|
22
|
+
* form the harness's provider parses. Unknown keys are dropped (the harness
|
|
23
|
+
* only consumes name/description/whenToUse and the two invocation flags);
|
|
24
|
+
* the body is preserved verbatim (trimmed).
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeSkillText(text: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Write one skill file atomically: `<root>/<name>/SKILL.md`, created via a
|
|
29
|
+
* same-directory temp file plus rename so a crash never leaves a torn file.
|
|
30
|
+
* The content's frontmatter is normalized first so the harness's strict YAML
|
|
31
|
+
* discovery always finds the skill.
|
|
32
|
+
* @param name - kebab-case skill name (validated).
|
|
33
|
+
* @param target - which skill root to write into.
|
|
34
|
+
* @param content - full Markdown text (frontmatter included).
|
|
35
|
+
* @param workspacePath - canonical workspace path; required for project targets.
|
|
36
|
+
* @returns the absolute path of the written file.
|
|
37
|
+
*/
|
|
38
|
+
export declare function writeSkillFile(name: string, target: ImportTarget, content: string, workspacePath?: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* List every installed skill across every registered workspace's project
|
|
41
|
+
* roots plus the user root. No rank deduplication: the management surface
|
|
42
|
+
* shows every location's copy (the framework's own catalog still applies
|
|
43
|
+
* rank at discovery time). Display order groups by source, then name.
|
|
44
|
+
* @param workspacePaths - canonical paths of the registered workspaces.
|
|
45
|
+
*/
|
|
46
|
+
export declare function listSkills(workspacePaths: readonly string[]): SkillListEntry[];
|
|
47
|
+
/**
|
|
48
|
+
* Delete one installed skill (its whole bundle directory `<root>/<name>/`,
|
|
49
|
+
* or the flat `<root>/<name>.md`), scoped to the skill roots only.
|
|
50
|
+
* @param name - kebab-case skill name (validated).
|
|
51
|
+
* @param source - which root the copy lives in.
|
|
52
|
+
* @param workspacePath - canonical workspace path; required for project sources.
|
|
53
|
+
* @returns true when something was removed, false when nothing matched.
|
|
54
|
+
*/
|
|
55
|
+
export declare function deleteSkillFile(name: string, source: ImportTarget, workspacePath?: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Fetch a URL's content for import. Markdown/plain responses pass through
|
|
58
|
+
* verbatim; HTML is roughly extracted to text (the import UI recommends
|
|
59
|
+
* `.md` sources).
|
|
60
|
+
* @param url - the source URL.
|
|
61
|
+
* @returns the text to write as the skill body.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fetchUrlContent(url: string): Promise<string>;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve one import request into a written file path. Shared by the file
|
|
66
|
+
* and URL routes; URL imports fetch first, then reuse the same write path.
|
|
67
|
+
* @param request - validated import body.
|
|
68
|
+
* @returns the absolute path of the written SKILL.md.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveImport(request: ImportRequest | ImportUrlRequest): Promise<string>;
|
|
71
|
+
/** Read and parse a JSON request body within a byte cap. */
|
|
72
|
+
export declare function readJsonBody(req: IncomingMessage, limit?: number): Promise<unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* Origin fence: the routes are served on the harness's loopback-only web
|
|
75
|
+
* server, so only the browser page itself (or a local curl) reaches them.
|
|
76
|
+
* A cross-origin page (any other website) is refused. Requests without an
|
|
77
|
+
* Origin header (curl, same-origin GET) pass.
|
|
78
|
+
*/
|
|
79
|
+
export declare function originAllowed(req: IncomingMessage): boolean;
|
|
80
|
+
/** Send one JSON response. */
|
|
81
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
|
|
82
|
+
/** Send a JSON error response with the given status. */
|
|
83
|
+
export declare function sendError(res: ServerResponse, status: number, error: string): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types between the host half (HTTP routes) and the client half
|
|
3
|
+
* (fetch calls). Pure types — no runtime code, no node imports — so the
|
|
4
|
+
* browser bundle can reference them safely.
|
|
5
|
+
*/
|
|
6
|
+
/** Where the imported skill file should land. */
|
|
7
|
+
export type ImportTarget = 'user' | 'project-agents' | 'project-dsh';
|
|
8
|
+
/** One installed skill row served by `/skill-importer/list`. */
|
|
9
|
+
export interface SkillListEntry {
|
|
10
|
+
/** Kebab-case skill name (frontmatter `name`). */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** One-line routing description (frontmatter `description`). */
|
|
13
|
+
readonly description: string;
|
|
14
|
+
/** Optional routing guidance (frontmatter `whenToUse`). */
|
|
15
|
+
readonly whenToUse?: string;
|
|
16
|
+
/** False marks a user-only skill (`disable-model-invocation`). */
|
|
17
|
+
readonly modelInvocable: boolean;
|
|
18
|
+
/** False marks a model-only skill (`user-invocable: false`). */
|
|
19
|
+
readonly userInvocable: boolean;
|
|
20
|
+
/** Winning discovery root ('project-dsh' | 'project-agents' | 'user'). */
|
|
21
|
+
readonly source: ImportTarget;
|
|
22
|
+
}
|
|
23
|
+
/** File-import request body. */
|
|
24
|
+
export interface ImportRequest {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly target: ImportTarget;
|
|
27
|
+
/** Full Markdown skill file text (frontmatter included). */
|
|
28
|
+
readonly content: string;
|
|
29
|
+
/** Canonical workspace path for project targets (host-validated against the registry). */
|
|
30
|
+
readonly workspacePath?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Delete-request body. */
|
|
33
|
+
export interface DeleteRequest {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
/** Which root the copy lives in ('user' | 'project-agents' | 'project-dsh'). */
|
|
36
|
+
readonly source: ImportTarget;
|
|
37
|
+
/** Canonical workspace path for project sources (host-validated). */
|
|
38
|
+
readonly workspacePath?: string;
|
|
39
|
+
}
|
|
40
|
+
/** URL-import request body. */
|
|
41
|
+
export interface ImportUrlRequest {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly target: ImportTarget;
|
|
44
|
+
/** Source URL the host fetches (`.md` preferred; HTML is roughly extracted). */
|
|
45
|
+
readonly url: string;
|
|
46
|
+
/** Canonical workspace path for project targets (host-validated against the registry). */
|
|
47
|
+
readonly workspacePath?: string;
|
|
48
|
+
}
|
|
49
|
+
/** `/skill-importer/list` response. */
|
|
50
|
+
export interface SkillListResponse {
|
|
51
|
+
readonly ok: true;
|
|
52
|
+
readonly skills: readonly SkillListEntry[];
|
|
53
|
+
}
|
|
54
|
+
/** Success response of an import. */
|
|
55
|
+
export interface ImportResponse {
|
|
56
|
+
readonly ok: true;
|
|
57
|
+
/** Absolute path of the written SKILL.md. */
|
|
58
|
+
readonly path: string;
|
|
59
|
+
}
|
|
60
|
+
/** Error response of any route. */
|
|
61
|
+
export interface ErrorResponse {
|
|
62
|
+
readonly ok: false;
|
|
63
|
+
readonly error: string;
|
|
64
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-skill-importer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web UI plugin for DeepSeek Harness: import skills from local Markdown files or URLs into the skill roots the harness discovers automatically",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/types/client/index.d.ts",
|
|
15
|
+
"default": "./lib/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"dsh": {
|
|
20
|
+
"client": {
|
|
21
|
+
"inject": [
|
|
22
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
23
|
+
"@deepseek-ai/dsh-client-connection",
|
|
24
|
+
"@deepseek-ai/dsh-client-locale",
|
|
25
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
27
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
28
|
+
],
|
|
29
|
+
"platform": "web"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"lib/index.js",
|
|
34
|
+
"lib/client.js",
|
|
35
|
+
"lib/types/**/*.d.ts"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "rm -rf lib && tsc -p tsconfig.json && tsdown",
|
|
39
|
+
"watch": "tsdown --watch"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
44
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
|
|
45
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
46
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
47
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
50
|
+
"react": "^18.2.0",
|
|
51
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
52
|
+
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6",
|
|
53
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
|
|
54
|
+
"@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.0-rc.6"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
|
|
61
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
62
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
63
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
66
|
+
"@types/react": "^18.3.0",
|
|
67
|
+
"react": "^18.2.0",
|
|
68
|
+
"tsdown": "^0.22.2",
|
|
69
|
+
"typescript": "^6.0.3",
|
|
70
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
71
|
+
"@types/node": "^24.0.0",
|
|
72
|
+
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6",
|
|
73
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
|
|
74
|
+
"@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.0-rc.6"
|
|
77
|
+
},
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "git+https://github.com/saitamahang/dsh-skill-importer.git"
|
|
81
|
+
},
|
|
82
|
+
"keywords": [
|
|
83
|
+
"dsh-plugin",
|
|
84
|
+
"deepseek-harness",
|
|
85
|
+
"dsh",
|
|
86
|
+
"skill",
|
|
87
|
+
"agent",
|
|
88
|
+
"plugin"
|
|
89
|
+
]
|
|
90
|
+
}
|