mdk-skills 2.4.19 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdk-skills",
3
- "version": "2.4.19",
3
+ "version": "2.4.21",
4
4
  "description": "mdk-engineer - 沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
5
5
  "author": "XiaoMa",
6
6
  "license": "MIT",
package/scripts/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
- const { getPackageSkills, getUserSkills, getSkillsSource, backupDir, listSkillDirs } = require("./core");
5
+ const { getPackageSkills, getUserSkills, getSkillsSource, backupDir, listSkillDirs, copyDir } = require("./core");
6
6
 
7
7
  // npx 运行时:process.cwd() 是用户项目目录
8
8
  // __dirname 是包内 scripts/ 目录
@@ -14,31 +14,44 @@ const projectRoot = (() => {
14
14
  return process.cwd();
15
15
  }
16
16
  })();
17
- let skillsSource = getSkillsSource(projectRoot);
18
- const claudeDest = path.join(projectRoot, ".claude");
19
- const skillsDest = path.join(claudeDest, "skills");
20
- const settingsPath = path.join(claudeDest, "settings.json");
21
- const pkgSkillsSource = skillsSource
22
- ? path.join(skillsSource, ".claude", "skills")
23
- : null;
17
+
18
+ let skillsSource;
19
+ let claudeDest;
20
+ let skillsDest;
21
+ let settingsPath;
22
+ let pkgSkillsSource;
23
+
24
+ async function pathExists(p) {
25
+ try { await fs.promises.access(p); return true; } catch { return false; }
26
+ }
27
+
28
+ async function initPaths() {
29
+ skillsSource = await getSkillsSource(projectRoot);
30
+ claudeDest = path.join(projectRoot, ".claude");
31
+ skillsDest = path.join(claudeDest, "skills");
32
+ settingsPath = path.join(claudeDest, "settings.json");
33
+ pkgSkillsSource = skillsSource
34
+ ? path.join(skillsSource, ".claude", "skills")
35
+ : null;
36
+ }
24
37
 
25
38
  // ---------- 文件写入 ----------
26
39
 
