dsh-skill-hub 0.3.6 → 0.3.7

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.
Files changed (38) hide show
  1. package/lib/client.js +890 -356
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +401 -47
  4. package/lib/types/client/SkillHubSettingsCard.d.ts +0 -2
  5. package/lib/types/client/api.d.ts +5 -1
  6. package/lib/types/client/helpers.d.ts +10 -0
  7. package/lib/types/client/locales.d.ts +30 -0
  8. package/lib/types/client/panel/DisabledRow.d.ts +2 -0
  9. package/lib/types/client/panel/SkillDetailView.d.ts +5 -1
  10. package/lib/types/client/panel/dialogs.d.ts +19 -0
  11. package/lib/types/client/panel/useSkillHub.d.ts +12 -1
  12. package/lib/types/client/settings-card.d.ts +0 -17
  13. package/lib/types/client/settings-form.d.ts +0 -1
  14. package/lib/types/protocol.d.ts +40 -17
  15. package/lib/types/repo.d.ts +4 -6
  16. package/lib/types/skillfs.d.ts +13 -0
  17. package/package.json +29 -28
  18. package/src/client/SkillHubSettingsCard.tsx +0 -2
  19. package/src/client/api.ts +18 -0
  20. package/src/client/helpers.ts +40 -0
  21. package/src/client/locales.ts +60 -0
  22. package/src/client/panel/DisabledRow.tsx +22 -7
  23. package/src/client/panel/MarketView.tsx +8 -1
  24. package/src/client/panel/ScenesView.tsx +1 -1
  25. package/src/client/panel/SkillDetailView.tsx +35 -11
  26. package/src/client/panel/SkillHubPanel.tsx +42 -4
  27. package/src/client/panel/SkillRow.tsx +5 -2
  28. package/src/client/panel/SourcesView.tsx +2 -17
  29. package/src/client/panel/dialogs.tsx +61 -0
  30. package/src/client/panel/panel.module.css +1 -8
  31. package/src/client/panel/useSkillHub.ts +97 -12
  32. package/src/client/settings-card.tsx +0 -42
  33. package/src/client/settings-form.ts +1 -5
  34. package/src/protocol.ts +42 -8
  35. package/src/repo.ts +71 -29
  36. package/src/routes.test.ts +22 -0
  37. package/src/routes.ts +181 -10
  38. package/src/skillfs.ts +188 -3
package/lib/client.js CHANGED
@@ -46,7 +46,9 @@ window.__ModuleLoader__.load({
46
46
  sourceSync: "/api/skill-hub/sources/sync",
47
47
  sourceDelete: "/api/skill-hub/sources/delete",
48
48
  sourceRestore: "/api/skill-hub/sources/restore",
49
- sourceTrashClear: "/api/skill-hub/sources/trash/clear"
49
+ sourceTrashClear: "/api/skill-hub/sources/trash/clear",
50
+ diagnosticFix: "/api/skill-hub/diagnostic/fix",
51
+ marketSourceVersions: "/api/skill-hub/market/source/versions"
50
52
  };
51
53
  /** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
52
54
  function isProjectSource(source) {
@@ -177,6 +179,10 @@ window.__ModuleLoader__.load({
177
179
  body: JSON.stringify({ repo })
178
180
  }));
179
181
  }
182
+ /** Releases + branches of a market source for the version picker. */
183
+ async marketSourceVersions(repo) {
184
+ return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSourceVersions + "?repo=" + encodeURIComponent(repo)));
185
+ }
180
186
  /** Pin a market source to an explicit ref (branch picker). */
