dsh-skill-hub 0.2.2 → 0.2.5

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/README.md +10 -14
  2. package/README.zh.md +9 -12
  3. package/lib/client.js +493 -177
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +213 -36
  6. package/lib/types/client/SkillHubSettingsCard.d.ts +4 -6
  7. package/lib/types/client/icons.d.ts +25 -0
  8. package/lib/types/client/locales.d.ts +7 -3
  9. package/lib/types/client/panel/format.d.ts +10 -0
  10. package/lib/types/client/settings-card.d.ts +18 -0
  11. package/lib/types/client/settings-form.d.ts +9 -0
  12. package/lib/types/client/slash-dots.d.ts +57 -0
  13. package/lib/types/index.d.ts +4 -0
  14. package/lib/types/protocol.d.ts +42 -1
  15. package/lib/types/stats.d.ts +53 -4
  16. package/lib/types/store.d.ts +7 -2
  17. package/package.json +68 -26
  18. package/src/client/SkillHubSettingsCard.tsx +35 -10
  19. package/src/client/icons.tsx +61 -0
  20. package/src/client/index.tsx +13 -1
  21. package/src/client/locales.ts +14 -6
  22. package/src/client/panel/SkillDetailView.tsx +6 -2
  23. package/src/client/panel/SkillHubPanel.tsx +2 -2
  24. package/src/client/panel/SkillRow.tsx +8 -3
  25. package/src/client/panel/format.ts +11 -0
  26. package/src/client/panel/panel.module.css +1 -0
  27. package/src/client/settings-card.tsx +49 -1
  28. package/src/client/settings-form.ts +21 -0
  29. package/src/client/slash-dots.test.ts +145 -0
  30. package/src/client/slash-dots.tsx +190 -0
  31. package/src/index.ts +34 -3
  32. package/src/protocol.ts +47 -1
  33. package/src/routes.test.ts +3 -1
  34. package/src/routes.ts +2 -2
  35. package/src/stats.test.ts +273 -2
  36. package/src/stats.ts +166 -23
  37. package/src/store.test.ts +32 -0
  38. package/src/store.ts +58 -3
package/lib/client.js CHANGED
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
8
  let react_jsx_runtime = require("react/jsx-runtime");
10
9
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
10
  //#region src/protocol.ts
