mdk-skills 2.4.20 → 2.4.21

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.
@@ -31,50 +31,7 @@ function parseBody(req) {
31
31
  });
32
32
  }
33
33
 
34
- // ===================== 文件系统(同步版 — CLI 使用)=====================
35
-
36
- // 安全删除目录/文件,处理 Windows 文件锁定(EBUSY)
37
- function safeRmSync(dir, label) {
38
- if (!fs.existsSync(dir)) return { removed: false, reason: "not_found" };
39
- try {
40
- fs.rmSync(dir, { recursive: true, force: true });
41
- return { removed: true };
42
- } catch (err) {
43
- if (err.code === "EBUSY") {
44
- console.error(`跳过删除 "${label || dir}"(文件被其他程序占用)`);
45
- return { removed: false, reason: "locked" };
46
- }
47
- console.error(`删除 "${label || dir}" 失败:`, err.message);
48
- return { removed: false, reason: "error", message: err.message };
49
- }
50
- }
51
-
52
- // 手动递归拷贝目录
53
- function copyDirSync(src, dest) {
54
- fs.mkdirSync(dest, { recursive: true });
55
- for (const item of fs.readdirSync(src)) {
56
- const srcPath = path.join(src, item);
57
- const destPath = path.join(dest, item);
58
- if (fs.statSync(srcPath).isDirectory()) {
59
- copyDirSync(srcPath, destPath);
60
- } else {
61
- fs.copyFileSync(srcPath, destPath);
62
- }
63
- }
64
- }
65
-
66
- // npx skills 会在系统临时目录留下 skills-* 工作目录,统一清理(同步版)
67
- function cleanNpxTempSync() {
68
- const tmp = os.tmpdir();
69
- for (const name of fs.readdirSync(tmp)) {
70
- if (/^skills-[A-Za-z0-9]+$/.test(name)) {
71
- const p = path.join(tmp, name);
72
- try { fs.rmSync(p, { recursive: true, force: true }); } catch {}
73
- }
74
- }
75
- }
76
-
77
- // ===================== 文件系统(异步版 — Web UI Server 使用)=====================
34
+ // ===================== 文件系统(异步)======================
78
35
 
79
36
  // 安全删除目录/文件,处理 Windows 文件锁定(EBUSY)
80
37
  async function safeRm(dir, label) {
@@ -92,23 +49,7 @@ async function safeRm(dir, label) {
92
49
  }
93
50
  }
94
51
 
95
- // 递归拷贝目录(异步版)
96
- async function copyDir(src, dest) {
97
- await fs.promises.mkdir(dest, { recursive: true });
98
- const items = await fs.promises.readdir(src);
99
- for (const item of items) {
100
- const srcPath = path.join(src, item);
101
- const destPath = path.join(dest, item);
102
- const stat = await fs.promises.stat(srcPath);
103
- if (stat.isDirectory()) {
104
- await copyDir(srcPath, destPath);
105
- } else {
106
- await fs.promises.copyFile(srcPath, destPath);
107
- }
108
- }
109
- }
110
-
111
- // 清理 npx 临时目录(异步版)
52
+ // 清理 npx 临时目录
112
53
  async function cleanNpxTemp() {
113
54
  const tmp = os.tmpdir();
114
55
  let names;
@@ -121,7 +62,7 @@ async function cleanNpxTemp() {
121
62
  }
122
63
  }
123
64
 
124
- // Windows 下获取可用盘符(异步版)
65
+ // Windows 下获取可用盘符
125
66
  async function getWindowsDrives() {
126
67
  const drives = [];
127
68
  for (let i = 65; i <= 90; i++) {
@@ -131,7 +72,7 @@ async function getWindowsDrives() {
131
72
  if (await pathExists(root)) {
132
73
  drives.push({ name: root, isDir: true });
133
74
  }
134
- } catch { /* 无权限访问的盘符跳过 */ }
75
+ } catch {}
135
76
  }
136
77
  return drives;
137
78
  }