181
187
  async setMarketSourceRef(repo, ref) {
182
188
  return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSourceRef, {
@@ -338,6 +344,14 @@ window.__ModuleLoader__.load({
338
344
  async clearTrash() {
339
345
  return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceTrashClear, { method: "POST" }));
340
346
  }
347
+ /** Auto-fix a fixable diagnostic (e.g. unquoted colon). */
348
+ async fixDiagnostic(path) {
349
+ return readJson(await fetchWithTimeout(SKILL_HUB_API.diagnosticFix, {
350
+ method: "POST",
351
+ headers: { "content-type": "application/json" },
352
+ body: JSON.stringify({ path })
353
+ }));
354
+ }
341
355
  };
342
356
  //#endregion
343
357
  //#region src/client/locales.ts
@@ -407,12 +421,22 @@ window.__ModuleLoader__.load({
407
421
  "market.branchTitle": "选择分支",
408
422
  "market.branchHint": "该仓库没有正式发布,选择要跟踪的分支(默认为 main):",
409
423
  "market.branchConfirm": "确定",
424
+ "market.versionTitle": "切换跟踪版本",
425
+ "market.versionText": "选择 {repo} 要跟踪的版本,切换后建议重新扫描并同步已装技能",
426
+ "market.versionCurrent": "当前 {ref}",
427
+ "market.versionReleases": "发布版",
428
+ "market.versionBranches": "分支",
429
+ "market.versionCustom": "自定义(tag / 分支 / commit)",
430
+ "market.versionConfirm": "切换到 {ref}",
431
+ "market.versionHint": "点击切换跟踪版本",
432
+ "market.unpinned": "未定版",
410
433
  "market.syncTitle": "同步本地技能",
411
434
  "market.syncText": "市场源已对齐到 {ref},要同步更新以下本地技能吗?",
412
435
  "market.syncNone": "该来源还没有导入本地技能。",
413
436
  "market.syncCancel": "跳过",
414
437
  "market.removeHint": "移除源不影响已安装的技能。",
415
438
  "repo.ready": "发现 {count} 个可导入技能",
439
+ "repo.truncated": "⚠ 仓库过大,仅展示部分技能(GitHub 截断),可缩小仓库或指定子目录",
416
440
  "repo.empty": "没有找到可导入技能。",
417
441
  "repo.selectAll": "全选",
418
442
  "repo.clearAll": "清空",
@@ -485,6 +509,26 @@ window.__ModuleLoader__.load({
485
509
  "row.enable": "启用",
486
510
  "row.delete": "移入回收站",
487
511
  "row.open": "查看 {name} 详情(回车)",
512
+ "row.duplicate": "重名",
513
+ "row.duplicateHint": "同名技能存在多个来源,当前显示为优先级最高的版本",
514
+ "row.copy": "复制",
515
+ "row.copyMention": "复制 $mention",
516
+ "row.copied": "已复制",
517
+ "row.copyPath": "复制路径",
518
+ "detail.copyMention": "复制 $mention",
519
+ "detail.copyPath": "复制路径",
520
+ "detail.copied": "已复制",
521
+ "diag.fix": "修复",
522
+ "diag.fixing": "修复中…",
523
+ "diag.fixed": "已修复",
524
+ "diag.fixConfirm": "用自动修复重写以下文件的 frontmatter?建议先备份:{path}",
525
+ "filter.invocation": "调用",
526
+ "filter.invocationAll": "全部调用",
527
+ "filter.modelOnly": "模型可调",
528
+ "filter.userOnly": "用户可调",
529
+ "filter.showing": "显示 {shown}/{total}(已启用){filtered}",
530
+ "filter.filteredSuffix": " · 已筛选",
531
+ "filter.shortened": "短描述 {count} 条,悬浮看全文",
488
532
  "delete.confirmTitle": "移入回收站?",
489
533
  "delete.confirmText": "确定把「{name}」移入回收站吗?可随时恢复。",
490
534
  "delete.confirm": "移入回收站",
@@ -632,12 +676,22 @@ window.__ModuleLoader__.load({
632
676
  "market.branchTitle": "Choose a branch",
633
677
  "market.branchHint": "This repo has no releases. Pick a branch to track (main by default):",
634
678
  "market.branchConfirm": "Confirm",
679
+ "market.versionTitle": "Switch tracked version",
680
+ "market.versionText": "Pick the version for {repo} to track; rescan and sync installed skills afterwards",
681
+ "market.versionCurrent": "current {ref}",
682
+ "market.versionReleases": "Releases",
683
+ "market.versionBranches": "Branches",
684
+ "market.versionCustom": "Custom (tag / branch / commit)",
685
+ "market.versionConfirm": "Switch to {ref}",
686
+ "market.versionHint": "Click to switch the tracked version",
687
+ "market.unpinned": "unpinned",
635
688
  "market.syncTitle": "Update local skills",
636
689
  "market.syncText": "Market source aligned to {ref}. Update these local skills?",
637
690
  "market.syncNone": "No local skills tracked from this repo yet.",
638
691
  "market.syncCancel": "Skip",
639
692
  "market.removeHint": "Removing a source does not affect installed skills.",
640
693
  "repo.ready": "{count} importable skills found",
694
+ "repo.truncated": "⚠ Repo too large, only partial skills shown (GitHub truncated)",
641
695
  "repo.empty": "No importable skills found.",
642
696
  "repo.selectAll": "Select all",
643
697
  "repo.clearAll": "Clear",
@@ -710,6 +764,26 @@ window.__ModuleLoader__.load({
710
764
  "row.enable": "Enable",
711
765
  "row.delete": "Move to trash",
712
766
  "row.open": "View {name} details (Enter)",
767
+ "row.duplicate": "duplicate",
768
+ "row.duplicateHint": "Multiple sources provide this name; the highest-priority one is shown",
769
+ "row.copy": "Copy",
770
+ "row.copyMention": "Copy $mention",
771
+ "row.copied": "Copied",
772
+ "row.copyPath": "Copy path",
773
+ "detail.copyMention": "Copy $mention",
774
+ "detail.copyPath": "Copy path",
775
+ "detail.copied": "Copied",
776
+ "diag.fix": "Fix",
777
+ "diag.fixing": "Fixing…",
778
+ "diag.fixed": "Fixed",
779
+ "diag.fixConfirm": "Rewrite the frontmatter of this file with the auto-fix? Back it up first: {path}",
780
+ "filter.invocation": "Invocation",
781
+ "filter.invocationAll": "All",
782
+ "filter.modelOnly": "Model",
783
+ "filter.userOnly": "User",
784
+ "filter.showing": "Showing {shown}/{total} (enabled){filtered}",
785
+ "filter.filteredSuffix": " · filtered",
786
+ "filter.shortened": "{count} short descriptions, hover for full text",
713
787
  "delete.confirmTitle": "Move to trash?",
714
788
  "delete.confirmText": "Move \"{name}\" to trash? It can be restored anytime.",
715
789
  "delete.confirm": "Move to trash",
@@ -951,6 +1025,40 @@ window.__ModuleLoader__.load({
951
1025
  if (error instanceof Error) return error.message;
952
1026
  return String(error);
953
1027
  }
1028
+ /**
1029
+ * Whether a displayName is worth showing beside the kebab-case name.
1030
+ * Pure case/punctuation variants (e.g. "Code Review" vs "code-review") are redundant.
1031
+ */
1032
+ function isDisplayNameDistinct(name, displayName) {
1033
+ if (displayName === void 0) return false;
1034
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "");
1035
+ return normalize(displayName) !== normalize(name);
1036
+ }
1037
+ /**
1038
+ * Copy text to the clipboard. Uses the async API with a positioned-textarea
1039
+ * fallback for insecure contexts. Resolves true only when the copy succeeded.
1040
+ */
1041
+ async function copyTextToClipboard(text) {
1042
+ if (navigator.clipboard?.writeText !== void 0) try {
1043
+ await navigator.clipboard.writeText(text);
1044
+ return true;
1045
+ } catch {}
1046
+ try {
1047
+ const ta = document.createElement("textarea");
1048
+ ta.value = text;
1049
+ ta.setAttribute("readonly", "");
1050
+ ta.style.position = "fixed";
1051
+ ta.style.top = "0";
1052
+ ta.style.opacity = "0";
1053
+ document.body.appendChild(ta);
1054
+ ta.select();
1055
+ const ok = document.execCommand("copy");
1056
+ ta.remove();
1057
+ return ok;
1058
+ } catch {
1059
+ return false;
1060
+ }
1061
+ }
954
1062
  //#endregion
955
1063
  //#region src/client/panel/format.ts
956
1064
  /** Model-invocable dot color default. Single source for the TS side; the
@@ -2405,7 +2513,7 @@ window.__ModuleLoader__.load({
2405
2513
  }
2406
2514
  async store(field, value) {
2407
2515
  await this.scope.set(field, value);
2408
- return this.userLayer()?.[field] === value;
2516
+ return this.userRecord()?.[field] === value;
2409
2517
  }
2410
2518
  userRecord() {
2411
2519
  const user = this.scope.getSnapshot().user;
@@ -2428,9 +2536,6 @@ window.__ModuleLoader__.load({
2428
2536
  const base = this.scope.getSnapshot().base;
2429
2537
  return typeof base === "object" && base !== null ? base[field] : void 0;
2430
2538
  }
2431
- userLayer() {
2432
- return this.userRecord();
2433
- }
2434
2539
  stored(field) {
2435
2540
  const user = this.userRecord();
2436
2541
  return user !== void 0 && Object.hasOwn(user, field);
@@ -2658,7 +2763,7 @@ window.__ModuleLoader__.load({
2658
2763
  }
2659
2764
  //#endregion
2660
2765
  //#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
2661
- const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{box-sizing:border-box;align-items:center;gap:8px;min-height:44px;padding:10px 14px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;align-items:center;gap:6px;min-width:0;font-size:13px;font-weight:600;line-height:1;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;min-height:24px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;align-self:center;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)translateY(-1px)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)translateY(-1px)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;margin-left:6px;font-size:12px;font-weight:500}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}._6VtqdG_dragHandle{cursor:grab;opacity:.28;letter-spacing:0;user-select:none;flex:none;justify-content:center;align-self:center;align-items:center;width:8px;height:16px;padding:0 1px;font-size:10px;display:inline-flex;overflow:hidden}._6VtqdG_dragHandle:active{cursor:grabbing}._6VtqdG_dragging{opacity:.45}._6VtqdG_dragOver{outline:2px dashed var(--hub-model,#2f81f7);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{background:#8080800f;border:.5px solid #80808021;border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:#80808021;border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-model,#2f81f7);height:100%;transition:width .2s}";
2766
+ const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{box-sizing:border-box;align-items:center;gap:8px;min-height:44px;padding:10px 14px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;align-items:center;gap:6px;min-width:0;font-size:13px;font-weight:600;line-height:1;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_displayName{opacity:.55;white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:0;max-width:160px;margin-left:6px;font-size:11px;font-weight:400;overflow:hidden}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;min-height:24px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;align-self:center;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)translateY(-1px)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)translateY(-1px)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;margin-left:6px;font-size:12px;font-weight:500}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}._6VtqdG_dragHandle{cursor:grab;opacity:.28;letter-spacing:0;user-select:none;flex:none;justify-content:center;align-self:center;align-items:center;width:8px;height:16px;padding:0 1px;font-size:10px;display:inline-flex;overflow:hidden}._6VtqdG_dragHandle:active{cursor:grabbing}._6VtqdG_dragging{opacity:.45}._6VtqdG_dragOver{outline:2px dashed var(--hub-model,#2f81f7);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{background:#8080800f;border:.5px solid #80808021;border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:#80808021;border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-model,#2f81f7);height:100%;transition:width .2s}";
2662
2767
  const tagId = "dsh-skill-hub/panel.module.css";
2663
2768
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2664
2769
  const tag = document.createElement("style");
@@ -2669,18 +2774,13 @@ window.__ModuleLoader__.load({
2669
2774
  }
2670
2775
  var panel_module_css_default = {
2671
2776
  "actions": "_6VtqdG_actions",
2672
- "actionsBottom": "_6VtqdG_actionsBottom",
2673
2777
  "actionsPadded": "_6VtqdG_actionsPadded",
2674
2778
  "actionsTop": "_6VtqdG_actionsTop",
2675
2779
  "back": "_6VtqdG_back",
2676
2780
  "badge": "_6VtqdG_badge",
2677
2781
  "badgeCount": "_6VtqdG_badgeCount",
2678
- "badgeDisabled": "_6VtqdG_badgeDisabled",
2679
- "badgeModel": "_6VtqdG_badgeModel",
2680
2782
  "badgeReadonly": "_6VtqdG_badgeReadonly",
2681
2783
  "badgeSource": "_6VtqdG_badgeSource",
2682
- "badgeUser": "_6VtqdG_badgeUser",
2683
- "badgeUses": "_6VtqdG_badgeUses",
2684
2784
  "badges": "_6VtqdG_badges",
2685
2785
  "button": "_6VtqdG_button",
2686
2786
  "buttons": "_6VtqdG_buttons",
@@ -2704,6 +2804,7 @@ window.__ModuleLoader__.load({
2704
2804
  "dialogText": "_6VtqdG_dialogText",
2705
2805
  "dialogTitle": "_6VtqdG_dialogTitle",
2706
2806
  "disclosure": "_6VtqdG_disclosure",
2807
+ "displayName": "_6VtqdG_displayName",
2707
2808
  "dot": "_6VtqdG_dot",
2708
2809
  "dotModel": "_6VtqdG_dotModel",
2709
2810
  "dotUser": "_6VtqdG_dotUser",
@@ -2747,7 +2848,6 @@ window.__ModuleLoader__.load({
2747
2848
  "row": "_6VtqdG_row",
2748
2849
  "rowDesc": "_6VtqdG_rowDesc",
2749
2850
  "rowMain": "_6VtqdG_rowMain",
2750
- "rowMeta": "_6VtqdG_rowMeta",
2751
2851
  "rowMuted": "_6VtqdG_rowMuted",
2752
2852
  "rowName": "_6VtqdG_rowName",
2753
2853
  "rowNameText": "_6VtqdG_rowNameText",
@@ -2845,32 +2945,108 @@ window.__ModuleLoader__.load({
2845
2945
  }
2846
2946
  //#endregion
2847
2947
  //#region src/client/panel/SkillDetailView.tsx
2948
+ /**
2949
+ * Full-page skill detail view: metadata, upstream source card with
2950
+ * check/sync/follow-delete actions, and the raw SKILL.md body. Pure
2951
+ * presentation — every state value and action arrives as a prop, so the
2952
+ * panel stays the single owner of state.
2953
+ */
2848
2954
  function SkillDetailView(props) {
2849
- const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, loading, onBack, onCheck, onSync, onFollowDelete } = props;
2955
+ const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, loading, disabled, onEnable, onBack, onCheck, onSync, onFollowDelete } = props;
2850
2956
  const detailSource = sourcesState?.sources.find((source) => source.skills.includes(detail.name));
2851
2957
  const detailCheck = detailSource !== void 0 ? sourceCheck[detailSource.repo] : void 0;
2958
+ const [copied, setCopied] = (0, react.useState)(null);
2959
+ const copyTimer = (0, react.useRef)(void 0);
2960
+ (0, react.useEffect)(() => () => {
2961
+ window.clearTimeout(copyTimer.current);
2962
+ }, []);
2963
+ const copyText = (text, key) => {
2964
+ copyTextToClipboard(text).then((ok) => {
2965
+ if (!ok) return;
2966
+ setCopied(key);
2967
+ window.clearTimeout(copyTimer.current);
2968
+ copyTimer.current = window.setTimeout(() => {
2969
+ setCopied(null);
2970
+ }, 1200);
2971
+ });
2972
+ };
2852
2973
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2853
2974
  className: panel_module_css_default.panel,
2854
2975
  children: [
2855
2976
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2856
2977
  className: panel_module_css_default.detailHead,
2857
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2858
- type: "button",
2859
- className: panel_module_css_default.back,
2860
- onClick: onBack,
2861
- children: tt("detail.back")
2862
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2863
- className: panel_module_css_default.detailName,
2864
- children: [detail.name, detail.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2865
- className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
2866
- style: dotStyle(hubConfig?.dotModelColor),
2867
- title: tt("legend.model")
2868
- }) : detail.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2869
- className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
2870
- style: dotStyle(hubConfig?.dotUserColor),
2871
- title: tt("legend.user")
2872
- }) : null]
2873
- })]
2978
+ children: [
2979
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2980
+ type: "button",
2981
+ className: panel_module_css_default.back,
2982
+ onClick: onBack,
2983
+ children: tt("detail.back")
2984
+ }),
2985
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2986
+ className: panel_module_css_default.detailName,
2987
+ style: {
2988
+ display: "inline-flex",
2989
+ alignItems: "center",
2990
+ gap: 6,
2991
+ flexWrap: "wrap"
2992
+ },
2993
+ children: [
2994
+ detail.name,
2995
+ isDisplayNameDistinct(detail.name, detail.displayName) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2996
+ className: panel_module_css_default.displayName,
2997
+ style: {
2998
+ fontSize: 13,
2999
+ marginLeft: 0
3000
+ },
3001
+ children: detail.displayName
3002
+ }) : null,
3003
+ detail.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3004
+ className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
3005
+ style: dotStyle(hubConfig?.dotModelColor),
3006
+ title: tt("legend.model")
3007
+ }) : detail.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3008
+ className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
3009
+ style: dotStyle(hubConfig?.dotUserColor),
3010
+ title: tt("legend.user")
3011
+ }) : null
3012
+ ]
3013
+ }),
3014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3015
+ className: panel_module_css_default.actions,
3016
+ style: {
3017
+ marginLeft: "auto",
3018
+ gap: 6
3019
+ },
3020
+ children: [
3021
+ disabled === true && onEnable !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3022
+ type: "button",
3023
+ className: panel_module_css_default.opBtn,
3024
+ role: "switch",
3025
+ "aria-checked": false,
3026
+ "aria-label": tt("row.enable"),
3027
+ onClick: onEnable,
3028
+ children: tt("row.enable")
3029
+ }) : null,
3030
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3031
+ type: "button",
3032
+ className: panel_module_css_default.opBtn,
3033
+ onClick: () => {
3034
+ copyText("$" + detail.name, "mention");
3035
+ },
3036
+ children: copied === "mention" ? tt("detail.copied") : tt("detail.copyMention")
3037
+ }),
3038
+ detail.path !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3039
+ type: "button",
3040
+ className: panel_module_css_default.opBtn,
3041
+ onClick: () => {
3042
+ const path = detail.path;
3043
+ if (path !== void 0) copyText(path, "path");
3044
+ },
3045
+ children: copied === "path" ? tt("detail.copied") : tt("detail.copyPath")
3046
+ }) : null
3047
+ ]
3048
+ })
3049
+ ]
2874
3050
  }),