@@ -318,7 +317,7 @@ window.__ModuleLoader__.load({
318
317
  "panel.search": "搜索技能…",
319
318
  "legend.model": "模型可调 — Agent 可自动调用",
320
319
  "legend.user": "用户可调 — 仅显式点名时触发",
321
- "legend.hint": "圆点标在技能名旁;无圆点表示该技能不可被调用。",
320
+ "legend.hint": "每个技能最多一个圆点;无圆点 = 不可被调用。",
322
321
  "panel.enabled": "已启用",
323
322
  "panel.disabled": "已禁用",
324
323
  "panel.diagnostics": "发现诊断",
@@ -487,15 +486,19 @@ window.__ModuleLoader__.load({
487
486
  "settings.announceToAgent": "向 Agent 公告",
488
487
  "settings.announceToAgentHint": "在系统提示中加入本插件说明,用户提到技能管理时 Agent 知道如何协作。",
489
488
  "settings.dotModelColor": "模型可调圆点颜色",
490
- "settings.dotModelColorHint": "技能名旁蓝色圆点的颜色(#rrggbb)。",
489
+ "settings.dotModelColorHint": "技能行/聊天 / 菜单中「模型可调」蓝色圆点的颜色(#rrggbb)。",
491
490
  "settings.dotUserColor": "用户可调圆点颜色",
492
- "settings.dotUserColorHint": "技能名旁绿色圆点的颜色(#rrggbb)。",
491
+ "settings.dotUserColorHint": "技能行/聊天 / 菜单中「仅用户可调」绿色圆点的颜色(#rrggbb)。",
493
492
  "settings.showUseCount": "显示调用次数",
494
493
  "settings.showUseCountHint": "在技能名旁显示琥珀色调用次数。",
495
494
  "settings.showUseTime": "显示最近调用时间",
496
495
  "settings.showUseTimeHint": "在技能名行右侧显示相对时间(如「3 天前」)。",
497
496
  "settings.showGroupSummary": "显示分组汇总",
498
497
  "settings.showGroupSummaryHint": "在分组标题后汇总调用次数与最近调用时间。",
498
+ "settings.statsWindowDays": "统计窗口(天)",
499
+ "settings.statsWindowDaysHint": "只统计最近 N 天的使用次数,默认 14 天;0 = 全部历史。改动立即生效。",
500
+ "settings.statsScanMinutes": "自动统计间隔(分钟)",
501
+ "settings.statsScanMinutesHint": "后台扫描会话日志的间隔,最小 1 分钟;扫描耗时会自动拉长间隔。",
499
502
  "settings.inherit": "继承",
500
503
  "settings.on": "开",
501
504
  "settings.off": "关",
@@ -526,7 +529,7 @@ window.__ModuleLoader__.load({
526
529
  "panel.search": "Search skills…",
527
530
  "legend.model": "Model-invocable — agents may call automatically",
528
531
  "legend.user": "User-invocable — explicit invocation only",
529
- "legend.hint": "Dots sit beside the skill name; no dot means the skill is not invocable.",
532
+ "legend.hint": "At most one dot per skill; no dot = not invocable.",
530
533
  "panel.enabled": "Enabled",
531
534
  "panel.disabled": "Disabled",
532
535
  "panel.diagnostics": "Discovery diagnostics",
@@ -695,15 +698,19 @@ window.__ModuleLoader__.load({
695
698
  "settings.announceToAgent": "Announce to agent",
696
699
  "settings.announceToAgentHint": "Adds a system-prompt section so agents know how to collaborate when users mention skill management.",
697
700
  "settings.dotModelColor": "Model-invocable dot color",
698
- "settings.dotModelColorHint": "Color of the blue dot beside skill names (#rrggbb).",
701
+ "settings.dotModelColorHint": "Color of the blue \"model-callable\" dot in the skill panel and the chat / menu (#rrggbb).",
699
702
  "settings.dotUserColor": "User-invocable dot color",
700
- "settings.dotUserColorHint": "Color of the green dot beside skill names (#rrggbb).",
703
+ "settings.dotUserColorHint": "Color of the green \"user-only\" dot in the skill panel and the chat / menu (#rrggbb).",
701
704
  "settings.showUseCount": "Show invocation count",
702
705
  "settings.showUseCountHint": "Show the amber invocation count beside each skill name.",
703
706
  "settings.showUseTime": "Show last-used time",
704
707
  "settings.showUseTimeHint": "Show relative last-used time on the name row (e.g. \"3 d ago\").",
705
708
  "settings.showGroupSummary": "Show group summaries",
706
709
  "settings.showGroupSummaryHint": "Summarize invocation count and last-used time after group titles.",
710
+ "settings.statsWindowDays": "Stats window (days)",
711
+ "settings.statsWindowDaysHint": "Count only usage within the last N days (default 14); 0 = full history. Changes apply immediately.",
712
+ "settings.statsScanMinutes": "Auto stats interval (minutes)",
713
+ "settings.statsScanMinutesHint": "How often the background session-log scan runs (min 1); heavy scans automatically stretch it.",
707
714
  "settings.inherit": "Inherit",
708
715
  "settings.on": "On",
709
716
  "settings.off": "Off",
@@ -769,6 +776,341 @@ window.__ModuleLoader__.load({
769
776
  };
770
777
  }
771
778
  //#endregion
779
+ //#region src/client/grouping.ts
780
+ /**
781
+ * Derive a group switch view from its member names and the set of currently
782
+ * enabled skill names (catalog.skills). Members not in the enabled set count
783
+ * as disabled (catalog.disabled or absent read-only rows).
784
+ */
785
+ function groupSwitchView(members, enabledNames) {
786
+ const enabled = [];
787
+ const disabled = [];
788
+ for (const name of members) if (enabledNames.has(name)) enabled.push(name);
789
+ else disabled.push(name);
790
+ return {
791
+ state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
792
+ enabled,
793
+ disabled
794
+ };
795
+ }
796
+ /** Names of every group a skill belongs to (tags + collections). */
797
+ function groupNamesOf(name, tags, collections) {
798
+ const names = [];
799
+ for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
800
+ for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
801
+ return names;
802
+ }
803
+ /**
804
+ * Members of a group that are currently enabled AND also belong to at least
805
+ * one other group — the set a "close" action must ask about.
806
+ */
807
+ function conflictsOnClose(members, enabledNames, otherGroups) {
808
+ return members.filter((name) => {
809
+ if (!enabledNames.has(name)) return false;
810
+ return otherGroups.some((group) => group.members.includes(name));
811
+ });
812
+ }
813
+ /** Origin-repo filter value: skills with no source record (private skills). */
814
+ const PRIVATE_SOURCE = "private";
815
+ /** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
816
+ function isProjectSource(source) {
817
+ return source === "project-dsh" || source === "project-agents";
818
+ }
819
+ /**
820
+ * Apply the origin filter ('all' or a specific origin repo; skills without a
821
+ * source record count as PRIVATE_SOURCE). The origins map is the store's
822
+ * skillName → repo derivation, so filtering follows the tracked source
823
+ * records instead of the filesystem root a skill happens to live under.
824
+ * 项目级技能(有 workspace 归属)永远不算「个人」。
825
+ */
826
+ function filterBySource(skills, source, origins) {
827
+ if (source === "all") return [...skills];
828
+ return skills.filter((skill) => {
829
+ if (isProjectSource(skill.source)) return false;
830
+ return (origins[skill.name] ?? "private") === source;
831
+ });
832
+ }
833
+ /**
834
+ * Sort a skill list in place-safe copy order: name ascending, added
835
+ * descending (newest first, unknown addedAt last), or uses descending
836
+ * (most-called first). Unknown values always trail.
837
+ */
838
+ function sortSkills(skills, key, getUses) {
839
+ const list = [...skills];
840
+ if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
841
+ else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
842
+ else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
843
+ return list;
844
+ }
845
+ /** Format an epoch-ms timestamp as a short relative time bucket. */
846
+ function formatRelativeTime(ms, now = Date.now()) {
847
+ const diff = Math.max(0, now - ms);
848
+ const minutes = Math.floor(diff / 6e4);
849
+ if (minutes < 1) return { key: "time.justNow" };
850
+ if (minutes < 60) return {
851
+ key: "time.minutesAgo",
852
+ value: minutes
853
+ };
854
+ const hours = Math.floor(minutes / 60);
855
+ if (hours < 24) return {
856
+ key: "time.hoursAgo",
857
+ value: hours
858
+ };
859
+ const days = Math.floor(hours / 24);
860
+ if (days < 7) return {
861
+ key: "time.daysAgo",
862
+ value: days
863
+ };
864
+ return {
865
+ key: "time.weeksAgo",
866
+ value: Math.floor(days / 7)
867
+ };
868
+ }
869
+ //#endregion
870
+ //#region src/client/helpers.ts
871
+ /**
872
+ * Shared panel helpers: the active-dictionary pick (document-language
873
+ * based, family precedent) plus a small error-message extractor.
874
+ */
875
+ /** Active dictionary, picked by the document language at call time. */
876
+ function dictionary() {
877
+ return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
878
+ }
879
+ /** Translate a key with optional {name} template params (current language). */
880
+ function tt(key, values) {
881
+ return t(dictionary(), key, values);
882
+ }
883
+ /** Human-readable error text from an unknown thrown value. */
884
+ function errorMessage(error) {
885
+ if (error instanceof Error) return error.message;
886
+ return String(error);
887
+ }
888
+ //#endregion
889
+ //#region src/client/panel/format.ts
890
+ /** Model-invocable dot color default. Single source for the TS side; the
891
+ * panel's CSS mirrors it via --hub-model (panel.module.css). */
892
+ const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
893
+ /** User-invocable dot color default. Single source for the TS side; the
894
+ * panel's CSS mirrors it via --hub-user (panel.module.css). */
895
+ const DEFAULT_DOT_USER_COLOR = "#3fb950";
896
+ /** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
897
+ function dotStyle(color) {
898
+ if (color === void 0) return void 0;
899
+ return {
900
+ background: color,
901
+ borderColor: color
902
+ };
903
+ }
904
+ /** Localized relative-time text for a Unix-ms timestamp. */
905
+ function relativeTimeText(ms) {
906
+ const rt = formatRelativeTime(ms);
907
+ return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
908
+ }
909
+ /** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
910
+ function formatDateTime(ms) {
911
+ return new Date(ms).toLocaleString();
912
+ }
913
+ /** Short form of a commit SHA for display. */
914
+ function shortSha(sha) {
915
+ return sha.length > 7 ? sha.slice(0, 7) : sha;
916
+ }
917
+ //#endregion
918
+ //#region src/client/slash-dots.tsx
919
+ /** The core plugin's skill source identity on the '/' trigger. */
920
+ const SKILL_SOURCE = {
921
+ trigger: "/",
922
+ name: "skill"
923
+ };
924
+ /** How long one catalog-derived modelInvocable map stays hot before refresh. */
925
+ const MODEL_TTL_MS = 6e4;
926
+ /**
927
+ * Find the core `/skill` source through the runtime registry, or undefined.
928
+ * The lookup never throws: a missing/reshaped registry just means no dots. Exported
929
+ * for unit tests; the apply path only calls it indirectly through setupSkillSlashDots.
930
+ * @param service - the ctx.inputTriggers service face.
931
+ * @returns the registered skill source, or undefined.
932
+ */
933
+ function findSkillSource(service) {
934
+ const live = service.live;
935
+ if (live?.sources === void 0) return void 0;
936
+ return live.sources.find((source) => source.trigger === SKILL_SOURCE.trigger && source.name === SKILL_SOURCE.name);
937
+ }
938
+ /** One catalog-derived name → modelInvocable snapshot with a load timestamp. */
939
+ let modelCache;
940
+ /**
941
+ * Clear the modelInvocable cache. Called on connection/reset so a fresh
942
+ * catalog wins after reconnect; exported for deterministic unit tests.
943
+ */
944
+ function resetModelCache() {
945
+ modelCache = void 0;
946
+ }
947
+ /**
948
+ * Resolve name → modelInvocable from the hub's catalog, cached for
949
+ * MODEL_TTL_MS. A failed load caches the failure briefly so a downed route
950
+ * doesn't hammer the host on every keystroke; the returned map is empty then
951
+ * (callers fall back to the model dot for unknown names).
952
+ * @param api - the hub browser API.
953
+ * @returns name → whether the model may call the skill.
954
+ */
955
+ async function modelInvocableMap(api) {
956
+ const now = Date.now();
957
+ const cached = modelCache;
958
+ if (cached !== void 0 && now - cached.at < MODEL_TTL_MS) return "failed" in cached ? /* @__PURE__ */ new Map() : cached.map;
959
+ try {
960
+ const catalog = await api.catalog();
961
+ const map = new Map(catalog.skills.map((skill) => [skill.name, skill.invocation.modelInvocable]));
962
+ modelCache = {
963
+ at: now,
964
+ map
965
+ };
966
+ return map;
967
+ } catch (error) {
968
+ console.error("[dsh-skill-hub] slash dot color lookup failed:", error);
969
+ modelCache = {
970
+ at: now,
971
+ failed: true
972
+ };
973
+ return /* @__PURE__ */ new Map();
974
+ }
975
+ }
976
+ /**
977
+ * One menu-row dot element. Inline span so it needs no CSS module; the
978
+ * candidate menu centers it inside its 16×16 leading icon slot.
979
+ * @param color - the dot's background color.
980
+ * @returns a React node (memory-only, never crosses the Host boundary).
981
+ */
982
+ function dotIcon(color) {
983
+ return (0, react.createElement)("span", {
984
+ "aria-hidden": true,
985
+ style: {
986
+ display: "inline-block",
987
+ width: 6,
988
+ height: 6,
989
+ borderRadius: 3,
990
+ background: color,
991
+ flex: "none"
992
+ }
993
+ });
994
+ }
995
+ /**
996
+ * Wrap the core skill source so every menu row carries the invocation dot.
997
+ * Exported for unit tests; production wiring goes through setupSkillSlashDots.
998
+ * @param source - the registered `/skill` source.
999
+ * @param api - hub browser API for the modelInvocable lookup.
1000
+ * @param scope - hub settings scope for the dot colors.
1001
+ * @returns a disposer restoring the original candidates.
1002
+ */
1003
+ function wrapSkillSource(source, api, scope) {
1004
+ const original = source.candidates;
1005
+ source.candidates = async (session, req) => {
1006
+ const items = await original(session, req);
1007
+ if (req.signal.aborted) return items;
1008
+ const modelByName = await modelInvocableMap(api);
1009
+ if (req.signal.aborted) return items;
1010
+ const snapshot = scope.getSnapshot();
1011
+ const modelColor = snapshot.value?.dotModelColor ?? "#2f81f7";
1012
+ const userColor = snapshot.value?.dotUserColor ?? "#3fb950";
1013
+ return items.map((item) => ({
1014
+ ...item,
1015
+ icon: dotIcon(modelByName.get(item.name) ?? true ? modelColor : userColor)
1016
+ }));
1017
+ };
1018
+ return () => {
1019
+ source.candidates = original;
1020
+ };
1021
+ }
1022
+ /**
1023
+ * Mount the slash-menu dots on the registered `/skill` source. Idempotent and
1024
+ * defensive: if the core source isn't registered yet (or the registry shape
1025
+ * changes), it retries briefly and then gives up silently — the chat keeps
1026
+ * working, it simply shows no dots. The returned disposer restores the
1027
+ * original candidates and clears the model cache.
1028
+ * @param ctx - the client root context (inputTriggers + events).
1029
+ * @param api - hub browser API.
1030
+ * @param scope - hub settings scope for dot colors.
1031
+ * @returns a cleanup function for `ctx.effect`.
1032
+ */
1033
+ function setupSkillSlashDots(ctx, api, scope) {
1034
+ const inputTriggers = ctx.get("inputTriggers");
1035
+ if (inputTriggers === void 0) return () => {};
1036
+ let disposed = false;
1037
+ let restore;
1038
+ let attempts = 0;
1039
+ const attempt = () => {
1040
+ if (disposed) return;
1041
+ const source = findSkillSource(inputTriggers);
1042
+ if (source === void 0) {
1043
+ if (attempts < 10) {
1044
+ attempts += 1;
1045
+ setTimeout(attempt, 100);
1046
+ }
1047
+ return;
1048
+ }
1049
+ restore = wrapSkillSource(source, api, scope);
1050
+ };
1051
+ attempt();
1052
+ const clearCache = () => {
1053
+ resetModelCache();
1054
+ };
1055
+ const offReset = ctx.on("connection/reset", clearCache);
1056
+ return () => {
1057
+ disposed = true;
1058
+ offReset();
1059
+ restore?.();
1060
+ restore = void 0;
1061
+ };
1062
+ }
1063
+ //#endregion
1064
+ //#region src/client/icons.tsx
1065
+ /** ic_ds_chevron_down_outline_14 */
1066
+ function IconChevronDownOutline14({ size = 14, className }) {
1067
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1068
+ width: size,
1069
+ height: size,
1070
+ className,
1071
+ viewBox: "0 0 14 14",
1072
+ fill: "none",
1073
+ xmlns: "http://www.w3.org/2000/svg",
1074
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1075
+ d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
1076
+ fill: "currentColor"
1077
+ })
1078
+ });
1079
+ }
1080
+ /** ic_ds_trash_outline_16 */
1081
+ function IconTrashOutline16({ size = 16, className }) {
1082
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1083
+ width: size,
1084
+ height: size,
1085
+ className,
1086
+ viewBox: "0 0 16 16",
1087
+ fill: "none",
1088
+ xmlns: "http://www.w3.org/2000/svg",
1089
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1090
+ d: "M14.4782 4.84067L14.2138 10.1152C14.1102 12.1872 14.067 13.0115 13.3866 13.9607C13.1044 14.3546 12.7498 14.6912 12.3424 14.9535C11.8239 15.2872 11.2415 15.4316 10.5585 15.4998C9.88727 15.5668 9.04946 15.5656 7.99998 15.5656C6.95051 15.5656 6.1127 15.5668 5.44142 15.4998C4.75851 15.4316 4.17602 15.2872 3.65753 14.9535C3.25012 14.6912 2.89559 14.3546 2.61332 13.9607C1.93296 13.0115 1.88979 12.1872 1.78619 10.1152L1.52179 4.84067L2.89006 4.77277L3.15343 10.0463C3.26221 12.2218 3.32452 12.6015 3.72646 13.1624C3.90825 13.4161 4.13686 13.6334 4.39927 13.8023C4.66204 13.9714 5.00263 14.0792 5.57825 14.1367C6.16562 14.1953 6.92298 14.1963 7.99998 14.1963C9.07699 14.1963 9.83434 14.1953 10.4217 14.1367C10.9973 14.0792 11.3379 13.9714 11.6007 13.8023C11.8631 13.6334 12.0917 13.4161 12.2735 13.1624C12.6755 12.6015 12.7378 12.2218 12.8465 10.0463L13.1099 4.77277L14.4782 4.84067ZM5.43011 6.22849H6.7994V11.3909H5.43011V6.22849ZM9.20056 6.22849H10.5699V11.3909H9.20056V6.22849ZM8.53597 0.434431C9.17976 0.434431 9.6522 0.426926 10.0966 0.571258C10.2357 0.616451 10.3717 0.672554 10.502 0.738948C10.9182 0.951107 11.2464 1.29099 11.7015 1.74612L12.4978 2.54136H15.3742V3.91169H0.625732V2.54136H3.50218L4.29845 1.74612C4.75358 1.29099 5.08174 0.951107 5.49801 0.738948C5.62831 0.672554 5.76425 0.616451 5.90334 0.571258C6.34776 0.426926 6.82021 0.434431 7.46399 0.434431H8.53597ZM7.46399 1.80476C6.73208 1.80476 6.51641 1.81187 6.32617 1.87369C6.25545 1.89667 6.18668 1.92533 6.12041 1.95907C5.96398 2.03878 5.82348 2.16253 5.44142 2.54136H10.5585C10.1765 2.16253 10.036 2.03878 9.87955 1.95907C9.81329 1.92533 9.74452 1.89667 9.6738 1.87369C9.48356 1.81187 9.26789 1.80476 8.53597 1.80476H7.46399Z",
1091
+ fill: "currentColor"
1092
+ })
1093
+ });
1094
+ }
1095
+ /** ic_ds_skill_outline_16 */
1096
+ function IconSkillOutline16({ size = 16, className }) {
1097
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1098
+ width: size,
1099
+ height: size,
1100
+ className,
1101
+ viewBox: "0 0 16 16",
1102
+ fill: "none",
1103
+ xmlns: "http://www.w3.org/2000/svg",
1104
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1105
+ d: "M12.5113 15.4067C12.4395 15.6249 12.1308 15.6249 12.059 15.4067L11.643 14.1416C11.454 13.567 11.0033 13.1164 10.4288 12.9274L9.16369 12.5113C8.94544 12.4395 8.94544 12.1308 9.16369 12.059L10.4288 11.643C11.0033 11.454 11.454 11.0033 11.643 10.4288L12.059 9.16369C12.1308 8.94544 12.4395 8.94544 12.5113 9.16369L12.9274 10.4288C13.1164 11.0033 13.567 11.454 14.1416 11.643L15.4067 12.059C15.6249 12.1308 15.6249 12.4395 15.4067 12.5113L14.1416 12.9274C13.567 13.1164 13.1164 13.567 12.9274 14.1416L12.5113 15.4067Z",
1106
+ fill: "currentColor"
1107
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1108
+ d: "M9.02246 0.546878C9.9822 0.546878 10.7564 0.545403 11.374 0.612307C12.0042 0.680586 12.5515 0.826244 13.0273 1.17188C13.3052 1.37376 13.5501 1.61868 13.752 1.89649C14.0975 2.37225 14.2432 2.91984 14.3115 3.54981C14.3784 4.16727 14.377 4.94206 14.377 5.90137V8.51367C13.9611 8.29533 13.5071 8.13985 13.0273 8.06055V5.90137C13.0273 4.9121 13.0259 4.22322 12.9688 3.69532C12.9129 3.18044 12.8098 2.89782 12.6592 2.69043C12.5406 2.52724 12.3966 2.38326 12.2334 2.26465C12.026 2.11404 11.7437 2.0109 11.2285 1.95508C10.7005 1.89789 10.0122 1.89649 9.02246 1.89649H6.55371C5.56395 1.89649 4.87569 1.89787 4.34766 1.95508C3.83242 2.01092 3.55022 2.11398 3.34278 2.26465C3.17953 2.38329 3.03564 2.52719 2.91699 2.69043C2.76642 2.89782 2.66325 3.18042 2.60742 3.69532C2.55027 4.22322 2.54883 4.9121 2.54883 5.90137V10.0986C2.54883 11.0878 2.55031 11.7768 2.60742 12.3047C2.66326 12.8196 2.76642 13.1032 2.91699 13.3105C3.03558 13.4736 3.17966 13.6178 3.34278 13.7363C3.5502 13.8869 3.83265 13.9901 4.34766 14.0459C4.87568 14.1031 5.56398 14.1035 6.55371 14.1035H8.08399C8.27443 14.6025 8.55077 15.0585 8.89551 15.4541H6.55371C5.59402 15.4541 4.81976 15.4546 4.20215 15.3877C3.57204 15.3194 3.02468 15.1738 2.54883 14.8281C2.27111 14.6263 2.02606 14.3813 1.82422 14.1035C1.47883 13.6278 1.33293 13.08 1.26465 12.4502C1.19783 11.8327 1.19922 11.0579 1.19922 10.0986V5.90137C1.19922 4.94206 1.1978 4.16727 1.26465 3.54981C1.33295 2.91984 1.47867 2.37225 1.82422 1.89649C2.02613 1.61864 2.27098 1.37379 2.54883 1.17188C3.02472 0.826181 3.57197 0.6806 4.20215 0.612307C4.81976 0.545393 5.594 0.546877 6.55371 0.546878H9.02246ZM9.19629 9.14649H4.5459V7.84571H9.19629V9.14649ZM11.0303 6.10645H4.5459V4.80567H11.0303V6.10645Z",
1109
+ fill: "currentColor"
1110
+ })]
1111
+ });
1112
+ }
1113
+ //#endregion
772
1114
  //#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/settings-card.module.css.mjs
