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.
package/scripts/core.js CHANGED
@@ -1,63 +1,80 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
- // 时间戳格式:YYYY-MM-DD HH:mm:ss
4
+ // ---------- 纯函数(无需 I/O,保持同步)----------
5
+
5
6
  function timestamp() {
6
7
  const d = new Date();
7
8
  const pad = (n) => String(n).padStart(2, "0");
8
9
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
9
10
  }
10
11
 
11
- // 紧凑时间戳用于备份目录名:YYYYMMDD.HHmmss
12
12
  function backupTimestamp() {
13
13
  const d = new Date();
14
14
  const pad = (n) => String(n).padStart(2, "0");
15
15
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}.${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
16
16
  }
17
17
 
18
- /**
19
- * 追加日志到 .claude/.install.log
20
- * 需要传入 logFile 路径
21
- */
22
- function appendLog(logFile, level, message) {
18
+ function isPlainObject(val) {
19
+ return Object.prototype.toString.call(val) === "[object Object]";
20
+ }
21
+
22
+ function deepMerge(userObj, pkgObj) {
23
+ const result = { ...userObj };
24
+ for (const key of Object.keys(pkgObj)) {
25
+ if (!(key in result)) {
26
+ result[key] = pkgObj[key];
27
+ } else if (isPlainObject(pkgObj[key]) && isPlainObject(result[key])) {
28
+ result[key] = deepMerge(result[key], pkgObj[key]);
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+
34
+ async function pathExists(p) {
35
+ try { await fs.promises.access(p); return true; } catch { return false; }
36
+ }
37
+
38
+ // ---------- 日志 ----------
39
+
40
+ async function appendLog(logFile, level, message) {
23
41
  const dir = path.dirname(logFile);
24
- if (!fs.existsSync(dir)) {
25
- fs.mkdirSync(dir, { recursive: true });
42
+ if (!(await pathExists(dir))) {
43
+ await fs.promises.mkdir(dir, { recursive: true });
26
44
  }
27
45
  const line = `[${timestamp()}] [${level}] ${message}\n`;
28
- fs.appendFileSync(logFile, line, "utf-8");
46
+ await fs.promises.appendFile(logFile, line, "utf-8");
29
47
  }
30
48
 
31
- /**
32
- * 备份已有目录到 dir/backups/<timestamp>/
33
- * 排除 backups/ 自身,清理超过 3 份的旧备份
34
- */
35
- function backupDir(dir) {
36
- if (!fs.existsSync(dir)) return null;
49
+ // ---------- 备份 ----------
50
+
51
+ async function backupDir(dir) {
52
+ if (!(await pathExists(dir))) return null;
37
53
 
38
54
  const backupsDir = path.join(dir, "backups");
39
- if (!fs.existsSync(backupsDir)) {
40
- fs.mkdirSync(backupsDir, { recursive: true });
55
+ if (!(await pathExists(backupsDir))) {
56
+ await fs.promises.mkdir(backupsDir, { recursive: true });
41
57
  }
42
58
 
43
59
  const ts = backupTimestamp();
44
60
  const bakPath = path.join(backupsDir, ts);
45
- fs.mkdirSync(bakPath, { recursive: true });
61
+ await fs.promises.mkdir(bakPath, { recursive: true });
46
62
 
47
- for (const item of fs.readdirSync(dir)) {
63
+ const items = await fs.promises.readdir(dir);
64
+ for (const item of items) {
48
65
  if (item === "backups") continue;
49
66
  const srcPath = path.join(dir, item);
50
67
  const destPath = path.join(bakPath, item);
51
- if (fs.statSync(srcPath).isDirectory()) {
52
- copyDirSync(srcPath, destPath);
68
+ const stat = await fs.promises.stat(srcPath);
69
+ if (stat.isDirectory()) {
70
+ await copyDir(srcPath, destPath);
53
71
  } else {
54
- fs.copyFileSync(srcPath, destPath);
72
+ await fs.promises.copyFile(srcPath, destPath);
55
73
  }
56
74
  }
57
75
 
58
76
  // 清理旧备份,只保留最近 3 份
59
- const backups = fs
60
- .readdirSync(backupsDir)
77
+ const backups = (await fs.promises.readdir(backupsDir))
61
78
  .filter((name) => /^\d{8}\.\d{6}$/.test(name))
62
79
  .sort()
63
80
  .reverse();
@@ -65,70 +82,52 @@ function backupDir(dir) {
65
82
  if (backups.length > 3) {
66
83
  for (const old of backups.slice(3)) {
67
84
  const oldPath = path.join(backupsDir, old);
68
- fs.rmSync(oldPath, { recursive: true, force: true });
85
+ await fs.promises.rm(oldPath, { recursive: true, force: true });
69
86
  }
70
87
  }
71
88
 
72
89
  return bakPath;
73
90
  }
74
91
 
75
- function isPlainObject(val) {
76
- return Object.prototype.toString.call(val) === "[object Object]";
77
- }
92
+ // ---------- 递归拷贝目录(异步,绕开 Windows fs.cpSync 中文路径 bug)----------
78
93
 
79
- /**
80
- * 手动递归拷贝目录,绕开 Windows 中文路径下 fs.cpSync({ recursive: true }) 死锁的 bug
81
- */
82
- function copyDirSync(src, dest) {
83
- fs.mkdirSync(dest, { recursive: true });
84
- for (const item of fs.readdirSync(src)) {
94
+ async function copyDir(src, dest) {
95
+ await fs.promises.mkdir(dest, { recursive: true });
96
+ const items = await fs.promises.readdir(src);
97
+ for (const item of items) {
85
98
  const srcPath = path.join(src, item);
86
99
  const destPath = path.join(dest, item);
87
- if (fs.statSync(srcPath).isDirectory()) {
88
- copyDirSync(srcPath, destPath);
100
+ const stat = await fs.promises.stat(srcPath);
101
+ if (stat.isDirectory()) {
102
+ await copyDir(srcPath, destPath);
89
103
  } else {
90
- fs.copyFileSync(srcPath, destPath);
104
+ await fs.promises.copyFile(srcPath, destPath);
91
105
  }
92
106
  }
93
107
  }
94
108
 
95
- /**
96
- * 递归深合并,用户值优先,目标新增字段补充
97
- */
98
- function deepMerge(userObj, pkgObj) {
99
- const result = { ...userObj };
109
+ // ---------- 列出技能目录 ----------
100
110
 
101
- for (const key of Object.keys(pkgObj)) {
102
- if (!(key in result)) {
103
- result[key] = pkgObj[key];
104
- } else if (isPlainObject(pkgObj[key]) && isPlainObject(result[key])) {
105
- result[key] = deepMerge(result[key], pkgObj[key]);
106
- }
111
+ async function listSkillDirs(skillsPath) {
112
+ if (!(await pathExists(skillsPath))) return [];
113
+ const names = await fs.promises.readdir(skillsPath);
114
+ const result = [];
115
+ for (const name of names) {
116
+ if (name.startsWith(".")) continue;
117
+ const fullPath = path.join(skillsPath, name);
118
+ const stat = await fs.promises.stat(fullPath);
119
+ if (stat.isDirectory()) result.push(name);
107
120
  }
108
-
109
121
  return result;
110
122
  }
111
123
 
112
- /**
113
- * 列出技能目录下的有效技能目录(排除非目录项和隐藏目录)
114
- */
115
- function listSkillDirs(skillsPath) {
116
- if (!fs.existsSync(skillsPath)) return [];
117
- return fs.readdirSync(skillsPath).filter((name) => {
118
- const fullPath = path.join(skillsPath, name);
119
- return fs.statSync(fullPath).isDirectory() && !name.startsWith(".");
120
- });
121
- }
124
+ // ---------- 解析 SKILL.md frontmatter ----------
122
125
 
123
- /**
124
- * 解析 SKILL.md 的 YAML frontmatter
125
- * 返回 { name?, description?, version?, tags? },解析失败返回 null
126
- */
127
- function parseFrontmatter(skillDir) {
126
+ async function parseFrontmatter(skillDir) {
128
127
  const mdPath = path.join(skillDir, "SKILL.md");
129
- if (!fs.existsSync(mdPath)) return null;
128
+ if (!(await pathExists(mdPath))) return null;
130
129
  let content;
131
- try { content = fs.readFileSync(mdPath, "utf-8"); } catch { return null; }
130
+ try { content = await fs.promises.readFile(mdPath, "utf-8"); } catch { return null; }
132
131
  content = content.replace(/\r\n/g, "\n");
133
132
  const match = content.match(/^---\n([\s\S]*?)\n---/);
134
133
  if (!match) return null;
@@ -157,18 +156,16 @@ function parseFrontmatter(skillDir) {
157
156
  return result;
158
157
  }
159
158
 
160
- /**
161
- * 读取技能元信息。
162
- * 优先从 .meta.json 读取;没有或解析失败时,从 SKILL.md frontmatter 兜底。
163
- */
164
- function readMeta(skillDir) {
159
+ // ---------- 读取技能元信息 ----------
160
+
161
+ async function readMeta(skillDir) {
165
162
  const metaPath = path.join(skillDir, ".meta.json");
166
- if (fs.existsSync(metaPath)) {
163
+ if (await pathExists(metaPath)) {
167
164
  try {
168
- return JSON.parse(fs.readFileSync(metaPath, "utf-8"));
165
+ return JSON.parse(await fs.promises.readFile(metaPath, "utf-8"));
169
166
  } catch {}
170
167
  }
171
- const fm = parseFrontmatter(skillDir);
168
+ const fm = await parseFrontmatter(skillDir);
172
169
  if (fm) {
173
170
  return {
174
171
  version: fm.version || "1.0.0",
@@ -179,43 +176,42 @@ function readMeta(skillDir) {
179
176
  return null;
180
177
  }
181
178
 
182
- /**
183
- * 安装技能:逐个复制,补缺不覆盖
184
- */
185
- function installSkills(skillsSource, skillsDest, logFile) {
186
- if (!fs.existsSync(skillsSource)) return [];
179
+ // ---------- 安装技能 ----------
180
+
181
+ async function installSkills(skillsSource, skillsDest, logFile) {
182
+ if (!(await pathExists(skillsSource))) return [];
187
183
 
188
- if (!fs.existsSync(skillsDest)) {
189
- fs.mkdirSync(skillsDest, { recursive: true });
184
+ if (!(await pathExists(skillsDest))) {
185
+ await fs.promises.mkdir(skillsDest, { recursive: true });
190
186
  }
191
187
 
192
- const skillDirs = listSkillDirs(skillsSource);
188
+ const skillDirs = await listSkillDirs(skillsSource);
193
189
  const results = [];
194
190
 
195
191
  for (const dir of skillDirs) {
196
192
  const srcSkill = path.join(skillsSource, dir);
197
193
  const destSkill = path.join(skillsDest, dir);
198
- const meta = readMeta(srcSkill);
194
+ const meta = await readMeta(srcSkill);
199
195
  const version = meta ? meta.version : "?";
200
196
 
201
- if (!fs.existsSync(destSkill)) {
202
- copyDirSync(srcSkill, destSkill);
203
- appendLog(logFile, "SKILL", `${dir} (${version}) → installed`);
197
+ if (!(await pathExists(destSkill))) {
198
+ await copyDir(srcSkill, destSkill);
199
+ await appendLog(logFile, "SKILL", `${dir} (${version}) → installed`);
204
200
  results.push({ name: dir, version, action: "installed" });
205
201
  } else {
206
- const destMeta = readMeta(destSkill);
202
+ const destMeta = await readMeta(destSkill);
207
203
  const needUpdate = meta && destMeta && meta.version !== destMeta.version;
208
204
 
209
205
  if (needUpdate) {
210
206
  const bakName = `${dir}.bak.${backupTimestamp()}`;
211
207
  const bakPath = path.join(skillsDest, bakName);
212
- copyDirSync(destSkill, bakPath);
213
- fs.rmSync(destSkill, { recursive: true, force: true });
214
- copyDirSync(srcSkill, destSkill);
215
- appendLog(logFile, "SKILL", `${dir} (${destMeta.version} → ${meta.version}) → updated`);
208
+ await copyDir(destSkill, bakPath);
209
+ await fs.promises.rm(destSkill, { recursive: true, force: true });
210
+ await copyDir(srcSkill, destSkill);
211
+ await appendLog(logFile, "SKILL", `${dir} (${destMeta.version} → ${meta.version}) → updated`);
216
212
  results.push({ name: dir, version: meta.version, action: "updated" });
217
213
  } else {
218
- appendLog(logFile, "SKILL", `${dir} (${version}) → skipped (exists)`);
214
+ await appendLog(logFile, "SKILL", `${dir} (${version}) → skipped (exists)`);
219
215
  results.push({ name: dir, version, action: "skipped" });
220
216
  }
221
217
  }
@@ -224,10 +220,9 @@ function installSkills(skillsSource, skillsDest, logFile) {
224
220
  return results;
225
221
  }
226
222
 
227
- /**
228
- * 安装 settings.json,深度合并
229
- */
230
- function installSettings(sourceDir, destDir, logFile) {
223
+ // ---------- 安装 settings.json ----------
224
+
225
+ async function installSettings(sourceDir, destDir, logFile) {
231
226
  const settingsSource = path.join(sourceDir, "settings.json");
232
227
  const settingsDest = path.join(destDir, "settings.json");
233
228
  const localSource = path.join(sourceDir, "settings.local.json");
@@ -237,84 +232,84 @@ function installSettings(sourceDir, destDir, logFile) {
237
232
  { src: settingsSource, dest: settingsDest, name: "settings.json" },
238
233
  { src: localSource, dest: localDest, name: "settings.local.json" },
239
234
  ]) {
240
- if (!fs.existsSync(src)) continue;
235
+ if (!(await pathExists(src))) continue;
241
236
 
242
- if (fs.existsSync(dest)) {
243
- const userData = JSON.parse(fs.readFileSync(dest, "utf-8"));
244
- const pkgData = JSON.parse(fs.readFileSync(src, "utf-8"));
237
+ if (await pathExists(dest)) {
238
+ const userData = JSON.parse(await fs.promises.readFile(dest, "utf-8"));
239
+ const pkgData = JSON.parse(await fs.promises.readFile(src, "utf-8"));
245
240
  const merged = deepMerge(userData, pkgData);
246
- fs.writeFileSync(dest, JSON.stringify(merged, null, 2) + "\n", "utf-8");
247
- appendLog(logFile, "SETTINGS", `${name} merged`);
241
+ await fs.promises.writeFile(dest, JSON.stringify(merged, null, 2) + "\n", "utf-8");
242
+ await appendLog(logFile, "SETTINGS", `${name} merged`);
248
243
  } else {
249
- fs.copyFileSync(src, dest);
250
- appendLog(logFile, "SETTINGS", `${name} created`);
244
+ await fs.promises.copyFile(src, dest);
245
+ await appendLog(logFile, "SETTINGS", `${name} created`);
251
246
  }
252
247
  }
253
248
  }
254
249
 
255
- /**
256
- * 获取包内的技能列表(带元信息)
257
- */
258
- function getPackageSkills(packageDir) {
250
+ // ---------- 获取包内技能列表 ----------
251
+
252
+ async function getPackageSkills(packageDir) {
259
253
  const skillsSource = path.join(packageDir, ".claude", "skills");
260
- return listSkillDirs(skillsSource).map((name) => {
261
- const meta = readMeta(path.join(skillsSource, name));
262
- return {
254
+ const names = await listSkillDirs(skillsSource);
255
+ const result = [];
256
+ for (const name of names) {
257
+ const meta = await readMeta(path.join(skillsSource, name));
258
+ result.push({
263
259
  name,
264
260
  version: meta ? meta.version : "?",
265
261
  description: meta ? meta.description : "",
266
262
  tags: meta ? meta.tags : [],
267
263
  _updateCount: meta ? (meta._updateCount || 0) : 0,
268
- };
269
- });
264
+ });
265
+ }
266
+ return result;
270
267
  }
271
268
 
272
- /**
273
- * 解析技能源路径:
274
- * settings.json 的 _skill_source 字段读取用户指定的本地技能目录
275
- * 没有设置则返回 null
276
- */
277
- function getSkillsSource(projectRoot) {
269
+ // ---------- 获取技能源路径 ----------
270
+
271
+ async function getSkillsSource(projectRoot) {
278
272
  const settingsPath = path.join(projectRoot, ".claude", "settings.json");
279
- if (fs.existsSync(settingsPath)) {
273
+ if (await pathExists(settingsPath)) {
280
274
  try {
281
- const s = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
275
+ const s = JSON.parse(await fs.promises.readFile(settingsPath, "utf-8"));
282
276
  if (s._skill_source) return s._skill_source;
283
277
  } catch {}
284
278
  }
285
279
  return null;
286
280
  }
287
281
 
288
- /**
289
- * 获取用户已安装的技能及其 settings 中的 enabled 状态
290
- */
291
- function getUserSkills(claudeDest) {
282
+ // ---------- 获取用户已安装技能 ----------
283
+
284
+ async function getUserSkills(claudeDest) {
292
285
  const skillsDest = path.join(claudeDest, "skills");
293
- if (!fs.existsSync(skillsDest)) return [];
286
+ if (!(await pathExists(skillsDest))) return [];
294
287
 
295
288
  const settingsPath = path.join(claudeDest, "settings.json");
296
289
  let settings = {};
297
- if (fs.existsSync(settingsPath)) {
290
+ if (await pathExists(settingsPath)) {
298
291
  try {
299
- settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
292
+ settings = JSON.parse(await fs.promises.readFile(settingsPath, "utf-8"));
300
293
  } catch {
301
294
  settings = {};
302
295
  }
303
296
  }
304
297
 
305
298
  const skillConfigs = settings.skills || {};
306
-
307
- return listSkillDirs(skillsDest).map((name) => {
308
- const meta = readMeta(path.join(skillsDest, name));
299
+ const names = await listSkillDirs(skillsDest);
300
+ const result = [];
301
+ for (const name of names) {
302
+ const meta = await readMeta(path.join(skillsDest, name));
309
303
  const config = skillConfigs[name] || {};
310
- return {
304
+ result.push({
311
305
  name,
312
306
  version: meta ? meta.version : "?",
313
307
  description: meta ? meta.description : config.description || "",
314
308
  tags: meta ? meta.tags : [],
315
309
  enabled: config.enabled !== false,
316
- };
317
- });
310
+ });
311
+ }
312
+ return result;
318
313
  }
319
314
 
320
315
  module.exports = {
@@ -322,7 +317,7 @@ module.exports = {
322
317
  backupTimestamp,
323
318
  appendLog,
324
319
  backupDir,
325
- copyDirSync,
320
+ copyDir,
326
321
  deepMerge,
327
322
  listSkillDirs,
328
323
  readMeta,
@@ -2,7 +2,7 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const core = require("../../core");
4
4
 
5
- // ---------- 路径(与 cli.js 保持一致) ----------
5
+ // ---------- 路径(与 cli.js 保持一致)----------
6
6
 
7
7
  const projectRoot = (() => {
8
8
  try {
@@ -12,11 +12,12 @@ const projectRoot = (() => {
12
12
  }
13
13
  })();
14
14
 
15
- let skillsSource = core.getSkillsSource(projectRoot);
15
+ // 初始为 null,refreshSource() 执行后赋值
16
+ let skillsSource = null;
16
17
  const claudeDest = path.join(projectRoot, ".claude");
17
18
  const skillsDest = path.join(claudeDest, "skills");
18
19
  const settingsPath = path.join(claudeDest, "settings.json");
19
- let pkgSkillsSource = skillsSource ? path.join(skillsSource, ".claude", "skills") : null;
20
+ let pkgSkillsSource = null;
20
21
 
21
22
  // skills 包二进制路径
22
23
  const skillsBin = (() => {
@@ -36,13 +37,17 @@ const skillsBin = (() => {
36
37
  return `"${path.join(binDir, process.platform === "win32" ? "skills.cmd" : "skills")}"`;
37
38
  })();
38
39
 
39
- function refreshSource() {
40
- skillsSource = core.getSkillsSource(projectRoot);
40
+ async function refreshSource() {
41
+ skillsSource = await core.getSkillsSource(projectRoot);
41
42
  pkgSkillsSource = skillsSource ? path.join(skillsSource, ".claude", "skills") : null;
42
43
  }
43
44
 
44
45
  // ---------- Settings(异步)----------
45
46
 
47
+ async function pathExists(p) {
48
+ try { await fs.promises.access(p); return true; } catch { return false; }
49
+ }
50
+
46
51
  async function readSettings() {
47
52
  if (!(await pathExists(settingsPath)))
48
53
  return { skills: {}, always_apply_skills: [] };
@@ -103,6 +108,7 @@ function requireSource(res, utils) {
103
108
  // ---------- profiles ----------
104
109
 
105
110
  async function loadProfiles() {
111
+ if (!skillsSource) return null;
106
112
  const profilesPath = path.join(skillsSource, ".claude", "profiles.json");
107
113
  if (!(await pathExists(profilesPath))) return null;
108
114
  try {
@@ -120,16 +126,10 @@ const runningTasks = new Map();
120
126
 
121
127
  const pullCache = new Map();
122
128
 
123
- // ---------- 路径检查工具 ----------
124
-
125
- async function pathExists(p) {
126
- try { await fs.promises.access(p); return true; } catch { return false; }
127
- }
128
-
129
129
  // ---------- 应用场景 / 安装(异步)----------
130
130
 
131
131
  async function applyProfile(profile, { copyDir, safeRm }) {
132
- const pkgSkills = core.listSkillDirs(pkgSkillsSource);
132
+ const pkgSkills = await core.listSkillDirs(pkgSkillsSource);
133
133
  const failedDelete = [];
134
134
  const failedInstall = [];
135
135
 
@@ -209,7 +209,7 @@ async function installSelectedSkills(selectedNames, { copyDir, safeRm }) {
209
209
  if (!(await pathExists(skillsDest))) {
210
210
  await fs.promises.mkdir(skillsDest, { recursive: true });
211
211
  }
212
- const allPkgSkills = core.listSkillDirs(pkgSkillsSource);
212
+ const allPkgSkills = await core.listSkillDirs(pkgSkillsSource);
213
213
  let installedCount = 0;
214
214
  const locked = [];
215
215
 
@@ -274,4 +274,4 @@ function buildContext(utils) {
274
274
  };
275
275
  }
276
276
 
277
- module.exports = { buildContext };
277
+ module.exports = { buildContext, refreshSource };
@@ -72,7 +72,7 @@ function register(router) {
72
72
 
73
73
  const skillsDir = path.join(sourcePath, ".claude", "skills");
74
74
  const skillNames = await pathExists(skillsDir)
75
- ? core.listSkillDirs(skillsDir)
75
+ ? await core.listSkillDirs(skillsDir)
76
76
  : [];
77
77
  const skillsList = skillNames.length
78
78
  ? skillNames.map((s) => `- \`${s}\``).join("\n")
@@ -160,7 +160,7 @@ ${skillsList}
160
160
  return utils.sendJSON(res, { error: "未设置技能目录,请在设置中连接" }, 400);
161
161
  }
162
162
  const results = [];
163
- const pkgSkills = core.getPackageSkills(ctx.skillsSource);
163
+ const pkgSkills = await core.getPackageSkills(ctx.skillsSource);
164
164
  for (const skill of pkgSkills) {
165
165
  const skillDir = path.join(ctx.skillsDest, skill.name);
166
166
  const issues = [];
@@ -169,7 +169,7 @@ ${skillsList}
169
169
  } else {
170
170
  if (!(await pathExists(path.join(skillDir, "SKILL.md")))) {
171
171
  issues.push("缺少 SKILL.md");
172
- } else if (!core.parseFrontmatter(skillDir)) {
172
+ } else if (!(await core.parseFrontmatter(skillDir))) {
173
173
  issues.push("SKILL.md frontmatter 格式异常");
174
174
  }
175
175
  }
@@ -234,7 +234,7 @@ ${skillsList}
234
234
  const valid = await pathExists(skillsDir);
235
235
  let skillCount = 0;
236
236
  if (valid) {
237
- skillCount = core.listSkillDirs(skillsDir).length;
237
+ skillCount = (await core.listSkillDirs(skillsDir)).length;
238
238
  }
239
239
  return utils.sendJSON(res, {
240
240
  ok: true,
@@ -9,6 +9,16 @@ const { buildContext } = require("../context");
9
9
 
10
10
  const ctx = buildContext(utils);
11
11
 
12
+ // 跨平台打开文件管理器
13
+ function openInExplorer(dirPath) {
14
+ const cmd = process.platform === "win32"
15
+ ? `start "" "${dirPath}"`
16
+ : process.platform === "darwin"
17
+ ? `open "${dirPath}"`
18
+ : `xdg-open "${dirPath}"`;
19
+ execSync(cmd, { stdio: "ignore" });
20
+ }
21
+
12
22
  async function pathExists(p) {
13
23
  try { await fs.promises.access(p); return true; } catch { return false; }
14
24
  }
@@ -84,9 +94,9 @@ function register(router) {
84
94
  // ----------------------------------------------------------------
85
95
  router.get("/api/skills", async (req, res) => {
86
96
  if (!requireSource(res)) return;
87
- const pkgSkills = core.getPackageSkills(ctx.skillsSource);
97
+ const pkgSkills = await core.getPackageSkills(ctx.skillsSource);
88
98
  const pkgNames = new Set(pkgSkills.map((s) => s.name));
89
- const userSkills = core.getUserSkills(ctx.claudeDest);
99
+ const userSkills = await core.getUserSkills(ctx.claudeDest);
90
100
  let cleaned = false;
91
101
  for (const us of userSkills) {
92
102
  if (!pkgNames.has(us.name)) {
@@ -106,7 +116,7 @@ function register(router) {
106
116
  }
107
117
  }
108
118
  if (settingsChanged) await ctx.writeSettings(settings);
109
- const refreshed = core.getUserSkills(ctx.claudeDest);
119
+ const refreshed = await core.getUserSkills(ctx.claudeDest);
110
120
  const userMap = new Map(refreshed.map((s) => [s.name, s]));
111
121
  const pullSource = await ctx.readPullSource();
112
122
  const result = pkgSkills.map((s) => ({
@@ -177,7 +187,7 @@ function register(router) {
177
187
  const cached = ctx.pullCache.get(url);
178
188
  if (cached) {
179
189
  const skillsDir = path.join(cached.tmpDir, ".claude", "skills");
180
- const skills = await pathExists(skillsDir) ? core.listSkillDirs(skillsDir) : [];
190
+ const skills = await pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
181
191
  return utils.sendJSON(res, { ok: true, skills, total: skills.length });
182
192
  }
183
193
  const tmpDir = path.join(os.tmpdir(), "mdk-preview-" + Date.now());
@@ -192,7 +202,7 @@ function register(router) {
192
202
  }
193
203
  await utils.cleanNpxTemp();
194
204
  const skillsDir = path.join(tmpDir, ".claude", "skills");
195
- const skills = await pathExists(skillsDir) ? core.listSkillDirs(skillsDir) : [];
205
+ const skills = await pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
196
206
  if (skills.length === 0) {
197
207
  await utils.safeRm(tmpDir, "临时目录");
198
208
  return utils.sendJSON(res, { error: "未找到有效技能" }, 400);
@@ -566,7 +576,7 @@ function register(router) {
566
576
  return utils.sendJSON(res, { error: "技能目录不存在" }, 404);
567
577
  }
568
578
  try {
569
- execSync("start \"\" \"" + skillDir + "\"", { stdio: "ignore" });
579
+ openInExplorer(skillDir);
570
580
  return utils.sendJSON(res, { ok: true });
571
581
  } catch {
572
582
  return utils.sendJSON(res, { error: "打开目录失败" }, 500);
@@ -614,7 +624,7 @@ function register(router) {
614
624
  router.post("/api/skills-dest/open", async (req, res) => {
615
625
  if (!requireSource(res)) return;
616
626
  try {
617
- execSync("start \"\" \"" + ctx.skillsDest + "\"", { stdio: "ignore" });
627
+ openInExplorer(ctx.skillsDest);
618
628
  return utils.sendJSON(res, { ok: true });
619
629
  } catch {
620
630
  return utils.sendJSON(res, { error: "打开目录失败" }, 500);
@@ -17,8 +17,8 @@ function register(router) {
17
17
  // ----------------------------------------------------------------
18
18
  router.get("/api/status", async (req, res) => {
19
19
  const settings = await ctx.readSettings();
20
- const userSkills = core.getUserSkills(ctx.claudeDest);
21
- const pkgSkills = ctx.skillsSource ? core.getPackageSkills(ctx.skillsSource) : [];
20
+ const userSkills = await core.getUserSkills(ctx.claudeDest);
21
+ const pkgSkills = ctx.skillsSource ? await core.getPackageSkills(ctx.skillsSource) : [];
22
22
  const profiles = ctx.skillsSource ? await ctx.loadProfiles() : null;
23
23
  const activeProfile = profiles?.find(
24
24
  (p) => p.id === settings._active_profile,
@@ -58,7 +58,7 @@ function register(router) {
58
58
  }
59
59
  const skillsDir = path.join(claudeDir, "skills");
60
60
  if (await pathExists(skillsDir)) {
61
- for (const name of core.listSkillDirs(skillsDir)) {
61
+ for (const name of await core.listSkillDirs(skillsDir)) {
62
62
  if (!(await pathExists(path.join(skillsDir, name, "SKILL.md")))) {
63
63
  missingSkillDocs.push(name);
64
64
  }
@@ -150,14 +150,14 @@ function register(router) {
150
150
  }
151
151
 
152
152
  const repoClaude = path.join(sourcePath, ".claude");
153
- core.backupDir(repoClaude);
153
+ await core.backupDir(repoClaude);
154
154
 
155
155
  // 技能反推
156
156
  const repoSkills = path.join(sourcePath, ".claude", "skills");
157
157
  if (!(await pathExists(repoSkills)))
158
158
  await fs.promises.mkdir(repoSkills, { recursive: true });
159
159
  if (await pathExists(ctx.skillsDest)) {
160
- for (const name of core.listSkillDirs(ctx.skillsDest)) {
160
+ for (const name of await core.listSkillDirs(ctx.skillsDest)) {
161
161
  const src = path.join(ctx.skillsDest, name);
162
162
  const dest = path.join(repoSkills, name);
163
163
  if (await pathExists(dest))
@@ -245,7 +245,7 @@ function register(router) {
245
245
 
246
246
  // 3. 每个 skill 目录的 .meta.json
247
247
  if (await pathExists(skillsDir)) {
248
- for (const name of core.listSkillDirs(skillsDir)) {
248
+ for (const name of await core.listSkillDirs(skillsDir)) {
249
249
  const skillDir = path.join(skillsDir, name);
250
250
  const metaPath = path.join(skillDir, ".meta.json");
251
251
  if (!(await pathExists(metaPath))) {