2875
3051
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2876
3052
  className: panel_module_css_default.detailMeta,
@@ -2972,34 +3148,30 @@ window.__ModuleLoader__.load({
2972
3148
  }),
2973
3149
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2974
3150
  className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
2975
- children: [
2976
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2977
- type: "button",
2978
- className: panel_module_css_default.opBtn,
2979
- disabled: checkingSource !== null,
2980
- onClick: () => {
2981
- onCheck(detailSource.repo);
2982
- },
2983
- children: checkingSource === detailSource.repo ? tt("source.checking") : tt("source.check")
2984
- }),
2985
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2986
- type: "button",
2987
- className: panel_module_css_default.opBtn,
2988
- disabled: syncingSource !== null,
2989
- onClick: () => {
2990
- onSync(detailSource.repo, [detail.name]);
2991
- },
2992
- children: syncingSource === detailSource.repo ? tt("source.syncing") : tt("source.sync")
2993
- }),
2994
- detailCheck?.deleted.includes(detail.name) === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2995
- type: "button",
2996
- className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
2997
- onClick: () => {
2998
- onFollowDelete(detailSource.repo, [detail.name]);
2999
- },
3000
- children: tt("source.followDelete")
3001
- }) : null
3002
- ]
3151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3152
+ type: "button",
3153
+ className: panel_module_css_default.opBtn,
3154
+ disabled: checkingSource !== null,
3155
+ onClick: () => {
3156
+ onCheck(detailSource.repo);
3157
+ },
3158
+ children: checkingSource === detailSource.repo ? tt("source.checking") : tt("source.check")
3159
+ }), disabled !== true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3160
+ type: "button",
3161
+ className: panel_module_css_default.opBtn,
3162
+ disabled: syncingSource !== null,
3163
+ onClick: () => {
3164
+ onSync(detailSource.repo, [detail.name]);
3165
+ },
3166
+ children: syncingSource === detailSource.repo ? tt("source.syncing") : tt("source.sync")
3167
+ }), detailCheck?.deleted.includes(detail.name) === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3168
+ type: "button",
3169
+ className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
3170
+ onClick: () => {
3171
+ onFollowDelete(detailSource.repo, [detail.name]);
3172
+ },
3173
+ children: tt("source.followDelete")
3174
+ }) : null] }) : null]
3003
3175
  })
3004
3176
  ]
3005
3177
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -3136,6 +3308,7 @@ window.__ModuleLoader__.load({
3136
3308
  const stat = hub.uses.get(skill.name);
3137
3309
  const count = stat?.count ?? 0;
3138
3310
  const lastUsed = stat?.lastUsed;
3311
+ const isDuplicate = hub.catalog?.duplicateNames?.includes(skill.name) === true;
3139
3312
  /** 单一状态圆点:模型可调 → 蓝;否则用户可调 → 绿。与聊天 / 菜单同规则。 */
3140
3313
  const dot = skill.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3141
3314
  className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
@@ -3172,11 +3345,21 @@ window.__ModuleLoader__.load({
3172
3345
  className: panel_module_css_default.rowNameText,
3173
3346
  children: skill.name
3174
3347
  }),
3348
+ isDisplayNameDistinct(skill.name, skill.displayName) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3349
+ className: panel_module_css_default.displayName,
3350
+ title: skill.displayName,
3351
+ children: skill.displayName
3352
+ }) : null,
3175
3353
  count > 0 && hub.hubConfig?.showUseCount !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3176
3354
  className: panel_module_css_default.useCount,
3177
3355
  children: count
3178
3356
  }) : null,
3179
3357
  dot,
3358
+ isDuplicate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3359
+ className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
3360
+ title: tt("row.duplicateHint"),
3361
+ children: tt("row.duplicate")
3362
+ }) : null,
3180
3363
  lastUsed !== void 0 && hub.hubConfig?.showUseTime !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3181
3364
  className: panel_module_css_default.useTime,
3182
3365
  children: relativeTimeText(lastUsed)
@@ -3184,7 +3367,8 @@ window.__ModuleLoader__.load({
3184
3367
  ]
3185
3368
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3186
3369
  className: panel_module_css_default.rowDesc,
3187
- children: skill.description
3370
+ title: skill.description,
3371
+ children: skill.shortDescription ?? skill.description
3188
3372
  })]
3189
3373
  }), skill.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3190
3374
  type: "button",
@@ -3222,17 +3406,36 @@ window.__ModuleLoader__.load({
3222
3406
  return tt("badge.source." + root);
3223
3407
  }
3224
3408
  function DisabledRow(props) {
3225
- const { record, busy, onEnable } = props;
3409
+ const { record, busy, onEnable, duplicate, onOpen } = props;
3410
+ /** 键盘打开详情:Enter 或空格。仅当焦点在行本身(而非行内开关)时生效。 */
3411
+ const onKeyDown = (event) => {
3412
+ if (onOpen === void 0 || event.target !== event.currentTarget) return;
3413
+ if (event.key === "Enter" || event.key === " ") {
3414
+ event.preventDefault();
3415
+ onOpen();
3416
+ }
3417
+ };
3226
3418
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3227
- className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
3419
+ className: panel_module_css_default.row + (onOpen === void 0 ? " " + panel_module_css_default.rowStatic : ""),
3420
+ role: onOpen === void 0 ? void 0 : "button",
3421
+ tabIndex: onOpen === void 0 ? void 0 : 0,
3422
+ "aria-label": onOpen === void 0 ? void 0 : tt("row.open", { name: record.name }),
3423
+ onClick: onOpen,
3424
+ onKeyDown,
3228
3425
  children: [
3229
3426
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3230
3427
  className: panel_module_css_default.rowMain,
3231
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3428
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3232
3429
  className: panel_module_css_default.rowName,
3233
- children: record.name
3430
+ children: [record.name, duplicate === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3431
+ className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
3432
+ style: { marginLeft: 6 },
3433
+ title: tt("row.duplicateHint"),
3434
+ children: tt("row.duplicate")
3435
+ }) : null]
3234
3436
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3235
3437
  className: panel_module_css_default.rowDesc,
3438
+ title: record.description,
3236
3439
  children: record.description
3237
3440
  })]