773
1115
  const css$1 = ".TqD_hG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.TqD_hG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.TqD_hG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.TqD_hG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.TqD_hG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.TqD_hG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.TqD_hG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_chevron,.TqD_hG_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.TqD_hG_chevronOpen{transform:rotate(180deg)}.TqD_hG_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.TqD_hG_readOnly,.TqD_hG_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.TqD_hG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.TqD_hG_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.TqD_hG_discard,.TqD_hG_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.TqD_hG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.TqD_hG_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.TqD_hG_discard:disabled,.TqD_hG_save:disabled{opacity:.4;cursor:default}.TqD_hG_discard:focus-visible,.TqD_hG_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.TqD_hG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.TqD_hG_field+.TqD_hG_field{border-top:1px solid var(--dsw-alias-border-l2)}.TqD_hG_head{align-items:center;gap:8px;display:flex}.TqD_hG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.TqD_hG_badges{align-items:center;gap:8px;display:inline-flex}.TqD_hG_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.TqD_hG_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.TqD_hG_reset:disabled{cursor:default}.TqD_hG_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.TqD_hG_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.TqD_hG_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.TqD_hG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.TqD_hG_colorRow{align-items:center;gap:8px;display:flex}.TqD_hG_colorInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);cursor:pointer;border-radius:8px;width:34px;height:34px;padding:0}.TqD_hG_colorInput:disabled{opacity:.5;cursor:default}.TqD_hG_colorInput::-webkit-color-swatch-wrapper{padding:2px}.TqD_hG_colorInput::-webkit-color-swatch{border:none;border-radius:4px}.TqD_hG_colorText{flex:1;min-width:0}.TqD_hG_switchRow{align-items:center;gap:12px;display:flex}.TqD_hG_switchText{flex:1;min-width:0}.TqD_hG_switch,.TqD_hG_switchOn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;border-radius:999px;flex:none;width:38px;height:22px;padding:2px;transition:background .15s,border-color .15s}.TqD_hG_switchOn{border-color:var(--dsw-alias-state-business-primary,#2f81f7);background:var(--dsw-alias-state-business-primary,#2f81f7)}.TqD_hG_switch:disabled,.TqD_hG_switchOn:disabled{opacity:.4;cursor:default}.TqD_hG_switchThumb{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}.TqD_hG_switchOn .TqD_hG_switchThumb{transform:translate(16px)}";