27
- function readSettings() {
28
- if (!fs.existsSync(settingsPath))
40
+ async function readSettings() {
41
+ if (!(await pathExists(settingsPath)))
29
42
  return { skills: {}, always_apply_skills: [] };
30
43
  try {
31
- return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
44
+ return JSON.parse(await fs.promises.readFile(settingsPath, "utf-8"));
32
45
  } catch {
33
46
  return { skills: {}, always_apply_skills: [] };
34
47
  }
35
48
  }
36
49
 
37
- function writeSettings(settings) {
38
- if (!fs.existsSync(claudeDest)) {
39
- fs.mkdirSync(claudeDest, { recursive: true });
50
+ async function writeSettings(settings) {
51
+ if (!(await pathExists(claudeDest))) {
52
+ await fs.promises.mkdir(claudeDest, { recursive: true });
40
53
  }
41
- fs.writeFileSync(
54
+ await fs.promises.writeFile(
42
55
  settingsPath,
43
56
  JSON.stringify(settings, null, 2) + "\n",
44
57
  "utf-8",
@@ -47,52 +60,35 @@ function writeSettings(settings) {
47
60
 
48
61
  // ---------- 安装勾选的技能 ----------
49
62
 
50
- // 手动递归拷贝目录,绕开 Windows 中文路径下 fs.cpSync({ recursive: true }) 死锁的 bug
51
- function copyDirSync(src, dest) {
52
- fs.mkdirSync(dest, { recursive: true });
53
- for (const item of fs.readdirSync(src)) {
54
- const srcPath = path.join(src, item);
55
- const destPath = path.join(dest, item);
56
- if (fs.statSync(srcPath).isDirectory()) {
57
- copyDirSync(srcPath, destPath);
58
- } else {
59
- fs.copyFileSync(srcPath, destPath);
60
- }
63
+ async function installSelectedSkills(selectedNames) {
64
+ if (!(await pathExists(skillsDest))) {
65
+ await fs.promises.mkdir(skillsDest, { recursive: true });
61
66
  }
62
- }
63
67
 
64
- function installSelectedSkills(selectedNames) {
65
- // 创建 .claude/ 和 skills/ 目录
66
- if (!fs.existsSync(skillsDest)) {
67
- fs.mkdirSync(skillsDest, { recursive: true });
68
- }
69
-
70
- const allPkgSkills = listSkillDirs(pkgSkillsSource); let installedCount = 0;
68
+ const allPkgSkills = await listSkillDirs(pkgSkillsSource);
69
+ let installedCount = 0;
71
70
 
72
71
  for (const name of allPkgSkills) {
73
72
  const src = path.join(pkgSkillsSource, name);
74
73
  const dest = path.join(skillsDest, name);
75
74
 
76
75
  if (selectedNames.includes(name)) {
77
- // 勾选了的 安装(不存在才装)
78
- if (!fs.existsSync(dest)) {
76
+ if (!(await pathExists(dest))) {
79
77
  try {
80
- copyDirSync(src, dest);
78
+ await copyDir(src, dest);
81
79
  installedCount++;
82
80
  } catch (err) {
83
81
  console.error(` ❌ 安装技能 "${name}" 失败:`, err.message);
84
82
  }
85
83
  }
86
84
  } else {
87
- // 没勾选的 如果之前装了,删掉
88
- if (fs.existsSync(dest)) {
89
- fs.rmSync(dest, { recursive: true, force: true });
85
+ if (await pathExists(dest)) {
86
+ await fs.promises.rm(dest, { recursive: true, force: true });
90
87
  }
91
88
  }
92
89
  }
93
90
 
94
- // 生成 settings.json
95
- const settings = readSettings();
91
+ const settings = await readSettings();
96
92
  if (!settings.skills) settings.skills = {};
97
93
 
98
94
  for (const name of allPkgSkills) {
@@ -103,21 +99,19 @@ function installSelectedSkills(selectedNames) {
103
99
  settings.skills[name].enabled = enabled;
104
100
  }
105
101
 
106
- writeSettings(settings);
102
+ await writeSettings(settings);
107
103
 
108
- // 安装 CLAUDE.md(没有才装)
109
104
  const mdSource = path.join(skillsSource, "CLAUDE.md");
110
105
  const mdDest = path.join(projectRoot, "CLAUDE.md");
111
- if (fs.existsSync(mdSource) && !fs.existsSync(mdDest)) {
112
- fs.copyFileSync(mdSource, mdDest);
106
+ if (await pathExists(mdSource) && !(await pathExists(mdDest))) {
107
+ await fs.promises.copyFile(mdSource, mdDest);
113
108
  console.log(" 📝 CLAUDE.md 已创建\n");
114
109
  }
115
110
 
116
- // 复制 profiles.json 到项目
117
111
  const profilesSource = path.join(skillsSource, ".claude", "profiles.json");
118
112
  const profilesDest = path.join(claudeDest, "profiles.json");
119
- if (fs.existsSync(profilesSource) && !fs.existsSync(profilesDest)) {
120
- fs.copyFileSync(profilesSource, profilesDest);
113
+ if (await pathExists(profilesSource) && !(await pathExists(profilesDest))) {
114
+ await fs.promises.copyFile(profilesSource, profilesDest);
121
115
  }
122
116
 
123
117
  return installedCount;
@@ -125,55 +119,50 @@ function installSelectedSkills(selectedNames) {
125
119
 
126
120
  // ---------- 场景选择(场景 → 一键装技能) ----------
127
121
 
128
- function loadProfiles() {
122
+ async function loadProfiles() {
129
123
  const profilesPath = path.join(skillsSource, ".claude", "profiles.json");
130
- if (!fs.existsSync(profilesPath)) return null;
124
+ if (!(await pathExists(profilesPath))) return null;
131
125
  try {
132
- return JSON.parse(fs.readFileSync(profilesPath, "utf-8")).profiles;
126
+ return JSON.parse(await fs.promises.readFile(profilesPath, "utf-8")).profiles;
133
127
  } catch {
134
128
  return null;
135
129
  }
136
130
  }
137
131
 
138
- function applyProfile(profile) {
139
- const pkgSkills = listSkillDirs(pkgSkillsSource);
140
- const settings = readSettings();
132
+ async function applyProfile(profile) {
133
+ const pkgSkills = await listSkillDirs(pkgSkillsSource);
134
+ const settings = await readSettings();
141
135
 
142
- // 确定要启用的技能列表
143
136
  let selected;
144
137
  if (profile.skills === null) {
145
- // null 表示"自定义",不做操作,由调用方处理
146
138
  return;
147
139
  } else if (profile.skills.length === 0) {
148
140
  selected = [];
149
141
  } else {
150
- // 只取包内实际存在的技能
151
142
  selected = profile.skills.filter((s) => pkgSkills.includes(s));
152
143
  }
153
144
 
154
- // 装启用的、删禁用的
155
- if (!fs.existsSync(skillsDest)) {
156
- fs.mkdirSync(skillsDest, { recursive: true });
145
+ if (!(await pathExists(skillsDest))) {
146
+ await fs.promises.mkdir(skillsDest, { recursive: true });
157
147
  }
158
148
  for (const name of pkgSkills) {
159
149
  const src = path.join(pkgSkillsSource, name);
160
150
  const dest = path.join(skillsDest, name);
161
151
  if (selected.includes(name)) {
162
- if (!fs.existsSync(dest)) {
152
+ if (!(await pathExists(dest))) {
163
153
  try {
164
- copyDirSync(src, dest);
154
+ await copyDir(src, dest);
165
155
  } catch (err) {
166
156
  console.error(` ❌ 安装技能 "${name}" 失败:`, err.message);
167
157
  }
168
158
  }
169
159
  } else {
170
- if (fs.existsSync(dest)) {
171
- fs.rmSync(dest, { recursive: true, force: true });
160
+ if (await pathExists(dest)) {
161
+ await fs.promises.rm(dest, { recursive: true, force: true });
172
162
  }
173
163
  }
174
164
  }
175
165
 
176
- // 更新 settings
177
166
  if (!settings.skills) settings.skills = {};
178
167
  for (const name of pkgSkills) {
179
168
  const enabled = selected.includes(name);
@@ -181,31 +170,26 @@ function applyProfile(profile) {
181
170
  settings.skills[name].enabled = enabled;
182
171
  }
183
172
 
184
- // 更新 always_apply_skills
185
173
  if (profile.always_apply) {
186
174
  settings.always_apply_skills = profile.always_apply;
187
175
  }
188
176
 
189
- // 记录当前活动场景
190
177
  settings._active_profile = profile.id;
191
- writeSettings(settings);
178
+ await writeSettings(settings);
192
179
 
193
- // 安装 CLAUDE.md(没有才装)
194
180
  const mdSource = path.join(skillsSource, "CLAUDE.md");
195
181
  const mdDest = path.join(projectRoot, "CLAUDE.md");
196
- if (fs.existsSync(mdSource) && !fs.existsSync(mdDest)) {
197
- fs.copyFileSync(mdSource, mdDest);
182
+ if (await pathExists(mdSource) && !(await pathExists(mdDest))) {
183
+ await fs.promises.copyFile(mdSource, mdDest);
198
184
  console.log(" 📝 CLAUDE.md 已创建\n");
199
185
  }
200
186
 
201
- // 复制 profiles.json 到项目(方便在项目里修改后 sync 推回)
202
187
  const profilesSource = path.join(skillsSource, ".claude", "profiles.json");
203
188
  const profilesDest = path.join(claudeDest, "profiles.json");
204
- if (fs.existsSync(profilesSource)) {
205
- fs.copyFileSync(profilesSource, profilesDest);
189
+ if (await pathExists(profilesSource)) {
190
+ await fs.promises.copyFile(profilesSource, profilesDest);
206
191
  }
207
192
 
208
- // 输出结果
209
193
  const enabledCount = selected.length;
210
194
  const disabledCount = pkgSkills.length - enabledCount;
211
195
  console.log(`\n ✅ 已切换到「${profile.name}」`);
@@ -215,10 +199,10 @@ function applyProfile(profile) {
215
199
  async function startSceneSelection() {
216
200
  const { select } = await import("@inquirer/prompts");
217
201
  const chalk = (await import("chalk")).default;
218
- const profiles = loadProfiles();
219
- const settings = readSettings();
202
+ const profiles = await loadProfiles();
203
+ const settings = await readSettings();
220
204
  const activeProfile = settings._active_profile;
221
- const hasExistingInstall = fs.existsSync(claudeDest);
205
+ const hasExistingInstall = await pathExists(claudeDest);
222
206
 
223
207
  const choices = profiles.map((p) => {
224
208
  const isActive = p.id === activeProfile;
@@ -232,7 +216,6 @@ async function startSceneSelection() {
232
216
  };
233
217
  });
234
218
 
235
- // 底部加个分隔
236
219
  choices.push(
237
220
  { name: chalk.dim("────────────────"), value: "__sep__", description: "" },
238
221
  { name: " 查看技能清单", value: "__list__", description: "列出所有技能状态" },
@@ -254,7 +237,6 @@ async function startSceneSelection() {
254
237
 
255
238
  if (selectedId === "__list__") {
256
239
  await cmdList();
257
- // 看完列表再回到场景选择
258
240
  console.log("");
259
241
  return startSceneSelection();
260
242
  }
@@ -262,13 +244,11 @@ async function startSceneSelection() {
262
244
  const profile = profiles.find((p) => p.id === selectedId);
263
245
 
264
246
  if (profile.skills === null) {
265
- // "自定义选择" → 跳到 checkbox
266
247
  await startInteractiveMenu();
267
248
  return;
268
249
  }
269
250
 
270
251
  if (profile.id === activeProfile) {
271
- // 选的已经是当前场景,问要不要微调
272
252
  const { confirm } = await import("@inquirer/prompts");
273
253
  const tweak = await confirm({
274
254
  message: "已是当前场景,是否进入自定义模式微调技能?",
@@ -280,20 +260,20 @@ async function startSceneSelection() {
280
260
  return;
281
261
  }
282
262
 
283
- applyProfile(profile);
263
+ await applyProfile(profile);
284
264
  }
285
265
 
286
266
  // ---------- 命令:list ----------
287
267
 
288
268
  async function cmdList() {
289
- if (!fs.existsSync(claudeDest)) {
269
+ if (!(await pathExists(claudeDest))) {
290
270
  console.log("\n ⚠️ 尚未安装技能,请先运行 npx mdk-skills\n");
291
271
  return;
292
272
  }
293
273
 
294
274
  const chalk = (await import("chalk")).default;
295
- const pkgSkills = getPackageSkills(skillsSource);
296
- const userSkills = getUserSkills(claudeDest);
275
+ const pkgSkills = await getPackageSkills(skillsSource);
276
+ const userSkills = await getUserSkills(claudeDest);
297
277
 
298
278
  const skillMap = new Map();
299
279
  for (const s of userSkills) skillMap.set(s.name, s);
@@ -333,27 +313,25 @@ async function cmdList() {
333
313
  async function startInteractiveMenu() {
334
314
  const { checkbox } = await import("@inquirer/prompts");
335
315
 
336
- if (!fs.existsSync(pkgSkillsSource)) {
316
+ if (!(await pathExists(pkgSkillsSource))) {
337
317
  console.log(" ⚠️ 包内没有找到技能文件\n");
338
318
  return;
339
319
  }
340
320
 
341
- const pkgSkills = getPackageSkills(skillsSource);
321
+ const pkgSkills = await getPackageSkills(skillsSource);
342
322
 
343
323
  if (pkgSkills.length === 0) {
344
324
  console.log(" ⚠️ 没有可用技能\n");
345
325
  return;
346
326
  }
347
327
 
348
- // 读取已安装状态:已装且启用的默认勾选
349
- const userSkills = getUserSkills(claudeDest);
328
+ const userSkills = await getUserSkills(claudeDest);
350
329
  const enabledSet = new Set(
351
330
  userSkills.filter((s) => s.enabled).map((s) => s.name),
352
331
  );
353
332
  const installedSet = new Set(userSkills.map((s) => s.name));
354
333
 
355
- // 首次运行全部默认勾选(省事),再次运行沿用已有状态
356
- const hasExistingInstall = fs.existsSync(claudeDest);
334
+ const hasExistingInstall = await pathExists(claudeDest);
357
335
  const defaultChecked = hasExistingInstall
358
336
  ? enabledSet
359
337
  : new Set(pkgSkills.map((s) => s.name));
@@ -378,7 +356,7 @@ async function startInteractiveMenu() {
378
356
  (name) => !selected.includes(name),
379
357
  ).length;
380
358
 
381
- const installed = installSelectedSkills(selected);
359
+ const installed = await installSelectedSkills(selected);
382
360
 
383
361
  const parts = [];
384
362
  if (newCount > 0) parts.push(`新装 ${newCount} 个`);
@@ -390,7 +368,7 @@ async function startInteractiveMenu() {
390
368
 
391
369
  // ---------- 本地源连接管理 ----------
392
370
 
393
- function cmdConnect(repoPath) {
371
+ async function cmdConnect(repoPath) {
394
372
  if (!repoPath) {
395
373
  console.log(" ⚠️ 用法:npx mdk-skills connect <仓库路径>\n");
396
374
  return;
@@ -398,20 +376,20 @@ function cmdConnect(repoPath) {
398
376
 
399
377
  repoPath = path.resolve(repoPath);
400
378
 
401
- if (!fs.existsSync(path.join(repoPath, ".claude", "skills"))) {
379
+ if (!(await pathExists(path.join(repoPath, ".claude", "skills")))) {
402
380
  console.log(` ❌ 路径 "${repoPath}" 下没有找到 .claude/skills/\n`);
403
381
  return;
404
382
  }
405
383
 
406
- const settings = readSettings();
384
+ const settings = await readSettings();
407
385
  settings._skill_source = repoPath;
408
- writeSettings(settings);
386
+ await writeSettings(settings);
409
387
 
410
388
  console.log(` ✅ 技能目录已设置: ${repoPath}\n`);
411
389
  }
412
390
 
413
- function cmdSync() {
414
- const settings = readSettings();
391
+ async function cmdSync() {
392
+ const settings = await readSettings();
415
393
  const sourcePath = settings._skill_source;
416
394
 
417
395
  if (!sourcePath) {
@@ -419,58 +397,53 @@ function cmdSync() {
419
397
  return;
420
398
  }
421
399
 
422
- if (!fs.existsSync(path.join(sourcePath, ".claude"))) {
400
+ if (!(await pathExists(path.join(sourcePath, ".claude")))) {
423
401
  console.log(` ❌ 绑定的路径 "${sourcePath}" 已失效\n`);
424
402
  return;
425
403
  }
426
404
 
427
- // 备份仓库旧内容
428
405
  const repoClaude = path.join(sourcePath, ".claude");
429
- if (fs.existsSync(repoClaude)) {
430
- backupDir(repoClaude);
406
+ if (await pathExists(repoClaude)) {
407
+ await backupDir(repoClaude);
431
408
  }
432
409
 
433
- // 技能 → 反推到仓库
434
410
  const repoSkills = path.join(sourcePath, ".claude", "skills");
435
- if (!fs.existsSync(repoSkills)) {
436
- fs.mkdirSync(repoSkills, { recursive: true });
411
+ if (!(await pathExists(repoSkills))) {
412
+ await fs.promises.mkdir(repoSkills, { recursive: true });
437
413
  }
438
- for (const name of listSkillDirs(skillsDest)) {
414
+ for (const name of await listSkillDirs(skillsDest)) {
439
415
  const src = path.join(skillsDest, name);
440
416
  const dest = path.join(repoSkills, name);
441
- if (fs.existsSync(dest)) {
442
- fs.rmSync(dest, { recursive: true, force: true });
417
+ if (await pathExists(dest)) {
418
+ await fs.promises.rm(dest, { recursive: true, force: true });
443
419
  }
444
- copyDirSync(src, dest);
420
+ await copyDir(src, dest);
445
421
  }
446
422
 
447
- // profiles.json → 反推
448
423
  const projectProfiles = path.join(claudeDest, "profiles.json");
449
- if (fs.existsSync(projectProfiles)) {
450
- fs.copyFileSync(projectProfiles, path.join(sourcePath, ".claude", "profiles.json"));
424
+ if (await pathExists(projectProfiles)) {
425
+ await fs.promises.copyFile(projectProfiles, path.join(sourcePath, ".claude", "profiles.json"));
451
426
  }
452
427
 
453
- // settings.json → 反推(过滤掉 _skill_source 和 _active_profile)
454
428
  const cleanSettings = { ...settings };
455
429
  delete cleanSettings._skill_source;
456
430
  delete cleanSettings._active_profile;
457
- fs.writeFileSync(
431
+ await fs.promises.writeFile(
458
432
  path.join(sourcePath, ".claude", "settings.json"),
459
433
  JSON.stringify(cleanSettings, null, 2) + "\n",
460
434
  "utf-8",
461
435
  );
462
436
 
463
- // CLAUDE.md → 反推
464
437
  const projectMd = path.join(projectRoot, "CLAUDE.md");
465
- if (fs.existsSync(projectMd)) {
466
- fs.copyFileSync(projectMd, path.join(sourcePath, "CLAUDE.md"));
438
+ if (await pathExists(projectMd)) {
439
+ await fs.promises.copyFile(projectMd, path.join(sourcePath, "CLAUDE.md"));
467
440
  }
468
441
 
469
442
  console.log(" ✅ 已同步到仓库\n");
470
443
  }
471
444
 
472
- function cmdClearSource() {
473
- const settings = readSettings();
445
+ async function cmdClearSource() {
446
+ const settings = await readSettings();
474
447
  if (!settings._skill_source) {
475
448
  console.log(" ⚠️ 当前未设置任何技能目录\n");
476
449
  return;
@@ -478,7 +451,7 @@ function cmdClearSource() {
478
451
 
479
452
  const oldPath = settings._skill_source;
480
453
  delete settings._skill_source;
481
- writeSettings(settings);
454
+ await writeSettings(settings);
482
455
 
483
456
  console.log(` ✅ 已清除技能目录设置: ${oldPath}\n`);
484
457
  console.log(` 💡 运行 npx mdk-skills ui 重新设置\n`);
@@ -489,15 +462,16 @@ function cmdClearSource() {
489
462
  const COMMANDS = ["list", "connect", "sync", "clear-source", "ui"];
490
463
 
491
464
  async function main() {
465
+ await initPaths();
466
+
492
467
  const command = process.argv[2];
493
468
  const args = process.argv.slice(3);
494
469
 
495
470
  if (!command) {
496
- const profiles = loadProfiles();
471
+ const profiles = await loadProfiles();
497
472
  if (profiles) {
498
473
  await startSceneSelection();
499
474
  } else {
500
- // 没有 profiles.json 时回退到原来的 checkbox
501
475
  await startInteractiveMenu();
502
476
  }
503
477
  } else if (command === "--help" || command === "-h") {
@@ -522,10 +496,10 @@ async function main() {
522
496
  npx mdk-skills ui
523
497
  `);
524
498
  } else if (command === "--connect") {
525
- cmdConnect(args[0]);
526
- skillsSource = getSkillsSource(projectRoot);
499
+ await cmdConnect(args[0]);
500
+ await initPaths();
527
501
  if (skillsSource) {
528
- const profiles = loadProfiles();
502
+ const profiles = await loadProfiles();
529
503
  if (profiles) {
530
504
  await startSceneSelection();
531
505
  } else {
@@ -533,12 +507,12 @@ async function main() {
533
507
  }
534
508
  }
535
509
  } else if (command === "connect") {
536
- cmdConnect(args[0]);
510
+ await cmdConnect(args[0]);
537
511
  } else if (command === "sync") {
538
512
  if (!skillsSource) { console.log(" ⚠️ 未设置技能目录,请先运行 npx mdk-skills ui\n"); return; }
539
- cmdSync();
513
+ await cmdSync();
540
514
  } else if (command === "clear-source") {
541
- cmdClearSource();
515
+ await cmdClearSource();
542
516
  } else if (command === "ui") {
543
517
  require("./web-ui/server");
544
518
  } else {