dsh-skill-hub 0.3.4 → 0.3.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.
package/lib/client.js CHANGED
@@ -846,333 +846,6 @@ window.__ModuleLoader__.load({
846
846
  };
847
847
  }
848
848
  //#endregion
849
- //#region src/client/grouping.ts
850
- /**
851
- * Derive a group switch view from its member names and the set of currently
852
- * enabled skill names (catalog.skills). Members not in the enabled set count
853
- * as disabled (catalog.disabled or absent read-only rows).
854
- */
855
- function groupSwitchView(members, enabledNames) {
856
- const enabled = [];
857
- const disabled = [];
858
- for (const name of members) if (enabledNames.has(name)) enabled.push(name);
859
- else disabled.push(name);
860
- return {
861
- state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
862
- enabled,
863
- disabled
864
- };
865
- }
866
- /** Names of every group a skill belongs to (tags + collections). */
867
- function groupNamesOf(name, tags, collections) {
868
- const names = [];
869
- for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
870
- for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
871
- return names;
872
- }
873
- /**
874
- * Members of a group that are currently enabled AND also belong to at least
875
- * one other group — the set a "close" action must ask about.
876
- */
877
- function conflictsOnClose(members, enabledNames, otherGroups) {
878
- return members.filter((name) => {
879
- if (!enabledNames.has(name)) return false;
880
- return otherGroups.some((group) => group.members.includes(name));
881
- });
882
- }
883
- /** Origin-repo filter value: skills with no source record (private skills). */
884
- const PRIVATE_SOURCE = "private";
885
- /**
886
- * Apply the origin filter ('all' or a specific origin repo; skills without a
887
- * source record count as PRIVATE_SOURCE). The origins map is the store's
888
- * skillName → repo derivation, so filtering follows the tracked source
889
- * records instead of the filesystem root a skill happens to live under.
890
- * 项目级技能(有 workspace 归属)永远不算「个人」。
891
- */
892
- function filterBySource(skills, source, origins) {
893
- if (source === "all") return [...skills];
894
- return skills.filter((skill) => {
895
- if (isProjectSource(skill.source)) return false;
896
- return (origins[skill.name] ?? "private") === source;
897
- });
898
- }
899
- /**
900
- * Sort a skill list in place-safe copy order: name ascending, added
901
- * descending (newest first, unknown addedAt last), or uses descending
902
- * (most-called first). Unknown values always trail.
903
- */
904
- function sortSkills(skills, key, getUses) {
905
- const list = [...skills];
906
- if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
907
- else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
908
- else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
909
- return list;
910
- }
911
- /** Format an epoch-ms timestamp as a short relative time bucket. */
912
- function formatRelativeTime(ms, now = Date.now()) {
913
- const diff = Math.max(0, now - ms);
914
- const minutes = Math.floor(diff / 6e4);
915
- if (minutes < 1) return { key: "time.justNow" };
916
- if (minutes < 60) return {
917
- key: "time.minutesAgo",
918
- value: minutes
919
- };
920
- const hours = Math.floor(minutes / 60);
921
- if (hours < 24) return {
922
- key: "time.hoursAgo",
923
- value: hours
924
- };
925
- const days = Math.floor(hours / 24);
926
- if (days < 7) return {
927
- key: "time.daysAgo",
928
- value: days
929
- };
930
- return {
931
- key: "time.weeksAgo",
932
- value: Math.floor(days / 7)
933
- };
934
- }
935
- //#endregion
936
- //#region src/client/helpers.ts
937
- /**
938
- * Shared panel helpers: the active-dictionary pick (document-language
939
- * based, family precedent) plus a small error-message extractor.
940
- */
941
- /** Active dictionary, picked by the document language at call time. */
942
- function dictionary() {
943
- return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
944
- }
945
- /** Translate a key with optional {name} template params (current language). */
946
- function tt(key, values) {
947
- return t(dictionary(), key, values);
948
- }
949
- /** Human-readable error text from an unknown thrown value. */
950
- function errorMessage(error) {
951
- if (error instanceof Error) return error.message;
952
- return String(error);
953
- }
954
- //#endregion
955
- //#region src/client/panel/format.ts
956
- /** Model-invocable dot color default. Single source for the TS side; the
957
- * panel's CSS mirrors it via --hub-model (panel.module.css). */
958
- const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
959
- /** User-invocable dot color default. Single source for the TS side; the
960
- * panel's CSS mirrors it via --hub-user (panel.module.css). */
961
- const DEFAULT_DOT_USER_COLOR = "#3fb950";
962
- /** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
963
- function dotStyle(color) {
964
- if (color === void 0) return void 0;
965
- return {
966
- background: color,
967
- borderColor: color
968
- };
969
- }
970
- /** Localized relative-time text for a Unix-ms timestamp. */
971
- function relativeTimeText(ms) {
972
- const rt = formatRelativeTime(ms);
973
- return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
974
- }
975
- /** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
976
- function formatDateTime(ms) {
977
- return new Date(ms).toLocaleString();
978
- }
979
- /** Short form of a commit SHA for display. */
980
- function shortSha(sha) {
981
- return sha.length > 7 ? sha.slice(0, 7) : sha;
982
- }
983
- //#endregion
984
- //#region src/client/slash-dots.tsx
985
- /** The core plugin's skill source identity on the '/' trigger. */
986
- const SKILL_SOURCE = {
987
- trigger: "/",
988
- name: "skill"
989
- };
990
- /** How long one catalog-derived modelInvocable map stays hot before refresh. */
991
- const MODEL_TTL_MS = 6e4;
992
- /**
993
- * Find the core `/skill` source through the runtime registry, or undefined.
994
- * The lookup never throws: a missing/reshaped registry just means no dots. Exported
995
- * for unit tests; the apply path only calls it indirectly through setupSkillSlashDots.
996
- * @param service - the ctx.inputTriggers service face.
997
- * @returns the registered skill source, or undefined.
998
- */
999
- function findSkillSource(service) {
1000
- const live = service.live;
1001
- if (live?.sources === void 0) return void 0;
1002
- return live.sources.find((source) => source.trigger === SKILL_SOURCE.trigger && source.name === SKILL_SOURCE.name);
1003
- }
1004
- /** One catalog-derived name → modelInvocable snapshot with a load timestamp. */
1005
- let modelCache;
1006
- /**
1007
- * Clear the modelInvocable cache. Called on connection/reset so a fresh
1008
- * catalog wins after reconnect; exported for deterministic unit tests.
1009
- */
1010
- function resetModelCache() {
1011
- modelCache = void 0;
1012
- }
1013
- /**
1014
- * Resolve name → modelInvocable from the hub's catalog, cached for
1015
- * MODEL_TTL_MS. A failed load caches the failure briefly so a downed route
1016
- * doesn't hammer the host on every keystroke; the returned map is empty then
1017
- * (callers fall back to the model dot for unknown names).
1018
- * @param api - the hub browser API.
1019
- * @returns name → whether the model may call the skill.
1020
- */
1021
- async function modelInvocableMap(api) {
1022
- const now = Date.now();
1023
- const cached = modelCache;
1024
- if (cached !== void 0 && now - cached.at < MODEL_TTL_MS) return "failed" in cached ? /* @__PURE__ */ new Map() : cached.map;
1025
- try {
1026
- const catalog = await api.catalog();
1027
- const map = new Map(catalog.skills.map((skill) => [skill.name, skill.invocation.modelInvocable]));
1028
- modelCache = {
1029
- at: now,
1030
- map
1031
- };
1032
- return map;
1033
- } catch (error) {
1034
- console.error("[dsh-skill-hub] slash dot color lookup failed:", error);
1035
- modelCache = {
1036
- at: now,
1037
- failed: true
1038
- };
1039
- return /* @__PURE__ */ new Map();
1040
- }
1041
- }
1042
- /**
1043
- * One menu-row dot element. Inline span so it needs no CSS module; the
1044
- * candidate menu centers it inside its 16×16 leading icon slot.
1045
- * @param color - the dot's background color.
1046
- * @returns a React node (memory-only, never crosses the Host boundary).
1047
- */
1048
- function dotIcon(color) {
1049
- return (0, react.createElement)("span", {
1050
- "aria-hidden": true,
1051
- style: {
1052
- display: "inline-block",
1053
- width: 6,
1054
- height: 6,
1055
- borderRadius: 3,
1056
- background: color,
1057
- flex: "none"
1058
- }
1059
- });
1060
- }
1061
- /**
1062
- * Wrap the core skill source so every menu row carries the invocation dot.
1063
- * Exported for unit tests; production wiring goes through setupSkillSlashDots.
1064
- * @param source - the registered `/skill` source.
1065
- * @param api - hub browser API for the modelInvocable lookup.
1066
- * @param scope - hub settings scope for the dot colors.
1067
- * @returns a disposer restoring the original candidates.
1068
- */
1069
- /**
1070
- * DOM 兜底:在 alpha.2 新版 MenuView(icon 仅枚举)下通过直接操作
1071
- * 已渲染的 `[role="option"]` 列表注入彩色点,绕过 `icon` 限制。
1072
- * 旧版仍走 `icon` 注入,此处仅为新版。
1073
- */
1074
- function injectDotsViaDOM(modelByName, modelColor, userColor) {
1075
- if (typeof document === "undefined" || typeof requestAnimationFrame === "undefined") return;
1076
- const run = () => {
1077
- const listbox = document.querySelector("[class*=\"_3e4SsG_menu\"]")?.querySelector("[role=\"listbox\"]") ?? document.querySelector("[role=\"listbox\"]");
1078
- if (listbox === null) return;
1079
- const options = listbox.querySelectorAll("[role=\"option\"]");
1080
- for (const opt of options) {
1081
- if (opt.querySelector("[data-skill-dot]") !== null) continue;
1082
- const nameEl = opt.querySelector("[class*=\"itemName\"]");
1083
- if (nameEl === null) continue;
1084
- const name = (nameEl.textContent ?? "").trim();
1085
- if (name.length === 0) continue;
1086
- if (!modelByName.has(name)) continue;
1087
- const color = modelByName.get(name) ?? true ? modelColor : userColor;
1088
- const wrapper = document.createElement("span");
1089
- wrapper.setAttribute("data-skill-dot", "");
1090
- wrapper.setAttribute("aria-hidden", "true");
1091
- wrapper.style.width = "16px";
1092
- wrapper.style.height = "16px";
1093
- wrapper.style.display = "inline-flex";
1094
- wrapper.style.justifyContent = "center";
1095
- wrapper.style.alignItems = "center";
1096
- wrapper.style.flex = "none";
1097
- const dot = document.createElement("span");
1098
- dot.style.display = "inline-block";
1099
- dot.style.width = "6px";
1100
- dot.style.height = "6px";
1101
- dot.style.borderRadius = "3px";
1102
- dot.style.background = color;
1103
- dot.style.flex = "none";
1104
- wrapper.appendChild(dot);
1105
- nameEl.parentElement?.insertBefore(wrapper, nameEl);
1106
- }
1107
- };
1108
- requestAnimationFrame(() => setTimeout(run, 0));
1109
- }
1110
- function wrapSkillSource(source, api, scope) {
1111
- const original = source.candidates;
1112
- source.candidates = async (session, req) => {
1113
- const items = await original(session, req);
1114
- if (req.signal.aborted) return items;
1115
- const isNewHost = req !== null && typeof req === "object" && "drilled" in req;
1116
- const modelByName = await modelInvocableMap(api);
1117
- if (req.signal.aborted) return items;
1118
- const snapshot = scope.getSnapshot();
1119
- const modelColor = snapshot.value?.dotModelColor ?? "#2f81f7";
1120
- const userColor = snapshot.value?.dotUserColor ?? "#3fb950";
1121
- if (isNewHost) {
1122
- injectDotsViaDOM(modelByName, modelColor, userColor);
1123
- return items;
1124
- }
1125
- return items.map((item) => ({
1126
- ...item,
1127
- icon: dotIcon(modelByName.get(item.name) ?? true ? modelColor : userColor)
1128
- }));
1129
- };
1130
- return () => {
1131
- source.candidates = original;
1132
- };
1133
- }
1134
- /**
1135
- * Mount the slash-menu dots on the registered `/skill` source. Idempotent and
1136
- * defensive: if the core source isn't registered yet (or the registry shape
1137
- * changes), it retries briefly and then gives up silently — the chat keeps
1138
- * working, it simply shows no dots. The returned disposer restores the
1139
- * original candidates and clears the model cache.
1140
- * @param ctx - the client root context (inputTriggers + events).
1141
- * @param api - hub browser API.
1142
- * @param scope - hub settings scope for dot colors.
1143
- * @returns a cleanup function for `ctx.effect`.
1144
- */
1145
- function setupSkillSlashDots(ctx, api, scope) {
1146
- const inputTriggers = ctx.get("inputTriggers");
1147
- if (inputTriggers === void 0) return () => {};
1148
- let disposed = false;
1149
- let restore;
1150
- let attempts = 0;
1151
- const attempt = () => {
1152
- if (disposed) return;
1153
- const source = findSkillSource(inputTriggers);
1154
- if (source === void 0) {
1155
- if (attempts < 10) {
1156
- attempts += 1;
1157
- setTimeout(attempt, 100);
1158
- }
1159
- return;
1160
- }
1161
- restore = wrapSkillSource(source, api, scope);
1162
- };
1163
- attempt();
1164
- const clearCache = () => {
1165
- resetModelCache();
1166
- };
1167
- const offReset = ctx.on("connection/reset", clearCache);
1168
- return () => {
1169
- disposed = true;
1170
- offReset();
1171
- restore?.();
1172
- restore = void 0;
1173
- };
1174
- }
1175
- //#endregion
1176
849
  //#region src/client/icons.tsx