774
1116
  const tagId$1 = "dsh-skill-hub/settings-card.module.css";
@@ -856,7 +1198,7 @@ window.__ModuleLoader__.load({
856
1198
  className: settings_card_module_css_default.pending,
857
1199
  children: props.t("settings.unsaved")
858
1200
  }) : null,
859
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron })
1201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutline14, { className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron })
860
1202
  ]
861
1203
  });
862
1204
  if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
@@ -1008,6 +1350,50 @@ window.__ModuleLoader__.load({
1008
1350
  ]
1009
1351
  });
1010
1352
  }
1353
+ function NumberField(props) {
1354
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1355
+ className: settings_card_module_css_default.field,
1356
+ children: [
1357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1358
+ className: settings_card_module_css_default.head,
1359
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1360
+ className: settings_card_module_css_default.label,
1361
+ htmlFor: props.id,
1362
+ children: props.label
1363
+ }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1364
+ className: settings_card_module_css_default.badges,
1365
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1366
+ className: settings_card_module_css_default.badge,
1367
+ children: props.overriddenLabel
1368
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1369
+ type: "button",
1370
+ className: settings_card_module_css_default.reset,
1371
+ disabled: props.disabled,
1372
+ onClick: props.onReset,
1373
+ children: props.resetLabel
1374
+ })]
1375
+ }) : null]
1376
+ }),
1377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1378
+ id: props.id,
1379
+ type: "text",
1380
+ inputMode: "numeric",
1381
+ className: settings_card_module_css_default.input,
1382
+ value: props.text,
1383
+ disabled: props.disabled,
1384
+ placeholder: props.inheritLabel,
1385
+ "aria-invalid": props.invalid === true,
1386
+ onChange: (event) => {
1387
+ props.onEdit(event.target.value);
1388
+ }
1389
+ }),
1390
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1391
+ className: settings_card_module_css_default.hint,
1392
+ children: props.hint
1393
+ })
1394
+ ]
1395
+ });
1396
+ }
1011
1397
  //#endregion