3238
3441
  }),
@@ -3250,7 +3453,10 @@ window.__ModuleLoader__.load({
3250
3453
  "aria-label": tt("row.enable"),
3251
3454
  className: panel_module_css_default.switch,
3252
3455
  disabled: busy,
3253
- onClick: onEnable,
3456
+ onClick: (event) => {
3457
+ event.stopPropagation();
3458
+ onEnable();
3459
+ },
3254
3460
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
3255
3461
  })
3256
3462
  ]
@@ -3607,8 +3813,12 @@ window.__ModuleLoader__.load({
3607
3813
  }, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
3608
3814
  record,
3609
3815
  busy: busyNames.has(record.name),
3816
+ duplicate: catalog?.duplicateNames?.includes(record.name) === true,
3610
3817
  onEnable: () => {
3611
3818
  enableDisabled(record);
3819
+ },
3820
+ onOpen: () => {
3821
+ hub.openDetail(record.name);
3612
3822
  }
3613
3823
  }, record.name))] }) : null]
3614
3824
  }, "col:" + collection.name);
@@ -3694,8 +3904,12 @@ window.__ModuleLoader__.load({
3694
3904
  }, skill.name)), personalDisabled.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
3695
3905
  record,
3696
3906
  busy: busyNames.has(record.name),
3907
+ duplicate: catalog?.duplicateNames?.includes(record.name) === true,
3697
3908
  onEnable: () => {
3698
3909
  enableDisabled(record);
3910
+ },
3911
+ onOpen: () => {
3912
+ hub.openDetail(record.name);
3699
3913
  }
3700
3914
  }, record.name))] }) : null]
3701
3915
  }, "uncategorized-source");
@@ -3849,8 +4063,12 @@ window.__ModuleLoader__.load({
3849
4063
  }, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
3850
4064
  record,
3851
4065
  busy: busyNames.has(record.name),
4066
+ duplicate: hub.catalog?.duplicateNames?.includes(record.name) === true,
3852
4067
  onEnable: () => {
3853
4068
  enableDisabled(record);
4069
+ },
4070
+ onOpen: () => {
4071
+ hub.openDetail(record.name);
3854
4072
  }
3855
4073
  }, record.name))] }) : null]
3856
4074
  }, "tag:" + tag.id);
@@ -4030,6 +4248,89 @@ window.__ModuleLoader__.load({
4030
4248
  ]
4031
4249
  });
4032
4250
  }
4251
+ /** Version picker: switch a market source's tracked ref (release / branch / custom). */
4252
+ function VersionChoiceDialog(props) {
4253
+ const { choice, busy, onSelect, onCustom, onCancel, onConfirm } = props;
4254
+ const effective = choice.custom.trim() !== "" ? choice.custom.trim() : choice.selected;
4255
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
4256
+ onClose: onCancel,
4257
+ children: [
4258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4259
+ className: panel_module_css_default.dialogTitle,
4260
+ children: tt("market.versionTitle")
4261
+ }),
4262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4263
+ className: panel_module_css_default.dialogText,
4264
+ children: [tt("market.versionText", { repo: choice.repo }), choice.current !== void 0 ? ` (${tt("market.versionCurrent", { ref: choice.current })})` : ""]
4265
+ }),
4266
+ choice.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4267
+ className: panel_module_css_default.dialogText,
4268
+ children: tt("market.scanning")
4269
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4270
+ choice.releases.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4271
+ className: panel_module_css_default.dialogText,
4272
+ style: { marginBottom: 4 },
4273
+ children: tt("market.versionReleases")
4274
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
4275
+ className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
4276
+ value: choice.releases.includes(choice.selected) ? choice.selected : choice.releases[0],
4277
+ onChange: (event) => {
4278
+ onSelect(event.target.value);
4279
+ onCustom("");
4280
+ },
4281
+ children: choice.releases.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4282
+ value: tag,
4283
+ children: tag
4284
+ }, tag))
4285
+ })] }) : null,
4286
+ choice.branches.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4287
+ className: panel_module_css_default.dialogText,
4288
+ style: { marginBottom: 4 },
4289
+ children: tt("market.versionBranches")
4290
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
4291
+ className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
4292
+ value: choice.branches.includes(choice.selected) ? choice.selected : choice.branches[0],
4293
+ onChange: (event) => {
4294
+ onSelect(event.target.value);
4295
+ onCustom("");
4296
+ },
4297
+ children: choice.branches.map((branch) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4298
+ value: branch,
4299
+ children: branch
4300
+ }, branch))
4301
+ })] }) : null,
4302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4303
+ className: panel_module_css_default.dialogText,
4304
+ style: { marginBottom: 4 },
4305
+ children: tt("market.versionCustom")
4306
+ }),
4307
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4308
+ className: panel_module_css_default.input + " " + panel_module_css_default.dialogSelect,
4309
+ value: choice.custom,
4310
+ onChange: (event) => {
4311
+ onCustom(event.target.value);
4312
+ },
4313
+ placeholder: "v1.2.3 / main / abc1234"
4314
+ })
4315
+ ] }),
4316
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4317
+ className: panel_module_css_default.dialogActions,
4318
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4319
+ type: "button",
4320
+ className: panel_module_css_default.button,
4321
+ onClick: onCancel,
4322
+ children: tt("form.cancel")
4323
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4324
+ type: "button",
4325
+ className: panel_module_css_default.button + " " + panel_module_css_default.primary,
4326
+ disabled: busy || choice.loading || effective === "",
4327
+ onClick: onConfirm,
4328
+ children: busy ? tt("source.syncing") : tt("market.versionConfirm", { ref: effective })
4329
+ })]
4330
+ })
4331
+ ]
4332
+ });
4333
+ }
4033
4334
  /** Post-sync selection: which tracked skills to batch-update to the new version. */
4034
4335
  function MarketSyncDialog(props) {
4035
4336
  const { dialog, busy, onToggle, onCancel, onConfirm } = props;
@@ -4170,10 +4471,19 @@ window.__ModuleLoader__.load({
4170
4471
  rel: "noreferrer",
4171
4472
  children: record.repo
4172
4473
  }),
4173
- record.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4475
+ type: "button",
4174
4476
  className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
4175
- children: record.ref
4176
- }) : null,
4477
+ style: {
4478
+ cursor: "pointer",
4479
+ fontFamily: "inherit"
4480
+ },
4481
+ title: tt("market.versionHint"),
4482
+ onClick: () => {
4483
+ hub.openVersionDialog(record.repo);
4484
+ },
4485
+ children: record.ref ?? tt("market.unpinned")
4486
+ }),
4177
4487
  installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4178
4488
  className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
4179
4489
  children: tt("market.installed", { count: installedCount })
@@ -4422,279 +4732,291 @@ window.__ModuleLoader__.load({
4422
4732
  return true;
4423
4733
  }).sort((a, b) => a.name.localeCompare(b.name));
4424
4734
  const paged = filtered.slice(0, visibleCount);
