mdk-skills 2.3.2 → 2.3.3
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-BItq1iGH.css → index-DgZ4NBMO.css} +1 -1
- package/scripts/web-ui/dist/assets/{index-BnMBIily.js → index-MFtHKGfF.js} +1531 -1531
- package/scripts/web-ui/dist/index.html +2 -2
- package/scripts/web-ui/server.js +54 -20
- package/scripts/web-ui/src/views/Dashboard.vue +120 -13
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mdk-skills 管理面板</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-MFtHKGfF.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DgZ4NBMO.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app"></div>
|
package/scripts/web-ui/server.js
CHANGED
|
@@ -169,22 +169,58 @@ function writeSkillMeta(dest, wasUpdated) {
|
|
|
169
169
|
|
|
170
170
|
// ---------- 异步 npx 管理:支持取消正在执行的进程 ----------
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
const runningTasks = new Map();
|
|
173
173
|
|
|
174
|
-
function
|
|
174
|
+
function runAsync(cmd, options = {}) {
|
|
175
|
+
const { cwd, timeout = 120000, taskId = "default" } = options;
|
|
175
176
|
return new Promise((resolve, reject) => {
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
// 同名任务已存在则先取消
|
|
178
|
+
if (runningTasks.has(taskId)) {
|
|
179
|
+
const old = runningTasks.get(taskId);
|
|
180
|
+
old.kill();
|
|
181
|
+
runningTasks.delete(taskId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const proc = exec(cmd, { cwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
runningTasks.delete(taskId);
|
|
178
187
|
if (err) {
|
|
179
|
-
|
|
188
|
+
const e = new Error(err.killed ? "已取消" : (stderr || err.message));
|
|
189
|
+
e.stdout = stdout || "";
|
|
190
|
+
e.stderr = stderr || "";
|
|
191
|
+
e.killed = err.killed;
|
|
192
|
+
reject(e);
|
|
180
193
|
} else {
|
|
181
|
-
resolve();
|
|
194
|
+
resolve({ stdout: stdout || "", stderr: stderr || "" });
|
|
182
195
|
}
|
|
183
196
|
});
|
|
184
|
-
|
|
197
|
+
|
|
198
|
+
const timer = setTimeout(() => {
|
|
199
|
+
proc.kill();
|
|
200
|
+
runningTasks.delete(taskId);
|
|
201
|
+
const e = new Error("操作超时");
|
|
202
|
+
e.stdout = ""; e.stderr = ""; e.killed = true;
|
|
203
|
+
reject(e);
|
|
204
|
+
}, timeout);
|
|
205
|
+
|
|
206
|
+
proc.on("error", () => { clearTimeout(timer); runningTasks.delete(taskId); });
|
|
207
|
+
runningTasks.set(taskId, proc);
|
|
185
208
|
});
|
|
186
209
|
}
|
|
187
210
|
|
|
211
|
+
function cancelTask(taskId) {
|
|
212
|
+
if (taskId) {
|
|
213
|
+
const proc = runningTasks.get(taskId);
|
|
214
|
+
if (proc) { proc.kill(); runningTasks.delete(taskId); }
|
|
215
|
+
} else {
|
|
216
|
+
// 不传 taskId 取消全部
|
|
217
|
+
for (const [id, proc] of runningTasks) {
|
|
218
|
+
proc.kill();
|
|
219
|
+
runningTasks.delete(id);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
188
224
|
// 读取 profiles.json
|
|
189
225
|
function loadProfiles() {
|
|
190
226
|
const profilesPath = path.join(skillsSource, ".claude", "profiles.json");
|
|
@@ -410,12 +446,12 @@ async function handleApi(req, res) {
|
|
|
410
446
|
const tmpDir = path.join(os.tmpdir(), "mdk-pull-" + Date.now());
|
|
411
447
|
fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
|
|
412
448
|
try {
|
|
413
|
-
await
|
|
449
|
+
await runAsync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-pull" });
|
|
414
450
|
} catch (e) {
|
|
415
451
|
cleanNpxTemp();
|
|
416
452
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
417
|
-
if (e.
|
|
418
|
-
return sendJSON(res, { error: "
|
|
453
|
+
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
454
|
+
return sendJSON(res, { error: "拉取失败:" + (e.stderr || e.message) }, 400);
|
|
419
455
|
}
|
|
420
456
|
cleanNpxTemp();
|
|
421
457
|
const imported = [];
|
|
@@ -472,12 +508,12 @@ async function handleApi(req, res) {
|
|
|
472
508
|
const tmpDir = path.join(os.tmpdir(), "mdk-update-" + Date.now());
|
|
473
509
|
fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
|
|
474
510
|
try {
|
|
475
|
-
await
|
|
511
|
+
await runAsync("npx --yes skills add \"" + source.url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-update-" + name });
|
|
476
512
|
} catch (e) {
|
|
477
513
|
cleanNpxTemp();
|
|
478
514
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
479
|
-
if (e.
|
|
480
|
-
return sendJSON(res, { error: "
|
|
515
|
+
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
516
|
+
return sendJSON(res, { error: "重新拉取失败:" + (e.stderr || e.message) }, 400);
|
|
481
517
|
}
|
|
482
518
|
|
|
483
519
|
cleanNpxTemp();
|
|
@@ -533,12 +569,12 @@ async function handleApi(req, res) {
|
|
|
533
569
|
const tmpDir = path.join(os.tmpdir(), "mdk-batch-" + Date.now());
|
|
534
570
|
fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
|
|
535
571
|
try {
|
|
536
|
-
await
|
|
572
|
+
await runAsync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-batch" });
|
|
537
573
|
} catch (e) {
|
|
538
574
|
cleanNpxTemp();
|
|
539
575
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
540
|
-
if (e.
|
|
541
|
-
return sendJSON(res, { error: "
|
|
576
|
+
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
577
|
+
return sendJSON(res, { error: "拉取失败:" + (e.stderr || e.message) }, 400);
|
|
542
578
|
}
|
|
543
579
|
|
|
544
580
|
cleanNpxTemp();
|
|
@@ -1159,10 +1195,8 @@ ${skillsList}
|
|
|
1159
1195
|
|
|
1160
1196
|
// POST /api/tasks/cancel — 取消正在执行的 npx 操作
|
|
1161
1197
|
if (method === "POST" && pathname === "/api/tasks/cancel") {
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
_npxProcess = null;
|
|
1165
|
-
}
|
|
1198
|
+
const body = await parseBody(req);
|
|
1199
|
+
cancelTask(body.taskId);
|
|
1166
1200
|
return sendJSON(res, { ok: true });
|
|
1167
1201
|
}
|
|
1168
1202
|
|
|
@@ -243,6 +243,57 @@
|
|
|
243
243
|
</div>
|
|
244
244
|
</div>
|
|
245
245
|
</n-modal>
|
|
246
|
+
|
|
247
|
+
<!-- 全部更新弹窗 -->
|
|
248
|
+
<n-modal
|
|
249
|
+
v-model:show="showBatchModal"
|
|
250
|
+
:title="batchPhase === 'confirm' ? '批量更新确认' : batchPhase === 'executing' ? '正在更新...' : batchPhase === 'done' ? '更新完成' : '更新失败'"
|
|
251
|
+
preset="card"
|
|
252
|
+
style="width: 480px"
|
|
253
|
+
:mask-closable="batchPhase !== 'executing'"
|
|
254
|
+
@update:show="(v) => { if (!v && batchPhase === 'executing') cancelTask(); }"
|
|
255
|
+
>
|
|
256
|
+
<div class="batch-modal-body">
|
|
257
|
+
<template v-if="batchPhase === 'confirm'">
|
|
258
|
+
<p class="batch-desc">确定更新 "{{ batchGroup?.url }}" 下的 {{ batchGroup?.skills?.length }} 个技能吗?</p>
|
|
259
|
+
<div class="batch-actions">
|
|
260
|
+
<n-button size="small" @click="showBatchModal = false">取消</n-button>
|
|
261
|
+
<n-button size="small" type="primary" @click="doBatchUpdate">开始更新</n-button>
|
|
262
|
+
</div>
|
|
263
|
+
</template>
|
|
264
|
+
<template v-if="batchPhase === 'executing'">
|
|
265
|
+
<div class="batch-executing">
|
|
266
|
+
<n-spin size="small" />
|
|
267
|
+
<span>正在更新,请稍候...</span>
|
|
268
|
+
</div>
|
|
269
|
+
</template>
|
|
270
|
+
<template v-if="batchPhase === 'done'">
|
|
271
|
+
<div class="batch-result" v-if="batchResult">
|
|
272
|
+
<div v-if="batchResult.updated?.length > 0" class="batch-result-line">
|
|
273
|
+
<span class="batch-result-label">已更新:</span>
|
|
274
|
+
<n-tag v-for="name in batchResult.updated" :key="name" size="small" type="success">{{ name }}</n-tag>
|
|
275
|
+
</div>
|
|
276
|
+
<div v-if="batchResult.unchanged?.length > 0" class="batch-result-line">
|
|
277
|
+
<span class="batch-result-label">已是最新:</span>
|
|
278
|
+
<n-tag v-for="name in batchResult.unchanged" :key="name" size="small">{{ name }}</n-tag>
|
|
279
|
+
</div>
|
|
280
|
+
<div v-if="batchResult.notFound?.length > 0" class="batch-result-line">
|
|
281
|
+
<span class="batch-result-label">未找到:</span>
|
|
282
|
+
<n-tag v-for="name in batchResult.notFound" :key="name" size="small" type="warning">{{ name }}</n-tag>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="batch-actions">
|
|
286
|
+
<n-button size="small" type="primary" @click="showBatchModal = false">完成</n-button>
|
|
287
|
+
</div>
|
|
288
|
+
</template>
|
|
289
|
+
<template v-if="batchPhase === 'error'">
|
|
290
|
+
<n-alert type="error" :bordered="false">{{ batchError }}</n-alert>
|
|
291
|
+
<div class="batch-actions" style="margin-top: 12px;">
|
|
292
|
+
<n-button size="small" type="primary" @click="showBatchModal = false">关闭</n-button>
|
|
293
|
+
</div>
|
|
294
|
+
</template>
|
|
295
|
+
</div>
|
|
296
|
+
</n-modal>
|
|
246
297
|
</div>
|
|
247
298
|
</template>
|
|
248
299
|
|
|
@@ -318,6 +369,13 @@ const updatedSiblings = ref(null);
|
|
|
318
369
|
const siblingCheck = ref({});
|
|
319
370
|
const updatingSiblings = ref(false);
|
|
320
371
|
|
|
372
|
+
// 全部更新弹窗
|
|
373
|
+
const showBatchModal = ref(false);
|
|
374
|
+
const batchPhase = ref("confirm");
|
|
375
|
+
const batchGroup = ref(null);
|
|
376
|
+
const batchResult = ref(null);
|
|
377
|
+
const batchError = ref("");
|
|
378
|
+
|
|
321
379
|
async function handleSiblingUpdate() {
|
|
322
380
|
const selected = Object.keys(siblingCheck.value).filter((k) => siblingCheck.value[k]);
|
|
323
381
|
if (selected.length === 0) {
|
|
@@ -578,25 +636,29 @@ async function handleUpdate() {
|
|
|
578
636
|
}
|
|
579
637
|
|
|
580
638
|
async function batchUpdateGroup(group) {
|
|
581
|
-
|
|
582
|
-
|
|
639
|
+
batchGroup.value = group;
|
|
640
|
+
batchResult.value = null;
|
|
641
|
+
batchError.value = "";
|
|
642
|
+
batchPhase.value = "confirm";
|
|
643
|
+
showBatchModal.value = true;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function doBatchUpdate() {
|
|
647
|
+
batchPhase.value = "executing";
|
|
583
648
|
try {
|
|
584
|
-
const names =
|
|
585
|
-
const res = await batchUpdateSkills(names,
|
|
649
|
+
const names = batchGroup.value.skills.map((s) => s.name);
|
|
650
|
+
const res = await batchUpdateSkills(names, batchGroup.value.url);
|
|
586
651
|
if (res.ok) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
message.info(`${res.unchanged.length} 个技能已是最新,跳过`);
|
|
590
|
-
}
|
|
591
|
-
if (res.notFound && res.notFound.length > 0) {
|
|
592
|
-
message.warning(`${res.notFound.length} 个技能未找到,已跳过`);
|
|
593
|
-
}
|
|
652
|
+
batchResult.value = { updated: res.updated, unchanged: res.unchanged, notFound: res.notFound };
|
|
653
|
+
batchPhase.value = "done";
|
|
594
654
|
await loadSkills();
|
|
595
655
|
} else {
|
|
596
|
-
|
|
656
|
+
batchError.value = res.error || "批量更新失败";
|
|
657
|
+
batchPhase.value = "error";
|
|
597
658
|
}
|
|
598
659
|
} catch {
|
|
599
|
-
|
|
660
|
+
batchError.value = "批量更新失败";
|
|
661
|
+
batchPhase.value = "error";
|
|
600
662
|
}
|
|
601
663
|
}
|
|
602
664
|
|
|
@@ -937,4 +999,49 @@ onUnmounted(() => document.removeEventListener("visibilitychange", onFocus));
|
|
|
937
999
|
justify-content: flex-end;
|
|
938
1000
|
gap: 8px;
|
|
939
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
/* 全部更新弹窗 */
|
|
1004
|
+
.batch-modal-body {
|
|
1005
|
+
min-height: 60px;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.batch-desc {
|
|
1009
|
+
font-size: 13px;
|
|
1010
|
+
line-height: 1.6;
|
|
1011
|
+
margin-bottom: 16px;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.batch-actions {
|
|
1015
|
+
display: flex;
|
|
1016
|
+
justify-content: flex-end;
|
|
1017
|
+
gap: 8px;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.batch-executing {
|
|
1021
|
+
display: flex;
|
|
1022
|
+
align-items: center;
|
|
1023
|
+
gap: 12px;
|
|
1024
|
+
padding: 24px 0;
|
|
1025
|
+
justify-content: center;
|
|
1026
|
+
color: #666;
|
|
1027
|
+
font-size: 13px;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
.batch-result {
|
|
1031
|
+
margin-bottom: 16px;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.batch-result-line {
|
|
1035
|
+
display: flex;
|
|
1036
|
+
align-items: center;
|
|
1037
|
+
flex-wrap: wrap;
|
|
1038
|
+
gap: 6px;
|
|
1039
|
+
margin-bottom: 10px;
|
|
1040
|
+
font-size: 13px;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.batch-result-label {
|
|
1044
|
+
color: #888;
|
|
1045
|
+
flex-shrink: 0;
|
|
1046
|
+
}
|
|
940
1047
|
</style>
|