mdk-skills 2.2.25 → 2.3.0

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.
@@ -0,0 +1 @@
1
+ .status-bar[data-v-bc9519f9]{background:#fff;border-bottom:1px solid #e5e7eb;flex-wrap:wrap;align-items:center;gap:12px;padding:10px 24px;display:flex}.status-item[data-v-bc9519f9]{color:#555;align-items:center;gap:6px;font-size:13px;display:flex}.status-item.clickable[data-v-bc9519f9]{cursor:pointer}.status-item.clickable[data-v-bc9519f9]:hover{color:#2080f0}.status-divider[data-v-bc9519f9]{background:#e0e0e0;width:1px;height:20px}[data-theme=dark] .status-bar{background:#1a1a2e;border-bottom-color:#333}[data-theme=dark] .status-item{color:#aaa}[data-theme=dark] .status-divider{background:#333}*{box-sizing:border-box;margin:0;padding:0}html,body,#app{background:#f5f7fa;height:100%}body{overflow-y:auto}*{scrollbar-width:none;-ms-overflow-style:none}::-webkit-scrollbar{display:none}.app-layout{flex-direction:column;min-height:100vh;padding-top:56px;display:flex}.app-header{z-index:100;background:#fff;border-bottom:1px solid #e5e7eb;align-items:center;height:56px;padding:0 24px;display:flex;position:fixed;top:0;left:0;right:0}.header-inner{justify-content:space-between;align-items:center;width:100%;display:flex}.header-left{align-items:center;gap:32px;display:flex}.logo{color:#1a1a1a;white-space:nowrap;font-size:18px;font-weight:700}.app-content{flex:1;width:100%;max-width:1000px;margin:0 auto;padding:24px}.markdown-content{color:#333;word-wrap:break-word;font-size:14px;line-height:1.7}.markdown-content h1{border-bottom:1px solid #eee;margin:20px 0 12px;padding-bottom:8px;font-size:22px;font-weight:700}.markdown-content h2{border-bottom:1px solid #eee;margin:18px 0 10px;padding-bottom:6px;font-size:18px;font-weight:700}.markdown-content h3{margin:16px 0 8px;font-size:16px;font-weight:600}.markdown-content h4{margin:14px 0 6px;font-size:14px;font-weight:600}.markdown-content h5,.markdown-content h6{margin:12px 0 6px;font-size:13px;font-weight:600}.markdown-content p{margin:8px 0}.markdown-content ul,.markdown-content ol{margin:8px 0;padding-left:24px}.markdown-content li{margin:4px 0}.markdown-content blockquote{color:#555;background:#f0f7ff;border-left:4px solid #2080f0;margin:10px 0;padding:8px 16px}.markdown-content blockquote p{margin:4px 0}.markdown-content a{color:#2080f0;text-decoration:none}.markdown-content a:hover{text-decoration:underline}.markdown-content hr{border:none;border-top:1px solid #e0e0e0;margin:16px 0}.markdown-content table{border-collapse:collapse;width:100%;margin:12px 0;font-size:13px}.markdown-content th,.markdown-content td{text-align:left;border:1px solid #e0e0e0;padding:8px 12px}.markdown-content th{background:#f5f7fa;font-weight:600}.markdown-content tr:nth-child(2n) td{background:#fafafa}.markdown-content img{border-radius:4px;max-width:100%;margin:8px 0}.markdown-content code{color:#d63384;background:#f0f0f0;border-radius:3px;padding:2px 6px;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px}.markdown-content pre{border:1px solid #e8e8e8;border-radius:6px;margin:12px 0;padding:0;position:relative;overflow:hidden}.markdown-content pre code{color:#333;tab-size:2;background:#f8f9fa;padding:14px 16px;font-size:13px;line-height:1.5;display:block;overflow-x:auto}.markdown-content pre .copy-btn{color:#999;cursor:pointer;opacity:0;background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:3px 8px;font-size:11px;transition:opacity .2s;position:absolute;top:6px;right:6px}.readme-fold{border:1px solid #e5e7eb;border-radius:6px;margin-bottom:20px;overflow:hidden}.fold-header{cursor:pointer;-webkit-user-select:none;user-select:none;background:#fafafa;align-items:center;gap:6px;padding:10px 16px;font-size:14px;font-weight:600;display:flex}.fold-header:hover{background:#f0f0f0}.fold-arrow{color:#999;font-size:11px;transition:transform .2s}.fold-arrow.open{transform:rotate(90deg)}.fold-body{padding:0 16px 16px}.markdown-content pre:hover .copy-btn{opacity:1}.markdown-content pre .copy-btn:hover{color:#2080f0;border-color:#2080f0}.header-right{align-items:center;gap:8px;display:flex}[data-theme=dark] html,[data-theme=dark] body,[data-theme=dark] #app{background:#1a1a2e}[data-theme=dark] .app-header{background:#1a1a2e;border-bottom-color:#333}[data-theme=dark] .app-content{background:0 0}[data-theme=dark] .readme-fold{border-color:#333}[data-theme=dark] .fold-header{color:#e0e0e0;background:#252538}[data-theme=dark] .fold-header:hover{background:#2a2a40}[data-theme=dark] .fold-body{background:#1e1e32}[data-theme=dark] .fold-arrow{color:#888}[data-theme=dark] .markdown-content{color:#ccc}[data-theme=dark] .markdown-content h1,[data-theme=dark] .markdown-content h2{color:#eee;border-bottom-color:#333}[data-theme=dark] .markdown-content h3,[data-theme=dark] .markdown-content h4{color:#eee}[data-theme=dark] .markdown-content code{color:#e06c9f;background:#2a2a40}[data-theme=dark] .markdown-content pre{border-color:#333}[data-theme=dark] .markdown-content pre code{color:#d4d4d4;background:#1e1e2e}[data-theme=dark] .markdown-content blockquote{color:#bbb;background:#252538;border-left-color:#4a6cf7}[data-theme=dark] .markdown-content a{color:#6a8cff}[data-theme=dark] .markdown-content th{background:#252538}[data-theme=dark] .markdown-content td{border-color:#333}[data-theme=dark] .markdown-content tr:nth-child(2n) td{background:#222238}[data-theme=dark] .markdown-content hr{border-top-color:#333}[data-theme=dark] .markdown-content img{filter:brightness(.85)}[data-theme=dark] .page-header h2{color:#e0e0e0}[data-theme=dark] .scene-desc{color:#aaa}.fade-enter-active,.fade-leave-active{transition:opacity .9s,transform .9s}.fade-enter-from{opacity:0;transform:translateY(6px)}.fade-leave-to{opacity:0}.skill-card[data-v-a11339d4]{cursor:pointer;margin-bottom:12px;transition:box-shadow .2s}.skill-card[data-v-a11339d4]:hover{box-shadow:0 2px 8px #00000014}.skill-meta[data-v-a11339d4]{align-items:center;gap:8px;margin-bottom:8px;display:flex}.skill-version[data-v-a11339d4]{color:#888;font-family:monospace;font-size:12px}.skill-desc[data-v-a11339d4]{color:#666;text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:13px;overflow:hidden}.page-header[data-v-2d1c5779]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:4px;display:flex}.page-header h2[data-v-2d1c5779]{font-size:20px;font-weight:600}.header-actions[data-v-2d1c5779]{align-items:center;gap:8px;display:flex}.tag-filter[data-v-2d1c5779]{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:16px;display:flex}.detail-status[data-v-2d1c5779]{justify-content:center;padding:40px 0;display:flex}.detail-body[data-v-2d1c5779]{max-height:65vh;padding:16px 24px;overflow-y:auto}.edit-panel[data-v-2d1c5779]{margin-bottom:4px}.edit-row[data-v-2d1c5779]{align-items:flex-start;gap:10px;margin-bottom:10px;display:flex}.edit-label[data-v-2d1c5779]{color:#888;flex-shrink:0;width:50px;font-size:13px;line-height:30px}.pull-modal-body[data-v-2d1c5779]{min-height:100px}.pull-section[data-v-2d1c5779]{margin-top:12px}.pull-input-row[data-v-2d1c5779]{gap:8px;margin-bottom:8px;display:flex}.pull-input-row .n-input[data-v-2d1c5779]{flex:1}.pull-error[data-v-2d1c5779]{margin-bottom:8px}.pull-result[data-v-2d1c5779]{margin-top:8px}.pull-result-line[data-v-2d1c5779]{flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px;font-size:13px;display:flex}.pull-result-label[data-v-2d1c5779]{color:#888;flex-shrink:0}.pull-result-hint[data-v-2d1c5779]{color:#999;font-size:12px}.install-panel[data-v-2d1c5779]{background:#00000005;border-radius:6px;margin-top:12px;padding:12px}.install-panel-label[data-v-2d1c5779]{color:#666;margin-bottom:8px;font-size:13px}.install-check-list[data-v-2d1c5779]{margin-bottom:10px}.install-btn[data-v-2d1c5779]{float:right}.edit-actions[data-v-2d1c5779]{justify-content:flex-end;gap:8px;display:flex}.skill-group[data-v-2d1c5779]{margin-bottom:4px}.group-header[data-v-2d1c5779]{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:4px;align-items:center;gap:8px;padding:8px 4px;display:flex}.group-header[data-v-2d1c5779]:hover{background:#00000008}.fold-arrow[data-v-2d1c5779]{color:#999;flex-shrink:0;font-size:10px;transition:transform .2s}.fold-arrow.open[data-v-2d1c5779]{transform:rotate(90deg)}.group-label[data-v-2d1c5779]{color:inherit;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:13px;font-weight:600;overflow:hidden}.group-count[data-v-2d1c5779]{color:#999;flex-shrink:0;font-size:12px}.group-update-btn[data-v-2d1c5779]{flex-shrink:0}.group-body[data-v-2d1c5779]{padding-left:4px}.source-info[data-v-2d1c5779]{margin-bottom:4px}.source-row[data-v-2d1c5779]{align-items:center;gap:10px;margin-bottom:6px;font-size:13px;display:flex}.source-label[data-v-2d1c5779]{color:#888;flex-shrink:0;width:70px}.source-value[data-v-2d1c5779]{word-break:break-all}.local-tag[data-v-2d1c5779]{color:#2e7d32;background:#e8f5e9;border-radius:3px;padding:1px 8px;font-size:12px;display:inline-block}.path-text[data-v-2d1c5779]{color:#666;font-family:monospace;font-size:12px}.source-actions[data-v-2d1c5779]{gap:8px;margin-top:8px;display:flex}.local-hint[data-v-2d1c5779]{color:#999;background:#00000005;border-radius:4px;margin-top:8px;padding:8px;font-size:12px;line-height:1.5}.siblings-body[data-v-2d1c5779]{min-height:60px}.siblings-desc[data-v-2d1c5779]{margin-bottom:12px;font-size:13px;line-height:1.6}.siblings-list[data-v-2d1c5779]{flex-direction:column;gap:8px;margin-bottom:16px;display:flex}.siblings-actions[data-v-2d1c5779]{justify-content:flex-end;gap:8px;display:flex}.page-header[data-v-1736a530]{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.page-header h2[data-v-1736a530]{font-size:20px;font-weight:600}.scene-grid[data-v-1736a530]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin:2px;display:grid}.scene-card[data-v-1736a530]{transition:box-shadow .2s}.scene-card.active[data-v-1736a530]{box-shadow:0 0 0 2px #2080f0}.scene-desc[data-v-1736a530]{color:#666;margin:0;font-size:13px}.scene-footer[data-v-1736a530]{justify-content:space-between;align-items:center;display:flex}.hint-text[data-v-1736a530]{color:#999;font-size:12px}.page-header[data-v-5f659620]{margin-bottom:20px}.page-header h2[data-v-5f659620]{font-size:20px;font-weight:600}.section[data-v-5f659620]{margin-bottom:20px}.init-alert[data-v-5f659620]{margin-bottom:12px}.missing-list[data-v-5f659620]{flex-direction:column;gap:4px;display:flex}.missing-item[data-v-5f659620]{font-size:13px;line-height:1.6}.missing-item code[data-v-5f659620]{background:#0000000f;border-radius:3px;padding:1px 6px;font-size:12px}.skill-tag[data-v-5f659620]{margin:1px 2px;display:inline-block}.missing-hint[data-v-5f659620]{opacity:.7;margin-top:4px;font-size:12px}.source-status[data-v-5f659620]{margin-bottom:16px}.source-actions[data-v-5f659620]{flex-direction:column;gap:12px;display:flex}.action-buttons[data-v-5f659620]{gap:8px;display:flex}.healthy-text[data-v-5f659620]{color:#18a058;font-size:13px}.issue-text[data-v-5f659620]{color:#d03050;font-size:13px}.readme-dialog-desc[data-v-5f659620]{color:#555;margin-bottom:16px;font-size:14px}.readme-checkbox[data-v-5f659620]{align-items:flex-start;margin-bottom:12px;display:flex}.readme-checkbox-content[data-v-5f659620]{flex-direction:column;gap:2px;display:flex}.readme-checkbox-title[data-v-5f659620]{font-size:14px;font-weight:500}.readme-checkbox-desc[data-v-5f659620]{color:#999;font-size:12px}.page-header[data-v-97d38fe1]{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.page-header h2[data-v-97d38fe1]{font-size:20px;font-weight:600}.fade-enter-active[data-v-97d38fe1],.fade-leave-active[data-v-97d38fe1]{transition:opacity .2s}.fade-enter-from[data-v-97d38fe1],.fade-leave-to[data-v-97d38fe1]{opacity:0}pre code.hljs{padding:1em;display:block;overflow-x:auto}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
@@ -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-DanvMbHP.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-B3SLlTdd.css">
7
+ <script type="module" crossorigin src="/assets/index--qLTFieg.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DhB4kj3N.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="app"></div>
@@ -69,6 +69,32 @@ function parseBody(req) {
69
69
  });
