mdk-skills 2.3.2 → 2.3.4

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-f2c0de50]{cursor:pointer;margin-bottom:12px;transition:box-shadow .2s}.skill-card[data-v-f2c0de50]:hover{box-shadow:0 2px 8px #00000014}.skill-meta[data-v-f2c0de50]{align-items:center;gap:8px;margin-bottom:8px;display:flex}.skill-version[data-v-f2c0de50]{color:#888;font-family:monospace;font-size:12px}.skill-desc[data-v-f2c0de50]{color:#666;text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:13px;overflow:hidden}.dashboard[data-v-f712ceef]{overflow-anchor:auto}.page-header[data-v-f712ceef]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:4px;display:flex}.page-header h2[data-v-f712ceef]{font-size:20px;font-weight:600}.header-actions[data-v-f712ceef]{align-items:center;gap:8px;display:flex}.tag-filter[data-v-f712ceef]{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:16px;display:flex}.detail-status[data-v-f712ceef]{justify-content:center;padding:40px 0;display:flex}.detail-body[data-v-f712ceef]{max-height:65vh;padding:16px 24px;overflow-y:auto}.edit-panel[data-v-f712ceef]{margin-bottom:4px}.edit-row[data-v-f712ceef]{align-items:flex-start;gap:10px;margin-bottom:10px;display:flex}.edit-label[data-v-f712ceef]{color:#888;flex-shrink:0;width:50px;font-size:13px;line-height:30px}.update-count-hint[data-v-f712ceef]{color:#e68a00;margin-left:8px;font-family:monospace;font-size:12px}.pull-modal-body[data-v-f712ceef]{min-height:100px}.pull-section[data-v-f712ceef]{margin-top:12px}.pull-input-row[data-v-f712ceef]{gap:8px;margin-bottom:8px;display:flex}.pull-input-row .n-input[data-v-f712ceef]{flex:1}.pull-error[data-v-f712ceef]{margin-bottom:8px}.pull-result[data-v-f712ceef]{margin-top:8px}.pull-result-line[data-v-f712ceef]{flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px;font-size:13px;display:flex}.pull-result-label[data-v-f712ceef]{color:#888;flex-shrink:0}.pull-result-hint[data-v-f712ceef]{color:#999;font-size:12px}.install-panel[data-v-f712ceef]{background:#00000005;border-radius:6px;margin-top:12px;padding:12px}.install-panel-label[data-v-f712ceef]{color:#666;margin-bottom:8px;font-size:13px}.install-check-list[data-v-f712ceef]{margin-bottom:10px}.install-btn[data-v-f712ceef]{float:right}.edit-actions[data-v-f712ceef]{justify-content:flex-end;gap:8px;display:flex}.skill-group[data-v-f712ceef]{margin-bottom:4px}.group-header[data-v-f712ceef]{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-f712ceef]:hover{background:#00000008}.fold-arrow[data-v-f712ceef]{color:#999;flex-shrink:0;font-size:10px;transition:transform .2s}.fold-arrow.open[data-v-f712ceef]{transform:rotate(90deg)}.group-label[data-v-f712ceef]{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-f712ceef]{color:#999;flex-shrink:0;font-size:12px}.group-update-btn[data-v-f712ceef]{flex-shrink:0}.group-body[data-v-f712ceef]{padding-left:4px}.source-info[data-v-f712ceef]{margin-bottom:4px}.source-row[data-v-f712ceef]{align-items:center;gap:10px;margin-bottom:6px;font-size:13px;display:flex}.source-label[data-v-f712ceef]{color:#888;flex-shrink:0;width:70px}.source-value[data-v-f712ceef]{word-break:break-all}.local-tag[data-v-f712ceef]{color:#2e7d32;background:#e8f5e9;border-radius:3px;padding:1px 8px;font-size:12px;display:inline-block}.path-text[data-v-f712ceef]{color:#666;font-family:monospace;font-size:12px}.source-actions[data-v-f712ceef]{gap:8px;margin-top:8px;display:flex}.local-hint[data-v-f712ceef]{color:#999;background:#00000005;border-radius:4px;margin-top:8px;padding:8px;font-size:12px;line-height:1.5}.siblings-body[data-v-f712ceef]{min-height:60px}.siblings-desc[data-v-f712ceef]{margin-bottom:12px;font-size:13px;line-height:1.6}.siblings-list[data-v-f712ceef]{flex-direction:column;gap:8px;margin-bottom:16px;display:flex}.siblings-actions[data-v-f712ceef]{justify-content:flex-end;gap:8px;display:flex}.batch-modal-body[data-v-f712ceef]{min-height:60px}.batch-desc[data-v-f712ceef]{margin-bottom:16px;font-size:13px;line-height:1.6}.batch-actions[data-v-f712ceef]{justify-content:flex-end;gap:8px;display:flex}.batch-executing[data-v-f712ceef]{color:#666;justify-content:center;align-items:center;gap:12px;padding:24px 0;font-size:13px;display:flex}.batch-result[data-v-f712ceef]{margin-bottom:16px}.batch-result-line[data-v-f712ceef]{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:10px;font-size:13px;display:flex}.batch-result-label[data-v-f712ceef]{color:#888;flex-shrink:0}.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-BnMBIily.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-BItq1iGH.css">
7
+ <script type="module" crossorigin src="/assets/index-Bq3Or_XK.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-Dgq5aj-Z.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="app"></div>
@@ -169,22 +169,58 @@ function writeSkillMeta(dest, wasUpdated) {
169
169
 
170
170
  // ---------- 异步 npx 管理:支持取消正在执行的进程 ----------
171
171
 
172
- let _npxProcess = null;
172
+ const runningTasks = new Map();
173
173
 
174
- function runNpx(cmd, cwd) {
174
+ function runAsync(cmd, options = {}) {
175
+ const { cwd, timeout = 120000, taskId = "default" } = options;
175
176
  return new Promise((resolve, reject) => {
176
- const proc = exec(cmd, { cwd, timeout: 120000 }, (err) => {
177
- _npxProcess = null;
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
- reject(err.killed ? new Error("已取消") : err);
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
- _npxProcess = proc;
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 runNpx("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", tmpDir);
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.message === "已取消") return sendJSON(res, { cancelled: true }, 499);
418
- return sendJSON(res, { error: "拉取失败,请检查地址或网络连接" }, 400);
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 runNpx("npx --yes skills add \"" + source.url + "\" --copy -y -a claude-code", tmpDir);
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.message === "已取消") return sendJSON(res, { cancelled: true }, 499);
480
- return sendJSON(res, { error: "重新拉取失败,请检查网络连接" }, 400);
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 runNpx("npx --yes skills add \"" + url + "\" --copy -y -a claude-code", tmpDir);
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.message === "已取消") return sendJSON(res, { cancelled: true }, 499);
541
- return sendJSON(res, { error: "拉取失败,请检查网络连接" }, 400);
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
- if (_npxProcess) {
1163
- _npxProcess.kill();
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) {
@@ -360,10 +418,12 @@ const installSelected = ref({});
360
418
  const installing = ref(false);
361
419
 
362
420
  function openPullModal() {
421
+ const savedY = window.scrollY;
363
422
  showPullModal.value = true;
364
423
  pullUrl.value = "";
365
424
  pullResult.value = null;
366
425
  pullError.value = "";
426
+ nextTick(() => window.scrollTo(0, savedY));
367
427
  }
368
428
 
369
429
  async function handlePull() {
@@ -512,6 +572,7 @@ async function loadReadme() {
512
572
  }
513
573
 
514
574
  async function showSkillDetail(skill) {
575
+ const savedY = window.scrollY;
515
576
  detailSkill.value = skill;
516
577
  detailVisible.value = true;
517
578
  detailLoading.value = true;
@@ -521,6 +582,7 @@ async function showSkillDetail(skill) {
521
582
  editVersion.value = skill.version || "1.0.0";
522
583
  editDescription.value = skill.description || "";
523
584
  editTags.value = [...(skill.tags || [])];
585
+ nextTick(() => window.scrollTo(0, savedY));
524
586
  // 查来源
525
587
  try {
526
588
  const res = await getSkillSource(skill.name);
@@ -578,25 +640,31 @@ async function handleUpdate() {
578
640
  }
579
641
 
580
642
  async function batchUpdateGroup(group) {
581
- const ok = window.confirm(`确定更新 "${group.url}" 下的 ${group.skills.length} 个技能吗?`);
582
- if (!ok) return;
643
+ const savedY = window.scrollY;
644
+ batchGroup.value = group;
645
+ batchResult.value = null;
646
+ batchError.value = "";
647
+ batchPhase.value = "confirm";
648
+ showBatchModal.value = true;
649
+ nextTick(() => window.scrollTo(0, savedY));
650
+ }
651
+
652
+ async function doBatchUpdate() {
653
+ batchPhase.value = "executing";
583
654
  try {
584
- const names = group.skills.map((s) => s.name);
585
- const res = await batchUpdateSkills(names, group.url);
655
+ const names = batchGroup.value.skills.map((s) => s.name);
656
+ const res = await batchUpdateSkills(names, batchGroup.value.url);
586
657
  if (res.ok) {
587
- message.success(`${res.updated.length} 个技能已更新`);
588
- if (res.unchanged && res.unchanged.length > 0) {
589
- message.info(`${res.unchanged.length} 个技能已是最新,跳过`);
590
- }
591
- if (res.notFound && res.notFound.length > 0) {
592
- message.warning(`${res.notFound.length} 个技能未找到,已跳过`);
593
- }
658
+ batchResult.value = { updated: res.updated, unchanged: res.unchanged, notFound: res.notFound };
659
+ batchPhase.value = "done";
594
660
  await loadSkills();
595
661
  } else {
596
- message.error(res.error || "批量更新失败");
662
+ batchError.value = res.error || "批量更新失败";
663
+ batchPhase.value = "error";
597
664
  }
598
665
  } catch {
599
- message.error("批量更新失败");
666
+ batchError.value = "批量更新失败";
667
+ batchPhase.value = "error";
600
668
  }
601
669
  }
602
670
 
@@ -667,6 +735,10 @@ onUnmounted(() => document.removeEventListener("visibilitychange", onFocus));
667
735
  </script>
668
736
 
669
737
  <style scoped>
738
+ .dashboard {
739
+ overflow-anchor: auto;
740
+ }
741
+
670
742
  .page-header {
671
743
  display: flex;
672
744
  align-items: center;
@@ -937,4 +1009,49 @@ onUnmounted(() => document.removeEventListener("visibilitychange", onFocus));
937
1009
  justify-content: flex-end;
938
1010
  gap: 8px;
939
1011
  }
1012
+
1013
+ /* 全部更新弹窗 */
1014
+ .batch-modal-body {
1015
+ min-height: 60px;
1016
+ }
1017
+
1018
+ .batch-desc {
1019
+ font-size: 13px;
1020
+ line-height: 1.6;
1021
+ margin-bottom: 16px;
1022
+ }
1023
+
1024
+ .batch-actions {
1025
+ display: flex;
1026
+ justify-content: flex-end;
1027
+ gap: 8px;
1028
+ }
1029
+
1030
+ .batch-executing {
1031
+ display: flex;
1032
+ align-items: center;
1033
+ gap: 12px;
1034
+ padding: 24px 0;
1035
+ justify-content: center;
1036
+ color: #666;
1037
+ font-size: 13px;
1038
+ }
1039
+
1040
+ .batch-result {
1041
+ margin-bottom: 16px;
1042
+ }
1043
+
1044
+ .batch-result-line {
1045
+ display: flex;
1046
+ align-items: center;
1047
+ flex-wrap: wrap;
1048
+ gap: 6px;
1049
+ margin-bottom: 10px;
1050
+ font-size: 13px;
1051
+ }
1052
+
1053
+ .batch-result-label {
1054
+ color: #888;
1055
+ flex-shrink: 0;
1056
+ }
940
1057
  </style>
@@ -1 +0,0 @@
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-f2c0de50]{cursor:pointer;margin-bottom:12px;transition:box-shadow .2s}.skill-card[data-v-f2c0de50]:hover{box-shadow:0 2px 8px #00000014}.skill-meta[data-v-f2c0de50]{align-items:center;gap:8px;margin-bottom:8px;display:flex}.skill-version[data-v-f2c0de50]{color:#888;font-family:monospace;font-size:12px}.skill-desc[data-v-f2c0de50]{color:#666;text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:13px;overflow:hidden}.page-header[data-v-b6b1cdad]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:4px;display:flex}.page-header h2[data-v-b6b1cdad]{font-size:20px;font-weight:600}.header-actions[data-v-b6b1cdad]{align-items:center;gap:8px;display:flex}.tag-filter[data-v-b6b1cdad]{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:16px;display:flex}.detail-status[data-v-b6b1cdad]{justify-content:center;padding:40px 0;display:flex}.detail-body[data-v-b6b1cdad]{max-height:65vh;padding:16px 24px;overflow-y:auto}.edit-panel[data-v-b6b1cdad]{margin-bottom:4px}.edit-row[data-v-b6b1cdad]{align-items:flex-start;gap:10px;margin-bottom:10px;display:flex}.edit-label[data-v-b6b1cdad]{color:#888;flex-shrink:0;width:50px;font-size:13px;line-height:30px}.update-count-hint[data-v-b6b1cdad]{color:#e68a00;margin-left:8px;font-family:monospace;font-size:12px}.pull-modal-body[data-v-b6b1cdad]{min-height:100px}.pull-section[data-v-b6b1cdad]{margin-top:12px}.pull-input-row[data-v-b6b1cdad]{gap:8px;margin-bottom:8px;display:flex}.pull-input-row .n-input[data-v-b6b1cdad]{flex:1}.pull-error[data-v-b6b1cdad]{margin-bottom:8px}.pull-result[data-v-b6b1cdad]{margin-top:8px}.pull-result-line[data-v-b6b1cdad]{flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px;font-size:13px;display:flex}.pull-result-label[data-v-b6b1cdad]{color:#888;flex-shrink:0}.pull-result-hint[data-v-b6b1cdad]{color:#999;font-size:12px}.install-panel[data-v-b6b1cdad]{background:#00000005;border-radius:6px;margin-top:12px;padding:12px}.install-panel-label[data-v-b6b1cdad]{color:#666;margin-bottom:8px;font-size:13px}.install-check-list[data-v-b6b1cdad]{margin-bottom:10px}.install-btn[data-v-b6b1cdad]{float:right}.edit-actions[data-v-b6b1cdad]{justify-content:flex-end;gap:8px;display:flex}.skill-group[data-v-b6b1cdad]{margin-bottom:4px}.group-header[data-v-b6b1cdad]{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-b6b1cdad]:hover{background:#00000008}.fold-arrow[data-v-b6b1cdad]{color:#999;flex-shrink:0;font-size:10px;transition:transform .2s}.fold-arrow.open[data-v-b6b1cdad]{transform:rotate(90deg)}.group-label[data-v-b6b1cdad]{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-b6b1cdad]{color:#999;flex-shrink:0;font-size:12px}.group-update-btn[data-v-b6b1cdad]{flex-shrink:0}.group-body[data-v-b6b1cdad]{padding-left:4px}.source-info[data-v-b6b1cdad]{margin-bottom:4px}.source-row[data-v-b6b1cdad]{align-items:center;gap:10px;margin-bottom:6px;font-size:13px;display:flex}.source-label[data-v-b6b1cdad]{color:#888;flex-shrink:0;width:70px}.source-value[data-v-b6b1cdad]{word-break:break-all}.local-tag[data-v-b6b1cdad]{color:#2e7d32;background:#e8f5e9;border-radius:3px;padding:1px 8px;font-size:12px;display:inline-block}.path-text[data-v-b6b1cdad]{color:#666;font-family:monospace;font-size:12px}.source-actions[data-v-b6b1cdad]{gap:8px;margin-top:8px;display:flex}.local-hint[data-v-b6b1cdad]{color:#999;background:#00000005;border-radius:4px;margin-top:8px;padding:8px;font-size:12px;line-height:1.5}.siblings-body[data-v-b6b1cdad]{min-height:60px}.siblings-desc[data-v-b6b1cdad]{margin-bottom:12px;font-size:13px;line-height:1.6}.siblings-list[data-v-b6b1cdad]{flex-direction:column;gap:8px;margin-bottom:16px;display:flex}.siblings-actions[data-v-b6b1cdad]{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}