1012
1398
  //#region src/client/settings-form.ts
1013
1399
  /**
@@ -1050,6 +1436,29 @@ window.__ModuleLoader__.load({
1050
1436
  }
1051
1437
  };
1052
1438
  }
1439
+ /**
1440
+ * A numeric field with range clamping. Integer by default: a fractional draft
1441
+ * is truncated so "5.5 分钟" cannot sneak past an integer-only schema.
1442
+ */
1443
+ function numberField(field, options = {}) {
1444
+ const min = options.min ?? Number.NEGATIVE_INFINITY;
1445
+ const max = options.max ?? Number.POSITIVE_INFINITY;
1446
+ const integer = options.integer ?? true;
1447
+ return {
1448
+ field,
1449
+ format: (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : "",
1450
+ parse: (text) => {
1451
+ const trimmed = text.trim();
1452
+ if (!/^-?\d+(\.\d+)?$/.test(trimmed)) return void 0;
1453
+ const parsed = integer ? Math.trunc(Number(trimmed)) : Number(trimmed);
1454
+ if (!Number.isFinite(parsed) || parsed < min || parsed > max) return void 0;
1455
+ return {
1456
+ kind: "set",
1457
+ value: parsed
1458
+ };
1459
+ }
1460
+ };
1461
+ }
1053
1462
  /** Stages one card's edits and writes them through the settings transport. */
1054
1463
  var CardForm = class {
1055
1464
  scope;
@@ -1216,12 +1625,6 @@ window.__ModuleLoader__.load({
1216
1625
  };
1217
1626
  //#endregion
1218
1627
  //#region src/client/SkillHubSettingsCard.tsx
1219
- /** Model-invocable dot color default. Single source for the TS side; the
1220
- * panel's CSS mirrors it via --hub-model (panel.module.css). */
1221
- const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
1222
- /** User-invocable dot color default. Single source for the TS side; the
1223
- * panel's CSS mirrors it via --hub-user (panel.module.css). */
1224
- const DEFAULT_DOT_USER_COLOR = "#3fb950";
1225
1628
  /** Bridges the hub's config scope onto the card's staged form. */
1226
1629
  var SkillHubSettingsCardController = class {
1227
1630
  form;
@@ -1235,7 +1638,15 @@ window.__ModuleLoader__.load({
1235
1638
  colorField("dotUserColor"),
1236
1639
  booleanField("showUseCount"),
1237
1640
  booleanField("showUseTime"),
1238
- booleanField("showGroupSummary")
1641
+ booleanField("showGroupSummary"),
1642
+ numberField("statsWindowDays", {
1643
+ min: 0,
1644
+ max: 3650
1645
+ }),
1646
+ numberField("statsScanMinutes", {
1647
+ min: 1,
1648
+ max: 1440
1649
+ })
1239
1650
  ]);
1240
1651
  this.store = this.form.bind(() => this.projection());
1241
1652
  }
@@ -1248,7 +1659,9 @@ window.__ModuleLoader__.load({
1248
1659
  dotUserColor: this.form.field("dotUserColor"),
1249
1660
  showUseCount: this.form.field("showUseCount"),
1250
1661
  showUseTime: this.form.field("showUseTime"),
1251
- showGroupSummary: this.form.field("showGroupSummary")
1662
+ showGroupSummary: this.form.field("showGroupSummary"),
1663
+ statsWindowDays: this.form.field("statsWindowDays"),
1664
+ statsScanMinutes: this.form.field("statsScanMinutes")
1252
1665
  };
1253
1666
  }
1254
1667
  /**
@@ -1373,146 +1786,45 @@ window.__ModuleLoader__.load({
1373
1786
  onReset: () => {
1374
1787
  props.resetField("showGroupSummary");
1375
1788
  }
1789
+ }),
1790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberField, {
1791
+ id: "skill-hub-stats-window-days",
1792
+ label: t("settings.statsWindowDays"),
1793
+ hint: t("settings.statsWindowDaysHint"),
1794
+ inheritLabel: t("settings.inherit"),
1795
+ ...fieldProps,
1796
+ text: state.statsWindowDays.text,
1797
+ overridden: state.statsWindowDays.overridden,
1798
+ invalid: state.statsWindowDays.invalid,
1799
+ onEdit: (text) => {
1800
+ props.edit("statsWindowDays", text);
1801
+ },
1802
+ onReset: () => {
1803
+ props.resetField("statsWindowDays");
1804
+ }
1805
+ }),
1806
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberField, {
1807
+ id: "skill-hub-stats-scan-minutes",
1808
+ label: t("settings.statsScanMinutes"),
1809
+ hint: t("settings.statsScanMinutesHint"),
1810
+ inheritLabel: t("settings.inherit"),
1811
+ ...fieldProps,
1812
+ text: state.statsScanMinutes.text,
1813
+ overridden: state.statsScanMinutes.overridden,
1814
+ invalid: state.statsScanMinutes.invalid,
1815
+ onEdit: (text) => {
1816
+ props.edit("statsScanMinutes", text);
1817
+ },
1818
+ onReset: () => {
1819
+ props.resetField("statsScanMinutes");
1820
+ }
1376
1821
  })
1377
1822
  ]
1378
1823
  });
1379
1824
  }
1380
1825
  //#endregion
1381
- //#region src/client/helpers.ts
1382
- /**
1383
- * Shared panel helpers: the active-dictionary pick (document-language
1384
- * based, family precedent) plus a small error-message extractor.
1385
- */
1386
- /** Active dictionary, picked by the document language at call time. */
1387
- function dictionary() {
1388
- return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
1389
- }
1390
- /** Translate a key with optional {name} template params (current language). */
1391
- function tt(key, values) {
1392
- return t(dictionary(), key, values);
1393
- }
1394
- /** Human-readable error text from an unknown thrown value. */
1395
- function errorMessage(error) {
1396
- if (error instanceof Error) return error.message;
1397
- return String(error);
1398
- }
1399
- //#endregion
1400
- //#region src/client/grouping.ts
1401
- /**
1402
- * Derive a group switch view from its member names and the set of currently
1403
- * enabled skill names (catalog.skills). Members not in the enabled set count
1404
- * as disabled (catalog.disabled or absent read-only rows).
1405
- */
1406
- function groupSwitchView(members, enabledNames) {
1407
- const enabled = [];
1408
- const disabled = [];
1409
- for (const name of members) if (enabledNames.has(name)) enabled.push(name);
1410
- else disabled.push(name);
1411
- return {
1412
- state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
1413
- enabled,
1414
- disabled
1415
- };
1416
- }
1417
- /** Names of every group a skill belongs to (tags + collections). */
1418
- function groupNamesOf(name, tags, collections) {
1419
- const names = [];
1420
- for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
1421
- for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
1422
- return names;
1423
- }
1424
- /**
1425
- * Members of a group that are currently enabled AND also belong to at least
1426
- * one other group — the set a "close" action must ask about.
1427
- */
1428
- function conflictsOnClose(members, enabledNames, otherGroups) {
1429
- return members.filter((name) => {
1430
- if (!enabledNames.has(name)) return false;
1431
- return otherGroups.some((group) => group.members.includes(name));
1432
- });
1433
- }
1434
- /** Origin-repo filter value: skills with no source record (private skills). */
1435
- const PRIVATE_SOURCE = "private";
1436
- /** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
1437
- function isProjectSource(source) {
1438
- return source === "project-dsh" || source === "project-agents";
1439
- }
1440
- /**
1441
- * Apply the origin filter ('all' or a specific origin repo; skills without a
1442
- * source record count as PRIVATE_SOURCE). The origins map is the store's
1443
- * skillName → repo derivation, so filtering follows the tracked source
1444
- * records instead of the filesystem root a skill happens to live under.
1445
- * 项目级技能(有 workspace 归属)永远不算「个人」。
1446
- */
1447
- function filterBySource(skills, source, origins) {
1448
- if (source === "all") return [...skills];
1449
- return skills.filter((skill) => {
1450
- if (isProjectSource(skill.source)) return false;
1451
- return (origins[skill.name] ?? "private") === source;
1452
- });
1453
- }
1454
- /**
1455
- * Sort a skill list in place-safe copy order: name ascending, added
1456
- * descending (newest first, unknown addedAt last), or uses descending
1457
- * (most-called first). Unknown values always trail.
1458
- */
1459
- function sortSkills(skills, key, getUses) {
1460
- const list = [...skills];
1461
- if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
1462
- else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
1463
- else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
1464
- return list;
1465
- }
1466
- /** Format an epoch-ms timestamp as a short relative time bucket. */
1467
- function formatRelativeTime(ms, now = Date.now()) {
1468
- const diff = Math.max(0, now - ms);
1469
- const minutes = Math.floor(diff / 6e4);
1470
- if (minutes < 1) return { key: "time.justNow" };
1471
- if (minutes < 60) return {
1472
- key: "time.minutesAgo",
1473
- value: minutes
1474
- };
1475
- const hours = Math.floor(minutes / 60);
1476
- if (hours < 24) return {
1477
- key: "time.hoursAgo",
1478
- value: hours
1479
- };
1480
- const days = Math.floor(hours / 24);
1481
- if (days < 7) return {
1482
- key: "time.daysAgo",
1483
- value: days
1484
- };
1485
- return {
1486
- key: "time.weeksAgo",
1487
- value: Math.floor(days / 7)
1488
- };
1489
- }
1490
- //#endregion
1491
- //#region src/client/panel/format.ts
1492
- /** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
1493
- function dotStyle(color) {
1494
- if (color === void 0) return void 0;
1495
- return {
1496
- background: color,
1497
- borderColor: color
1498
- };
1499
- }
1500
- /** Localized relative-time text for a Unix-ms timestamp. */
1501
- function relativeTimeText(ms) {
1502
- const rt = formatRelativeTime(ms);
1503
- return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
1504
- }
1505
- /** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
1506
- function formatDateTime(ms) {
1507
- return new Date(ms).toLocaleString();
1508
- }
1509
- /** Short form of a commit SHA for display. */
1510
- function shortSha(sha) {
1511
- return sha.length > 7 ? sha.slice(0, 7) : sha;
1512
- }
1513
- //#endregion
1514
1826
  //#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
1515
- 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{align-items:center;gap:8px;padding:12px 14px 4px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;min-width:0;font-size:13px;font-weight:600}._6VtqdG_groupOps{flex:none;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;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;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._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_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;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}";
1827
+ 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{align-items:center;gap:8px;padding:12px 14px 4px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;min-width:0;font-size:13px;font-weight:600}._6VtqdG_groupOps{flex:none;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;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;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)}._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_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;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}";
1516
1828
  const tagId = "dsh-skill-hub/panel.module.css";
1517
1829
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1518
1830
  const tag = document.createElement("style");
@@ -1592,6 +1904,7 @@ window.__ModuleLoader__.load({
1592
1904
  "opBtn": "_6VtqdG_opBtn",
1593
1905
  "opDanger": "_6VtqdG_opDanger",
1594
1906
  "panel": "_6VtqdG_panel",
1907
+ "pluginVersion": "_6VtqdG_pluginVersion",
1595
1908
  "primary": "_6VtqdG_primary",
1596
1909
  "projectNest": "_6VtqdG_projectNest",
1597
1910
  "row": "_6VtqdG_row",
@@ -1708,19 +2021,15 @@ window.__ModuleLoader__.load({
1708
2021
  children: tt("detail.back")
1709
2022
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1710
2023
  className: panel_module_css_default.detailName,
1711
- children: [
1712
- detail.name,
1713
- detail.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1714
- className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
1715
- style: dotStyle(hubConfig?.dotModelColor),
1716
- title: tt("legend.model")
1717
- }) : null,
1718
- detail.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1719
- className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
1720
- style: dotStyle(hubConfig?.dotUserColor),
1721
- title: tt("legend.user")
1722
- }) : null
1723
- ]
2024
+ children: [detail.name, detail.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2025
+ className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
2026
+ style: dotStyle(hubConfig?.dotModelColor),
2027
+ title: tt("legend.model")
2028
+ }) : detail.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2029
+ className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
2030
+ style: dotStyle(hubConfig?.dotUserColor),
2031
+ title: tt("legend.user")
2032
+ }) : null]
1724
2033
  })]