70
70
  }
71
71
 
72
+ // ---------- pull-source 存储 ----------
73
+
74
+ function getPullSourcePath() {
75
+ return path.join(pkgSkillsSource, ".pull-source.json");
76
+ }
77
+
78
+ function readPullSource() {
79
+ const filePath = getPullSourcePath();
80
+ if (!fs.existsSync(filePath)) return {};
81
+ try {
82
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
87
+
88
+ function writePullSource(data) {
89
+ fs.writeFileSync(getPullSourcePath(), JSON.stringify(data, null, 2) + "\n", "utf-8");
90
+ }
91
+
92
+ function addPullSource(skillName, url) {
93
+ const data = readPullSource();
94
+ data[skillName] = { url, pulledAt: new Date().toISOString() };
95
+ writePullSource(data);
96
+ }
97
+
72
98
  // 手动递归拷贝目录
73
99
  function copyDirSync(src, dest) {
74
100
  fs.mkdirSync(dest, { recursive: true });
@@ -261,10 +287,12 @@ async function handleApi(req, res) {
261
287
  }
262
288
  const refreshed = core.getUserSkills(claudeDest);
263
289
  const userMap = new Map(refreshed.map((s) => [s.name, s]));
290
+ const pullSource = readPullSource();
264
291
  const result = pkgSkills.map((s) => ({
265
292
  ...s,
266
293
  enabled: userMap.has(s.name) ? userMap.get(s.name).enabled : false,
267
294
  installed: userMap.has(s.name),
295
+ source: pullSource[s.name] ? { type: "remote", url: pullSource[s.name].url } : { type: "local" },
268
296
  }));
269
297
  return sendJSON(res, result);
270
298
  }
@@ -336,6 +364,7 @@ async function handleApi(req, res) {
336
364
  tags: fm.tags || [],
337
365
  }, null, 2) + "\n", "utf-8");