1177
850
  /** ic_ds_chevron_down_outline_14 */
1178
851
  function IconChevronDownOutline14({ size = 14, className }) {
@@ -2440,6 +2113,141 @@ window.__ModuleLoader__.load({
2440
2113
  }
2441
2114
  };
2442
2115
  //#endregion
2116
+ //#region src/client/grouping.ts
2117
+ /**
2118
+ * Derive a group switch view from its member names and the set of currently
2119
+ * enabled skill names (catalog.skills). Members not in the enabled set count
2120
+ * as disabled (catalog.disabled or absent read-only rows).
2121
+ */
2122
+ function groupSwitchView(members, enabledNames) {
2123
+ const enabled = [];
2124
+ const disabled = [];
2125
+ for (const name of members) if (enabledNames.has(name)) enabled.push(name);
2126
+ else disabled.push(name);
2127
+ return {
2128
+ state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
2129
+ enabled,
2130
+ disabled
2131
+ };
2132
+ }
2133
+ /** Names of every group a skill belongs to (tags + collections). */
2134
+ function groupNamesOf(name, tags, collections) {
2135
+ const names = [];
2136
+ for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
2137
+ for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
2138
+ return names;
2139
+ }
2140
+ /**
2141
+ * Members of a group that are currently enabled AND also belong to at least
2142
+ * one other group — the set a "close" action must ask about.
2143
+ */
2144
+ function conflictsOnClose(members, enabledNames, otherGroups) {
2145
+ return members.filter((name) => {
2146
+ if (!enabledNames.has(name)) return false;
2147
+ return otherGroups.some((group) => group.members.includes(name));
2148
+ });
2149
+ }
2150
+ /** Origin-repo filter value: skills with no source record (private skills). */
2151
+ const PRIVATE_SOURCE = "private";
2152
+ /**
2153
+ * Apply the origin filter ('all' or a specific origin repo; skills without a
2154
+ * source record count as PRIVATE_SOURCE). The origins map is the store's
2155
+ * skillName → repo derivation, so filtering follows the tracked source
2156
+ * records instead of the filesystem root a skill happens to live under.
2157
+ * 项目级技能(有 workspace 归属)永远不算「个人」。
2158
+ */
2159
+ function filterBySource(skills, source, origins) {
2160
+ if (source === "all") return [...skills];
2161
+ return skills.filter((skill) => {
2162
+ if (isProjectSource(skill.source)) return false;
2163
+ return (origins[skill.name] ?? "private") === source;
2164
+ });
2165
+ }
2166
+ /**
2167
+ * Sort a skill list in place-safe copy order: name ascending, added
2168
+ * descending (newest first, unknown addedAt last), or uses descending
2169
+ * (most-called first). Unknown values always trail.
2170
+ */
2171
+ function sortSkills(skills, key, getUses) {
2172
+ const list = [...skills];
2173
+ if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
2174
+ else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
2175
+ else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
2176
+ return list;
2177
+ }
2178
+ /** Format an epoch-ms timestamp as a short relative time bucket. */
2179
+ function formatRelativeTime(ms, now = Date.now()) {
2180
+ const diff = Math.max(0, now - ms);
2181
+ const minutes = Math.floor(diff / 6e4);
2182
+ if (minutes < 1) return { key: "time.justNow" };
2183
+ if (minutes < 60) return {
2184
+ key: "time.minutesAgo",
2185
+ value: minutes
2186
+ };
2187
+ const hours = Math.floor(minutes / 60);
2188
+ if (hours < 24) return {
2189
+ key: "time.hoursAgo",
2190
+ value: hours
2191
+ };
2192
+ const days = Math.floor(hours / 24);
2193
+ if (days < 7) return {
2194
+ key: "time.daysAgo",
2195
+ value: days
2196
+ };
2197
+ return {
2198
+ key: "time.weeksAgo",
2199
+ value: Math.floor(days / 7)
2200
+ };
2201
+ }
2202
+ //#endregion
2203
+ //#region src/client/helpers.ts
2204
+ /**
2205
+ * Shared panel helpers: the active-dictionary pick (document-language
2206
+ * based, family precedent) plus a small error-message extractor.
2207
+ */
2208
+ /** Active dictionary, picked by the document language at call time. */
2209
+ function dictionary() {
2210
+ return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
2211
+ }
2212
+ /** Translate a key with optional {name} template params (current language). */
2213
+ function tt(key, values) {
2214
+ return t(dictionary(), key, values);
2215
+ }
2216
+ /** Human-readable error text from an unknown thrown value. */
2217
+ function errorMessage(error) {
2218
+ if (error instanceof Error) return error.message;
2219
+ return String(error);
2220
+ }
2221
+ //#endregion
2222
+ //#region src/client/panel/format.ts
2223
+ /** Model-invocable dot color default. Single source for the TS side; the
2224
+ * panel's CSS mirrors it via --hub-model (panel.module.css). */
2225
+ const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
2226
+ /** User-invocable dot color default. Single source for the TS side; the
2227
+ * panel's CSS mirrors it via --hub-user (panel.module.css). */
2228
+ const DEFAULT_DOT_USER_COLOR = "#3fb950";
2229
+ /** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
2230
+ function dotStyle(color) {
2231
+ if (color === void 0) return void 0;
2232
+ return {
2233
+ background: color,
2234
+ borderColor: color
2235
+ };
2236
+ }
2237
+ /** Localized relative-time text for a Unix-ms timestamp. */
2238
+ function relativeTimeText(ms) {
2239
+ const rt = formatRelativeTime(ms);
2240
+ return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
2241
+ }
2242
+ /** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
2243
+ function formatDateTime(ms) {
2244
+ return new Date(ms).toLocaleString();
2245
+ }
2246
+ /** Short form of a commit SHA for display. */
2247
+ function shortSha(sha) {
2248
+ return sha.length > 7 ? sha.slice(0, 7) : sha;
2249
+ }
2250
+ //#endregion
2443
2251
  //#region src/client/SkillHubSettingsCard.tsx
2444
2252
  /**
2445
2253
  * The dsh-skill-hub plugin settings card: bridges the hub's settings
@@ -6525,8 +6333,7 @@ window.__ModuleLoader__.load({
6525
6333
  "locale",
6526
6334
  "connection",
6527
6335
  "remote",
6528
- "settingsScope",
6529
- "inputTriggers"
6336
+ "settingsScope"
6530
6337
  ];
6531
6338
  /**
6532
6339
  * Mount the settings card and the skill hub section.
@@ -6540,7 +6347,6 @@ window.__ModuleLoader__.load({
6540
6347
  const t = ctx.locale.bind(NS);
6541
6348
  const api = new SkillHubApi();
6542
6349
  const settingsCard = new SkillHubSettingsCardController(ctx.settingsScope.bind({ namespace: NS }));
6543
- ctx.effect(() => setupSkillSlashDots(ctx, api, ctx.settingsScope.bind({ namespace: NS })), "dsh-skill-hub: slash dots");
6544
6350
  ctx.effect(() => ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
6545
6351
  name: "settings.plugin.item",
6546
6352
  key: NS,