1725
2034
  }),
1726
2035
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1987,6 +2296,16 @@ window.__ModuleLoader__.load({
1987
2296
  const stat = hub.uses.get(skill.name);
1988
2297
  const count = stat?.count ?? 0;
1989
2298
  const lastUsed = stat?.lastUsed;
2299
+ /** 单一状态圆点:模型可调 → 蓝;否则用户可调 → 绿。与聊天 / 菜单同规则。 */
2300
+ const dot = skill.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2301
+ className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
2302
+ style: dotStyle(hub.hubConfig?.dotModelColor),
2303
+ title: tt("legend.model")
2304
+ }) : skill.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2305
+ className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
2306
+ style: dotStyle(hub.hubConfig?.dotUserColor),
2307
+ title: tt("legend.user")
2308
+ }) : null;
1990
2309
  /** 键盘打开详情:Enter 或空格。仅当焦点在行本身(而非行内按钮)时生效。 */
1991
2310
  const onKeyDown = (event) => {
1992
2311
  if (event.target !== event.currentTarget) return;
@@ -2017,16 +2336,7 @@ window.__ModuleLoader__.load({
2017
2336
  className: panel_module_css_default.useCount,
2018
2337
  children: count
2019
2338
  }) : null,
2020
- skill.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2021
- className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
2022
- style: dotStyle(hub.hubConfig?.dotModelColor),
2023
- title: tt("legend.model")
2024
- }) : null,
2025
- skill.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2026
- className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
2027
- style: dotStyle(hub.hubConfig?.dotUserColor),
2028
- title: tt("legend.user")
2029
- }) : null,
2339
+ dot,
2030
2340
  lastUsed !== void 0 && hub.hubConfig?.showUseTime !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2031