338
366
  }
367
+ addPullSource(entry.name, url);
339
368
  imported.push(entry.name);
340
369
  } else {
341
370
  skipped.push(entry.name);
@@ -348,6 +377,157 @@ async function handleApi(req, res) {
348
377
  return sendJSON(res, { ok: true, imported, skipped });
349
378
  }
350
379
 
380
+ // GET /api/skills/:name/source — 查询技能来源
381
+ const sourceMatch = pathname.match(/^\/api\/skills\/([^/]+)\/source$/);
382
+ if (method === "GET" && sourceMatch) {
383
+ const name = sourceMatch[1];
384
+ const pullSource = readPullSource();
385
+ if (pullSource[name]) {
386
+ return sendJSON(res, { type: "remote", ...pullSource[name] });
387
+ }
388
+ // 本地技能:返回路径信息
389
+ const skillDir = path.join(pkgSkillsSource, name);
390
+ if (fs.existsSync(skillDir)) {
391
+ return sendJSON(res, { type: "local", path: skillDir });
392
+ }
393
+ return sendJSON(res, null);
394
+ }
395
+
396
+ // POST /api/skills/:name/update — 更新单个技能
397
+ const updateMatch = pathname.match(/^\/api\/skills\/([^/]+)\/update$/);
398
+ if (method === "POST" && updateMatch) {
399
+ const name = updateMatch[1];
400
+ const pullSource = readPullSource();
401
+ const source = pullSource[name];
402
+ if (!source) return sendJSON(res, { error: "该技能无远程来源,无法更新" }, 400);
403
+
404
+ const tmpDir = path.join(os.tmpdir(), "mdk-update-" + Date.now());
405
+ fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
406
+ try {
407
+ execSync("npx --yes skills add \"" + source.url + "\" --copy -y -a claude-code", {
408
+ cwd: tmpDir, stdio: "pipe", timeout: 120000,
409
+ });
410
+ } catch {
411
+ fs.rmSync(tmpDir, { recursive: true, force: true });
412
+ return sendJSON(res, { error: "重新拉取失败,请检查网络连接" }, 400);
413
+ }
414
+
415
+ const skillPath = path.join(tmpDir, ".claude", "skills", name);
416
+ if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
417
+ fs.rmSync(tmpDir, { recursive: true, force: true });
418
+ return sendJSON(res, { error: "远程仓库中未找到该技能" }, 400);
419
+ }
420
+
421
+ // 覆盖源目录
422
+ const dest = path.join(pkgSkillsSource, name);
423
+ if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
424
+ copyDirSync(skillPath, dest);
425
+
426
+ // 如果项目目录已启用,同步覆盖
427
+ const projectSkill = path.join(skillsDest, name);
428
+ if (fs.existsSync(projectSkill)) {
429
+ fs.rmSync(projectSkill, { recursive: true, force: true });
430
+ copyDirSync(skillPath, projectSkill);
431
+ }
432
+
433
+ // 更新 meta
434
+ const fm = core.parseFrontmatter(dest);
435
+ if (fm) {
436
+ fs.writeFileSync(path.join(dest, ".meta.json"), JSON.stringify({
437
+ version: fm.version || "1.0.0",
438
+ description: fm.description || "",
439
+ tags: fm.tags || [],
440
+ }, null, 2) + "\n", "utf-8");
441
+ }
442
+
443
+ // 更新时间戳
444
+ source.pulledAt = new Date().toISOString();
445
+ writePullSource(pullSource);
446
+
447
+ // 找同源兄弟
448
+ const siblings = Object.keys(pullSource).filter(k => k !== name && pullSource[k].url === source.url);
449
+
450
+ fs.rmSync(tmpDir, { recursive: true, force: true });
451
+ return sendJSON(res, { ok: true, name, updatedAt: source.pulledAt, siblings });
452
+ }
453
+
454
+ // POST /api/skills/batch-update — 批量更新同源技能
455
+ if (method === "POST" && pathname === "/api/skills/batch-update") {
456
+ const body = await parseBody(req);
457
+ const { names, url } = body;
458
+ if (!Array.isArray(names) || names.length === 0 || !url) {
459
+ return sendJSON(res, { error: "参数错误" }, 400);
460
+ }
461
+
462
+ const tmpDir = path.join(os.tmpdir(), "mdk-batch-" + Date.now());
463
+ fs.mkdirSync(path.join(tmpDir, ".claude", "skills"), { recursive: true });
464
+ try {
465
+ execSync("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", {
466
+ cwd: tmpDir, stdio: "pipe", timeout: 120000,
467
+ });
468
+ } catch {
469
+ fs.rmSync(tmpDir, { recursive: true, force: true });
470
+ return sendJSON(res, { error: "拉取失败,请检查网络连接" }, 400);
471
+ }
472
+
473
+ const updated = [];
474
+ const notFound = [];
475
+ for (const name of names) {
476
+ const skillPath = path.join(tmpDir, ".claude", "skills", name);
477
+ if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
478
+ notFound.push(name);
479
+ continue;
480
+ }
481
+ const dest = path.join(pkgSkillsSource, name);
482
+ if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
483
+ copyDirSync(skillPath, dest);
484
+
485
+ const projectSkill = path.join(skillsDest, name);
486
+ if (fs.existsSync(projectSkill)) {
487
+ fs.rmSync(projectSkill, { recursive: true, force: true });
488
+ copyDirSync(skillPath, projectSkill);
489
+ }
490
+
491
+ const fm = core.parseFrontmatter(dest);
492
+ if (fm) {
493
+ fs.writeFileSync(path.join(dest, ".meta.json"), JSON.stringify({
494
+ version: fm.version || "1.0.0",
495
+ description: fm.description || "",
496
+ tags: fm.tags || [],
497
+ }, null, 2) + "\n", "utf-8");
498
+ }
499
+ updated.push(name);
500
+ }
501
+
502
+ // 更新时间戳
503
+ const pullSource = readPullSource();
504
+ for (const name of updated) {
505
+ if (pullSource[name]) {
506
+ pullSource[name].pulledAt = new Date().toISOString();
507
+ }
508
+ }
509
+ writePullSource(pullSource);
510
+
511
+ fs.rmSync(tmpDir, { recursive: true, force: true });
512
+ return sendJSON(res, { ok: true, updated, notFound });
513
+ }
514
+
515
+ // POST /api/skills/:name/open — 在文件管理器中打开技能目录
516
+ const openMatch = pathname.match(/^\/api\/skills\/([^/]+)\/open$/);
517
+ if (method === "POST" && openMatch) {
518
+ const name = openMatch[1];
519
+ const skillDir = path.join(pkgSkillsSource, name);
520
+ if (!fs.existsSync(skillDir)) {
521
+ return sendJSON(res, { error: "技能目录不存在" }, 404);
522
+ }
523
+ try {
524
+ execSync("start \"\" \"" + skillDir + "\"", { stdio: "ignore" });
525
+ return sendJSON(res, { ok: true });
526
+ } catch {
527
+ return sendJSON(res, { error: "打开目录失败" }, 500);
528
+ }
529
+ }
530
+
351
531
  // GET /api/readme — 获取根目录 README.md
