dsh-skills-manager 0.1.5 → 0.1.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.
package/README.md CHANGED
@@ -33,8 +33,9 @@ After changing the bundle or installed dependencies, restart the Harness service
33
33
  2. Choose **本机** to select a local skill folder, or **仓库** to enter a GitHub `owner/repo`, HTTPS URL, SSH URL, or other Git URL.
34
34
  3. Enable **全局** or select a workspace and enable **项目**.
35
35
  4. Use the refresh icon on a row to reinstall that skill from the source recorded in `skills-lock.json`.
36
- 5. Use the wrench control beside the composer attachment button to toggle installed skills for the current session workspace only.
37
- 6. Remove an installed skill with **移除**. This deletes only the plugin's private library entry, not the local source or remote repository.
36
+ 5. Use **检测** to check every installed skill for upstream updates without applying them; rows show a badge (有更新 / 已是最新 / 上游已删除 / 无法检测). When updates are found, **全部更新 (N)** applies them one by one.
37
+ 6. Use the wrench control beside the composer attachment button to toggle installed skills for the current session workspace only.
38
+ 7. Remove an installed skill with **移除**. This deletes only the plugin's private library entry, not the local source or remote repository.
38
39
 
39
40
  Repository imports and refreshes are delegated to the `skills` CLI. It supports GitHub `owner/repo`, HTTPS, SSH, Git and file URLs, and reuses the machine's existing Git credential helper or SSH configuration. Do not put credentials in repository URLs; the plugin rejects embedded credentials before invoking the CLI.
40
41
 