2341
  className: panel_module_css_default.useTime,
2032
2342
  children: relativeTimeText(lastUsed)
@@ -2046,7 +2356,7 @@ window.__ModuleLoader__.load({
2046
2356
  event.stopPropagation();
2047
2357
  hub.requestDeleteSkill(skill.name);
2048
2358
  },
2049
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
2359
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrashOutline16, { size: 14 })
2050
2360
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2051
2361
  type: "button",
2052
2362
  role: "switch",
@@ -4114,12 +4424,16 @@ window.__ModuleLoader__.load({
4114
4424
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
4115
4425
  className: panel_module_css_default.title,
4116
4426
  children: [
4117
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, {
4427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSkillOutline16, {
4118
4428
  size: 16,
4119
4429
  className: panel_module_css_default.titleIcon
4120
4430
  }),
4121
4431
  " ",
4122
- tt("panel.title")
4432
+ tt("panel.title"),
4433
+ catalog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4434
+ className: panel_module_css_default.pluginVersion,
4435
+ children: ["v", catalog.pluginVersion]
4436
+ }) : null
4123
4437
  ]
4124
4438
  }),
4125
4439
  catalog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -4612,7 +4926,8 @@ window.__ModuleLoader__.load({
4612
4926
  "locale",
4613
4927
  "connection",
4614
4928
  "remote",
4615
- "settingsScope"
4929
+ "settingsScope",
4930
+ "inputTriggers"
4616
4931
  ];
4617
4932
  /**
4618
4933
  * Mount the settings card and the skill hub section.
@@ -4626,6 +4941,7 @@ window.__ModuleLoader__.load({
4626
4941
  const t = ctx.locale.bind(NS);
4627
4942
  const api = new SkillHubApi();
4628
4943
  const settingsCard = new SkillHubSettingsCardController(ctx.settingsScope.bind({ namespace: NS }));
4944
+ ctx.effect(() => setupSkillSlashDots(ctx, api, ctx.settingsScope.bind({ namespace: NS })), "dsh-skill-hub: slash dots");
4629
4945
  ctx.effect(() => ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
4630
4946
  name: "settings.plugin.item",
4631
4947
  key: NS,