352
532
  if (method === "GET" && pathname === "/api/readme") {
353
533
  const p = path.join(skillsSource, "README.md");
@@ -109,19 +109,42 @@ export function updateSkillMeta(name, data) {
109
109
  });
110
110
  }
111
111
 
112
- export function pullSkills(url) {
113
- return request("/skills/pull", {
114
- method: "POST",
115
- body: JSON.stringify({ url }),
116
- });
117
- }
118
-
119
- export function deleteSkill(name) {
120
- return request("/skills/" + encodeURIComponent(name), {
121
- method: "DELETE",
122
- });
123
- }
124
-
112
+ export function pullSkills(url) {
113
+ return request("/skills/pull", {
114
+ method: "POST",
115
+ body: JSON.stringify({ url }),
116
+ });
117
+ }
118
+
119
+ export function deleteSkill(name) {
120
+ return request("/skills/" + encodeURIComponent(name), {
121
+ method: "DELETE",
122
+ });
123
+ }
124
+
125
+ export function getSkillSource(name) {
126
+ return request("/skills/" + encodeURIComponent(name) + "/source");
127
+ }
128
+
129
+ export function updateSkill(name) {
130
+ return request("/skills/" + encodeURIComponent(name) + "/update", {
131
+ method: "POST",
132
+ });
133
+ }
134
+
135
+ export function batchUpdateSkills(names, url) {
136
+ return request("/skills/batch-update", {
137
+ method: "POST",
138
+ body: JSON.stringify({ names, url }),
139
+ });
140
+ }
141
+
142
+ export function openSkillDir(name) {
143
+ return request("/skills/" + encodeURIComponent(name) + "/open", {
144
+ method: "POST",
145
+ });
146
+ }
147
+
125
148
  export function createReadme(data) {
126
149
  return request("/readme/create", {
127
150
  method: "POST",