@@ -183,28 +124,7 @@ function parseSkillsFindOutput(stdout) {
183
124
 
184
125
  // ===================== 指纹 & 元信息 =====================
185
126
 
186
- // 计算技能目录的文件指纹(同步版)
187
- function calcSkillFingerprintSync(dir) {
188
- if (!fs.existsSync(dir)) return "";
189
- const hash = crypto.createHash("md5");
190
- const walk = (d) => {
191
- for (const item of fs.readdirSync(d).sort()) {
192
- const p = path.join(d, item);
193
- if (item === ".meta.json") continue;
194
- const stat = fs.statSync(p);
195
- if (stat.isDirectory()) {
196
- walk(p);
197
- } else {
198
- hash.update(item);
199
- hash.update(fs.readFileSync(p));
200
- }
201
- }
202
- };
203
- walk(dir);
204
- return hash.digest("hex");
205
- }
206
-
207
- // 计算技能目录的文件指纹(异步版)
127
+ // 计算技能目录的文件指纹(MD5,排除 .meta.json)
208
128
  async function calcSkillFingerprint(dir) {
209
129
  if (!(await pathExists(dir))) return "";
210
130
  const hash = crypto.createHash("md5");
@@ -232,7 +152,7 @@ async function calcSkillFingerprint(dir) {
232
152
 
233
153
  // 写入技能元信息(异步版)
234
154
  async function writeSkillMeta(dest, wasUpdated) {
235
- const fm = core.parseFrontmatter(dest);
155
+ const fm = await core.parseFrontmatter(dest);
236
156
  if (!fm) return;
237
157
  const metaPath = path.join(dest, ".meta.json");
238
158
  let oldMeta = {};
@@ -255,103 +175,14 @@ async function writeSkillMeta(dest, wasUpdated) {
255
175
  await fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
256
176
  }
257
177
 
258
- // 写入技能元信息(同步版)
259
- function writeSkillMetaSync(dest, wasUpdated) {
260
- const fm = core.parseFrontmatter(dest);
261
- if (!fm) return;
262
- const metaPath = path.join(dest, ".meta.json");
263
- let oldMeta = {};
264
- if (fs.existsSync(metaPath)) {
265
- try { oldMeta = JSON.parse(fs.readFileSync(metaPath, "utf-8")); } catch {}
266
- }
267
- const hasUpstream = !!fm.version;
268
- const meta = {
269
- version: fm.version || "1.0.0",
270
- description: fm.description || "",
271
- tags: fm.tags || [],
272
- };
273
- if (hasUpstream) {
274
- meta._updateCount = 0;
275
- } else if (wasUpdated) {
276
- meta._updateCount = (oldMeta._updateCount || 0) + 1;
277
- } else {
278
- meta._updateCount = oldMeta._updateCount || 0;
279
- }
280
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
281
- }
282
-
283
178
  // ===================== 幽灵技能清理 =====================
284
179
 
285
- // 幽灵技能清理(同步版)
286
- function cleanGhostSkillsSync(sourcePath) {
287
- const claudeDir = path.join(sourcePath, ".claude");
288
- const skillsDir = path.join(claudeDir, "skills");
289
- if (!fs.existsSync(skillsDir)) return { cleaned: false, reason: "skills 目录不存在" };
290
-
291
- const actualSkills = fs.existsSync(skillsDir) ? core.listSkillDirs(skillsDir) : [];
292
- const skillSet = new Set(actualSkills);
293
- const cleaned = { profiles: 0, alwaysApply: 0, settings: 0, removed: [] };
294
-
295
- const profilesPath = path.join(claudeDir, "profiles.json");
296
- if (fs.existsSync(profilesPath)) {
297
- try {
298
- const profilesData = JSON.parse(fs.readFileSync(profilesPath, "utf-8"));
299
- let changed = false;
300
- for (const profile of profilesData.profiles || []) {
301
- if (!profile.skills || !Array.isArray(profile.skills)) continue;
302
- const filtered = profile.skills.filter((s) => {
303
- const exists = skillSet.has(s);
304
- if (!exists) { cleaned.removed.push(s); changed = true; }
305
- return exists;
306
- });
307
- if (changed) profile.skills = filtered;
308
- }
309
- if (changed) {
310
- cleaned.profiles++;
311
- fs.writeFileSync(profilesPath, JSON.stringify(profilesData, null, 2) + "\n", "utf-8");
312
- }
313
- } catch {}
314
- }
315
-
316
- const settingsPathInRepo = path.join(claudeDir, "settings.json");
317
- if (fs.existsSync(settingsPathInRepo)) {
318
- try {
319
- const settings = JSON.parse(fs.readFileSync(settingsPathInRepo, "utf-8"));
320
- let changed = false;
321
- if (Array.isArray(settings.always_apply_skills)) {
322
- const filtered = settings.always_apply_skills.filter((s) => {
323
- const exists = skillSet.has(s);
324
- if (!exists) { if (!cleaned.removed.includes(s)) cleaned.removed.push(s); changed = true; }
325
- return exists;
326
- });
327
- if (changed) settings.always_apply_skills = filtered;
328
- }
329
- if (settings.skills && typeof settings.skills === "object") {
330
- for (const name of Object.keys(settings.skills)) {
331
- if (!skillSet.has(name)) {
332
- delete settings.skills[name];
333
- if (!cleaned.removed.includes(name)) cleaned.removed.push(name);
334
- changed = true;
335
- }
336
- }
337
- }
338
- if (changed) {
339
- cleaned.settings++;
340
- fs.writeFileSync(settingsPathInRepo, JSON.stringify(settings, null, 2) + "\n", "utf-8");
341
- }
342
- } catch {}
343
- }
344
-
345
- return cleaned;
346
- }
347
-
348
- // 幽灵技能清理(异步版)
349
180
  async function cleanGhostSkills(sourcePath) {
350
181
  const claudeDir = path.join(sourcePath, ".claude");
351
182
  const skillsDir = path.join(claudeDir, "skills");
352
183
  if (!(await pathExists(skillsDir))) return { cleaned: false, reason: "skills 目录不存在" };
353
184
 
354
- const actualSkills = await pathExists(skillsDir) ? core.listSkillDirs(skillsDir) : [];
185
+ const actualSkills = await pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
355
186
  const skillSet = new Set(actualSkills);
356
187
  const cleaned = { profiles: 0, alwaysApply: 0, settings: 0, removed: [] };
357
188
 
@@ -412,25 +243,20 @@ module.exports = {
412
243
  // HTTP
413
244
  sendJSON,
414
245
  parseBody,
415
- // 同步版
416
- safeRmSync,
417
- copyDirSync,
418
- cleanNpxTempSync,
419
- calcSkillFingerprintSync,
420
- writeSkillMetaSync,
421
- cleanGhostSkillsSync,
422
- // 异步版
246
+ // 文件系统
423
247
  pathExists,
424
248
  safeRm,
425
- copyDir,
249
+ copyDir: core.copyDir,
426
250
  cleanNpxTemp,
427
251
  getWindowsDrives,
428
- // 通用工具
252
+ // 技能解析
429
253
  stripAnsi,
430
254
  escapeShellArg,
431
255
  parseInstallCount,
432
256
  parseSkillsFindOutput,
257
+ // 指纹 & 元信息
433
258
  calcSkillFingerprint,
434
259
  writeSkillMeta,
260
+ // 幽灵清理
435
261
  cleanGhostSkills,
436
262
  };
@@ -8,6 +8,7 @@ const fs = require("fs");
8
8
 
9
9
  const { Router } = require("./server/router");
10
10
  const utils = require("./server/utils");
11
+ const { refreshSource } = require("./server/context");
11
12
 
12
13
  // ===================== 路由注册 =====================
13
14
 
@@ -136,14 +137,19 @@ async function startDev() {
136
137
 
137
138
  // ===================== 启动 =====================
138
139
 
139
- const distDir = path.join(__dirname, "dist");
140
- fs.stat(distDir, (err) => {
141
- if (!err) {
140
+ async function start() {
141
+ await refreshSource();
142
+ const distDir = path.join(__dirname, "dist");
143
+ let stats;
144
+ try { stats = await fs.promises.stat(distDir); } catch {}
145
+ if (stats) {
142
146
  startProduction(distDir);
143
147
  } else {
144
- startDev().catch((err) => {
145
- console.error("启动失败:", err);
146
- process.exit(1);
147
- });
148
+ await startDev();
148
149
  }
150
+ }
151
+
152
+ start().catch((err) => {
153
+ console.error("启动失败:", err);
154
+ process.exit(1);
149
155
  });