mdk-skills 2.3.26 → 2.3.28
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 +1 -1
- package/scripts/web-ui/dist/assets/{index-qfXYFvR5.css → index-B3_Vjkxx.css} +1 -1
- package/scripts/web-ui/dist/assets/{index-D57SEgQI.js → index-Dr0-RSil.js} +26 -26
- package/scripts/web-ui/dist/index.html +2 -2
- package/scripts/web-ui/server.js +146 -51
- package/scripts/web-ui/src/api/skills.js +18 -0
- package/scripts/web-ui/src/components/SkillCard.vue +2 -0
- package/scripts/web-ui/src/views/Dashboard.vue +20 -6
- package/scripts/web-ui/src/views/SceneSwitch.vue +267 -11
|
@@ -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-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-Dr0-RSil.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B3_Vjkxx.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
package/scripts/web-ui/server.js
CHANGED
|
@@ -96,6 +96,23 @@ function addPullSource(skillName, url) {
|
|
|
96
96
|
writePullSource(data);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// 安全删除目录/文件,处理 Windows 文件锁定(EBUSY)
|
|
100
|
+
// 返回 { removed: boolean, reason?: 'not_found' | 'locked' | 'error', message?: string }
|
|
101
|
+
function safeRmSync(dir, label) {
|
|
102
|
+
if (!fs.existsSync(dir)) return { removed: false, reason: "not_found" };
|
|
103
|
+
try {
|
|
104
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
105
|
+
return { removed: true };
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (err.code === "EBUSY") {
|
|
108
|
+
console.error(`跳过删除 "${label || dir}"(文件被其他程序占用)`);
|
|
109
|
+
return { removed: false, reason: "locked" };
|
|
110
|
+
}
|
|
111
|
+
console.error(`删除 "${label || dir}" 失败:`, err.message);
|
|
112
|
+
return { removed: false, reason: "error", message: err.message };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
99
116
|
// 检查技能源是否已设置,未设置则返回 400
|
|
100
117
|
function requireSource(res) {
|
|
101
118
|
if (!skillsSource || !pkgSkillsSource) {
|
|
@@ -258,6 +275,8 @@ function loadProfiles() {
|
|
|
258
275
|
function applyProfile(profile) {
|
|
259
276
|
const pkgSkills = core.listSkillDirs(pkgSkillsSource);
|
|
260
277
|
const settings = readSettings();
|
|
278
|
+
const failedDelete = [];
|
|
279
|
+
const failedInstall = [];
|
|
261
280
|
|
|
262
281
|
let selected;
|
|
263
282
|
if (profile.skills === null) {
|
|
@@ -281,24 +300,18 @@ function applyProfile(profile) {
|
|
|
281
300
|
copyDirSync(src, dest);
|
|
282
301
|
} catch (err) {
|
|
283
302
|
console.error(`安装技能 "${name}" 失败:`, err.message);
|
|
303
|
+
failedInstall.push(name);
|
|
284
304
|
}
|
|
285
305
|
}
|
|
286
306
|
} else {
|
|
287
307
|
if (fs.existsSync(dest)) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
} catch (err) {
|
|
291
|
-
if (err.code === 'EBUSY') {
|
|
292
|
-
console.error(`切换场景:跳过删除 "${name}"(文件被其他程序占用)`);
|
|
293
|
-
} else {
|
|
294
|
-
console.error(`切换场景:删除 "${name}" 失败:`, err.message);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
308
|
+
const r = safeRmSync(dest, name);
|
|
309
|
+
if (!r.removed) failedDelete.push(name);
|
|
297
310
|
}
|
|
298
311
|
}
|
|
299
312
|
}
|
|
300
313
|
|
|
301
|
-
// 更新 settings
|
|
314
|
+
// 更新 settings(文件操作已执行完毕,现在写配置)
|
|
302
315
|
if (!settings.skills) settings.skills = {};
|
|
303
316
|
for (const name of pkgSkills) {
|
|
304
317
|
const enabled = selected.includes(name);
|
|
@@ -331,12 +344,14 @@ function applyProfile(profile) {
|
|
|
331
344
|
selected,
|
|
332
345
|
enabled: selected.length,
|
|
333
346
|
disabled: pkgSkills.length - selected.length,
|
|
347
|
+
failedDelete,
|
|
348
|
+
failedInstall,
|
|
334
349
|
};
|
|
335
350
|
}
|
|
336
351
|
|
|
337
352
|
// 安装勾选的技能
|
|
338
353
|
function installSelectedSkills(selectedNames) {
|
|
339
|
-
if (!fs.existsSync(pkgSkillsSource)) return { installed: 0 };
|
|
354
|
+
if (!fs.existsSync(pkgSkillsSource)) return { installed: 0, locked: [] };
|
|
340
355
|
|
|
341
356
|
if (!fs.existsSync(skillsDest)) {
|
|
342
357
|
fs.mkdirSync(skillsDest, { recursive: true });
|
|
@@ -344,6 +359,7 @@ function installSelectedSkills(selectedNames) {
|
|
|
344
359
|
|
|
345
360
|
const allPkgSkills = core.listSkillDirs(pkgSkillsSource);
|
|
346
361
|
let installedCount = 0;
|
|
362
|
+
const locked = [];
|
|
347
363
|
|
|
348
364
|
for (const name of allPkgSkills) {
|
|
349
365
|
const src = path.join(pkgSkillsSource, name);
|
|
@@ -360,15 +376,8 @@ function installSelectedSkills(selectedNames) {
|
|
|
360
376
|
}
|
|
361
377
|
} else {
|
|
362
378
|
if (fs.existsSync(dest)) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
} catch (err) {
|
|
366
|
-
if (err.code === 'EBUSY') {
|
|
367
|
-
console.error(`安装技能:跳过删除 "${name}"(文件被其他程序占用)`);
|
|
368
|
-
} else {
|
|
369
|
-
console.error(`安装技能:删除 "${name}" 失败:`, err.message);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
379
|
+
const r = safeRmSync(dest, name);
|
|
380
|
+
if (!r.removed && r.reason === 'locked') locked.push(name);
|
|
372
381
|
}
|
|
373
382
|
}
|
|
374
383
|
}
|
|
@@ -389,7 +398,7 @@ function installSelectedSkills(selectedNames) {
|
|
|
389
398
|
fs.copyFileSync(mdSource, mdDest);
|
|
390
399
|
}
|
|
391
400
|
|
|
392
|
-
return { installed: installedCount };
|
|
401
|
+
return { installed: installedCount, locked };
|
|
393
402
|
}
|
|
394
403
|
|
|
395
404
|
// ---------- API 路由 ----------
|
|
@@ -424,8 +433,8 @@ async function handleApi(req, res) {
|
|
|
424
433
|
if (!pkgNames.has(us.name)) {
|
|
425
434
|
const dest = path.join(skillsDest, us.name);
|
|
426
435
|
if (fs.existsSync(dest)) {
|
|
427
|
-
|
|
428
|
-
cleaned = true;
|
|
436
|
+
const r = safeRmSync(dest, us.name);
|
|
437
|
+
if (r.removed) cleaned = true;
|
|
429
438
|
}
|
|
430
439
|
}
|
|
431
440
|
}
|
|
@@ -462,7 +471,10 @@ async function handleApi(req, res) {
|
|
|
462
471
|
}
|
|
463
472
|
} else {
|
|
464
473
|
if (fs.existsSync(dest)) {
|
|
465
|
-
|
|
474
|
+
const r = safeRmSync(dest, name);
|
|
475
|
+
if (!r.removed && r.reason === "locked") {
|
|
476
|
+
return sendJSON(res, { error: `技能 "${name}" 正被其他程序使用,无法停用` }, 409);
|
|
477
|
+
}
|
|
466
478
|
}
|
|
467
479
|
}
|
|
468
480
|
const settings = readSettings();
|
|
@@ -509,7 +521,7 @@ async function handleApi(req, res) {
|
|
|
509
521
|
await runAsync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-pull-preview" });
|
|
510
522
|
} catch (e) {
|
|
511
523
|
cleanNpxTemp();
|
|
512
|
-
|
|
524
|
+
safeRmSync(tmpDir, "临时目录");
|
|
513
525
|
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
514
526
|
return sendJSON(res, { error: "预览失败:" + (e.stderr || e.message) }, 400);
|
|
515
527
|
}
|
|
@@ -517,7 +529,7 @@ async function handleApi(req, res) {
|
|
|
517
529
|
const skillsDir = path.join(tmpDir, ".claude", "skills");
|
|
518
530
|
const skills = fs.existsSync(skillsDir) ? core.listSkillDirs(skillsDir) : [];
|
|
519
531
|
if (skills.length === 0) {
|
|
520
|
-
|
|
532
|
+
safeRmSync(tmpDir, "临时目录");
|
|
521
533
|
return sendJSON(res, { error: "未找到有效技能" }, 400);
|
|
522
534
|
}
|
|
523
535
|
// 缓存,不删 tmpDir
|
|
@@ -542,7 +554,11 @@ async function handleApi(req, res) {
|
|
|
542
554
|
}
|
|
543
555
|
const dest = path.join(pkgSkillsSource, name);
|
|
544
556
|
if (fs.existsSync(dest)) {
|
|
545
|
-
|
|
557
|
+
const r = safeRmSync(dest, name + "(源目录)");
|
|
558
|
+
if (!r.removed && r.reason === "locked") {
|
|
559
|
+
skipped.push(name);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
546
562
|
}
|
|
547
563
|
copyDirSync(skillPath, dest);
|
|
548
564
|
writeSkillMeta(dest, false);
|
|
@@ -585,19 +601,26 @@ async function handleApi(req, res) {
|
|
|
585
601
|
if (!Array.isArray(names)) return sendJSON(res, { error: "参数错误" }, 400);
|
|
586
602
|
const settings = readSettings();
|
|
587
603
|
if (!settings.skills) settings.skills = {};
|
|
604
|
+
const locked = [];
|
|
588
605
|
for (const name of names) {
|
|
589
606
|
const src = path.join(pkgSkillsSource, name);
|
|
590
607
|
const dest = path.join(skillsDest, name);
|
|
591
608
|
if (enabled) {
|
|
592
609
|
if (fs.existsSync(src) && !fs.existsSync(dest)) copyDirSync(src, dest);
|
|
593
610
|
} else {
|
|
594
|
-
if (fs.existsSync(dest))
|
|
611
|
+
if (fs.existsSync(dest)) {
|
|
612
|
+
const r = safeRmSync(dest, name);
|
|
613
|
+
if (!r.removed && r.reason === "locked") {
|
|
614
|
+
locked.push(name);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
595
618
|
}
|
|
596
619
|
if (!settings.skills[name]) settings.skills[name] = {};
|
|
597
620
|
settings.skills[name].enabled = !!enabled;
|
|
598
621
|
}
|
|
599
622
|
writeSettings(settings);
|
|
600
|
-
return sendJSON(res, { ok: true });
|
|
623
|
+
return sendJSON(res, { ok: true, locked });
|
|
601
624
|
}
|
|
602
625
|
|
|
603
626
|
// POST /api/skills/bulk-delete — 批量删除技能(源目录 + 项目目录)
|
|
@@ -607,15 +630,22 @@ async function handleApi(req, res) {
|
|
|
607
630
|
const { names } = body;
|
|
608
631
|
if (!Array.isArray(names) || names.length === 0) return sendJSON(res, { error: "参数错误" }, 400);
|
|
609
632
|
const settings = readSettings();
|
|
633
|
+
const locked = [];
|
|
634
|
+
const deleted = [];
|
|
610
635
|
for (const name of names) {
|
|
611
636
|
const sourceDir = path.join(pkgSkillsSource, name);
|
|
612
637
|
const destDir = path.join(skillsDest, name);
|
|
613
|
-
|
|
614
|
-
|
|
638
|
+
const r1 = safeRmSync(sourceDir, name + "(源目录)");
|
|
639
|
+
const r2 = safeRmSync(destDir, name + "(项目目录)");
|
|
640
|
+
if (r1.reason === "locked" || r2.reason === "locked") {
|
|
641
|
+
locked.push(name);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
615
644
|
if (settings.skills && settings.skills[name] !== undefined) delete settings.skills[name];
|
|
645
|
+
deleted.push(name);
|
|
616
646
|
}
|
|
617
647
|
writeSettings(settings);
|
|
618
|
-
return sendJSON(res, { ok: true, deleted
|
|
648
|
+
return sendJSON(res, { ok: true, deleted, locked });
|
|
619
649
|
}
|
|
620
650
|
|
|
621
651
|
// POST /api/skills/pull-cancel — 取消预览,清理缓存和临时目录
|
|
@@ -662,7 +692,7 @@ async function handleApi(req, res) {
|
|
|
662
692
|
await runAsync("npx --yes skills add \"" + source.url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-update-" + name });
|
|
663
693
|
} catch (e) {
|
|
664
694
|
cleanNpxTemp();
|
|
665
|
-
|
|
695
|
+
safeRmSync(tmpDir, "临时目录");
|
|
666
696
|
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
667
697
|
return sendJSON(res, { error: "重新拉取失败:" + (e.stderr || e.message) }, 400);
|
|
668
698
|
}
|
|
@@ -670,7 +700,7 @@ async function handleApi(req, res) {
|
|
|
670
700
|
cleanNpxTemp();
|
|
671
701
|
const skillPath = path.join(tmpDir, ".claude", "skills", name);
|
|
672
702
|
if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
673
|
-
|
|
703
|
+
safeRmSync(tmpDir, "临时目录");
|
|
674
704
|
return sendJSON(res, { error: "远程仓库中未找到该技能" }, 400);
|
|
675
705
|
}
|
|
676
706
|
|
|
@@ -680,18 +710,26 @@ async function handleApi(req, res) {
|
|
|
680
710
|
const newFingerprint = calcSkillFingerprint(skillPath);
|
|
681
711
|
|
|
682
712
|
if (oldFingerprint === newFingerprint) {
|
|
683
|
-
|
|
713
|
+
safeRmSync(tmpDir, "临时目录");
|
|
684
714
|
return sendJSON(res, { ok: true, updated: false });
|
|
685
715
|
}
|
|
686
716
|
|
|
687
717
|
// 有变更:覆盖源目录
|
|
688
|
-
|
|
718
|
+
const rSrc = safeRmSync(dest, name + "(源目录)");
|
|
719
|
+
if (!rSrc.removed && rSrc.reason === "locked") {
|
|
720
|
+
safeRmSync(tmpDir, "临时目录");
|
|
721
|
+
return sendJSON(res, { error: `技能"${name}"被其他程序占用,无法更新`, locked: true }, 409);
|
|
722
|
+
}
|
|
689
723
|
copyDirSync(skillPath, dest);
|
|
690
724
|
|
|
691
725
|
// 如果项目目录已启用,同步覆盖
|
|
692
726
|
const projectSkill = path.join(skillsDest, name);
|
|
693
727
|
if (fs.existsSync(projectSkill)) {
|
|
694
|
-
|
|
728
|
+
const rProj = safeRmSync(projectSkill, name + "(项目目录)");
|
|
729
|
+
if (!rProj.removed && rProj.reason === "locked") {
|
|
730
|
+
safeRmSync(tmpDir, "临时目录");
|
|
731
|
+
return sendJSON(res, { error: `技能"${name}"项目目录被其他程序占用,无法更新`, locked: true }, 409);
|
|
732
|
+
}
|
|
695
733
|
copyDirSync(skillPath, projectSkill);
|
|
696
734
|
}
|
|
697
735
|
|
|
@@ -705,7 +743,7 @@ async function handleApi(req, res) {
|
|
|
705
743
|
// 找同源兄弟
|
|
706
744
|
const siblings = Object.keys(pullSource).filter(k => k !== name && pullSource[k].url === source.url);
|
|
707
745
|
|
|
708
|
-
|
|
746
|
+
safeRmSync(tmpDir, "临时目录");
|
|
709
747
|
return sendJSON(res, { ok: true, name, updatedAt: source.pulledAt, siblings });
|
|
710
748
|
}
|
|
711
749
|
|
|
@@ -724,7 +762,7 @@ async function handleApi(req, res) {
|
|
|
724
762
|
await runAsync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-batch" });
|
|
725
763
|
} catch (e) {
|
|
726
764
|
cleanNpxTemp();
|
|
727
|
-
|
|
765
|
+
safeRmSync(tmpDir, "临时目录");
|
|
728
766
|
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
729
767
|
return sendJSON(res, { error: "拉取失败:" + (e.stderr || e.message) }, 400);
|
|
730
768
|
}
|
|
@@ -734,6 +772,7 @@ async function handleApi(req, res) {
|
|
|
734
772
|
const updated = [];
|
|
735
773
|
const unchanged = [];
|
|
736
774
|
const notFound = [];
|
|
775
|
+
const locked = [];
|
|
737
776
|
for (const name of names) {
|
|
738
777
|
const skillPath = path.join(tmpDir, ".claude", "skills", name);
|
|
739
778
|
if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
@@ -747,12 +786,20 @@ async function handleApi(req, res) {
|
|
|
747
786
|
unchanged.push(name);
|
|
748
787
|
continue;
|
|
749
788
|
}
|
|
750
|
-
|
|
789
|
+
const rSrc = safeRmSync(dest, name + "(源目录)");
|
|
790
|
+
if (!rSrc.removed && rSrc.reason === "locked") {
|
|
791
|
+
locked.push(name);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
751
794
|
copyDirSync(skillPath, dest);
|
|
752
795
|
|
|
753
796
|
const projectSkill = path.join(skillsDest, name);
|
|
754
797
|
if (fs.existsSync(projectSkill)) {
|
|
755
|
-
|
|
798
|
+
const rProj = safeRmSync(projectSkill, name + "(项目目录)");
|
|
799
|
+
if (!rProj.removed && rProj.reason === "locked") {
|
|
800
|
+
locked.push(name);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
756
803
|
copyDirSync(skillPath, projectSkill);
|
|
757
804
|
}
|
|
758
805
|
|
|
@@ -769,8 +816,8 @@ async function handleApi(req, res) {
|
|
|
769
816
|
}
|
|
770
817
|
writePullSource(pullSource);
|
|
771
818
|
|
|
772
|
-
|
|
773
|
-
return sendJSON(res, { ok: true, updated, unchanged, notFound });
|
|
819
|
+
safeRmSync(tmpDir, "临时目录");
|
|
820
|
+
return sendJSON(res, { ok: true, updated, unchanged, notFound, locked });
|
|
774
821
|
}
|
|
775
822
|
|
|
776
823
|
// POST /api/skills/:name/open — 在文件管理器中打开技能目录
|
|
@@ -790,6 +837,50 @@ async function handleApi(req, res) {
|
|
|
790
837
|
}
|
|
791
838
|
}
|
|
792
839
|
|
|
840
|
+
// POST /api/skills/:name/retry-delete — 重试删除单个技能(项目目录)
|
|
841
|
+
const retryDeleteMatch = pathname.match(/^\/api\/skills\/([^/]+)\/retry-delete$/);
|
|
842
|
+
if (method === "POST" && retryDeleteMatch) {
|
|
843
|
+
if (!requireSource(res)) return;
|
|
844
|
+
const name = retryDeleteMatch[1];
|
|
845
|
+
const dest = path.join(skillsDest, name);
|
|
846
|
+
if (!fs.existsSync(dest)) return sendJSON(res, { ok: true, name, skipped: true });
|
|
847
|
+
const r = safeRmSync(dest, name);
|
|
848
|
+
if (r.removed) return sendJSON(res, { ok: true, name });
|
|
849
|
+
if (r.reason === "locked") return sendJSON(res, { error: `技能"${name}"被其他程序占用,无法删除`, locked: true }, 409);
|
|
850
|
+
return sendJSON(res, { error: `删除"${name}"失败: ${r.message || "未知错误"}` }, 500);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// POST /api/skills/:name/retry-install — 重试安装单个技能
|
|
854
|
+
const retryInstallMatch = pathname.match(/^\/api\/skills\/([^/]+)\/retry-install$/);
|
|
855
|
+
if (method === "POST" && retryInstallMatch) {
|
|
856
|
+
if (!requireSource(res)) return;
|
|
857
|
+
const name = retryInstallMatch[1];
|
|
858
|
+
const src = path.join(pkgSkillsSource, name);
|
|
859
|
+
const dest = path.join(skillsDest, name);
|
|
860
|
+
if (fs.existsSync(dest)) {
|
|
861
|
+
const r = safeRmSync(dest, name);
|
|
862
|
+
if (!r.removed) return sendJSON(res, { error: `无法覆盖现有目录: ${r.reason}` }, r.reason === "locked" ? 409 : 500);
|
|
863
|
+
}
|
|
864
|
+
if (!fs.existsSync(src)) return sendJSON(res, { error: `源目录不存在: ${name}` }, 404);
|
|
865
|
+
try {
|
|
866
|
+
copyDirSync(src, dest);
|
|
867
|
+
return sendJSON(res, { ok: true, name });
|
|
868
|
+
} catch (err) {
|
|
869
|
+
return sendJSON(res, { error: `安装"${name}"失败: ${err.message}` }, 500);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// POST /api/skills-dest/open — 打开项目技能目录
|
|
874
|
+
if (method === "POST" && pathname === "/api/skills-dest/open") {
|
|
875
|
+
if (!requireSource(res)) return;
|
|
876
|
+
try {
|
|
877
|
+
execSync("start \"\" \"" + skillsDest + "\"", { stdio: "ignore" });
|
|
878
|
+
return sendJSON(res, { ok: true });
|
|
879
|
+
} catch {
|
|
880
|
+
return sendJSON(res, { error: "打开目录失败" }, 500);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
793
884
|
// GET /api/readme — 获取根目录 README.md
|
|
794
885
|
if (method === "GET" && pathname === "/api/readme") {
|
|
795
886
|
if (!skillsSource) return sendJSON(res, { content: null, found: false });
|
|
@@ -868,14 +959,18 @@ async function handleApi(req, res) {
|
|
|
868
959
|
const sourceDir = path.join(pkgSkillsSource, name);
|
|
869
960
|
const destDir = path.join(skillsDest, name);
|
|
870
961
|
let deleted = [];
|
|
962
|
+
let locked = false;
|
|
871
963
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
964
|
+
const r1 = safeRmSync(sourceDir, name + "(源目录)");
|
|
965
|
+
if (r1.removed) deleted.push("源目录");
|
|
966
|
+
else if (r1.reason === "locked") locked = true;
|
|
967
|
+
|
|
968
|
+
const r2 = safeRmSync(destDir, name + "(项目目录)");
|
|
969
|
+
if (r2.removed) deleted.push("项目目录");
|
|
970
|
+
else if (r2.reason === "locked") locked = true;
|
|
971
|
+
|
|
972
|
+
if (locked) {
|
|
973
|
+
return sendJSON(res, { error: `技能"${name}"被其他程序占用,无法删除`, locked: true }, 409);
|
|
879
974
|
}
|
|
880
975
|
|
|
881
976
|
// 清理 settings 中的启用状态
|
|
@@ -1125,7 +1220,7 @@ async function handleApi(req, res) {
|
|
|
1125
1220
|
const src = path.join(skillsDest, name);
|
|
1126
1221
|
const dest = path.join(repoSkills, name);
|
|
1127
1222
|
if (fs.existsSync(dest))
|
|
1128
|
-
|
|
1223
|
+
safeRmSync(dest, name);
|
|
1129
1224
|
copyDirSync(src, dest);
|
|
1130
1225
|
}
|
|
1131
1226
|
}
|
|
@@ -152,6 +152,24 @@ export function openSkillDir(name) {
|
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
export function retryDelete(name) {
|
|
156
|
+
return request("/skills/" + encodeURIComponent(name) + "/retry-delete", {
|
|
157
|
+
method: "POST",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function retryInstall(name) {
|
|
162
|
+
return request("/skills/" + encodeURIComponent(name) + "/retry-install", {
|
|
163
|
+
method: "POST",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function openSkillsDest() {
|
|
168
|
+
return request("/skills-dest/open", {
|
|
169
|
+
method: "POST",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
155
173
|
export function createReadme(data) {
|
|
156
174
|
return request("/readme/create", {
|
|
157
175
|
method: "POST",
|
|
@@ -45,6 +45,8 @@ async function onToggle(value) {
|
|
|
45
45
|
if (value) recordUsage(props.skill.name);
|
|
46
46
|
message.success(`技能 "${props.skill.name}" 已${value ? "启用" : "停用"}`);
|
|
47
47
|
emit("refresh");
|
|
48
|
+
} else if (res.locked) {
|
|
49
|
+
message.warning(`技能 "${props.skill.name}" 被其他程序占用,无法${value ? "启用" : "停用"}`);
|
|
48
50
|
}
|
|
49
51
|
} catch {
|
|
50
52
|
message.error("操作失败");
|
|
@@ -922,6 +922,8 @@ async function handleDelete() {
|
|
|
922
922
|
message.success(`技能 "${detailSkill.value.name}" 已删除`);
|
|
923
923
|
detailVisible.value = false;
|
|
924
924
|
await loadSkills();
|
|
925
|
+
} else if (res.locked) {
|
|
926
|
+
message.warning(res.error || `技能 "${detailSkill.value.name}" 被其他程序占用,无法删除`);
|
|
925
927
|
}
|
|
926
928
|
} catch {
|
|
927
929
|
message.error("删除失败");
|
|
@@ -1008,8 +1010,12 @@ async function onBatchMenuSelect(key, group) {
|
|
|
1008
1010
|
switch (key) {
|
|
1009
1011
|
case "enable":
|
|
1010
1012
|
try {
|
|
1011
|
-
await bulkToggleSkills(names, true);
|
|
1012
|
-
|
|
1013
|
+
const r1 = await bulkToggleSkills(names, true);
|
|
1014
|
+
if (r1.locked?.length) {
|
|
1015
|
+
message.warning(`已启用 ${names.length - r1.locked.length} 个技能,${r1.locked.length} 个被占用`);
|
|
1016
|
+
} else {
|
|
1017
|
+
message.success(`${names.length} 个技能已启用`);
|
|
1018
|
+
}
|
|
1013
1019
|
await loadSkills();
|
|
1014
1020
|
} catch {
|
|
1015
1021
|
message.error("操作失败");
|
|
@@ -1017,8 +1023,12 @@ async function onBatchMenuSelect(key, group) {
|
|
|
1017
1023
|
break;
|
|
1018
1024
|
case "disable":
|
|
1019
1025
|
try {
|
|
1020
|
-
await bulkToggleSkills(names, false);
|
|
1021
|
-
|
|
1026
|
+
const r2 = await bulkToggleSkills(names, false);
|
|
1027
|
+
if (r2.locked?.length) {
|
|
1028
|
+
message.warning(`已停用 ${names.length - r2.locked.length} 个技能,${r2.locked.length} 个被占用`);
|
|
1029
|
+
} else {
|
|
1030
|
+
message.success(`${names.length} 个技能已停用`);
|
|
1031
|
+
}
|
|
1022
1032
|
await loadSkills();
|
|
1023
1033
|
} catch {
|
|
1024
1034
|
message.error("操作失败");
|
|
@@ -1035,8 +1045,12 @@ async function onBatchMenuSelect(key, group) {
|
|
|
1035
1045
|
negativeText: "取消",
|
|
1036
1046
|
onPositiveClick: async () => {
|
|
1037
1047
|
try {
|
|
1038
|
-
await bulkDeleteSkills(names);
|
|
1039
|
-
|
|
1048
|
+
const r3 = await bulkDeleteSkills(names);
|
|
1049
|
+
if (r3.locked?.length) {
|
|
1050
|
+
message.warning(`已删除 ${r3.deleted?.length || 0} 个技能,${r3.locked.length} 个被占用`);
|
|
1051
|
+
} else {
|
|
1052
|
+
message.success(`${names.length} 个技能已删除`);
|
|
1053
|
+
}
|
|
1040
1054
|
await loadSkills();
|
|
1041
1055
|
} catch {
|
|
1042
1056
|
message.error("删除失败");
|