mdk-skills 2.3.12 → 2.3.13
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-BB6MUl8O.css → index-DdbAYqm7.css} +1 -1
- package/scripts/web-ui/dist/assets/{index-D3otR9pd.js → index-pHDOtonU.js} +26 -26
- package/scripts/web-ui/dist/index.html +2 -2
- package/scripts/web-ui/server.js +68 -74
- package/scripts/web-ui/src/api/skills.js +0 -7
- package/scripts/web-ui/src/views/Dashboard.vue +2 -2
|
@@ -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-pHDOtonU.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DdbAYqm7.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app"></div>
|
package/scripts/web-ui/server.js
CHANGED
|
@@ -121,6 +121,19 @@ function cleanNpxTemp() {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// pull 缓存:预览克隆到临时目录后暂存,拉取时直接复用
|
|
125
|
+
const pullCache = new Map(); // url → { tmpDir, createdAt }
|
|
126
|
+
|
|
127
|
+
function cleanPullCache() {
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
for (const [url, entry] of pullCache) {
|
|
130
|
+
if (now - entry.createdAt > 10 * 60 * 1000) {
|
|
131
|
+
try { fs.rmSync(entry.tmpDir, { recursive: true, force: true }); } catch {}
|
|
132
|
+
pullCache.delete(url);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
124
137
|
// 计算技能目录的文件指纹(基于所有文件内容 md5),用于检测是否有真实变更
|
|
125
138
|
function calcSkillFingerprint(dir) {
|
|
126
139
|
if (!fs.existsSync(dir)) return "";
|
|
@@ -438,95 +451,76 @@ async function handleApi(req, res) {
|
|
|
438
451
|
const result = installSelectedSkills(selected);
|
|
439
452
|
return sendJSON(res, { ok: true, ...result });
|
|
440
453
|
}
|
|
441
|
-
// POST /api/skills/
|
|
442
|
-
|
|
454
|
+
// POST /api/skills/pull — 智能路由:预览或拉取远程技能
|
|
455
|
+
// - names 为空 → 预览模式:克隆到临时目录并缓存,返回技能列表
|
|
456
|
+
// - names 非空 → 拉取模式:从缓存读取 tmpDir,复制选中技能
|
|
457
|
+
if (method === "POST" && pathname === "/api/skills/pull") {
|
|
443
458
|
const body = await parseBody(req);
|
|
444
459
|
const url = (body.url || "").trim();
|
|
445
460
|
if (!url) return sendJSON(res, { error: "请输入仓库地址" }, 400);
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
for (const line of stdout.split("\n")) {
|
|
458
|
-
// 匹配 "│ skillname" — 竖线 + 4空格 + 技能名
|
|
459
|
-
const m = line.match(/│\s{4}([a-zA-Z][\w.-]*)/);
|
|
460
|
-
if (m && !seen.has(m[1])) {
|
|
461
|
-
seen.add(m[1]);
|
|
462
|
-
allSkills.push(m[1]);
|
|
463
|
-
}
|
|
461
|
+
const names = body.names;
|
|
462
|
+
|
|
463
|
+
// ---------- 预览模式 ----------
|
|
464
|
+
if (!Array.isArray(names) || names.length === 0) {
|
|
465
|
+
cleanPullCache();
|
|
466
|
+
// 已有缓存则直接返回
|
|
467
|
+
const cached = pullCache.get(url);
|
|
468
|
+
if (cached) {
|
|
469
|
+
const skillsDir = path.join(cached.tmpDir, ".claude", "skills");
|
|
470
|
+
const skills = fs.existsSync(skillsDir) ? core.listSkillDirs(skillsDir) : [];
|
|
471
|
+
return sendJSON(res, { ok: true, skills, total: skills.length });
|
|
464
472
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
473
|
+
const tmpDir = path.join(os.tmpdir(), "mdk-preview-" + Date.now());
|
|
474
|
+
fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
|
|
475
|
+
try {
|
|
476
|
+
await runAsync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", { cwd: tmpDir, taskId: "npx-pull-preview" });
|
|
477
|
+
} catch (e) {
|
|
478
|
+
cleanNpxTemp();
|
|
479
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
480
|
+
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
481
|
+
return sendJSON(res, { error: "预览失败:" + (e.stderr || e.message) }, 400);
|
|
474
482
|
}
|
|
475
|
-
} catch (e) {
|
|
476
483
|
cleanNpxTemp();
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
484
|
+
const skillsDir = path.join(tmpDir, ".claude", "skills");
|
|
485
|
+
const skills = fs.existsSync(skillsDir) ? core.listSkillDirs(skillsDir) : [];
|
|
486
|
+
if (skills.length === 0) {
|
|
487
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
488
|
+
return sendJSON(res, { error: "未找到有效技能" }, 400);
|
|
489
|
+
}
|
|
490
|
+
// 缓存,不删 tmpDir
|
|
491
|
+
pullCache.set(url, { tmpDir, createdAt: Date.now() });
|
|
492
|
+
return sendJSON(res, { ok: true, skills, total: skills.length });
|
|
480
493
|
}
|
|
481
|
-
cleanNpxTemp();
|
|
482
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
483
|
-
return sendJSON(res, { ok: true, skills: allSkills, total: allSkills.length });
|
|
484
|
-
}
|
|
485
494
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (!url) return sendJSON(res, { error: "请输入仓库地址" }, 400);
|
|
491
|
-
const tmpDir = path.join(os.tmpdir(), "mdk-pull-" + Date.now());
|
|
492
|
-
fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
|
|
493
|
-
// 选择性拉取:指定 names 时追加 --skill 参数
|
|
494
|
-
const names = body.names;
|
|
495
|
-
const skillArg = Array.isArray(names) && names.length > 0
|
|
496
|
-
? " --skill " + names.join(",")
|
|
497
|
-
: "";
|
|
498
|
-
try {
|
|
499
|
-
await runAsync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code" + skillArg, { cwd: tmpDir, taskId: "npx-pull" });
|
|
500
|
-
} catch (e) {
|
|
501
|
-
cleanNpxTemp();
|
|
502
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
503
|
-
if (e.killed) return sendJSON(res, { cancelled: true }, 499);
|
|
504
|
-
return sendJSON(res, { error: "拉取失败:" + (e.stderr || e.message) }, 400);
|
|
495
|
+
// ---------- 拉取模式 ----------
|
|
496
|
+
const entry = pullCache.get(url);
|
|
497
|
+
if (!entry) {
|
|
498
|
+
return sendJSON(res, { error: "请先预览该仓库" }, 400);
|
|
505
499
|
}
|
|
506
|
-
|
|
500
|
+
const tmpDir = entry.tmpDir;
|
|
507
501
|
const imported = [];
|
|
508
502
|
const skipped = [];
|
|
509
503
|
const skillsDir = path.join(tmpDir, ".claude", "skills");
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
if (!
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
copyDirSync(skillPath, dest);
|
|
520
|
-
writeSkillMeta(dest, false);
|
|
521
|
-
addPullSource(entry.name, url);
|
|
522
|
-
imported.push(entry.name);
|
|
523
|
-
} else {
|
|
524
|
-
skipped.push(entry.name);
|
|
504
|
+
for (const name of names) {
|
|
505
|
+
const skillPath = path.join(skillsDir, name);
|
|
506
|
+
if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
507
|
+
skipped.push(name);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const dest = path.join(pkgSkillsSource, name);
|
|
511
|
+
if (fs.existsSync(dest)) {
|
|
512
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
525
513
|
}
|
|
514
|
+
copyDirSync(skillPath, dest);
|
|
515
|
+
writeSkillMeta(dest, false);
|
|
516
|
+
addPullSource(name, url);
|
|
517
|
+
imported.push(name);
|
|
526
518
|
}
|
|
527
|
-
|
|
519
|
+
// 清理缓存 + 临时目录
|
|
520
|
+
pullCache.delete(url);
|
|
521
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
528
522
|
if (imported.length === 0) {
|
|
529
|
-
return sendJSON(res, { error: "
|
|
523
|
+
return sendJSON(res, { error: "所选技能均无效(需包含 SKILL.md)", skipped }, 400);
|
|
530
524
|
}
|
|
531
525
|
return sendJSON(res, { ok: true, imported, skipped });
|
|
532
526
|
}
|
|
@@ -109,13 +109,6 @@ export function updateSkillMeta(name, data) {
|
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export function previewSkills(url) {
|
|
113
|
-
return request("/skills/preview", {
|
|
114
|
-
method: "POST",
|
|
115
|
-
body: JSON.stringify({ url }),
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
112
|
export function pullSkills(url, names = []) {
|
|
120
113
|
return request("/skills/pull", {
|
|
121
114
|
method: "POST",
|
|
@@ -328,7 +328,7 @@ import { marked } from "marked";
|
|
|
328
328
|
import hljs from "highlight.js";
|
|
329
329
|
import SkillCard from "../components/SkillCard.vue";
|
|
330
330
|
import ModalComp from "../components/ModalComp.vue";
|
|
331
|
-
import { getSkills, getReadme, getSkillReadme, updateSkillMeta, deleteSkill,
|
|
331
|
+
import { getSkills, getReadme, getSkillReadme, updateSkillMeta, deleteSkill, pullSkills, installSkills, getSkillSource, updateSkill, batchUpdateSkills, openSkillDir, cancelTask } from "../api/skills";
|
|
332
332
|
import { sortSkills, getUsageMap, recordUsage } from "../utils/usage";
|
|
333
333
|
|
|
334
334
|
// marked 配置:代码高亮 + 外链安全
|
|
@@ -477,7 +477,7 @@ async function handlePreview() {
|
|
|
477
477
|
pullResult.value = null;
|
|
478
478
|
pullError.value = "";
|
|
479
479
|
try {
|
|
480
|
-
const res = await
|
|
480
|
+
const res = await pullSkills(pullUrl.value.trim());
|
|
481
481
|
if (!showPullModal.value) return;
|
|
482
482
|
if (res.ok) {
|
|
483
483
|
previewSkillList.value = res.skills || [];
|