4425
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4426
- className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintInline,
4427
- children: [
4428
- tt("repo.ready", { count: entries.length }),
4429
- " · ",
4430
- repoDiscoverState.data.ref !== null ? `ref ${repoDiscoverState.data.ref}` : ""
4431
- ]
4432
- }), entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4433
- className: panel_module_css_default.empty,
4434
- children: tt("repo.empty")
4435
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4436
- (() => {
4437
- const order = /* @__PURE__ */ new Map([["skills", 0], ["design-templates", 1]]);
4438
- const roots = [...new Set(entries.map((e) => e.root))].sort((a, b) => {
4439
- const ao = order.get(a) ?? 99;
4440
- const bo = order.get(b) ?? 99;
4441
- return ao !== bo ? ao - bo : a.localeCompare(b);
4442
- });
4443
- const counts = /* @__PURE__ */ new Map();
4444
- for (const e of entries) counts.set(e.root, (counts.get(e.root) ?? 0) + 1);
4445
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4446
- style: {
4447
- display: "flex",
4448
- gap: 8,
4449
- marginBottom: 8,
4450
- flexWrap: "wrap"
4451
- },
4452
- children: [
4453
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4454
- className: panel_module_css_default.input,
4455
- style: {
4456
- flex: 1,
4457
- minWidth: 140
4458
- },
4459
- value: repoSearch,
4460
- onChange: (e) => setRepoSearch(e.target.value),
4461
- placeholder: "搜索技能名…"
4462
- }),
4463
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4464
- type: "button",
4465
- className: panel_module_css_default.button + (repoFilter === "all" ? " " + panel_module_css_default.primary : ""),
4466
- style: {
4467
- padding: "6px 10px",
4468
- fontSize: 12
4469
- },
4470
- onClick: () => setRepoFilter("all"),
4471
- children: ["全部 ", entries.length]
4472
- }),
4473
- roots.map((root) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4474
- type: "button",
4475
- className: panel_module_css_default.button + (repoFilter === root ? " " + panel_module_css_default.primary : ""),
4476
- style: {
4477
- padding: "6px 10px",
4478
- fontSize: 12
4479
- },
4480
- onClick: () => setRepoFilter(root),
4481
- children: [
4482
- root,
4483
- " ",
4484
- counts.get(root) ?? 0
4485
- ]
4486
- }, root))
4487
- ]
4488
- });
4489
- })(),
4490
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4491
- className: panel_module_css_default.hintLine,
4735
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4737
+ className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintInline,
4738
+ children: [
4739
+ tt("repo.ready", { count: entries.length }),
4740
+ " · ",
4741
+ repoDiscoverState.data.ref !== null ? `ref ${repoDiscoverState.data.ref}` : ""
4742
+ ]
4743
+ }),
4744
+ repoDiscoverState.data.truncated === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4745
+ className: panel_module_css_default.errorBanner,
4492
4746
  style: {
4493
- display: "flex",
4494
- justifyContent: "space-between",
4495
- flexWrap: "wrap",
4496
- gap: 8
4747
+ background: "rgba(210,153,34,0.08)",
4748
+ color: "#d29922",
4749
+ borderLeft: "3px solid #d29922"
4497
4750
  },
4498
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
4499
- "已选 ",
4500
- selected.length,
4501
- "/",
4502
- entries.length,
4503
- " · ",
4504
- formatBytes(selectedBytes),
4505
- " · 显示 ",
4506
- paged.length,
4507
- "/",
4508
- filtered.length,
4509
- "(过滤后)"
4510
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4751
+ children: tt("repo.truncated")
4752
+ }) : null,
4753
+ entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4754
+ className: panel_module_css_default.empty,
4755
+ children: tt("repo.empty")
4756
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4757
+ (() => {
4758
+ const order = /* @__PURE__ */ new Map([["skills", 0], ["design-templates", 1]]);
4759
+ const roots = [...new Set(entries.map((e) => e.root))].sort((a, b) => {
4760
+ const ao = order.get(a) ?? 99;
4761
+ const bo = order.get(b) ?? 99;
4762
+ return ao !== bo ? ao - bo : a.localeCompare(b);
4763
+ });
4764
+ const counts = /* @__PURE__ */ new Map();
4765
+ for (const e of entries) counts.set(e.root, (counts.get(e.root) ?? 0) + 1);
4766
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4767
+ style: {
4768
+ display: "flex",
4769
+ gap: 8,
4770
+ marginBottom: 8,
4771
+ flexWrap: "wrap"
4772
+ },
4773
+ children: [
4774
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4775
+ className: panel_module_css_default.input,
4776
+ style: {
4777
+ flex: 1,
4778
+ minWidth: 140
4779
+ },
4780
+ value: repoSearch,
4781
+ onChange: (e) => setRepoSearch(e.target.value),
4782
+ placeholder: "搜索技能名…"
4783
+ }),
4784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4785
+ type: "button",
4786
+ className: panel_module_css_default.button + (repoFilter === "all" ? " " + panel_module_css_default.primary : ""),
4787
+ style: {
4788
+ padding: "6px 10px",
4789
+ fontSize: 12
4790
+ },
4791
+ onClick: () => setRepoFilter("all"),
4792
+ children: ["全部 ", entries.length]
4793
+ }),
4794
+ roots.map((root) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4795
+ type: "button",
4796
+ className: panel_module_css_default.button + (repoFilter === root ? " " + panel_module_css_default.primary : ""),
4797
+ style: {
4798
+ padding: "6px 10px",
4799
+ fontSize: 12
4800
+ },
4801
+ onClick: () => setRepoFilter(root),
4802
+ children: [
4803
+ root,
4804
+ " ",
4805
+ counts.get(root) ?? 0
4806
+ ]
4807
+ }, root))
4808
+ ]
4809
+ });
4810
+ })(),
4811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4812
+ className: panel_module_css_default.hintLine,
4511
4813
  style: {
4512
4814
  display: "flex",
4513
- gap: 6
4815
+ justifyContent: "space-between",
4816
+ flexWrap: "wrap",
4817
+ gap: 8
4514
4818
  },
4515
- children: [
4516
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4517
- type: "button",
4518
- className: panel_module_css_default.button,
4519
- style: {
4520
- padding: "4px 8px",
4521
- fontSize: 11
4522
- },
4523
- onClick: () => {
4524
- setRepoSelected(new Set(filtered.filter((e) => !isExisting(e)).slice(0, visibleCount).map((e) => e.path)));
4525
- },
4526
- children: "全选当前显示"
4527
- }),
4528
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4529
- type: "button",
4530
- className: panel_module_css_default.button,
4531
- style: {
4532
- padding: "4px 8px",
4533
- fontSize: 11
4534
- },
4535
- onClick: () => {
4536
- setRepoSelected(new Set(entries.filter((e) => !isExisting(e)).map((e) => e.path)));
4537
- },
4538
- children: ["全选全部 ", entries.filter((e) => !isExisting(e)).length]
4539
- }),
4540
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4541
- type: "button",
4542
- className: panel_module_css_default.button,
4543
- style: {
4544
- padding: "4px 8px",
4545
- fontSize: 11
4546
- },
4547
- onClick: () => {
4548
- setRepoSelected(/* @__PURE__ */ new Set());
4549
- },
4550
- children: tt("repo.clearAll")
4551
- })
4552
- ]
4553
- })]
4554
- }),
4555
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4556
- className: panel_module_css_default.scanList,
4557
- children: paged.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4558
- className: panel_module_css_default.empty,
4559
- style: { padding: 20 },
4560
- children: "无匹配技能"
4561
- }) : paged.map((entry) => {
4562
- const existing = isExisting(entry);
4563
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4564
- className: panel_module_css_default.row + (existing ? " " + panel_module_css_default.rowMuted : ""),
4819
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
4820
+ "已选 ",
4821
+ selected.length,
4822
+ "/",
4823
+ entries.length,
4824
+ " · ",
4825
+ formatBytes(selectedBytes),
4826
+ " · 显示 ",
4827
+ paged.length,
4828
+ "/",
4829
+ filtered.length,
4830
+ "(过滤后)"
4831
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4832
+ style: {
4833
+ display: "flex",
4834
+ gap: 6
4835
+ },
4565
4836
  children: [
4566
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4567
- type: "checkbox",
4568
- checked: repoSelected.has(entry.path),
4569
- disabled: existing,
4570
- onChange: (event) => {
4571
- toggleRepoSelected(entry.path, event.target.checked);
4572
- }
4837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4838
+ type: "button",
4839
+ className: panel_module_css_default.button,
4840
+ style: {
4841
+ padding: "4px 8px",
4842
+ fontSize: 11
4843
+ },
4844
+ onClick: () => {
4845
+ setRepoSelected(new Set(filtered.filter((e) => !isExisting(e)).slice(0, visibleCount).map((e) => e.path)));
4846
+ },
4847
+ children: "全选当前显示"
4573
4848
  }),
4574
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4575
- className: panel_module_css_default.rowMain,
4576
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4577
- className: panel_module_css_default.rowName,
4578
- children: entry.name
4579
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4580
- className: panel_module_css_default.rowDesc,
4581
- children: [
4582
- entry.dir,
4583
- " · ",
4584
- tt("repo.files", {
4585
- count: entry.fileCount,
4586
- size: formatBytes(entry.totalBytes)
4587
- })
4588
- ]
4589
- })]
4849
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4850
+ type: "button",
4851
+ className: panel_module_css_default.button,
4852
+ style: {
4853
+ padding: "4px 8px",
4854
+ fontSize: 11
4855
+ },
4856
+ onClick: () => {
4857
+ setRepoSelected(new Set(entries.filter((e) => !isExisting(e)).map((e) => e.path)));
4858
+ },
4859
+ children: ["全选全部 ", entries.filter((e) => !isExisting(e)).length]
4590
4860
  }),
4591
- existing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4592
- className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
4593
- children: tt("repo.existing")
4594
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4595
- className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
4596
- children: entry.origin
4861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4862
+ type: "button",
4863
+ className: panel_module_css_default.button,
4864
+ style: {
4865
+ padding: "4px 8px",
4866
+ fontSize: 11
4867
+ },
4868
+ onClick: () => {
4869
+ setRepoSelected(/* @__PURE__ */ new Set());
4870
+ },
4871
+ children: tt("repo.clearAll")
4597
4872
  })
4598
4873
  ]
