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.
@@ -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-CApptHBt.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-DukcIZOP.css">
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
- if (!(await pathExists(path.join(resolved, ".claude", "skills")))) {
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
- return '"' + String(str).replace(/"/g, '\\"') + '"';
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) {
@@ -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 {}