package/lib/client.js CHANGED
@@ -135,26 +135,26 @@ const createLucideIcon = (iconName, iconNode) => {
135
135
 
136
136
  //#endregion
137
137
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/wrench.mjs
138
- const __iconNode$11 = [["path", {
138
+ const __iconNode$15 = [["path", {
139
139
  d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",
140
140
  key: "1ngwbx"
141
141
  }]];
142
- const Wrench = createLucideIcon("wrench", __iconNode$11);
142
+ const Wrench = createLucideIcon("wrench", __iconNode$15);
143
143
 
144
144
  //#endregion
145
145
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/plus.mjs
146
- const __iconNode$10 = [["path", {
146
+ const __iconNode$14 = [["path", {
147
147
  d: "M5 12h14",
148
148
  key: "1ays0h"
149
149
  }], ["path", {
150
150
  d: "M12 5v14",
151
151
  key: "s699le"
152
152
  }]];
153
- const Plus = createLucideIcon("plus", __iconNode$10);
153
+ const Plus = createLucideIcon("plus", __iconNode$14);
154
154
 
155
155
  //#endregion
156
156
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/refresh-cw.mjs
157
- const __iconNode$9 = [
157
+ const __iconNode$13 = [
158
158
  ["path", {
159
159
  d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",
160
160
  key: "v9h5vc"
@@ -172,11 +172,11 @@ const __iconNode$9 = [
172
172
  key: "1cv678"
173
173
  }]
174
174
  ];
175
- const RefreshCw = createLucideIcon("refresh-cw", __iconNode$9);
175
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$13);
176
176
 
177
177
  //#endregion
178
178
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/search.mjs
179
- const __iconNode$8 = [["path", {
179
+ const __iconNode$12 = [["path", {
180
180
  d: "m21 21-4.34-4.34",
181
181
  key: "14j7rj"
182
182
  }], ["circle", {
@@ -185,22 +185,22 @@ const __iconNode$8 = [["path", {
185
185
  r: "8",
186
186
  key: "4ej97u"
187
187
  }]];
188
- const Search = createLucideIcon("search", __iconNode$8);
188
+ const Search = createLucideIcon("search", __iconNode$12);
189
189
 
190
190
  //#endregion
191
191
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/x.mjs
192
- const __iconNode$7 = [["path", {
192
+ const __iconNode$11 = [["path", {
193
193
  d: "M18 6 6 18",
194
194
  key: "1bl5f8"
195
195
  }], ["path", {
196
196
  d: "m6 6 12 12",
197
197
  key: "d8bk6v"
198
198
  }]];
199
- const X = createLucideIcon("x", __iconNode$7);
199
+ const X = createLucideIcon("x", __iconNode$11);
200
200
 
201
201
  //#endregion
202
202
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/trash-2.mjs
203
- const __iconNode$6 = [
203
+ const __iconNode$10 = [
204
204
  ["path", {
205
205
  d: "M10 11v6",
206
206
  key: "nco0om"
@@ -222,11 +222,11 @@ const __iconNode$6 = [
222
222
  key: "e791ji"
223
223
  }]
224
224
  ];
225
- const Trash2 = createLucideIcon("trash-2", __iconNode$6);
225
+ const Trash2 = createLucideIcon("trash-2", __iconNode$10);
226
226
 
227
227
  //#endregion
228
228
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/globe.mjs
229
- const __iconNode$5 = [
229
+ const __iconNode$9 = [
230
230
  ["circle", {
231
231
  cx: "12",
232
232
  cy: "12",
@@ -242,11 +242,11 @@ const __iconNode$5 = [
242
242
  key: "9i4pu4"
243
243
  }]
244
244
  ];
245
- const Globe = createLucideIcon("globe", __iconNode$5);
245
+ const Globe = createLucideIcon("globe", __iconNode$9);
246
246
 
247
247
  //#endregion
248
248
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/folder-git.mjs
249
- const __iconNode$4 = [
249
+ const __iconNode$8 = [
250
250
  ["circle", {
251
251
  cx: "12",
252
252
  cy: "13",
@@ -266,11 +266,11 @@ const __iconNode$4 = [
266
266
  key: "1pygq7"
267
267
  }]
268
268
  ];
269
- const FolderGit = createLucideIcon("folder-git", __iconNode$4);
269
+ const FolderGit = createLucideIcon("folder-git", __iconNode$8);
270
270
 
271
271
  //#endregion
272
272
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/git-branch.mjs
273
- const __iconNode$3 = [
273
+ const __iconNode$7 = [
274
274
  ["path", {
275
275
  d: "M15 6a9 9 0 0 0-9 9V3",
276
276
  key: "1cii5b"
@@ -288,11 +288,11 @@ const __iconNode$3 = [
288
288
  key: "fqmcym"
289
289
  }]
290
290
  ];
291
- const GitBranch = createLucideIcon("git-branch", __iconNode$3);
291
+ const GitBranch = createLucideIcon("git-branch", __iconNode$7);
292
292
 
293
293
  //#endregion
294
294
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/package-open.mjs
295
- const __iconNode$2 = [
295
+ const __iconNode$6 = [
296
296
  ["path", {
297
297
  d: "M12 22v-9",
298
298
  key: "x3hkom"
@@ -310,19 +310,19 @@ const __iconNode$2 = [
310
310
  key: "12ttoo"
311
311
  }]
312
312
  ];
313
- const PackageOpen = createLucideIcon("package-open", __iconNode$2);
313
+ const PackageOpen = createLucideIcon("package-open", __iconNode$6);
314
314
 
315
315
  //#endregion
316
316
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/puzzle.mjs
317
- const __iconNode$1 = [["path", {
317
+ const __iconNode$5 = [["path", {
318
318
  d: "M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z",
319
319
  key: "w46dr5"
320
320
  }]];
321
- const Puzzle = createLucideIcon("puzzle", __iconNode$1);
321
+ const Puzzle = createLucideIcon("puzzle", __iconNode$5);
322
322
 
323
323
  //#endregion
324
324
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/circle-alert.mjs
325
- const __iconNode = [
325
+ const __iconNode$4 = [
326
326
  ["circle", {
327
327
  cx: "12",
328
328
  cy: "12",
@@ -344,7 +344,61 @@ const __iconNode = [
344
344
  key: "4dfq90"
345
345
  }]
346
346
  ];
347
- const CircleAlert = createLucideIcon("circle-alert", __iconNode);
347
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$4);
348
+
349
+ //#endregion
350
+ //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/list.mjs
351
+ const __iconNode$3 = [
352
+ ["path", {
353
+ d: "M3 5h.01",
354
+ key: "18ugdj"
355
+ }],
356
+ ["path", {
357
+ d: "M3 12h.01",
358
+ key: "nlz23k"
359
+ }],
360
+ ["path", {
361
+ d: "M3 19h.01",
362
+ key: "noohij"
363
+ }],
364
+ ["path", {
365
+ d: "M8 5h13",
366
+ key: "1pao27"
367
+ }],
368
+ ["path", {
369
+ d: "M8 12h13",
370
+ key: "1za7za"
371
+ }],
372
+ ["path", {
373
+ d: "M8 19h13",
374
+ key: "m83p4d"
375
+ }]
376
+ ];
377
+ const List = createLucideIcon("list", __iconNode$3);
378
+
379
+ //#endregion
380
+ //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/folder.mjs
381
+ const __iconNode$2 = [["path", {
382
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",
383
+ key: "1kt360"
384
+ }]];
385
+ const Folder = createLucideIcon("folder", __iconNode$2);
386
+
387
+ //#endregion
388
+ //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/chevron-down.mjs
389
+ const __iconNode$1 = [["path", {
390
+ d: "m6 9 6 6 6-6",
391
+ key: "qrunsl"
392
+ }]];
393
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$1);
394
+
395
+ //#endregion
396
+ //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/chevron-right.mjs
397
+ const __iconNode = [["path", {
398
+ d: "m9 18 6-6-6-6",
399
+ key: "mthhwq"
400
+ }]];
401
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode);
348
402
 
349
403
  //#endregion
350
404
  //#region lib/types/client/popover.js
@@ -489,6 +543,11 @@ function apply(ctx) {
489
543
  };
490
544
  const list = (projectRoot) => request("list", projectRoot ? { projectRoot } : {});
491
545
  const mutate = request;
546
+ const checkUpdates = async () => {
547
+ const result = await connection.rpc.call("/skill-manager", "check-updates", {});
548
+ if (!result.ok) throw new Error(result.error.message);
549
+ return result.value;
550
+ };
492
551
  ctx.slots.inject("settings.section", () => ctx.slots.register({
493
552
  name: "settings.section",
494
553
  id: "skills",
@@ -497,6 +556,7 @@ function apply(ctx) {
497
556
  inject: () => ({
498
557
  list,
499
558
  mutate,
559
+ checkUpdates,
500
560
  pickDirectory: () => ctx.workspaces.pickDirectory(),
501
561
  loopback: connection.isLoopback
502
562
  })
@@ -512,15 +572,19 @@ function apply(ctx) {
512
572
  }, ComposerSkillButton));
513
573
  }
514
574
  function SkillManagerSection(props) {
515
- const { list, mutate, pickDirectory, loopback, useWorkspaces } = props;
575
+ const { list, mutate, checkUpdates, pickDirectory, loopback, useWorkspaces } = props;
516
576
  const workspaces = useWorkspaces((state) => state);
517
577
  const [projectRoot, setProjectRoot] = (0, react.useState)(void 0);
518
578
  const [catalog, setCatalog] = (0, react.useState)(null);
519
579
  const [query, setQuery] = (0, react.useState)("");
520
580
  const [busy, setBusy] = (0, react.useState)(false);
581
+ const [checking, setChecking] = (0, react.useState)(false);
582
+ const [statuses, setStatuses] = (0, react.useState)({});
521
583
  const [error, setError] = (0, react.useState)(null);
522
584
  const [repositoryOpen, setRepositoryOpen] = (0, react.useState)(false);
523
585
  const [repository, setRepository] = (0, react.useState)("");
586
+ const [view, setView] = (0, react.useState)("flat");
587
+ const [collapsed, setCollapsed] = (0, react.useState)({});
524
588
  (0, react.useEffect)(() => {
525
589
  ensureKeyframes();
526
590
  }, []);
@@ -558,6 +622,7 @@ function SkillManagerSection(props) {
558
622
  }, []);
559
623
  const normalizedQuery = query.trim().toLocaleLowerCase();
560
624
  const filtered = (catalog?.entries ?? []).filter((entry) => normalizedQuery.length === 0 || `${entry.name} ${entry.description}`.toLocaleLowerCase().includes(normalizedQuery));
625
+ const groups = groupBySource(filtered);
561
626
  const importSkill = async () => {
562
627
  const path = await pickDirectory();
563
628
  if (path) await run(() => mutate("install", {
@@ -586,7 +651,60 @@ function SkillManagerSection(props) {
586
651
  projectRoot
587
652
  }));
588
653
  };
654
+ const checkAll = async () => {
655
+ setChecking(true);
656
+ setError(null);
657
+ try {
658
+ const report = await checkUpdates();
659
+ setStatuses(Object.fromEntries(report.entries.map((entry) => [entry.name, entry.status])));
660
+ } catch (reason) {
661
+ setError(messageOf(reason));
662
+ } finally {
663
+ setChecking(false);
664
+ }
665
+ };
666
+ const updatable = (catalog?.entries ?? []).filter((entry) => statuses[entry.name] === "update-available" && entry.updateSupported);
667
+ const updateAll = async () => {
668
+ setBusy(true);
669
+ setError(null);
670
+ try {
671
+ for (const entry of updatable) setCatalog(await mutate("update", {
672
+ name: entry.name,
673
+ projectRoot
674
+ }));
675
+ setStatuses({});
676
+ } catch (reason) {
677
+ setError(messageOf(reason));
678
+ } finally {
679
+ setBusy(false);
680
+ }
681
+ };
589
682
  const selectedWorkspace = workspaces.items.find((item) => item.path === projectRoot);
683
+ const rowProps = (entry) => ({
684
+ key: entry.name,
685
+ entry,
686
+ projectLabel: selectedWorkspace?.title ?? "当前项目",
687
+ projectAvailable: projectRoot !== void 0,
688
+ busy,
689
+ updateStatus: statuses[entry.name],
690
+ onGlobalChange: (enabled) => void run(() => mutate("set-enabled", {
691
+ name: entry.name,
692
+ scope: "global",
693
+ projectRoot,
694
+ enabled
695
+ })),
696
+ onProjectChange: (enabled) => void run(() => mutate("set-enabled", {
697
+ name: entry.name,
698
+ scope: "project",
699
+ projectRoot,
700
+ enabled
701
+ })),
702
+ onUpdate: () => void updateSkill(entry),
703
+ onRemove: () => void run(() => mutate("remove", {
704
+ name: entry.name,
705
+ projectRoot
706
+ }))
707
+ });
590
708
  return (0, react.createElement)("div", {
591
709
  style: panelStyle,
592
710
  "aria-busy": busy
@@ -617,6 +735,25 @@ function SkillManagerSection(props) {
617
735
  gap: 8,
618
736
  flexWrap: "wrap"
619
737
  } }, (0, react.createElement)("button", {
738
+ type: "button",
739
+ style: buttonStyle("ghost", busy || checking),
740
+ onClick: () => void checkAll(),
741
+ disabled: busy || checking,
742
+ title: "检测全部 skill 是否有更新(只检测,不更新)"
743
+ }, (0, react.createElement)(RefreshCw, {
744
+ size: 14,
745
+ "aria-hidden": true,
746
+ style: checking ? spinnerStyle : void 0
747
+ }), checking ? "检测中…" : "检测"), updatable.length > 0 ? (0, react.createElement)("button", {
748
+ type: "button",
749
+ style: buttonStyle("primary", busy || checking || !loopback),
750
+ onClick: () => void updateAll(),
751
+ disabled: busy || checking || !loopback,
752
+ title: loopback ? `更新全部 ${updatable.length} 个有更新的 skill` : "仅在本机连接时可更新"
753
+ }, (0, react.createElement)(RefreshCw, {
754
+ size: 14,
755
+ "aria-hidden": true
756
+ }), `全部更新 (${updatable.length})`) : null, (0, react.createElement)("button", {
620
757
  type: "button",
621
758
  style: buttonStyle("ghost", busy || !loopback),
622
759
  onClick: () => setRepositoryOpen((value) => !value),
@@ -733,7 +870,49 @@ function SkillManagerSection(props) {
733
870
  }, (0, react.createElement)("option", { value: "__global__" }, "只看全局"), ...workspaces.items.map((workspace) => (0, react.createElement)("option", {
734
871
  key: workspace.workspaceId,
735
872
  value: workspace.path
736
- }, workspace.title))))), error ? (0, react.createElement)("div", {
873
+ }, workspace.title)))), (0, react.createElement)("div", {
874
+ role: "group",
875
+ "aria-label": "视图切换",
876
+ style: {
877
+ display: "flex",
878
+ flex: "0 0 auto",
879
+ border: `1px solid ${tokens.border}`,
880
+ borderRadius: 8,
881
+ overflow: "hidden"
882
+ }
883
+ }, (0, react.createElement)("button", {
884
+ type: "button",
885
+ onClick: () => setView("flat"),
886
+ "aria-pressed": view === "flat",
887
+ title: "平铺列表",
888
+ style: {
889
+ ...iconButtonStyle,
890
+ width: 32,
891
+ height: 32,
892
+ borderRadius: 0,
893
+ color: view === "flat" ? tokens.accent : tokens.secondary,
894
+ background: view === "flat" ? "rgba(99, 102, 241, .12)" : "transparent"
895
+ }
896
+ }, (0, react.createElement)(List, {
897
+ size: 15,
898
+ "aria-hidden": true
899
+ })), (0, react.createElement)("button", {
900
+ type: "button",
901
+ onClick: () => setView("grouped"),
902
+ "aria-pressed": view === "grouped",
903
+ title: "按来源仓库分组",
904
+ style: {
905
+ ...iconButtonStyle,
906
+ width: 32,
907
+ height: 32,
908
+ borderRadius: 0,
909
+ color: view === "grouped" ? tokens.accent : tokens.secondary,
910
+ background: view === "grouped" ? "rgba(99, 102, 241, .12)" : "transparent"
911
+ }
912
+ }, (0, react.createElement)(Folder, {
913
+ size: 15,
914
+ "aria-hidden": true
915
+ })))), error ? (0, react.createElement)("div", {
737
916
  role: "alert",
738
917
  style: {
739
918
  display: "flex",
@@ -764,30 +943,89 @@ function SkillManagerSection(props) {
764
943
  textAlign: "center",
765
944
  fontSize: 13,
766
945
  color: tokens.secondary
767
- } }, "正在读取…") : filtered.length === 0 ? (0, react.createElement)(EmptyState, { hasAny: (catalog.entries.length ?? 0) > 0 }) : filtered.map((entry) => (0, react.createElement)(SkillRow, {
768
- key: entry.name,
769
- entry,
770
- projectLabel: selectedWorkspace?.title ?? "当前项目",
771
- projectAvailable: projectRoot !== void 0,
772
- busy,
773
- onGlobalChange: (enabled) => void run(() => mutate("set-enabled", {
774
- name: entry.name,
775
- scope: "global",
776
- projectRoot,
777
- enabled
778
- })),
779
- onProjectChange: (enabled) => void run(() => mutate("set-enabled", {
780
- name: entry.name,
781
- scope: "project",
782
- projectRoot,
783
- enabled
946
+ } }, "正在读取…") : filtered.length === 0 ? (0, react.createElement)(EmptyState, { hasAny: (catalog.entries.length ?? 0) > 0 }) : view === "flat" ? filtered.map((entry) => (0, react.createElement)(SkillRow, rowProps(entry))) : groups.map((group) => (0, react.createElement)("div", {
947
+ key: group.key,
948
+ style: {
949
+ display: "grid",
950
+ gap: 8
951
+ }
952
+ }, (0, react.createElement)("button", {
953
+ type: "button",
954
+ onClick: () => setCollapsed((prev) => ({
955
+ ...prev,
956
+ [group.key]: !prev[group.key]
784
957
  })),
785
- onUpdate: () => void updateSkill(entry),
786
- onRemove: () => void run(() => mutate("remove", {
787
- name: entry.name,
788
- projectRoot
789
- }))
790
- }))));
958
+ "aria-expanded": !collapsed[group.key],
959
+ style: {
960
+ display: "flex",
961
+ alignItems: "center",
962
+ gap: 8,
963
+ padding: "6px 4px",
964
+ border: "none",
965
+ background: "transparent",
966
+ color: "inherit",
967
+ cursor: "pointer",
968
+ fontSize: 13,
969
+ fontWeight: 600,
970
+ textAlign: "left"
971
+ }
972
+ }, collapsed[group.key] ? (0, react.createElement)(ChevronRight, {
973
+ size: 14,
974
+ "aria-hidden": true,
975
+ style: {
976
+ color: tokens.secondary,
977
+ flex: "0 0 auto"
978
+ }
979
+ }) : (0, react.createElement)(ChevronDown, {
980
+ size: 14,
981
+ "aria-hidden": true,
982
+ style: {
983
+ color: tokens.secondary,
984
+ flex: "0 0 auto"
985
+ }
986
+ }), (0, react.createElement)(GitBranch, {
987
+ size: 13,
988
+ "aria-hidden": true,
989
+ style: {
990
+ color: tokens.secondary,
991
+ flex: "0 0 auto"
992
+ }
993
+ }), (0, react.createElement)("span", {
994
+ title: group.key === "__local__" ? void 0 : group.key,
995
+ style: {
996
+ ...monoStyle,
997
+ minWidth: 0,
998
+ overflow: "hidden",
999
+ textOverflow: "ellipsis",
1000
+ whiteSpace: "nowrap"
1001
+ }
1002
+ }, group.label), (0, react.createElement)("span", { style: {
1003
+ color: tokens.secondary,
1004
+ fontWeight: 400,
1005
+ flex: "0 0 auto"
1006
+ } }, `${group.entries.length} 个`)), collapsed[group.key] ? null : group.entries.map((entry) => (0, react.createElement)(SkillRow, rowProps(entry)))))));
1007
+ }
1008
+ function groupBySource(entries) {
1009
+ const groups = /* @__PURE__ */ new Map();
1010
+ for (const entry of entries) {
1011
+ const key = entry.sourceType === "git" && entry.sourceUrl ? entry.sourceUrl : "__local__";
1012
+ const label = key === "__local__" ? "本机导入" : shortRepoLabel(key);
1013
+ const group = groups.get(key) ?? {
1014
+ label,
1015
+ entries: []
1016
+ };
1017
+ group.entries.push(entry);
1018
+ groups.set(key, group);
1019
+ }
1020
+ return [...groups.entries()].map(([key, group]) => ({
1021
+ key,
1022
+ label: group.label,
1023
+ entries: group.entries
1024
+ })).sort((a, b) => a.key === "__local__" ? 1 : b.key === "__local__" ? -1 : a.label.localeCompare(b.label));
1025
+ }
1026
+ function shortRepoLabel(url) {
1027
+ const match = /^(?:https?:\/\/|git@|ssh:\/\/git@)?([^/:]+)[:/]([^/]+\/[^/]+?)(?:\.git)?\/?$/.exec(url);
1028
+ return match ? `${match[2]} (${match[1]})` : url;
791
1029
  }
792
1030
  function EmptyState(props) {
793
1031
  return (0, react.createElement)("div", { style: {
@@ -1043,8 +1281,35 @@ function ProjectSkillPopover(props) {
1043
1281
  style: spinnerStyle
1044
1282
  })) : null);
1045
1283
  }
1284
+ const UPDATE_BADGE = {
1285
+ "update-available": {
1286
+ label: "有更新",
1287
+ color: tokens.accent,
1288
+ background: "rgba(99, 102, 241, .1)",
1289
+ border: "1px solid rgba(99, 102, 241, .35)"
1290
+ },
1291
+ "current": {
1292
+ label: "已是最新",
1293
+ color: tokens.success,
1294
+ background: "rgba(22, 163, 74, .1)",
1295
+ border: "1px solid rgba(22, 163, 74, .3)"
1296
+ },
1297
+ "deleted-upstream": {
1298
+ label: "上游已删除",
1299
+ color: tokens.danger,
1300
+ background: "rgba(220, 38, 38, .08)",
1301
+ border: "1px solid rgba(220, 38, 38, .3)"
1302
+ },
1303
+ "unknown": {
1304
+ label: "无法检测",
1305
+ color: tokens.secondary,
1306
+ background: "rgba(148, 163, 184, .09)",
1307
+ border: "1px solid transparent"
1308
+ }
1309
+ };
1046
1310
  function SkillRow(props) {
1047
- const { entry, projectLabel, projectAvailable, busy, onGlobalChange, onProjectChange, onUpdate, onRemove } = props;
1311
+ const { entry, projectLabel, projectAvailable, busy, updateStatus, onGlobalChange, onProjectChange, onUpdate, onRemove } = props;
1312
+ const badge = updateStatus ? UPDATE_BADGE[updateStatus] : void 0;
1048
1313
  return (0, react.createElement)("article", {
1049
1314
  className: "dsh-skill-manager-row",
1050
1315
  style: {
@@ -1094,7 +1359,19 @@ function SkillRow(props) {
1094
1359
  overflow: "hidden",
1095
1360
  textOverflow: "ellipsis",
1096
1361
  whiteSpace: "nowrap"
1097
- } }, entry.name), (0, react.createElement)(SourceBadge, { entry }), (0, react.createElement)(StatusBadge, {
1362
+ } }, entry.name), badge ? (0, react.createElement)("span", { style: {
1363
+ display: "inline-flex",
1364
+ alignItems: "center",
1365
+ flex: "0 1 auto",
1366
+ padding: "2px 6px",
1367
+ borderRadius: 999,
1368
+ fontSize: 11,
1369
+ lineHeight: "16px",
1370
+ whiteSpace: "nowrap",
1371
+ color: badge.color,
1372
+ background: badge.background,
1373
+ border: badge.border
1374
+ } }, badge.label) : null, (0, react.createElement)(SourceBadge, { entry }), (0, react.createElement)(StatusBadge, {
1098
1375
  active: entry.globalEnabled,
1099
1376
  icon: Globe,
1100
1377
  label: "全局"