4599
- }, entry.path);
4600
- })
4601
- }),
4602
- visibleCount < filtered.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4603
- style: {
4604
- textAlign: "center",
4605
- marginTop: 8
4606
- },
4607
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4608
- type: "button",
4609
- className: panel_module_css_default.button,
4610
- onClick: () => setVisibleCount((n) => n + 50),
4611
- children: [
4612
- "加载更多 50 (剩余 ",
4613
- filtered.length - visibleCount,
4614
- ")"
4615
- ]
4616
- })
4617
- }) : null,
4618
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4619
- className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
4620
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4621
- type: "button",
4622
- className: panel_module_css_default.button + " " + panel_module_css_default.primary,
4623
- disabled: selected.length === 0 || repoImporting,
4624
- onClick: () => {
4625
- importRepo();
4626
- },
4627
- children: repoImporting ? tt("repo.importing") : `${tt("repo.import", { count: selected.length })} · ${formatBytes(selectedBytes)}`
4628
- }), repoImporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4629
- type: "button",
4630
- className: panel_module_css_default.button,
4631
- onClick: () => {
4632
- cancelImport();
4633
- },
4634
- children: tt("repo.cancel")
4635
- }) : null]
4636
- }),
4637
- repoResult !== null && repoResult.status === "running" ? (() => {
4638
- const pct = repoResult.totalBytes > 0 ? Math.min(100, Math.round(repoResult.downloadedBytes / repoResult.totalBytes * 100)) : repoResult.total > 0 ? Math.round(repoResult.done / repoResult.total * 100) : 0;
4639
- const speed = formatSpeed(repoResult.bytesPerSecond);
4640
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4641
- className: panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
4642
- children: [
4643
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4644
- style: {
4645
- display: "flex",
4646
- alignItems: "center",
4647
- gap: 8,
4648
- marginBottom: 6
4649
- },
4650
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4651
- className: panel_module_css_default.scanProgressTrack,
4652
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4653
- className: panel_module_css_default.scanProgressFill,
4654
- style: { width: `${pct}%` }
4874
+ })]
4875
+ }),
4876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4877
+ className: panel_module_css_default.scanList,
4878
+ children: paged.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4879
+ className: panel_module_css_default.empty,
4880
+ style: { padding: 20 },
4881
+ children: "无匹配技能"
4882
+ }) : paged.map((entry) => {
4883
+ const existing = isExisting(entry);
4884
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4885
+ className: panel_module_css_default.row + (existing ? " " + panel_module_css_default.rowMuted : ""),
4886
+ children: [
4887
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4888
+ type: "checkbox",
4889
+ checked: repoSelected.has(entry.path),
4890
+ disabled: existing,
4891
+ onChange: (event) => {
4892
+ toggleRepoSelected(entry.path, event.target.checked);
4893
+ }
4894
+ }),
4895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4896
+ className: panel_module_css_default.rowMain,
4897
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4898
+ className: panel_module_css_default.rowName,
4899
+ children: entry.name
4900
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4901
+ className: panel_module_css_default.rowDesc,
4902
+ children: [
4903
+ entry.dir,
4904
+ " · ",
4905
+ tt("repo.files", {
4906
+ count: entry.fileCount,
4907
+ size: formatBytes(entry.totalBytes)
4908
+ })
4909
+ ]
4910
+ })]
4911
+ }),
4912
+ existing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4913
+ className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
4914
+ children: tt("repo.existing")
4915
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4916
+ className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
4917
+ children: entry.origin
4655
4918
  })
4656
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4919
+ ]
4920
+ }, entry.path);
4921
+ })
4922
+ }),
4923
+ visibleCount < filtered.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4924
+ style: {
4925
+ textAlign: "center",
4926
+ marginTop: 8
4927
+ },
4928
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4929
+ type: "button",
4930
+ className: panel_module_css_default.button,
4931
+ onClick: () => setVisibleCount((n) => n + 50),
4932
+ children: [
4933
+ "加载更多 50 (剩余 ",
4934
+ filtered.length - visibleCount,
4935
+ ")"
4936
+ ]
4937
+ })
4938
+ }) : null,
4939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4940
+ className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
4941
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4942
+ type: "button",
4943
+ className: panel_module_css_default.button + " " + panel_module_css_default.primary,
4944
+ disabled: selected.length === 0 || repoImporting,
4945
+ onClick: () => {
4946
+ importRepo();
4947
+ },
4948
+ children: repoImporting ? tt("repo.importing") : `${tt("repo.import", { count: selected.length })} · ${formatBytes(selectedBytes)}`
4949
+ }), repoImporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4950
+ type: "button",
4951
+ className: panel_module_css_default.button,
4952
+ onClick: () => {
4953
+ cancelImport();
4954
+ },
4955
+ children: tt("repo.cancel")
4956
+ }) : null]
4957
+ }),
4958
+ repoResult !== null && repoResult.status === "running" ? (() => {
4959
+ const pct = repoResult.totalBytes > 0 ? Math.min(100, Math.round(repoResult.downloadedBytes / repoResult.totalBytes * 100)) : repoResult.total > 0 ? Math.round(repoResult.done / repoResult.total * 100) : 0;
4960
+ const speed = formatSpeed(repoResult.bytesPerSecond);
4961
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4962
+ className: panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
4963
+ children: [
4964
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4657
4965
  style: {
4658
- fontSize: 11,
4659
- opacity: .75,
4660
- minWidth: 32,
4661
- textAlign: "right"
4966
+ display: "flex",
4967
+ alignItems: "center",
4968
+ gap: 8,
4969
+ marginBottom: 6
4662
4970
  },
4663
- children: [pct, "%"]
4664
- })]
4665
- }),
4666
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4667
- className: panel_module_css_default.hintLine,
4668
- children: [
4669
- formatBytes(repoResult.downloadedBytes),
4670
- " / ",
4671
- formatBytes(repoResult.totalBytes),
4672
- speed !== "" ? ` · ${speed}` : "",
4673
- " · ",
4674
- repoResult.done,
4675
- "/",
4676
- repoResult.total,
4677
- " 个技能"
4678
- ]
4679
- }),
4680
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4681
- className: panel_module_css_default.hintLine,
4682
- style: { marginTop: 4 },
4683
- children: [repoResult.current !== void 0 ? tt("repo.importingCurrent", {
4684
- name: repoResult.current,
4685
- done: repoResult.done + 1,
4686
- total: repoResult.total
4687
- }) : tt("repo.importing"), repoResult.currentFile !== void 0 ? ` · ${repoResult.currentFile}` : ""]
4688
- }),
4689
- repoResult.failed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4690
- className: panel_module_css_default.errorBanner,
4691
- style: { marginTop: 6 },
4692
- children: repoResult.failed.map((f) => `${f.name}: ${f.error}`).join("; ")
4693
- }) : null
4694
- ]
4695
- });
4696
- })() : null
4697
- ] })] });
4971
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4972
+ className: panel_module_css_default.scanProgressTrack,
4973
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4974
+ className: panel_module_css_default.scanProgressFill,
4975
+ style: { width: `${pct}%` }
4976
+ })
4977
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4978
+ style: {
4979
+ fontSize: 11,
4980
+ opacity: .75,
4981
+ minWidth: 32,
4982
+ textAlign: "right"
4983
+ },
4984
+ children: [pct, "%"]
4985
+ })]
4986
+ }),
4987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4988
+ className: panel_module_css_default.hintLine,
4989
+ children: [
4990
+ formatBytes(repoResult.downloadedBytes),
4991
+ " / ",
4992
+ formatBytes(repoResult.totalBytes),
4993
+ speed !== "" ? ` · ${speed}` : "",
4994
+ " · ",
4995
+ repoResult.done,
4996
+ "/",
4997
+ repoResult.total,
4998
+ " 个技能"
4999
+ ]
5000
+ }),
5001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5002
+ className: panel_module_css_default.hintLine,
5003
+ style: { marginTop: 4 },
5004
+ children: [repoResult.current !== void 0 ? tt("repo.importingCurrent", {
5005
+ name: repoResult.current,
5006
+ done: repoResult.done + 1,
5007
+ total: repoResult.total
5008
+ }) : tt("repo.importing"), repoResult.currentFile !== void 0 ? ` · ${repoResult.currentFile}` : ""]
5009
+ }),
5010
+ repoResult.failed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5011
+ className: panel_module_css_default.errorBanner,
5012
+ style: { marginTop: 6 },
5013
+ children: repoResult.failed.map((f) => `${f.name}: ${f.error}`).join("; ")
5014
+ }) : null
5015
+ ]
5016
+ });
5017
+ })() : null
5018
+ ] })
5019
+ ] });
4698
5020
  })() : null,
4699
5021
  repoResult !== null && repoResult.status !== "running" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4700
5022
  className: repoResult.status === "cancelled" ? panel_module_css_default.errorBanner + " " + panel_module_css_default.actionsTop : panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
