mdk-skills 2.4.21 → 2.4.23
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/package.json +1 -1
- package/scripts/web-ui/dist/assets/index-C0y7WZcr.css +1 -0
- package/scripts/web-ui/dist/assets/index-CsV5t-7d.js +7439 -0
- package/scripts/web-ui/dist/index.html +2 -2
- package/scripts/web-ui/server/routes/others.js +9 -13
- package/scripts/web-ui/server/routes/profiles.js +2 -6
- package/scripts/web-ui/server/routes/skills.js +24 -28
- package/scripts/web-ui/server/routes/source.js +64 -26
- package/scripts/web-ui/server/utils.js +7 -1
- package/scripts/web-ui/server.js +19 -0
- package/scripts/web-ui/dist/assets/index-CApptHBt.js +0 -7439
- package/scripts/web-ui/dist/assets/index-DukcIZOP.css +0 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<script>
|
|
8
8
|
(function(){var t=localStorage.getItem("mdk-theme");if(t==="dark")document.documentElement.setAttribute("data-theme","dark")})();
|
|
9
9
|
</script>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CsV5t-7d.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C0y7WZcr.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
|
@@ -6,10 +6,6 @@ const { buildContext } = require("../context");
|
|
|
6
6
|
|
|
7
7
|
const ctx = buildContext(utils);
|
|
8
8
|
|
|
9
|
-
async function pathExists(p) {
|
|
10
|
-
try { await fs.promises.access(p); return true; } catch { return false; }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
function register(router) {
|
|
14
10
|
|
|
15
11
|
// ----------------------------------------------------------------
|
|
@@ -19,7 +15,7 @@ function register(router) {
|
|
|
19
15
|
if (!ctx.skillsSource) return utils.sendJSON(res, { content: null, found: false });
|
|
20
16
|
const p = path.join(ctx.skillsSource, "README.md");
|
|
21
17
|
let content = null;
|
|
22
|
-
if (await pathExists(p)) {
|
|
18
|
+
if (await utils.pathExists(p)) {
|
|
23
19
|
content = await fs.promises.readFile(p, "utf-8");
|
|
24
20
|
}
|
|
25
21
|
return utils.sendJSON(res, { content, found: !!content });
|
|
@@ -32,7 +28,7 @@ function register(router) {
|
|
|
32
28
|
if (!ctx.skillsSource) return utils.sendJSON(res, { content: null, found: false });
|
|
33
29
|
const p = path.join(ctx.skillsSource, ".claude", "skills", "README.md");
|
|
34
30
|
let content = null;
|
|
35
|
-
if (await pathExists(p)) {
|
|
31
|
+
if (await utils.pathExists(p)) {
|
|
36
32
|
content = await fs.promises.readFile(p, "utf-8");
|
|
37
33
|
}
|
|
38
34
|
return utils.sendJSON(res, { content, found: !!content });
|
|
@@ -47,7 +43,7 @@ function register(router) {
|
|
|
47
43
|
: [path.join(ctx.projectRoot, "CLAUDE.md")];
|
|
48
44
|
let content = null;
|
|
49
45
|
for (const p of paths) {
|
|
50
|
-
if (await pathExists(p)) {
|
|
46
|
+
if (await utils.pathExists(p)) {
|
|
51
47
|
content = await fs.promises.readFile(p, "utf-8");
|
|
52
48
|
break;
|
|
53
49
|
}
|
|
@@ -71,7 +67,7 @@ function register(router) {
|
|
|
71
67
|
const skipped = [];
|
|
72
68
|
|
|
73
69
|
const skillsDir = path.join(sourcePath, ".claude", "skills");
|
|
74
|
-
const skillNames = await pathExists(skillsDir)
|
|
70
|
+
const skillNames = await utils.pathExists(skillsDir)
|
|
75
71
|
? await core.listSkillDirs(skillsDir)
|
|
76
72
|
: [];
|
|
77
73
|
const skillsList = skillNames.length
|
|
@@ -81,7 +77,7 @@ function register(router) {
|
|
|
81
77
|
// 根目录 README.md
|
|
82
78
|
if (root) {
|
|
83
79
|
const rootPath = path.join(sourcePath, "README.md");
|
|
84
|
-
if (!(await pathExists(rootPath))) {
|
|
80
|
+
if (!(await utils.pathExists(rootPath))) {
|
|
85
81
|
const tmpl = `# ${name}
|
|
86
82
|
|
|
87
83
|
> 维护团队:${team}
|
|
@@ -117,7 +113,7 @@ ${skillsList}
|
|
|
117
113
|
// skills/README.md
|
|
118
114
|
if (skills) {
|
|
119
115
|
const skillsReadmePath = path.join(skillsDir, "README.md");
|
|
120
|
-
if (!(await pathExists(skillsReadmePath))) {
|
|
116
|
+
if (!(await utils.pathExists(skillsReadmePath))) {
|
|
121
117
|
const tmpl = `# 技能说明
|
|
122
118
|
|
|
123
119
|
## 快速开始
|
|
@@ -164,10 +160,10 @@ ${skillsList}
|
|
|
164
160
|
for (const skill of pkgSkills) {
|
|
165
161
|
const skillDir = path.join(ctx.skillsDest, skill.name);
|
|
166
162
|
const issues = [];
|
|
167
|
-
if (!(await pathExists(skillDir))) {
|
|
163
|
+
if (!(await utils.pathExists(skillDir))) {
|
|
168
164
|
issues.push("未安装");
|
|
169
165
|
} else {
|
|
170
|
-
if (!(await pathExists(path.join(skillDir, "SKILL.md")))) {
|
|
166
|
+
if (!(await utils.pathExists(path.join(skillDir, "SKILL.md")))) {
|
|
171
167
|
issues.push("缺少 SKILL.md");
|
|
172
168
|
} else if (!(await core.parseFrontmatter(skillDir))) {
|
|
173
169
|
issues.push("SKILL.md frontmatter 格式异常");
|
|
@@ -231,7 +227,7 @@ ${skillsList}
|
|
|
231
227
|
if (!dirPath) return utils.sendJSON(res, { error: "缺少路径" }, 400);
|
|
232
228
|
|
|
233
229
|
const skillsDir = path.join(dirPath, ".claude", "skills");
|
|
234
|
-
const valid = await pathExists(skillsDir);
|
|
230
|
+
const valid = await utils.pathExists(skillsDir);
|
|
235
231
|
let skillCount = 0;
|
|
236
232
|
if (valid) {
|
|
237
233
|
skillCount = (await core.listSkillDirs(skillsDir)).length;
|
|
@@ -5,10 +5,6 @@ const { buildContext } = require("../context");
|
|
|
5
5
|
|
|
6
6
|
const ctx = buildContext(utils);
|
|
7
7
|
|
|
8
|
-
async function pathExists(p) {
|
|
9
|
-
try { await fs.promises.access(p); return true; } catch { return false; }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
function register(router) {
|
|
13
9
|
|
|
14
10
|
// ----------------------------------------------------------------
|
|
@@ -56,7 +52,7 @@ function register(router) {
|
|
|
56
52
|
|
|
57
53
|
const profilesPath = path.join(ctx.skillsSource, ".claude", "profiles.json");
|
|
58
54
|
let data = { version: 1, profiles: [] };
|
|
59
|
-
if (await pathExists(profilesPath)) {
|
|
55
|
+
if (await utils.pathExists(profilesPath)) {
|
|
60
56
|
try {
|
|
61
57
|
data = JSON.parse(await fs.promises.readFile(profilesPath, "utf-8"));
|
|
62
58
|
} catch {}
|
|
@@ -110,7 +106,7 @@ function register(router) {
|
|
|
110
106
|
return utils.sendJSON(res, { error: "不能删除内置场景" }, 403);
|
|
111
107
|
|
|
112
108
|
const profilesPath = path.join(ctx.skillsSource, ".claude", "profiles.json");
|
|
113
|
-
if (!(await pathExists(profilesPath)))
|
|
109
|
+
if (!(await utils.pathExists(profilesPath)))
|
|
114
110
|
return utils.sendJSON(res, { error: "场景文件不存在" }, 404);
|
|
115
111
|
|
|
116
112
|
const data = JSON.parse(await fs.promises.readFile(profilesPath, "utf-8"));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const os = require("os");
|
|
4
|
-
const { execSync } = require("child_process");
|
|
4
|
+
const { execSync, exec } = require("child_process");
|
|
5
5
|
const core = require("../../../core");
|
|
6
6
|
|
|
7
7
|
const utils = require("../utils");
|
|
@@ -19,10 +19,6 @@ function openInExplorer(dirPath) {
|
|
|
19
19
|
execSync(cmd, { stdio: "ignore" });
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async function pathExists(p) {
|
|
23
|
-
try { await fs.promises.access(p); return true; } catch { return false; }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
22
|
function requireSource(res) {
|
|
27
23
|
if (!ctx.skillsSource || !ctx.pkgSkillsSource) {
|
|
28
24
|
utils.sendJSON(res, { error: "未设置技能目录,请在设置中连接" }, 400);
|
|
@@ -43,7 +39,6 @@ async function cleanPullCache() {
|
|
|
43
39
|
|
|
44
40
|
function runAsync(cmd, options = {}) {
|
|
45
41
|
const { cwd, timeout = 120000, taskId = "default" } = options;
|
|
46
|
-
const { exec } = require("child_process");
|
|
47
42
|
return new Promise((resolve, reject) => {
|
|
48
43
|
if (ctx.runningTasks.has(taskId)) {
|
|
49
44
|
const old = ctx.runningTasks.get(taskId);
|
|
@@ -101,7 +96,7 @@ function register(router) {
|
|
|
101
96
|
for (const us of userSkills) {
|
|
102
97
|
if (!pkgNames.has(us.name)) {
|
|
103
98
|
const dest = path.join(ctx.skillsDest, us.name);
|
|
104
|
-
if (await pathExists(dest)) {
|
|
99
|
+
if (await utils.pathExists(dest)) {
|
|
105
100
|
const r = await utils.safeRm(dest, us.name);
|
|
106
101
|
if (r.removed) cleaned = true;
|
|
107
102
|
}
|
|
@@ -139,11 +134,11 @@ function register(router) {
|
|
|
139
134
|
const src = path.join(ctx.pkgSkillsSource, name);
|
|
140
135
|
const dest = path.join(ctx.skillsDest, name);
|
|
141
136
|
if (enabled) {
|
|
142
|
-
if (await pathExists(src) && !(await pathExists(dest))) {
|
|
137
|
+
if (await utils.pathExists(src) && !(await utils.pathExists(dest))) {
|
|
143
138
|
await utils.copyDir(src, dest);
|
|
144
139
|
}
|
|
145
140
|
} else {
|
|
146
|
-
if (await pathExists(dest)) {
|
|
141
|
+
if (await utils.pathExists(dest)) {
|
|
147
142
|
const r = await utils.safeRm(dest, name);
|
|
148
143
|
if (!r.removed && r.reason === "locked") {
|
|
149
144
|
return utils.sendJSON(res, { error: `技能 "${name}" 正被其他程序使用,无法停用` }, 409);
|
|
@@ -187,7 +182,7 @@ function register(router) {
|
|
|
187
182
|
const cached = ctx.pullCache.get(url);
|
|
188
183
|
if (cached) {
|
|
189
184
|
const skillsDir = path.join(cached.tmpDir, ".claude", "skills");
|
|
190
|
-
const skills = await pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
|
|
185
|
+
const skills = await utils.pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
|
|
191
186
|
return utils.sendJSON(res, { ok: true, skills, total: skills.length });
|
|
192
187
|
}
|
|
193
188
|
const tmpDir = path.join(os.tmpdir(), "mdk-preview-" + Date.now());
|
|
@@ -202,7 +197,7 @@ function register(router) {
|
|
|
202
197
|
}
|
|
203
198
|
await utils.cleanNpxTemp();
|
|
204
199
|
const skillsDir = path.join(tmpDir, ".claude", "skills");
|
|
205
|
-
const skills = await pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
|
|
200
|
+
const skills = await utils.pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
|
|
206
201
|
if (skills.length === 0) {
|
|
207
202
|
await utils.safeRm(tmpDir, "临时目录");
|
|
208
203
|
return utils.sendJSON(res, { error: "未找到有效技能" }, 400);
|
|
@@ -222,12 +217,12 @@ function register(router) {
|
|
|
222
217
|
const skillsDir = path.join(tmpDir, ".claude", "skills");
|
|
223
218
|
for (const name of names) {
|
|
224
219
|
const skillPath = path.join(skillsDir, name);
|
|
225
|
-
if (!(await pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
220
|
+
if (!(await utils.pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
226
221
|
skipped.push(name);
|
|
227
222
|
continue;
|
|
228
223
|
}
|
|
229
224
|
const dest = path.join(ctx.pkgSkillsSource, name);
|
|
230
|
-
if (await pathExists(dest)) {
|
|
225
|
+
if (await utils.pathExists(dest)) {
|
|
231
226
|
const r = await utils.safeRm(dest, name + "(源目录)");
|
|
232
227
|
if (!r.removed && r.reason === "locked") {
|
|
233
228
|
skipped.push(name);
|
|
@@ -282,9 +277,9 @@ function register(router) {
|
|
|
282
277
|
const src = path.join(ctx.pkgSkillsSource, name);
|
|
283
278
|
const dest = path.join(ctx.skillsDest, name);
|
|
284
279
|
if (enabled) {
|
|
285
|
-
if (await pathExists(src) && !(await pathExists(dest))) await utils.copyDir(src, dest);
|
|
280
|
+
if (await utils.pathExists(src) && !(await utils.pathExists(dest))) await utils.copyDir(src, dest);
|
|
286
281
|
} else {
|
|
287
|
-
if (await pathExists(dest)) {
|
|
282
|
+
if (await utils.pathExists(dest)) {
|
|
288
283
|
const r = await utils.safeRm(dest, name);
|
|
289
284
|
if (!r.removed && r.reason === "locked") {
|
|
290
285
|
locked.push(name);
|
|
@@ -335,6 +330,7 @@ function register(router) {
|
|
|
335
330
|
// POST /api/skills/pull-cancel — 取消预览清理缓存
|
|
336
331
|
// ----------------------------------------------------------------
|
|
337
332
|
router.post("/api/skills/pull-cancel", async (req, res) => {
|
|
333
|
+
await cleanPullCache();
|
|
338
334
|
const body = await utils.parseBody(req);
|
|
339
335
|
const url = (body.url || "").trim();
|
|
340
336
|
if (url && ctx.pullCache.has(url)) {
|
|
@@ -356,7 +352,7 @@ function register(router) {
|
|
|
356
352
|
return utils.sendJSON(res, { type: "remote", ...pullSource[name] });
|
|
357
353
|
}
|
|
358
354
|
const skillDir = path.join(ctx.pkgSkillsSource, name);
|
|
359
|
-
if (await pathExists(skillDir)) {
|
|
355
|
+
if (await utils.pathExists(skillDir)) {
|
|
360
356
|
return utils.sendJSON(res, { type: "local", path: skillDir });
|
|
361
357
|
}
|
|
362
358
|
return utils.sendJSON(res, null);
|
|
@@ -384,7 +380,7 @@ function register(router) {
|
|
|
384
380
|
}
|
|
385
381
|
await utils.cleanNpxTemp();
|
|
386
382
|
const skillPath = path.join(tmpDir, ".claude", "skills", name);
|
|
387
|
-
if (!(await pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
383
|
+
if (!(await utils.pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
388
384
|
await utils.safeRm(tmpDir, "临时目录");
|
|
389
385
|
return utils.sendJSON(res, { error: "远程仓库中未找到该技能" }, 400);
|
|
390
386
|
}
|
|
@@ -405,7 +401,7 @@ function register(router) {
|
|
|
405
401
|
await utils.copyDir(skillPath, dest);
|
|
406
402
|
|
|
407
403
|
const projectSkill = path.join(ctx.skillsDest, name);
|
|
408
|
-
if (await pathExists(projectSkill)) {
|
|
404
|
+
if (await utils.pathExists(projectSkill)) {
|
|
409
405
|
const rProj = await utils.safeRm(projectSkill, name + "(项目目录)");
|
|
410
406
|
if (!rProj.removed && rProj.reason === "locked") {
|
|
411
407
|
await utils.safeRm(tmpDir, "临时目录");
|
|
@@ -468,7 +464,7 @@ function register(router) {
|
|
|
468
464
|
await utils.cleanNpxTemp();
|
|
469
465
|
|
|
470
466
|
const skillPath = path.join(tmpDir, ".claude", "skills", skillName);
|
|
471
|
-
if (!(await pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
467
|
+
if (!(await utils.pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
472
468
|
await utils.safeRm(tmpDir, "临时目录");
|
|
473
469
|
return utils.sendJSON(res, { error: "未找到技能文件" }, 400);
|
|
474
470
|
}
|
|
@@ -521,7 +517,7 @@ function register(router) {
|
|
|
521
517
|
const locked = [];
|
|
522
518
|
for (const name of names) {
|
|
523
519
|
const skillPath = path.join(tmpDir, ".claude", "skills", name);
|
|
524
|
-
if (!(await pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
520
|
+
if (!(await utils.pathExists(path.join(skillPath, "SKILL.md")))) {
|
|
525
521
|
notFound.push(name);
|
|
526
522
|
continue;
|
|
527
523
|
}
|
|
@@ -540,7 +536,7 @@ function register(router) {
|
|
|
540
536
|
await utils.copyDir(skillPath, dest);
|
|
541
537
|
|
|
542
538
|
const projectSkill = path.join(ctx.skillsDest, name);
|
|
543
|
-
if (await pathExists(projectSkill)) {
|
|
539
|
+
if (await utils.pathExists(projectSkill)) {
|
|
544
540
|
const rProj = await utils.safeRm(projectSkill, name + "(项目目录)");
|
|
545
541
|
if (!rProj.removed && rProj.reason === "locked") {
|
|
546
542
|
locked.push(name);
|
|
@@ -572,7 +568,7 @@ function register(router) {
|
|
|
572
568
|
if (!requireSource(res)) return;
|
|
573
569
|
const name = params.name;
|
|
574
570
|
const skillDir = path.join(ctx.pkgSkillsSource, name);
|
|
575
|
-
if (!(await pathExists(skillDir))) {
|
|
571
|
+
if (!(await utils.pathExists(skillDir))) {
|
|
576
572
|
return utils.sendJSON(res, { error: "技能目录不存在" }, 404);
|
|
577
573
|
}
|
|
578
574
|
try {
|
|
@@ -590,7 +586,7 @@ function register(router) {
|
|
|
590
586
|
if (!requireSource(res)) return;
|
|
591
587
|
const name = params.name;
|
|
592
588
|
const dest = path.join(ctx.skillsDest, name);
|
|
593
|
-
if (!(await pathExists(dest))) return utils.sendJSON(res, { ok: true, name, skipped: true });
|
|
589
|
+
if (!(await utils.pathExists(dest))) return utils.sendJSON(res, { ok: true, name, skipped: true });
|
|
594
590
|
const r = await utils.safeRm(dest, name);
|
|
595
591
|
if (r.removed) return utils.sendJSON(res, { ok: true, name });
|
|
596
592
|
if (r.reason === "locked") return utils.sendJSON(res, { error: `技能"${name}"被其他程序占用,无法删除`, locked: true }, 409);
|
|
@@ -605,11 +601,11 @@ function register(router) {
|
|
|
605
601
|
const name = params.name;
|
|
606
602
|
const src = path.join(ctx.pkgSkillsSource, name);
|
|
607
603
|
const dest = path.join(ctx.skillsDest, name);
|
|
608
|
-
if (await pathExists(dest)) {
|
|
604
|
+
if (await utils.pathExists(dest)) {
|
|
609
605
|
const r = await utils.safeRm(dest, name);
|
|
610
606
|
if (!r.removed) return utils.sendJSON(res, { error: `无法覆盖现有目录: ${r.reason}` }, r.reason === "locked" ? 409 : 500);
|
|
611
607
|
}
|
|
612
|
-
if (!(await pathExists(src))) return utils.sendJSON(res, { error: `源目录不存在: ${name}` }, 404);
|
|
608
|
+
if (!(await utils.pathExists(src))) return utils.sendJSON(res, { error: `源目录不存在: ${name}` }, 404);
|
|
613
609
|
try {
|
|
614
610
|
await utils.copyDir(src, dest);
|
|
615
611
|
return utils.sendJSON(res, { ok: true, name });
|
|
@@ -641,7 +637,7 @@ function register(router) {
|
|
|
641
637
|
const altDir = path.join(ctx.pkgSkillsSource, name);
|
|
642
638
|
let readmePath = null;
|
|
643
639
|
for (const p of [path.join(skillDir, "SKILL.md"), path.join(altDir, "SKILL.md")]) {
|
|
644
|
-
if (await pathExists(p)) { readmePath = p; break; }
|
|
640
|
+
if (await utils.pathExists(p)) { readmePath = p; break; }
|
|
645
641
|
}
|
|
646
642
|
const content = readmePath ? await fs.promises.readFile(readmePath, "utf-8") : null;
|
|
647
643
|
return utils.sendJSON(res, { content, found: !!readmePath });
|
|
@@ -654,12 +650,12 @@ function register(router) {
|
|
|
654
650
|
if (!requireSource(res)) return;
|
|
655
651
|
const name = params.name;
|
|
656
652
|
const skillDir = path.join(ctx.pkgSkillsSource, name);
|
|
657
|
-
if (!(await pathExists(skillDir))) {
|
|
653
|
+
if (!(await utils.pathExists(skillDir))) {
|
|
658
654
|
return utils.sendJSON(res, { error: "技能不存在" }, 404);
|
|
659
655
|
}
|
|
660
656
|
const metaPath = path.join(skillDir, ".meta.json");
|
|
661
657
|
let meta = {};
|
|
662
|
-
if (await pathExists(metaPath)) {
|
|
658
|
+
if (await utils.pathExists(metaPath)) {
|
|
663
659
|
try { meta = JSON.parse(await fs.promises.readFile(metaPath, "utf-8")); } catch {}
|
|
664
660
|
}
|
|
665
661
|
const body = await utils.parseBody(req);
|
|
@@ -6,10 +6,6 @@ const { buildContext } = require("../context");
|
|
|
6
6
|
|
|
7
7
|
const ctx = buildContext(utils);
|
|
8
8
|
|
|
9
|
-
async function pathExists(p) {
|
|
10
|
-
try { await fs.promises.access(p); return true; } catch { return false; }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
function register(router) {
|
|
14
10
|
|
|
15
11
|
// ----------------------------------------------------------------
|
|
@@ -24,7 +20,7 @@ function register(router) {
|
|
|
24
20
|
(p) => p.id === settings._active_profile,
|
|
25
21
|
);
|
|
26
22
|
const enabledCount = userSkills.filter((s) => s.enabled).length;
|
|
27
|
-
const mdExists = await pathExists(path.join(ctx.projectRoot, "CLAUDE.md"));
|
|
23
|
+
const mdExists = await utils.pathExists(path.join(ctx.projectRoot, "CLAUDE.md"));
|
|
28
24
|
return utils.sendJSON(res, {
|
|
29
25
|
pkgSkills,
|
|
30
26
|
enabledCount,
|
|
@@ -49,17 +45,17 @@ function register(router) {
|
|
|
49
45
|
const missingSkillDocs = [];
|
|
50
46
|
if (sourcePath) {
|
|
51
47
|
const claudeDir = path.join(sourcePath, ".claude");
|
|
52
|
-
if (await pathExists(claudeDir)) {
|
|
53
|
-
if (!(await pathExists(path.join(claudeDir, "profiles.json")))) {
|
|
48
|
+
if (await utils.pathExists(claudeDir)) {
|
|
49
|
+
if (!(await utils.pathExists(path.join(claudeDir, "profiles.json")))) {
|
|
54
50
|
missingConfig.push("profiles.json");
|
|
55
51
|
}
|
|
56
|
-
if (!(await pathExists(path.join(claudeDir, "settings.json")))) {
|
|
52
|
+
if (!(await utils.pathExists(path.join(claudeDir, "settings.json")))) {
|
|
57
53
|
missingConfig.push("settings.json");
|
|
58
54
|
}
|
|
59
55
|
const skillsDir = path.join(claudeDir, "skills");
|
|
60
|
-
if (await pathExists(skillsDir)) {
|
|
56
|
+
if (await utils.pathExists(skillsDir)) {
|
|
61
57
|
for (const name of await core.listSkillDirs(skillsDir)) {
|
|
62
|
-
if (!(await pathExists(path.join(skillsDir, name, "SKILL.md")))) {
|
|
58
|
+
if (!(await utils.pathExists(path.join(skillsDir, name, "SKILL.md")))) {
|
|
63
59
|
missingSkillDocs.push(name);
|
|
64
60
|
}
|
|
65
61
|
}
|
|
@@ -74,8 +70,8 @@ function register(router) {
|
|
|
74
70
|
if (sourcePath) {
|
|
75
71
|
const skillsDir = path.join(sourcePath, ".claude", "skills");
|
|
76
72
|
readmeStatus = {
|
|
77
|
-
root: await pathExists(path.join(sourcePath, "README.md")),
|
|
78
|
-
skills: await pathExists(path.join(skillsDir, "README.md")),
|
|
73
|
+
root: await utils.pathExists(path.join(sourcePath, "README.md")),
|
|
74
|
+
skills: await utils.pathExists(path.join(skillsDir, "README.md")),
|
|
79
75
|
};
|
|
80
76
|
}
|
|
81
77
|
|
|
@@ -97,20 +93,62 @@ function register(router) {
|
|
|
97
93
|
const repoPath = body.path;
|
|
98
94
|
if (!repoPath) return utils.sendJSON(res, { error: "缺少目录路径" }, 400);
|
|
99
95
|
const resolved = path.resolve(repoPath);
|
|
100
|
-
|
|
96
|
+
|
|
97
|
+
// 校验技能目录结构
|
|
98
|
+
const claudeDir = path.join(resolved, ".claude");
|
|
99
|
+
if (!(await utils.pathExists(claudeDir))) {
|
|
100
|
+
return utils.sendJSON(
|
|
101
|
+
res,
|
|
102
|
+
{ error: `路径 "${resolved}" 下没有 .claude/ 目录` },
|
|
103
|
+
400,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const skillsDir = path.join(claudeDir, "skills");
|
|
107
|
+
if (!(await utils.pathExists(skillsDir))) {
|
|
101
108
|
return utils.sendJSON(
|
|
102
109
|
res,
|
|
103
110
|
{ error: `路径 "${resolved}" 下没有 .claude/skills/` },
|
|
104
111
|
400,
|
|
105
112
|
);
|
|
106
113
|
}
|
|
114
|
+
|
|
115
|
+
// 校验配置完整性
|
|
116
|
+
const missingConfig = [];
|
|
117
|
+
if (!(await utils.pathExists(path.join(claudeDir, "settings.json")))) {
|
|
118
|
+
missingConfig.push("settings.json");
|
|
119
|
+
}
|
|
120
|
+
if (!(await utils.pathExists(path.join(claudeDir, "profiles.json")))) {
|
|
121
|
+
missingConfig.push("profiles.json");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 校验每个技能目录的 SKILL.md
|
|
125
|
+
const missingSkillDocs = [];
|
|
126
|
+
if (await utils.pathExists(skillsDir)) {
|
|
127
|
+
for (const name of await core.listSkillDirs(skillsDir)) {
|
|
128
|
+
const skillDir = path.join(skillsDir, name);
|
|
129
|
+
if (!(await utils.pathExists(path.join(skillDir, "SKILL.md")))) {
|
|
130
|
+
missingSkillDocs.push(name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (missingConfig.length > 0 || missingSkillDocs.length > 0) {
|
|
136
|
+
const warnings = [];
|
|
137
|
+
if (missingConfig.length > 0) warnings.push(`缺少配置文件: ${missingConfig.join(", ")}`);
|
|
138
|
+
if (missingSkillDocs.length > 0) warnings.push(`以下技能缺少 SKILL.md: ${missingSkillDocs.join(", ")}`);
|
|
139
|
+
return utils.sendJSON(res, {
|
|
140
|
+
error: `技能源结构不完整: ${warnings.join("; ")}`,
|
|
141
|
+
detail: { missingConfig, missingSkillDocs },
|
|
142
|
+
}, 400);
|
|
143
|
+
}
|
|
144
|
+
|
|
107
145
|
const settings = await ctx.readSettings();
|
|
108
146
|
settings._skill_source = resolved;
|
|
109
147
|
await ctx.writeSettings(settings);
|
|
110
148
|
// 复制 CLAUDE.md(不存在才装)
|
|
111
149
|
const mdSource = path.join(resolved, "CLAUDE.md");
|
|
112
150
|
const mdDest = path.join(ctx.projectRoot, "CLAUDE.md");
|
|
113
|
-
if (await pathExists(mdSource) && !(await pathExists(mdDest))) {
|
|
151
|
+
if (await utils.pathExists(mdSource) && !(await utils.pathExists(mdDest))) {
|
|
114
152
|
await fs.promises.copyFile(mdSource, mdDest);
|
|
115
153
|
}
|
|
116
154
|
|
|
@@ -145,7 +183,7 @@ function register(router) {
|
|
|
145
183
|
const settings = await ctx.readSettings();
|
|
146
184
|
const sourcePath = settings._skill_source;
|
|
147
185
|
if (!sourcePath) return utils.sendJSON(res, { error: "尚未绑定技能源" }, 400);
|
|
148
|
-
if (!(await pathExists(path.join(sourcePath, ".claude")))) {
|
|
186
|
+
if (!(await utils.pathExists(path.join(sourcePath, ".claude")))) {
|
|
149
187
|
return utils.sendJSON(res, { error: "绑定的路径已失效" }, 400);
|
|
150
188
|
}
|
|
151
189
|
|
|
@@ -154,13 +192,13 @@ function register(router) {
|
|
|
154
192
|
|
|
155
193
|
// 技能反推
|
|
156
194
|
const repoSkills = path.join(sourcePath, ".claude", "skills");
|
|
157
|
-
if (!(await pathExists(repoSkills)))
|
|
195
|
+
if (!(await utils.pathExists(repoSkills)))
|
|
158
196
|
await fs.promises.mkdir(repoSkills, { recursive: true });
|
|
159
|
-
if (await pathExists(ctx.skillsDest)) {
|
|
197
|
+
if (await utils.pathExists(ctx.skillsDest)) {
|
|
160
198
|
for (const name of await core.listSkillDirs(ctx.skillsDest)) {
|
|
161
199
|
const src = path.join(ctx.skillsDest, name);
|
|
162
200
|
const dest = path.join(repoSkills, name);
|
|
163
|
-
if (await pathExists(dest))
|
|
201
|
+
if (await utils.pathExists(dest))
|
|
164
202
|
await utils.safeRm(dest, name);
|
|
165
203
|
await utils.copyDir(src, dest);
|
|
166
204
|
}
|
|
@@ -168,7 +206,7 @@ function register(router) {
|
|
|
168
206
|
|
|
169
207
|
// profiles.json 反推
|
|
170
208
|
const projectProfiles = path.join(ctx.claudeDest, "profiles.json");
|
|
171
|
-
if (await pathExists(projectProfiles)) {
|
|
209
|
+
if (await utils.pathExists(projectProfiles)) {
|
|
172
210
|
await fs.promises.copyFile(
|
|
173
211
|
projectProfiles,
|
|
174
212
|
path.join(sourcePath, ".claude", "profiles.json"),
|
|
@@ -187,7 +225,7 @@ function register(router) {
|
|
|
187
225
|
|
|
188
226
|
// CLAUDE.md 反推
|
|
189
227
|
const projectMd = path.join(ctx.projectRoot, "CLAUDE.md");
|
|
190
|
-
if (await pathExists(projectMd)) {
|
|
228
|
+
if (await utils.pathExists(projectMd)) {
|
|
191
229
|
await fs.promises.copyFile(projectMd, path.join(sourcePath, "CLAUDE.md"));
|
|
192
230
|
}
|
|
193
231
|
|
|
@@ -210,7 +248,7 @@ function register(router) {
|
|
|
210
248
|
const created = { profiles: false, settings: false, metaFiles: 0 };
|
|
211
249
|
|
|
212
250
|
// 1. profiles.json
|
|
213
|
-
if (!(await pathExists(profilesPath))) {
|
|
251
|
+
if (!(await utils.pathExists(profilesPath))) {
|
|
214
252
|
await fs.promises.writeFile(
|
|
215
253
|
profilesPath,
|
|
216
254
|
JSON.stringify(
|
|
@@ -234,7 +272,7 @@ function register(router) {
|
|
|
234
272
|
}
|
|
235
273
|
|
|
236
274
|
// 2. settings.json
|
|
237
|
-
if (!(await pathExists(settingsPath))) {
|
|
275
|
+
if (!(await utils.pathExists(settingsPath))) {
|
|
238
276
|
await fs.promises.writeFile(
|
|
239
277
|
settingsPath,
|
|
240
278
|
JSON.stringify({ skills: {}, always_apply_skills: [] }, null, 2) + "\n",
|
|
@@ -244,11 +282,11 @@ function register(router) {
|
|
|
244
282
|
}
|
|
245
283
|
|
|
246
284
|
// 3. 每个 skill 目录的 .meta.json
|
|
247
|
-
if (await pathExists(skillsDir)) {
|
|
285
|
+
if (await utils.pathExists(skillsDir)) {
|
|
248
286
|
for (const name of await core.listSkillDirs(skillsDir)) {
|
|
249
287
|
const skillDir = path.join(skillsDir, name);
|
|
250
288
|
const metaPath = path.join(skillDir, ".meta.json");
|
|
251
|
-
if (!(await pathExists(metaPath))) {
|
|
289
|
+
if (!(await utils.pathExists(metaPath))) {
|
|
252
290
|
await utils.writeSkillMeta(skillDir, false);
|
|
253
291
|
created.metaFiles++;
|
|
254
292
|
}
|
|
@@ -256,8 +294,8 @@ function register(router) {
|
|
|
256
294
|
}
|
|
257
295
|
|
|
258
296
|
const readmeStatus = {
|
|
259
|
-
root: await pathExists(path.join(sourcePath, "README.md")),
|
|
260
|
-
skills: await pathExists(path.join(skillsDir, "README.md")),
|
|
297
|
+
root: await utils.pathExists(path.join(sourcePath, "README.md")),
|
|
298
|
+
skills: await utils.pathExists(path.join(skillsDir, "README.md")),
|
|
261
299
|
};
|
|
262
300
|
|
|
263
301
|
return utils.sendJSON(res, { ok: true, ...created, readmeStatus });
|
|
@@ -84,7 +84,13 @@ function stripAnsi(str) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function escapeShellArg(str) {
|
|
87
|
-
|
|
87
|
+
const s = String(str);
|
|
88
|
+
if (process.platform === "win32") {
|
|
89
|
+
// Windows cmd.exe: 双引号内用 "" 转义
|
|
90
|
+
return '"' + s.replace(/"/g, '""') + '"';
|
|
91
|
+
}
|
|
92
|
+
// Unix: 单引号包裹,内嵌单引号用 '\'' 跳出
|
|
93
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
function parseInstallCount(str) {
|
package/scripts/web-ui/server.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const http = require("http");
|
|
7
7
|
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
8
9
|
|
|
9
10
|
const { Router } = require("./server/router");
|
|
10
11
|
const utils = require("./server/utils");
|
|
@@ -137,8 +138,26 @@ async function startDev() {
|
|
|
137
138
|
|
|
138
139
|
// ===================== 启动 =====================
|
|
139
140
|
|
|
141
|
+
// 启动时清理残留的预览临时目录
|
|
142
|
+
async function cleanOrphanedTempDirs() {
|
|
143
|
+
const tmpDir = os.tmpdir();
|
|
144
|
+
try {
|
|
145
|
+
const entries = await fs.promises.readdir(tmpDir, { withFileTypes: true });
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
if (entry.isDirectory() && entry.name.startsWith("mdk-preview-")) {
|
|
148
|
+
const full = path.join(tmpDir, entry.name);
|
|
149
|
+
try {
|
|
150
|
+
await fs.promises.rm(full, { recursive: true, force: true });
|
|
151
|
+
console.log(`[清理孤儿临时目录] ${full}`);
|
|
152
|
+
} catch {}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
157
|
+
|
|
140
158
|
async function start() {
|
|
141
159
|
await refreshSource();
|
|
160
|
+
await cleanOrphanedTempDirs();
|
|
142
161
|
const distDir = path.join(__dirname, "dist");
|
|
143
162
|
let stats;
|
|
144
163
|
try { stats = await fs.promises.stat(distDir); } catch {}
|