dsh-skill-importer 0.2.3 → 0.2.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/README.md +20 -8
- package/README.zh.md +19 -7
- package/lib/client.js +715 -73
- package/lib/index.js +199 -41
- package/lib/types/client/SkillImporterSection.d.ts +1 -1
- package/lib/types/client/SkillsPicker.d.ts +1 -1
- package/lib/types/client/effectiveSkills.d.ts +3 -0
- package/lib/types/client/index.d.ts +11 -3
- package/lib/types/client/locales.d.ts +80 -2
- package/lib/types/index.d.ts +1 -0
- package/lib/types/server.d.ts +6 -2
- package/lib/types/types.d.ts +23 -3
- package/lib/types/update.d.ts +6 -0
- package/package.json +4 -1
- package/src/server.ts +85 -37
- package/src/types.ts +25 -3
- package/tests/batch-import.test.mjs +5 -5
- package/tests/effective-skills.test.mjs +25 -0
- package/tests/skill-list.test.mjs +50 -0
- package/tests/update.test.mjs +22 -0
package/lib/index.js
CHANGED
|
@@ -107,17 +107,29 @@ function dshHomeDir() {
|
|
|
107
107
|
/** Absolute skill root for one target under one workspace. */
|
|
108
108
|
function skillRoot(target, workspacePath) {
|
|
109
109
|
switch (target) {
|
|
110
|
-
case "
|
|
110
|
+
case "project-dsh": return join(workspacePath, ".dsh", "skills");
|
|
111
111
|
case "project-agents": return join(workspacePath, ".agents", "skills");
|
|
112
|
+
case "user-dsh": return join(dshHomeDir(), "skills");
|
|
113
|
+
case "user-agents": return join(process.env.DSH_AGENTS_HOME ?? join(homedir(), ".agents"), "skills");
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
/** Discovery rank per root (lower wins), mirroring dsh-skill-filesystem. */
|
|
115
117
|
const ROOT_RANK = {
|
|
118
|
+
"project-dsh": 100,
|
|
116
119
|
"project-agents": 200,
|
|
117
|
-
user: 400
|
|
120
|
+
"user-dsh": 400,
|
|
121
|
+
"user-agents": 500
|
|
118
122
|
};
|
|
119
|
-
/**
|
|
120
|
-
const SCAN_ORDER = [
|
|
123
|
+
/** Official default-root order (custom rank 300 is not a stable import destination). */
|
|
124
|
+
const SCAN_ORDER = [
|
|
125
|
+
"project-dsh",
|
|
126
|
+
"project-agents",
|
|
127
|
+
"user-dsh",
|
|
128
|
+
"user-agents"
|
|
129
|
+
];
|
|
130
|
+
function isProjectTarget(target) {
|
|
131
|
+
return target === "project-dsh" || target === "project-agents";
|
|
132
|
+
}
|
|
121
133
|
/**
|
|
122
134
|
* Quote one frontmatter string value when plain YAML would mis-parse it:
|
|
123
135
|
* a value containing `: ` (or any colon), leading/trailing whitespace, a
|
|
@@ -163,7 +175,7 @@ function normalizeSkillText(text) {
|
|
|
163
175
|
function writeSkillFile(name, target, content, workspacePath) {
|
|
164
176
|
if (!KEBAB_CASE.test(name)) throw new Error("技能名必须是 kebab-case(小写字母、数字、短横线)");
|
|
165
177
|
if (Buffer.byteLength(content, "utf8") > 262144) throw new Error(`内容超过 ${MAX_CONTENT_BYTES / 1024} KB`);
|
|
166
|
-
if (target
|
|
178
|
+
if (isProjectTarget(target) && workspacePath === void 0) throw new Error("项目目标需要 workspacePath(当前工作区路径)");
|
|
167
179
|
const normalized = normalizeSkillText(content);
|
|
168
180
|
const directory = join(skillRoot(target, workspacePath ?? ""), name);
|
|
169
181
|
mkdirSync(directory, { recursive: true });
|
|
@@ -174,28 +186,47 @@ function writeSkillFile(name, target, content, workspacePath) {
|
|
|
174
186
|
return file;
|
|
175
187
|
}
|
|
176
188
|
/** Scan one skill root and fold its skills into the name-keyed map (rank-aware). */
|
|
177
|
-
function scanRoot(root, source, out) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
189
|
+
function scanRoot(root, source, workspacePath, out, issues) {
|
|
190
|
+
try {
|
|
191
|
+
if (!existsSync(root)) return;
|
|
192
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
193
|
+
const entryPath = join(root, entry.name);
|
|
194
|
+
let file;
|
|
195
|
+
if (entry.isDirectory()) {
|
|
196
|
+
const candidate = join(entryPath, "SKILL.md");
|
|
197
|
+
if (existsSync(candidate)) file = candidate;
|
|
198
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) file = entryPath;
|
|
199
|
+
if (file === void 0) continue;
|
|
200
|
+
try {
|
|
201
|
+
const { frontmatter } = parseSkillFile(readFileSync(file, "utf8"));
|
|
202
|
+
if (frontmatter.name === void 0 || frontmatter.description === void 0) continue;
|
|
203
|
+
if (!isValidSkillName(frontmatter.name)) continue;
|
|
204
|
+
const existing = out.get(frontmatter.name);
|
|
205
|
+
if (existing !== void 0 && ROOT_RANK[existing.source] <= ROOT_RANK[source]) continue;
|
|
206
|
+
out.set(frontmatter.name, {
|
|
207
|
+
name: frontmatter.name,
|
|
208
|
+
description: frontmatter.description,
|
|
209
|
+
...frontmatter.whenToUse !== void 0 ? { whenToUse: frontmatter.whenToUse } : {},
|
|
210
|
+
modelInvocable: frontmatter.disableModelInvocation !== true,
|
|
211
|
+
userInvocable: frontmatter.userInvocable !== false,
|
|
212
|
+
source,
|
|
213
|
+
...workspacePath === void 0 ? {} : { workspacePath }
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
issues.push({
|
|
217
|
+
source,
|
|
218
|
+
path: file,
|
|
219
|
+
...workspacePath === void 0 ? {} : { workspacePath },
|
|
220
|
+
message: error instanceof Error ? error.message : String(error)
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
issues.push({
|
|
226
|
+
source,
|
|
227
|
+
path: root,
|
|
228
|
+
...workspacePath === void 0 ? {} : { workspacePath },
|
|
229
|
+
message: error instanceof Error ? error.message : String(error)
|
|
199
230
|
});
|
|
200
231
|
}
|
|
201
232
|
}
|
|
@@ -208,12 +239,24 @@ function scanRoot(root, source, out) {
|
|
|
208
239
|
*/
|
|
209
240
|
function listSkills(workspacePaths) {
|
|
210
241
|
const rows = [];
|
|
242
|
+
const issues = [];
|
|
211
243
|
for (const target of SCAN_ORDER) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
244
|
+
if (!isProjectTarget(target)) {
|
|
245
|
+
const out = /* @__PURE__ */ new Map();
|
|
246
|
+
scanRoot(skillRoot(target, ""), target, void 0, out, issues);
|
|
247
|
+
rows.push(...out.values());
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
for (const workspacePath of workspacePaths) {
|
|
251
|
+
const out = /* @__PURE__ */ new Map();
|
|
252
|
+
scanRoot(skillRoot(target, workspacePath), target, workspacePath, out, issues);
|
|
253
|
+
rows.push(...out.values());
|
|
254
|
+
}
|
|
215
255
|
}
|
|
216
|
-
return
|
|
256
|
+
return {
|
|
257
|
+
skills: rows.sort((a, b) => a.name.localeCompare(b.name)),
|
|
258
|
+
issues
|
|
259
|
+
};
|
|
217
260
|
}
|
|
218
261
|
/**
|
|
219
262
|
* Delete one installed skill (its whole bundle directory `<root>/<name>/`,
|
|
@@ -225,7 +268,7 @@ function listSkills(workspacePaths) {
|
|
|
225
268
|
*/
|
|
226
269
|
function deleteSkillFile(name, source, workspacePath) {
|
|
227
270
|
if (!KEBAB_CASE.test(name)) throw new Error("技能名必须是 kebab-case(小写字母、数字、短横线)");
|
|
228
|
-
if (source
|
|
271
|
+
if (isProjectTarget(source) && workspacePath === void 0) throw new Error("项目来源需要 workspacePath(当前工作区路径)");
|
|
229
272
|
const root = skillRoot(source, workspacePath ?? "");
|
|
230
273
|
const directory = join(root, name);
|
|
231
274
|
if (existsSync(directory)) {
|
|
@@ -308,7 +351,7 @@ function scanBatch(request) {
|
|
|
308
351
|
const leaf = basename(sourcePath).toLowerCase();
|
|
309
352
|
const parent = basename(dirname(sourcePath)).toLowerCase();
|
|
310
353
|
if (!(leaf === "skills") && !(parent === "skills")) throw new Error("批量导入仅支持名为 skills 的目录或其中的单个技能目录");
|
|
311
|
-
if (request.target
|
|
354
|
+
if (isProjectTarget(request.target) && request.workspacePath === void 0) throw new Error("项目目标需要 workspacePath(当前工作区路径)");
|
|
312
355
|
const sources = batchSources(sourcePath);
|
|
313
356
|
if (sources.length === 0) throw new Error("所选目录中没有找到可导入的技能");
|
|
314
357
|
if (sources.length > 200) throw new Error(`一次最多扫描 200 个技能`);
|
|
@@ -621,6 +664,80 @@ function sendError(res, status, error) {
|
|
|
621
664
|
});
|
|
622
665
|
}
|
|
623
666
|
//#endregion
|
|
667
|
+
//#region src/update.ts
|
|
668
|
+
const REGISTRY_URL = "https://registry.npmjs.org/dsh-skill-importer/latest";
|
|
669
|
+
const CACHE_TTL_MS = 216e5;
|
|
670
|
+
const REQUEST_TIMEOUT_MS = 5e3;
|
|
671
|
+
let cachedLatest;
|
|
672
|
+
function parseVersion(value) {
|
|
673
|
+
const match = /^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
674
|
+
if (match === null) return void 0;
|
|
675
|
+
const prerelease = match[4] === void 0 ? [] : match[4].split(".").map((part) => /^\d+$/.test(part) ? Number(part) : part);
|
|
676
|
+
return {
|
|
677
|
+
core: [
|
|
678
|
+
Number(match[1]),
|
|
679
|
+
Number(match[2]),
|
|
680
|
+
Number(match[3])
|
|
681
|
+
],
|
|
682
|
+
prerelease
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
/** SemVer ordering used to avoid offering a downgrade or an older prerelease. */
|
|
686
|
+
function compareVersions(left, right) {
|
|
687
|
+
const a = parseVersion(left);
|
|
688
|
+
const b = parseVersion(right);
|
|
689
|
+
if (a === void 0 || b === void 0) throw new Error("npm 返回了无效版本号");
|
|
690
|
+
for (const index of [
|
|
691
|
+
0,
|
|
692
|
+
1,
|
|
693
|
+
2
|
|
694
|
+
]) if (a.core[index] !== b.core[index]) return a.core[index] < b.core[index] ? -1 : 1;
|
|
695
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
696
|
+
if (a.prerelease.length === b.prerelease.length) return 0;
|
|
697
|
+
return a.prerelease.length === 0 ? 1 : -1;
|
|
698
|
+
}
|
|
699
|
+
const length = Math.max(a.prerelease.length, b.prerelease.length);
|
|
700
|
+
for (let index = 0; index < length; index += 1) {
|
|
701
|
+
const av = a.prerelease[index];
|
|
702
|
+
const bv = b.prerelease[index];
|
|
703
|
+
if (av === void 0) return -1;
|
|
704
|
+
if (bv === void 0) return 1;
|
|
705
|
+
if (av === bv) continue;
|
|
706
|
+
if (typeof av === "number" && typeof bv === "string") return -1;
|
|
707
|
+
if (typeof av === "string" && typeof bv === "number") return 1;
|
|
708
|
+
return av < bv ? -1 : 1;
|
|
709
|
+
}
|
|
710
|
+
return 0;
|
|
711
|
+
}
|
|
712
|
+
/** Read from package metadata in both src and built lib layouts. */
|
|
713
|
+
function currentVersion() {
|
|
714
|
+
const metadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
715
|
+
if (typeof metadata.version !== "string" || parseVersion(metadata.version) === void 0) throw new Error("package.json 缺少有效版本号");
|
|
716
|
+
return metadata.version;
|
|
717
|
+
}
|
|
718
|
+
async function latestVersion(now = Date.now()) {
|
|
719
|
+
if (cachedLatest !== void 0 && cachedLatest.expiresAt > now) return cachedLatest.version;
|
|
720
|
+
const response = await fetch(REGISTRY_URL, {
|
|
721
|
+
headers: {
|
|
722
|
+
accept: "application/json",
|
|
723
|
+
"user-agent": "dsh-skill-importer-update-check"
|
|
724
|
+
},
|
|
725
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
726
|
+
});
|
|
727
|
+
if (!response.ok) throw new Error(`npm registry 返回 ${response.status}`);
|
|
728
|
+
const metadata = await response.json();
|
|
729
|
+
if (typeof metadata.version !== "string" || parseVersion(metadata.version) === void 0) throw new Error("npm registry 返回了无效版本信息");
|
|
730
|
+
cachedLatest = {
|
|
731
|
+
version: metadata.version,
|
|
732
|
+
expiresAt: now + CACHE_TTL_MS
|
|
733
|
+
};
|
|
734
|
+
return metadata.version;
|
|
735
|
+
}
|
|
736
|
+
function updateCommand(version) {
|
|
737
|
+
if (parseVersion(version) === void 0) throw new Error("无法为无效版本生成更新命令");
|
|
738
|
+
return `npx @deepseek-ai/dsh plugin --profile web add dsh-skill-importer@${version}`;
|
|
739
|
+
}
|
|
740
|
+
//#endregion
|
|
624
741
|
//#region src/index.ts
|
|
625
742
|
/** Route paths this plugin owns (exact matches; the client fetches the same literals). */
|
|
626
743
|
const ROUTES = {
|
|
@@ -630,10 +747,20 @@ const ROUTES = {
|
|
|
630
747
|
importUrl: "/skill-importer/import-url",
|
|
631
748
|
delete: "/skill-importer/delete",
|
|
632
749
|
batchScan: "/skill-importer/batch/scan",
|
|
633
|
-
batchCommit: "/skill-importer/batch/commit"
|
|
750
|
+
batchCommit: "/skill-importer/batch/commit",
|
|
751
|
+
updateStatus: "/skill-importer/update-status"
|
|
634
752
|
};
|
|
635
753
|
/** Required services: the harness web server's route registry and the workspace registry. */
|
|
636
754
|
const inject = ["webServer", "workspaceRegistry"];
|
|
755
|
+
const IMPORT_TARGETS = /* @__PURE__ */ new Set([
|
|
756
|
+
"project-dsh",
|
|
757
|
+
"project-agents",
|
|
758
|
+
"user-dsh",
|
|
759
|
+
"user-agents"
|
|
760
|
+
]);
|
|
761
|
+
function isImportTarget(value) {
|
|
762
|
+
return typeof value === "string" && IMPORT_TARGETS.has(value);
|
|
763
|
+
}
|
|
637
764
|
const handle = (fn) => (req, res) => {
|
|
638
765
|
fn(req, res).catch((error) => {
|
|
639
766
|
if (res.writableEnded) return;
|
|
@@ -660,13 +787,40 @@ function apply(ctx) {
|
|
|
660
787
|
{
|
|
661
788
|
kind: "exact",
|
|
662
789
|
path: ROUTES.list,
|
|
663
|
-
handler: handle(async (
|
|
790
|
+
handler: handle(async (req, res) => {
|
|
791
|
+
const url = new URL(req.url ?? ROUTES.list, "http://localhost");
|
|
792
|
+
const requested = url.searchParams.get("workspacePath");
|
|
664
793
|
sendJson(res, 200, {
|
|
665
794
|
ok: true,
|
|
666
|
-
|
|
795
|
+
...listSkills(url.searchParams.get("scope") === "global" ? [] : requested === null ? projectPaths() : [requireRegisteredWorkspace(requested)])
|
|
667
796
|
});
|
|
668
797
|
})
|
|
669
798
|
},
|
|
799
|
+
{
|
|
800
|
+
kind: "exact",
|
|
801
|
+
path: ROUTES.updateStatus,
|
|
802
|
+
handler: handle(async (_req, res) => {
|
|
803
|
+
const installed = currentVersion();
|
|
804
|
+
try {
|
|
805
|
+
const latest = await latestVersion();
|
|
806
|
+
const updateAvailable = compareVersions(installed, latest) < 0;
|
|
807
|
+
sendJson(res, 200, {
|
|
808
|
+
ok: true,
|
|
809
|
+
currentVersion: installed,
|
|
810
|
+
latestVersion: latest,
|
|
811
|
+
updateAvailable,
|
|
812
|
+
command: updateAvailable ? updateCommand(latest) : void 0
|
|
813
|
+
});
|
|
814
|
+
} catch (error) {
|
|
815
|
+
sendJson(res, 200, {
|
|
816
|
+
ok: true,
|
|
817
|
+
currentVersion: installed,
|
|
818
|
+
updateAvailable: false,
|
|
819
|
+
error: error instanceof Error ? error.message : String(error)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
})
|
|
823
|
+
},
|
|
670
824
|
{
|
|
671
825
|
kind: "exact",
|
|
672
826
|
path: ROUTES.import,
|
|
@@ -676,7 +830,7 @@ function apply(ctx) {
|
|
|
676
830
|
return;
|
|
677
831
|
}
|
|
678
832
|
const body = await readJsonBody(req);
|
|
679
|
-
if (typeof body.name !== "string" || typeof body.content !== "string" || body.target
|
|
833
|
+
if (typeof body.name !== "string" || typeof body.content !== "string" || !isImportTarget(body.target)) {
|
|
680
834
|
sendError(res, 400, "请求缺少 name / content / target 字段");
|
|
681
835
|
return;
|
|
682
836
|
}
|
|
@@ -701,7 +855,7 @@ function apply(ctx) {
|
|
|
701
855
|
return;
|
|
702
856
|
}
|
|
703
857
|
const body = await readJsonBody(req);
|
|
704
|
-
if (typeof body.name !== "string" || typeof body.url !== "string" || body.target
|
|
858
|
+
if (typeof body.name !== "string" || typeof body.url !== "string" || !isImportTarget(body.target)) {
|
|
705
859
|
sendError(res, 400, "请求缺少 name / url / target 字段");
|
|
706
860
|
return;
|
|
707
861
|
}
|
|
@@ -726,7 +880,7 @@ function apply(ctx) {
|
|
|
726
880
|
return;
|
|
727
881
|
}
|
|
728
882
|
const body = await readJsonBody(req);
|
|
729
|
-
if (typeof body.name !== "string" || body.source
|
|
883
|
+
if (typeof body.name !== "string" || !isImportTarget(body.source)) {
|
|
730
884
|
sendError(res, 400, "请求缺少 name / source 字段");
|
|
731
885
|
return;
|
|
732
886
|
}
|
|
@@ -746,7 +900,7 @@ function apply(ctx) {
|
|
|
746
900
|
return;
|
|
747
901
|
}
|
|
748
902
|
const body = await readJsonBody(req);
|
|
749
|
-
if (typeof body.sourcePath !== "string" || body.target
|
|
903
|
+
if (typeof body.sourcePath !== "string" || !isImportTarget(body.target)) {
|
|
750
904
|
sendError(res, 400, "请求缺少 sourcePath / target 字段");
|
|
751
905
|
return;
|
|
752
906
|
}
|
|
@@ -806,11 +960,15 @@ function apply(ctx) {
|
|
|
806
960
|
* from the host process cwd, which may differ from the user's workspace).
|
|
807
961
|
*/
|
|
808
962
|
function requireWorkspace(target, candidate) {
|
|
809
|
-
if (target
|
|
963
|
+
if (!isProjectTarget(target)) return void 0;
|
|
810
964
|
if (typeof candidate !== "string") throw new Error("项目目标需要 workspacePath(当前工作区路径)");
|
|
811
965
|
if (!projectPaths().includes(candidate)) throw new Error("workspacePath 不是已注册的工作区");
|
|
812
966
|
return candidate;
|
|
813
967
|
}
|
|
968
|
+
function requireRegisteredWorkspace(candidate) {
|
|
969
|
+
if (!projectPaths().includes(candidate)) throw new Error("workspacePath 不是已注册的工作区");
|
|
970
|
+
return candidate;
|
|
971
|
+
}
|
|
814
972
|
ctx.effect(() => {
|
|
815
973
|
const disposers = routes.map((route) => ctx.webServer.register(route));
|
|
816
974
|
return () => {
|
|
@@ -13,4 +13,4 @@ import type { SkillImporterInjected } from './index.ts';
|
|
|
13
13
|
/** Props the renderer binds for the section. */
|
|
14
14
|
export type SkillImporterProps = PropsRuntime<'settings.section'> & PropsLocale<'skills.importer'> & InjectFace<SkillImporterInjected>;
|
|
15
15
|
/** The settings section's page body. */
|
|
16
|
-
export declare function SkillImporterSection({ t, useSkills, useWorkspaces, actions }: SkillImporterProps): ReactNode;
|
|
16
|
+
export declare function SkillImporterSection({ t, useSkills, useSkillIssues, useWorkspaces, actions }: SkillImporterProps): ReactNode;
|
|
@@ -13,4 +13,4 @@ import type { SkillImporterInjected } from './index.ts';
|
|
|
13
13
|
/** Props the renderer binds for the picker. */
|
|
14
14
|
export type SkillsPickerProps = PropsRuntime<'conversation.input.left'> & PropsLocale<'skills.importer'> & InjectFace<SkillImporterInjected>;
|
|
15
15
|
/** One row of the composer skill picker. */
|
|
16
|
-
export declare function SkillsPicker({ t, sessionId,
|
|
16
|
+
export declare function SkillsPicker({ t, sessionId, useInput, inputActions, actions }: SkillsPickerProps): ReactNode;
|
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-api-remotes/client';
|
|
2
3
|
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
4
|
import type { SkillImporterProps } from './SkillImporterSection.tsx';
|
|
4
5
|
import type { SkillsPickerProps } from './SkillsPicker.tsx';
|
|
5
6
|
import { type SkillImporterKey } from './locales.ts';
|
|
6
|
-
import type { BatchCommitRequest, BatchCommitResponse, BatchScanRequest, BatchScanResponse, DeleteRequest, ImportRequest, ImportUrlRequest, SkillListResponse } from '../types.ts';
|
|
7
|
+
import type { BatchCommitRequest, BatchCommitResponse, BatchScanRequest, BatchScanResponse, DeleteRequest, ImportRequest, ImportUrlRequest, SkillListResponse, UpdateStatusResponse } from '../types.ts';
|
|
7
8
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
8
9
|
interface LocaleNamespaceMap {
|
|
9
10
|
/** The skill importer section's copy. */
|
|
10
11
|
'skills.importer': SkillImporterKey;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
|
-
/** Installed skill catalog snapshot
|
|
14
|
+
/** Installed skill catalog snapshot used by the all-workspace settings view. */
|
|
14
15
|
export type SkillCatalogSnapshot = SkillListResponse["skills"];
|
|
16
|
+
export type SkillIssueSnapshot = SkillListResponse["issues"];
|
|
15
17
|
/** Registration-side business face for the section. */
|
|
16
18
|
export interface SkillImporterInjected {
|
|
17
19
|
hooks: {
|
|
18
20
|
/** Last successful `/skill-importer/list` result. */
|
|
19
21
|
skills: HostObservable<SkillCatalogSnapshot>;
|
|
22
|
+
/** Non-fatal filesystem failures from the last catalog refresh. */
|
|
23
|
+
skillIssues: HostObservable<SkillIssueSnapshot>;
|
|
20
24
|
};
|
|
21
25
|
actions: {
|
|
22
26
|
/** Re-fetch the installed catalog; resolves to the fresh rows. */
|
|
23
|
-
refreshSkills: () => Promise<SkillCatalogSnapshot>;
|
|
27
|
+
refreshSkills: (workspacePath?: string) => Promise<SkillCatalogSnapshot>;
|
|
28
|
+
/** Load the effective catalog for one conversation without mutating the settings catalog. */
|
|
29
|
+
refreshSkillsForSession: (sessionId: SessionId) => Promise<SkillCatalogSnapshot>;
|
|
24
30
|
/** Write one skill file via the host route; resolves to the written path. */
|
|
25
31
|
importSkill: (request: ImportRequest) => Promise<string>;
|
|
26
32
|
/** Fetch a URL and write the skill via the host route; resolves to the written path. */
|
|
@@ -33,6 +39,8 @@ export interface SkillImporterInjected {
|
|
|
33
39
|
scanBatch: (request: BatchScanRequest) => Promise<BatchScanResponse>;
|
|
34
40
|
/** Commit one preflight once, replacing only explicitly confirmed names. */
|
|
35
41
|
commitBatch: (request: BatchCommitRequest) => Promise<BatchCommitResponse>;
|
|
42
|
+
/** Return the installed version and the latest npm version when reachable. */
|
|
43
|
+
checkForUpdates: () => Promise<UpdateStatusResponse>;
|
|
36
44
|
};
|
|
37
45
|
}
|
|
38
46
|
/** Required services (cordis fiber inject). */
|
|
@@ -13,7 +13,12 @@ export declare const zh: {
|
|
|
13
13
|
pickerLabel: string;
|
|
14
14
|
pickerPlaceholder: string;
|
|
15
15
|
pickerSearchPlaceholder: string;
|
|
16
|
+
pickerLoading: string;
|
|
16
17
|
pickerEmpty: string;
|
|
18
|
+
pickerSourceProjectDsh: string;
|
|
19
|
+
pickerSourceProjectAgents: string;
|
|
20
|
+
pickerSourceUserDsh: string;
|
|
21
|
+
pickerSourceUserAgents: string;
|
|
17
22
|
commandDescription: string;
|
|
18
23
|
entryTitle: string;
|
|
19
24
|
entryDescription: string;
|
|
@@ -29,6 +34,7 @@ export declare const zh: {
|
|
|
29
34
|
deleteFailed: string;
|
|
30
35
|
deleted: string;
|
|
31
36
|
installedFailed: string;
|
|
37
|
+
installedPartial: string;
|
|
32
38
|
userOnly: string;
|
|
33
39
|
modelOnly: string;
|
|
34
40
|
importTitle: string;
|
|
@@ -79,8 +85,29 @@ export declare const zh: {
|
|
|
79
85
|
batchStatusSkipped: string;
|
|
80
86
|
batchStatusError: string;
|
|
81
87
|
targetLabel: string;
|
|
82
|
-
|
|
88
|
+
targetProjectDsh: string;
|
|
83
89
|
targetProjectAgents: string;
|
|
90
|
+
targetUserDsh: string;
|
|
91
|
+
targetUserAgents: string;
|
|
92
|
+
locationChange: string;
|
|
93
|
+
locationTitle: string;
|
|
94
|
+
locationProject: string;
|
|
95
|
+
locationGlobal: string;
|
|
96
|
+
workspaceSearch: string;
|
|
97
|
+
workspaceNoMatch: string;
|
|
98
|
+
chooseRootHint: string;
|
|
99
|
+
rootProjectDsh: string;
|
|
100
|
+
rootProjectAgents: string;
|
|
101
|
+
rootUserDsh: string;
|
|
102
|
+
rootUserAgents: string;
|
|
103
|
+
priorityHighest: string;
|
|
104
|
+
priorityHigher: string;
|
|
105
|
+
priorityLower: string;
|
|
106
|
+
priorityLowest: string;
|
|
107
|
+
locationSummaryProjectDsh: string;
|
|
108
|
+
locationSummaryProjectAgents: string;
|
|
109
|
+
locationSummaryUserDsh: string;
|
|
110
|
+
locationSummaryUserAgents: string;
|
|
84
111
|
targetProjectNoWorkspace: string;
|
|
85
112
|
noWorkspace: string;
|
|
86
113
|
import: string;
|
|
@@ -88,6 +115,18 @@ export declare const zh: {
|
|
|
88
115
|
imported: string;
|
|
89
116
|
importFailed: string;
|
|
90
117
|
hostUnreachable: string;
|
|
118
|
+
versionChecking: string;
|
|
119
|
+
versionCurrent: string;
|
|
120
|
+
versionLatest: string;
|
|
121
|
+
versionAvailable: string;
|
|
122
|
+
versionCheckFailed: string;
|
|
123
|
+
versionRetry: string;
|
|
124
|
+
versionUpdate: string;
|
|
125
|
+
updateTitle: string;
|
|
126
|
+
updateDescription: string;
|
|
127
|
+
copyCommand: string;
|
|
128
|
+
commandCopied: string;
|
|
129
|
+
restartHint: string;
|
|
91
130
|
};
|
|
92
131
|
/** The skill importer namespace key union. */
|
|
93
132
|
export type SkillImporterKey = keyof typeof zh;
|
|
@@ -97,7 +136,12 @@ export declare const en: {
|
|
|
97
136
|
pickerLabel: string;
|
|
98
137
|
pickerPlaceholder: string;
|
|
99
138
|
pickerSearchPlaceholder: string;
|
|
139
|
+
pickerLoading: string;
|
|
100
140
|
pickerEmpty: string;
|
|
141
|
+
pickerSourceProjectDsh: string;
|
|
142
|
+
pickerSourceProjectAgents: string;
|
|
143
|
+
pickerSourceUserDsh: string;
|
|
144
|
+
pickerSourceUserAgents: string;
|
|
101
145
|
commandDescription: string;
|
|
102
146
|
entryTitle: string;
|
|
103
147
|
entryDescription: string;
|
|
@@ -113,6 +157,7 @@ export declare const en: {
|
|
|
113
157
|
deleteFailed: string;
|
|
114
158
|
deleted: string;
|
|
115
159
|
installedFailed: string;
|
|
160
|
+
installedPartial: string;
|
|
116
161
|
userOnly: string;
|
|
117
162
|
modelOnly: string;
|
|
118
163
|
importTitle: string;
|
|
@@ -163,8 +208,29 @@ export declare const en: {
|
|
|
163
208
|
batchStatusSkipped: string;
|
|
164
209
|
batchStatusError: string;
|
|
165
210
|
targetLabel: string;
|
|
166
|
-
|
|
211
|
+
targetProjectDsh: string;
|
|
167
212
|
targetProjectAgents: string;
|
|
213
|
+
targetUserDsh: string;
|
|
214
|
+
targetUserAgents: string;
|
|
215
|
+
locationChange: string;
|
|
216
|
+
locationTitle: string;
|
|
217
|
+
locationProject: string;
|
|
218
|
+
locationGlobal: string;
|
|
219
|
+
workspaceSearch: string;
|
|
220
|
+
workspaceNoMatch: string;
|
|
221
|
+
chooseRootHint: string;
|
|
222
|
+
rootProjectDsh: string;
|
|
223
|
+
rootProjectAgents: string;
|
|
224
|
+
rootUserDsh: string;
|
|
225
|
+
rootUserAgents: string;
|
|
226
|
+
priorityHighest: string;
|
|
227
|
+
priorityHigher: string;
|
|
228
|
+
priorityLower: string;
|
|
229
|
+
priorityLowest: string;
|
|
230
|
+
locationSummaryProjectDsh: string;
|
|
231
|
+
locationSummaryProjectAgents: string;
|
|
232
|
+
locationSummaryUserDsh: string;
|
|
233
|
+
locationSummaryUserAgents: string;
|
|
168
234
|
targetProjectNoWorkspace: string;
|
|
169
235
|
noWorkspace: string;
|
|
170
236
|
import: string;
|
|
@@ -172,4 +238,16 @@ export declare const en: {
|
|
|
172
238
|
imported: string;
|
|
173
239
|
importFailed: string;
|
|
174
240
|
hostUnreachable: string;
|
|
241
|
+
versionChecking: string;
|
|
242
|
+
versionCurrent: string;
|
|
243
|
+
versionLatest: string;
|
|
244
|
+
versionAvailable: string;
|
|
245
|
+
versionCheckFailed: string;
|
|
246
|
+
versionRetry: string;
|
|
247
|
+
versionUpdate: string;
|
|
248
|
+
updateTitle: string;
|
|
249
|
+
updateDescription: string;
|
|
250
|
+
copyCommand: string;
|
|
251
|
+
commandCopied: string;
|
|
252
|
+
restartHint: string;
|
|
175
253
|
};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare const ROUTES: {
|
|
|
28
28
|
readonly delete: "/skill-importer/delete";
|
|
29
29
|
readonly batchScan: "/skill-importer/batch/scan";
|
|
30
30
|
readonly batchCommit: "/skill-importer/batch/commit";
|
|
31
|
+
readonly updateStatus: "/skill-importer/update-status";
|
|
31
32
|
};
|
|
32
33
|
/** Required services: the harness web server's route registry and the workspace registry. */
|
|
33
34
|
export declare const inject: string[];
|
package/lib/types/server.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* and the skill-filesystem watcher discovers it in place.
|
|
9
9
|
*/
|
|
10
10
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
11
|
-
import type { BatchCommitEntry, BatchScanEntry, BatchScanRequest, ImportRequest, ImportTarget, ImportUrlRequest, SkillListEntry } from './types.ts';
|
|
11
|
+
import type { BatchCommitEntry, BatchScanEntry, BatchScanRequest, ImportRequest, ImportTarget, ImportUrlRequest, SkillListEntry, SkillListIssue } from './types.ts';
|
|
12
12
|
/** Hard cap for one imported skill body (matches the client preview limit). */
|
|
13
13
|
export declare const MAX_CONTENT_BYTES: number;
|
|
14
14
|
/** Cap for one request body read (JSON overhead above the content cap). */
|
|
@@ -22,6 +22,7 @@ export declare const BATCH_SCAN_TTL_MS: number;
|
|
|
22
22
|
export declare function dshHomeDir(): string;
|
|
23
23
|
/** Absolute skill root for one target under one workspace. */
|
|
24
24
|
export declare function skillRoot(target: ImportTarget, workspacePath: string): string;
|
|
25
|
+
export declare function isProjectTarget(target: ImportTarget): boolean;
|
|
25
26
|
/**
|
|
26
27
|
* Rebuild a skill file's frontmatter in the canonical, strictly-YAML-valid
|
|
27
28
|
* form the harness's provider parses. Unknown keys are dropped (the harness
|
|
@@ -48,7 +49,10 @@ export declare function writeSkillFile(name: string, target: ImportTarget, conte
|
|
|
48
49
|
* rank at discovery time). Display order groups by source, then name.
|
|
49
50
|
* @param workspacePaths - canonical paths of the registered workspaces.
|
|
50
51
|
*/
|
|
51
|
-
export declare function listSkills(workspacePaths: readonly string[]):
|
|
52
|
+
export declare function listSkills(workspacePaths: readonly string[]): {
|
|
53
|
+
skills: SkillListEntry[];
|
|
54
|
+
issues: SkillListIssue[];
|
|
55
|
+
};
|
|
52
56
|
/**
|
|
53
57
|
* Delete one installed skill (its whole bundle directory `<root>/<name>/`,
|
|
54
58
|
* or the flat `<root>/<name>.md`), scoped to the skill roots only.
|
package/lib/types/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* browser bundle can reference them safely.
|
|
5
5
|
*/
|
|
6
6
|
/** Where the imported skill file should land. */
|
|
7
|
-
export type ImportTarget = '
|
|
7
|
+
export type ImportTarget = 'project-dsh' | 'project-agents' | 'user-dsh' | 'user-agents';
|
|
8
8
|
/** One installed skill row served by `/skill-importer/list`. */
|
|
9
9
|
export interface SkillListEntry {
|
|
10
10
|
/** Kebab-case skill name (frontmatter `name`). */
|
|
@@ -17,8 +17,17 @@ export interface SkillListEntry {
|
|
|
17
17
|
readonly modelInvocable: boolean;
|
|
18
18
|
/** False marks a model-only skill (`user-invocable: false`). */
|
|
19
19
|
readonly userInvocable: boolean;
|
|
20
|
-
/**
|
|
20
|
+
/** Writable DSH skill root that owns this copy. */
|
|
21
21
|
readonly source: ImportTarget;
|
|
22
|
+
/** Exact registered workspace that owns a project-scoped copy. */
|
|
23
|
+
readonly workspacePath?: string;
|
|
24
|
+
}
|
|
25
|
+
/** One skill root or entry that could not be inspected without failing the whole list. */
|
|
26
|
+
export interface SkillListIssue {
|
|
27
|
+
readonly source: ImportTarget;
|
|
28
|
+
readonly path: string;
|
|
29
|
+
readonly workspacePath?: string;
|
|
30
|
+
readonly message: string;
|
|
22
31
|
}
|
|
23
32
|
/** File-import request body. */
|
|
24
33
|
export interface ImportRequest {
|
|
@@ -32,7 +41,7 @@ export interface ImportRequest {
|
|
|
32
41
|
/** Delete-request body. */
|
|
33
42
|
export interface DeleteRequest {
|
|
34
43
|
readonly name: string;
|
|
35
|
-
/** Which
|
|
44
|
+
/** Which writable DSH root owns this copy. */
|
|
36
45
|
readonly source: ImportTarget;
|
|
37
46
|
/** Canonical workspace path for project sources (host-validated). */
|
|
38
47
|
readonly workspacePath?: string;
|
|
@@ -95,6 +104,17 @@ export interface BatchCommitResponse {
|
|
|
95
104
|
export interface SkillListResponse {
|
|
96
105
|
readonly ok: true;
|
|
97
106
|
readonly skills: readonly SkillListEntry[];
|
|
107
|
+
readonly issues: readonly SkillListIssue[];
|
|
108
|
+
}
|
|
109
|
+
/** `/skill-importer/update-status` response. */
|
|
110
|
+
export interface UpdateStatusResponse {
|
|
111
|
+
readonly ok: true;
|
|
112
|
+
readonly currentVersion: string;
|
|
113
|
+
readonly latestVersion?: string;
|
|
114
|
+
readonly updateAvailable: boolean;
|
|
115
|
+
readonly command?: string;
|
|
116
|
+
/** A failed registry check is non-fatal: the installed version is still useful. */
|
|
117
|
+
readonly error?: string;
|
|
98
118
|
}
|
|
99
119
|
/** Success response of an import. */
|
|
100
120
|
export interface ImportResponse {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** SemVer ordering used to avoid offering a downgrade or an older prerelease. */
|
|
2
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
3
|
+
/** Read from package metadata in both src and built lib layouts. */
|
|
4
|
+
export declare function currentVersion(): string;
|
|
5
|
+
export declare function latestVersion(now?: number): Promise<string>;
|
|
6
|
+
export declare function updateCommand(version: string): string;
|