@@ -4801,6 +5123,7 @@ window.__ModuleLoader__.load({
4801
5123
  const [tab, setTab] = (0, react.useState)("sources");
4802
5124
  const [skillView, setSkillView] = (0, react.useState)("groups");
4803
5125
  const [sourceFilter, setSourceFilter] = (0, react.useState)("all");
5126
+ const [invocationFilter, setInvocationFilter] = (0, react.useState)("all");
4804
5127
  const [sortKey, setSortKey] = (0, react.useState)("name");
4805
5128
  const [marketState, setMarketState] = (0, react.useState)({
4806
5129
  status: "loading",
@@ -4831,8 +5154,9 @@ window.__ModuleLoader__.load({
4831
5154
  const [newTagName, setNewTagName] = (0, react.useState)("");
4832
5155
  const [tagBusy, setTagBusy] = (0, react.useState)(false);
4833
5156
  const [editSearch, setEditSearch] = (0, react.useState)("");
4834
- /** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project 树键)。 */
5157
+ /** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project 树键)。技能总数 >80 时首次加载自动折叠 personal。 */
4835
5158
  const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
5159
+ const autoCollapsedPersonal = (0, react.useRef)(false);
4836
5160
  /** 项目级三级树里已细分(按 .dsh/.agents)的项目键。 */
4837
5161
  const [subdividedProjects, setSubdividedProjects] = (0, react.useState)(/* @__PURE__ */ new Set());
4838
5162
  const [showLegend, setShowLegend] = (0, react.useState)(false);
@@ -4857,6 +5181,10 @@ window.__ModuleLoader__.load({
4857
5181
  try {
4858
5182
  const next = await api.catalog(workspace !== "" ? { cwd: workspace } : void 0);
4859
5183
  setCatalog(next);
5184
+ if (!autoCollapsedPersonal.current && next.skills.length + next.disabled.length > 80) {
5185
+ autoCollapsedPersonal.current = true;
5186
+ setCollapsedGroups((previous) => new Set(previous).add("uncategorized-source"));
5187
+ }
4860
5188
  setLoadError(null);
4861
5189
  } catch (error) {
4862
5190
  setLoadError(errorMessage(error));
@@ -5183,6 +5511,25 @@ window.__ModuleLoader__.load({
5183
5511
  load,
5184
5512
  loadSources
5185
5513
  ]);
5514
+ const [fixingPaths, setFixingPaths] = (0, react.useState)(/* @__PURE__ */ new Set());
5515
+ const fixDiagnostic = (0, react.useCallback)(async (path) => {
5516
+ setFixingPaths((previous) => new Set(previous).add(path));
5517
+ setLoadError(null);
5518
+ try {
5519
+ if (!window.confirm(tt("diag.fixConfirm", { path }))) return;
5520
+ await api.fixDiagnostic(path);
5521
+ await load();
5522
+ setSuccessBanner(tt("diag.fixed"));
5523
+ } catch (error) {
5524
+ setLoadError(errorMessage(error));
5525
+ } finally {
5526
+ setFixingPaths((previous) => {
5527
+ const next = new Set(previous);
5528
+ next.delete(path);
5529
+ return next;
5530
+ });
5531
+ }
5532
+ }, [api, load]);
5186
5533
  /** 打开单个技能的删除确认(移入回收站,可恢复)。 */
5187
5534
  const requestDeleteSkill = (0, react.useCallback)((name) => {
5188
5535
  setDeleteSkillDialog(name);
@@ -5466,6 +5813,7 @@ window.__ModuleLoader__.load({
5466
5813
  status: "ready",
5467
5814
  data
5468
5815
  });
5816
+ loadMarket();
5469
5817
  } catch (error) {
5470
5818
  setRepoDiscoverState({
5471
5819
  status: "error",
@@ -5474,7 +5822,7 @@ window.__ModuleLoader__.load({
5474
5822
  } finally {
5475
5823
  setScanningRepo(null);
5476
5824
  }
5477
- }, [api]);
5825
+ }, [api, loadMarket]);
5478
5826
  /** 确认分支选择:持久化 ref 后重新扫描。 */
5479
5827
  const confirmBranchChoice = (0, react.useCallback)(async () => {
5480
5828
  if (branchChoice === null) return;
@@ -5494,6 +5842,59 @@ window.__ModuleLoader__.load({
5494
5842
  branchChoice,
5495
5843
  scanRepo
5496
5844
  ]);
5845
+ /** 版本对话框:打开时拉 releases + branches,确认后定版并重扫。 */
5846
+ const [versionDialog, setVersionDialog] = (0, react.useState)(null);
5847
+ const [versionBusy, setVersionBusy] = (0, react.useState)(false);
5848
+ const openVersionDialog = (0, react.useCallback)(async (repo) => {
5849
+ const current = marketState.repos.find((item) => item.repo === repo)?.ref;
5850
+ setVersionDialog({
5851
+ repo,
5852
+ ...current !== void 0 ? { current } : {},
5853
+ releases: [],
5854
+ branches: [],
5855
+ selected: current ?? "",
5856
+ custom: "",
5857
+ loading: true
5858
+ });
5859
+ setLoadError(null);
5860
+ try {
5861
+ const data = await api.marketSourceVersions(repo);
5862
+ setVersionDialog({
5863
+ repo,
5864
+ ...data.current !== void 0 ? { current: data.current } : {},
5865
+ releases: data.releases,
5866
+ branches: data.branches,
5867
+ selected: data.current ?? data.releases[0] ?? data.branches[0] ?? "",
5868
+ custom: "",
5869
+ loading: false
5870
+ });
5871
+ } catch (error) {
5872
+ setLoadError(errorMessage(error));
5873
+ setVersionDialog(null);
5874
+ }
5875
+ }, [api, marketState]);
5876
+ const confirmVersionDialog = (0, react.useCallback)(async () => {
5877
+ if (versionDialog === null) return;
5878
+ const ref = versionDialog.custom.trim() !== "" ? versionDialog.custom.trim() : versionDialog.selected;
5879
+ if (ref === "") return;
5880
+ setVersionBusy(true);
5881
+ setLoadError(null);
5882
+ try {
5883
+ await api.setMarketSourceRef(versionDialog.repo, ref);
5884
+ setVersionDialog(null);
5885
+ await loadMarket();
5886
+ await scanRepo(versionDialog.repo);
5887
+ } catch (error) {
5888
+ setLoadError(errorMessage(error));
5889
+ } finally {
5890
+ setVersionBusy(false);
5891
+ }
5892
+ }, [
5893
+ api,
5894
+ versionDialog,
5895
+ loadMarket,
5896
+ scanRepo
5897
+ ]);
5497
5898
  /** Toggle one repo preview row. */
5498
5899
  const toggleRepoSelected = (0, react.useCallback)((path, checked) => {
5499
5900
  setRepoSelected((previous) => {
@@ -5776,7 +6177,18 @@ window.__ModuleLoader__.load({
5776
6177
  const hasPrivate = skills.some((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
5777
6178
  return [...repos, ...hasPrivate ? [PRIVATE_SOURCE] : []];
5778
6179
  }, [catalog, origins]);
5779
- const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) => normalized.length === 0 || skill.name.toLocaleLowerCase().includes(normalized) || skill.description.toLocaleLowerCase().includes(normalized)), [catalog, normalized]);
6180
+ const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) => {
6181
+ if (invocationFilter === "model" && !skill.invocation.modelInvocable) return false;
6182
+ if (invocationFilter === "user" && !skill.invocation.userInvocable) return false;
6183
+ if (normalized.length === 0) return true;
6184
+ return skill.name.toLocaleLowerCase().includes(normalized) || skill.description.toLocaleLowerCase().includes(normalized) || skill.displayName?.toLocaleLowerCase().includes(normalized) || skill.shortDescription?.toLocaleLowerCase().includes(normalized);
6185
+ }), [
6186
+ catalog,
6187
+ normalized,
6188
+ invocationFilter
6189
+ ]);
6190
+ /** Rows actually rendered with a shortened description (shortDescription in use). */
6191
+ const shortenedCount = (0, react.useMemo)(() => filtered.filter((skill) => skill.shortDescription !== void 0).length, [filtered]);
5780
6192
  return {
5781
6193
  catalog,
5782
6194
  loading,
@@ -5806,6 +6218,7 @@ window.__ModuleLoader__.load({
5806
6218
  tab,
5807
6219
  skillView,
5808
6220
  sourceFilter,
6221
+ invocationFilter,
5809
6222
  sortKey,
5810
6223
  marketState,
5811
6224
  marketCheck,
@@ -5836,6 +6249,8 @@ window.__ModuleLoader__.load({
5836
6249
  subdividedProjects,
5837
6250
  showLegend,
5838
6251
  editMode,
6252
+ versionDialog,
6253
+ versionBusy,
5839
6254
  actionNames,
5840
6255
  viewNames,
5841
6256
  normalized,
@@ -5847,6 +6262,7 @@ window.__ModuleLoader__.load({
5847
6262
  sortKey,
5848
6263
  uses
5849
6264
  ]),
6265
+ shortenedCount,
5850
6266
  setLoadError,
5851
6267
  setSuccessBanner,
5852
6268
  setSearch,
@@ -5861,6 +6277,7 @@ window.__ModuleLoader__.load({
5861
6277
  setTab,
5862
6278
  setSkillView,
5863
6279
  setSourceFilter,
6280
+ setInvocationFilter,
5864
6281
  setSortKey,
5865
6282
  setBranchChoice,
5866
6283
  setMarketSyncDialog,
@@ -5878,6 +6295,7 @@ window.__ModuleLoader__.load({
5878
6295
  setEditSearch,
5879
6296
  setShowLegend,
5880
6297
  setEditMode,
6298
+ setVersionDialog,
5881
6299
  toggleGroupCollapse,
5882
6300
  toggleSubdivide,
5883
6301
  checkUpdate,
@@ -5894,6 +6312,15 @@ window.__ModuleLoader__.load({
5894
6312
  requestDelete,
5895
6313
  restoreTrash,
5896
6314
  clearTrash,
6315
+ fixingPaths,
6316
+ fixDiagnostic,
6317
+ clearListFilters: (0, react.useCallback)(() => {
6318
+ setSearch("");
6319
+ setSourceFilter("all");
6320
+ setInvocationFilter("all");
6321
+ }, []),
6322
+ openVersionDialog,
6323
+ confirmVersionDialog,
5897
6324
  requestDeleteSkill,
5898
6325
  runDeleteSkill,
5899
6326
  requestDeleteGroup,
@@ -5940,25 +6367,35 @@ window.__ModuleLoader__.load({
5940
6367
  /** 工作区输入草稿:回车才应用,避免每次按键都触发目录重拉。 */
5941
6368
  const [workspaceDraft, setWorkspaceDraft] = (0, react.useState)("");
5942
6369
  const { catalog, loading, loadError, successBanner, updateState, detail, detailLoading, showForm, formName, formDesc, formRoot, formBusy, formMessage, hubConfig, tab, skillView, sourceFilter, sortKey, search, workspace, setWorkspace, sourcesState, tagBusy, busyNames, normalized, origins, sourceOptions, filtered, conflictDialog, confirmDialog, deleteSkillDialog, deleteGroupDialog, confirmClearTrash, branchChoice, branchBusy, marketSyncDialog, syncBusy, editingTag, editName, membersDraft, editSearch, uses, groupsState, sourceCheck, checkingSource, syncingSource, showLegend, editMode, setLoadError, setSuccessBanner, setDetail, setShowForm, setFormName, setFormDesc, setFormRoot, setFormMessage, setTab, setSkillView, setSourceFilter, setSortKey, setSearch, setConflictDialog, setConfirmDialog, setDeleteSkillDialog, setDeleteGroupDialog, setConfirmClearTrash, setBranchChoice, setMarketSyncDialog, setEditingTag, setEditName, setMembersDraft, setEditSearch, setShowLegend, setEditMode, checkUpdate, loadMarket, checkSources, requestSync, requestDelete, restoreTrash, clearTrash, runDeleteSkill, runDeleteGroup, runConfirmed, resolveConflict, confirmBranchChoice, confirmMarketSync, create, saveTag, deleteTag, enableDisabled } = hub;
5943
- if (detail !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDetailView, {
5944
- detail,
5945
- hubConfig,
5946
- uses,
5947
- groupsState,
5948
- sourcesState,
5949
- sourceCheck,
5950
- checkingSource,
5951
- syncingSource,
5952
- loading: detailLoading,
5953
- onBack: () => {
5954
- setDetail(null);
5955
- },
5956
- onCheck: (repo) => {
5957
- checkSources(repo);
5958
- },
5959
- onSync: requestSync,
5960
- onFollowDelete: requestDelete
5961
- });
6370
+ const { shortenedCount, fixingPaths, clearListFilters } = hub;
6371
+ if (detail !== null) {
6372
+ const disabledRecord = catalog?.disabled.find((record) => record.name === detail.name);
6373
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDetailView, {
6374
+ detail,
6375
+ hubConfig,
6376
+ uses,
6377
+ groupsState,
6378
+ sourcesState,
6379
+ sourceCheck,
6380
+ checkingSource,
6381
+ syncingSource,
6382
+ loading: detailLoading,
6383
+ disabled: disabledRecord !== void 0,
6384
+ onEnable: disabledRecord !== void 0 ? () => {
6385
+ enableDisabled(disabledRecord).then(() => {
6386
+ setDetail(null);
6387
+ });
6388
+ } : void 0,
6389
+ onBack: () => {
6390
+ setDetail(null);
6391
+ },
6392
+ onCheck: (repo) => {
6393
+ checkSources(repo);
6394
+ },
6395
+ onSync: requestSync,
6396
+ onFollowDelete: requestDelete
6397
+ });
6398
+ }
5962
6399
  if (editingTag !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TagEditorView, {
5963
6400
  tag: editingTag,
5964
6401
  editName,
@@ -6033,6 +6470,20 @@ window.__ModuleLoader__.load({
6033
6470
  className: panel_module_css_default.hint,
6034
6471
  children: tt("panel.incomplete")
6035
6472
  }) : null,
6473
+ catalog !== null && (catalog.duplicateNames?.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6474
+ type: "button",
6475
+ className: panel_module_css_default.opBtn,
6476
+ title: tt("row.duplicateHint"),
6477
+ onClick: () => {
6478
+ clearListFilters();
6479
+ },
6480
+ children: [
6481
+ "⚠ ",
6482
+ tt("row.duplicate"),
6483
+ "×",
6484
+ (catalog.duplicateNames ?? []).length
6485
+ ]
6486
+ }) : null,
6036
6487
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6037
6488
  className: panel_module_css_default.actions,
6038
6489
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -6323,6 +6774,27 @@ window.__ModuleLoader__.load({
6323
6774
  })
6324
6775
  ]
6325
6776
  }),
6777
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
6778
+ className: panel_module_css_default.select,
6779
+ value: hub.invocationFilter,
6780
+ onChange: (event) => {
6781
+ hub.setInvocationFilter(event.target.value);
6782
+ },
6783
+ children: [
6784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6785
+ value: "all",
6786
+ children: tt("filter.invocationAll")
6787
+ }),
6788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6789
+ value: "model",
6790
+ children: tt("filter.modelOnly")
6791
+ }),
6792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6793
+ value: "user",
6794
+ children: tt("filter.userOnly")
6795
+ })
6796
+ ]
6797
+ }),
6326
6798
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6327
6799
  className: panel_module_css_default.search,
6328
6800
  value: search,
@@ -6340,6 +6812,23 @@ window.__ModuleLoader__.load({
6340
6812
  })
6341
6813
  ]
6342
6814
  }),
6815
+ catalog !== null && (filtered.length !== catalog.skills.length || hub.invocationFilter !== "all" || sourceFilter !== "all") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6816
+ className: panel_module_css_default.hintLine,
6817
+ style: {
6818
+ margin: "2px 2px 0",
6819
+ display: "flex",
6820
+ gap: 8,
6821
+ flexWrap: "wrap"
6822
+ },
6823
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("filter.showing", {
6824
+ shown: filtered.length,
6825
+ total: catalog.skills.length,
6826
+ filtered: hub.invocationFilter !== "all" || sourceFilter !== "all" ? tt("filter.filteredSuffix") : ""
6827
+ }) }), shortenedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6828
+ title: tt("filter.shortened", { count: shortenedCount }),
6829
+ children: tt("filter.shortened", { count: shortenedCount })
6830
+ }) : null]
6831
+ }) : null,
6343
6832
  filtered.length === 0 && search.trim() !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6344
6833
  className: panel_module_css_default.empty,
6345
6834
  children: tt("panel.empty")
@@ -6394,13 +6883,36 @@ window.__ModuleLoader__.load({
6394
6883
  children: tt("panel.diagnostics")
6395
6884
  }), catalog.diagnostics.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6396
6885
  className: panel_module_css_default.diagRow,
6397
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6398
- className: panel_module_css_default.diagPath,
6399
- children: entry.path
6400
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6401
- className: panel_module_css_default.diagReason,
6402
- children: entry.reason
6403
- })]
6886
+ style: {
6887
+ display: "flex",
6888
+ alignItems: "flex-start",
6889
+ gap: 10
6890
+ },
6891
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6892
+ style: {
6893
+ flex: 1,
6894
+ minWidth: 0
6895
+ },
6896
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6897
+ className: panel_module_css_default.diagPath,
6898
+ children: entry.path
6899
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6900
+ className: panel_module_css_default.diagReason,
6901
+ children: entry.reason
6902
+ })]
6903
+ }), entry.fixable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6904
+ type: "button",
6905
+ className: panel_module_css_default.opBtn,
6906
+ disabled: fixingPaths.has(entry.path),
6907
+ onClick: () => {
6908
+ hub.fixDiagnostic(entry.path);
6909
+ },
6910
+ style: {
6911
+ flex: "none",
6912
+ alignSelf: "center"
6913
+ },
6914
+ children: fixingPaths.has(entry.path) ? tt("diag.fixing") : tt("diag.fix")
6915
+ }) : null]
6404
6916
  }, entry.path))]
6405
6917
  }) : null
6406
6918
  ] }) : null,
@@ -6447,6 +6959,28 @@ window.__ModuleLoader__.load({
6447
6959
  confirmBranchChoice();
6448
6960
  }
6449
6961
  }) : null,
6962
+ hub.versionDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionChoiceDialog, {
6963
+ choice: hub.versionDialog,
6964
+ busy: hub.versionBusy,
6965
+ onSelect: (selected) => {
6966
+ hub.setVersionDialog({
6967
+ ...hub.versionDialog,
6968
+ selected
6969
+ });
6970
+ },
6971
+ onCustom: (custom) => {
6972
+ hub.setVersionDialog({
6973
+ ...hub.versionDialog,
6974
+ custom
6975
+ });
6976
+ },
6977
+ onCancel: () => {
6978
+ hub.setVersionDialog(null);
6979
+ },
6980
+ onConfirm: () => {
6981
+ hub.confirmVersionDialog();
6982
+ }
6983
+ }) : null,
6450
6984
  marketSyncDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSyncDialog, {
6451
6985
  dialog: marketSyncDialog,
6452
6986
  busy: syncBusy,