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