dsh-skill-importer 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,8 +5,8 @@ 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 react_jsx_runtime = require("react/jsx-runtime");
9
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
10
  //#region src/frontmatter.ts
11
11
  /** Kebab-case name rule shared with `dsh-skill` (`^[a-z0-9]+(?:-[a-z0-9]+)*$`). */
12
12
  const KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
@@ -225,14 +225,24 @@ window.__ModuleLoader__.load({
225
225
  });
226
226
  }
227
227
  /** The settings section's page body. */
228
- function SkillImporterSection({ t, useSkills, useWorkspaces, actions }) {
228
+ function SkillImporterSection({ t, useSkills, useSkillIssues, useWorkspaces, actions }) {
229
229
  const skills = useSkills((value) => value);
230
+ const skillIssues = useSkillIssues((value) => value);
230
231
  const workspaces = useWorkspaces((value) => value);
231
- const workspace = (0, react.useMemo)(() => workspaces.items.find((item) => item.workspaceId === workspaces.recentWorkspaceId) ?? workspaces.items[0], [workspaces.items, workspaces.recentWorkspaceId]);
232
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = (0, react.useState)(() => workspaces.recentWorkspaceId ?? workspaces.items[0]?.workspaceId);
233
+ (0, react.useEffect)(() => {
234
+ if (selectedWorkspaceId !== void 0 && workspaces.items.some((item) => item.workspaceId === selectedWorkspaceId)) return;
235
+ setSelectedWorkspaceId(workspaces.recentWorkspaceId ?? workspaces.items[0]?.workspaceId);
236
+ }, [
237
+ selectedWorkspaceId,
238
+ workspaces.items,
239
+ workspaces.recentWorkspaceId
240
+ ]);
241
+ const workspace = (0, react.useMemo)(() => workspaces.items.find((item) => item.workspaceId === selectedWorkspaceId), [selectedWorkspaceId, workspaces.items]);
232
242
  const [listState, setListState] = (0, react.useState)("idle");
233
243
  const [listError, setListError] = (0, react.useState)();
234
244
  const [listEpoch, setListEpoch] = (0, react.useState)(0);
235
- const [collapsedSources, setCollapsedSources] = (0, react.useState)(() => /* @__PURE__ */ new Set());
245
+ const [expandedGroups, setExpandedGroups] = (0, react.useState)(() => /* @__PURE__ */ new Set());
236
246
  (0, react.useEffect)(() => {
237
247
  let cancelled = false;
238
248
  setListState("loading");
@@ -249,7 +259,10 @@ window.__ModuleLoader__.load({
249
259
  };
250
260
  }, [listEpoch, actions]);
251
261
  const [mode, setMode] = (0, react.useState)("file");
252
- const [target, setTarget] = (0, react.useState)("project-agents");
262
+ const [target, setTarget] = (0, react.useState)("project-dsh");
263
+ (0, react.useEffect)(() => {
264
+ if (workspaces.items.length === 0 && target.startsWith("project-")) setTarget("user-dsh");
265
+ }, [target, workspaces.items.length]);
253
266
  const [fileDraft, setFileDraft] = (0, react.useState)();
254
267
  const [fileError, setFileError] = (0, react.useState)();
255
268
  const [url, setUrl] = (0, react.useState)("");
@@ -261,7 +274,42 @@ window.__ModuleLoader__.load({
261
274
  const [batchScan, setBatchScan] = (0, react.useState)();
262
275
  const [batchResults, setBatchResults] = (0, react.useState)();
263
276
  const [replaceNames, setReplaceNames] = (0, react.useState)(() => /* @__PURE__ */ new Set());
277
+ const [pendingDelete, setPendingDelete] = (0, react.useState)();
278
+ const [locationOpen, setLocationOpen] = (0, react.useState)(false);
279
+ const [workspaceQuery, setWorkspaceQuery] = (0, react.useState)("");
280
+ const [draftTarget, setDraftTarget] = (0, react.useState)("project-dsh");
281
+ const [draftWorkspaceId, setDraftWorkspaceId] = (0, react.useState)();
282
+ const [updateState, setUpdateState] = (0, react.useState)("checking");
283
+ const [updateStatus, setUpdateStatus] = (0, react.useState)();
284
+ const [updateDialogOpen, setUpdateDialogOpen] = (0, react.useState)(false);
285
+ const [commandCopied, setCommandCopied] = (0, react.useState)(false);
286
+ const [batchConfirmation, setBatchConfirmation] = (0, react.useState)();
264
287
  const fileInput = (0, react.useRef)(null);
288
+ const checkForUpdates = () => {
289
+ setUpdateState("checking");
290
+ actions.checkForUpdates().then((status) => {
291
+ setUpdateStatus(status);
292
+ setUpdateState(status.error === void 0 ? "ready" : "error");
293
+ }, () => setUpdateState("error"));
294
+ };
295
+ (0, react.useEffect)(() => {
296
+ let cancelled = false;
297
+ actions.checkForUpdates().then((status) => {
298
+ if (cancelled) return;
299
+ setUpdateStatus(status);
300
+ setUpdateState(status.error === void 0 ? "ready" : "error");
301
+ }, () => {
302
+ if (!cancelled) setUpdateState("error");
303
+ });
304
+ return () => {
305
+ cancelled = true;
306
+ };
307
+ }, [actions]);
308
+ const copyUpdateCommand = async () => {
309
+ if (updateStatus?.command === void 0) return;
310
+ await navigator.clipboard.writeText(updateStatus.command);
311
+ setCommandCopied(true);
312
+ };
265
313
  const onFilePicked = async (file) => {
266
314
  setFileDraft(void 0);
267
315
  setFileError(void 0);
@@ -302,8 +350,9 @@ window.__ModuleLoader__.load({
302
350
  }, () => pollForSkill(name, attemptsLeft - 1));
303
351
  }, 2e3);
304
352
  };
305
- const removeSkill = async (skill) => {
306
- if (!window.confirm(t("deleteConfirm", { name: skill.name }))) return;
353
+ const removeSkill = async () => {
354
+ if (pendingDelete === void 0) return;
355
+ const skill = pendingDelete;
307
356
  setBusy(true);
308
357
  setMessage(void 0);
309
358
  setMessageError(false);
@@ -311,8 +360,11 @@ window.__ModuleLoader__.load({
311
360
  if (await actions.deleteSkill({
312
361
  name: skill.name,
313
362
  source: skill.source,
314
- workspacePath: workspace?.path
315
- })) setMessage(t("deleted"));
363
+ workspacePath: skill.workspacePath
364
+ })) {
365
+ setMessage(t("deleted"));
366
+ setPendingDelete(void 0);
367
+ }
316
368
  setListEpoch((epoch) => epoch + 1);
317
369
  } catch (error) {
318
370
  setMessageError(true);
@@ -403,17 +455,20 @@ window.__ModuleLoader__.load({
403
455
  setBusy(false);
404
456
  }
405
457
  };
406
- const commitBatchDirectory = async () => {
458
+ const requestBatchCommit = () => {
407
459
  if (batchScan === void 0) return;
408
460
  const ready = batchScan.entries.filter((entry) => entry.status === "ready");
409
461
  const replacing = ready.filter((entry) => replaceNames.has(entry.name)).length;
410
462
  const importing = ready.filter((entry) => !entry.conflict).length;
411
463
  const skipped = ready.filter((entry) => entry.conflict && !replaceNames.has(entry.name)).length;
412
- if (!window.confirm(t("batchConfirm", {
464
+ setBatchConfirmation({
413
465
  importing,
414
466
  replacing,
415
467
  skipped
416
- }))) return;
468
+ });
469
+ };
470
+ const commitBatchDirectory = async () => {
471
+ if (batchScan === void 0 || batchConfirmation === void 0) return;
417
472
  setBusy(true);
418
473
  setMessage(void 0);
419
474
  setMessageError(false);
@@ -425,6 +480,7 @@ window.__ModuleLoader__.load({
425
480
  setBatchResults(result.results);
426
481
  setBatchScan(void 0);
427
482
  setReplaceNames(/* @__PURE__ */ new Set());
483
+ setBatchConfirmation(void 0);
428
484
  setListEpoch((epoch) => epoch + 1);
429
485
  } catch (error) {
430
486
  setMessageError(true);
@@ -444,27 +500,57 @@ window.__ModuleLoader__.load({
444
500
  setTarget(next);
445
501
  if (mode === "batch") resetBatchPreflight();
446
502
  };
447
- const canSubmit = busy || mode === "file" && fileDraft === void 0 || mode === "url" && (url.trim().length === 0 || !/^https?:\/\//i.test(url.trim())) || mode === "batch" && batchPath === void 0 || target !== "user" && workspace === void 0;
503
+ const chooseDestination = (nextTarget, nextWorkspaceId) => {
504
+ if (nextWorkspaceId !== void 0) setSelectedWorkspaceId(nextWorkspaceId);
505
+ changeTarget(nextTarget);
506
+ setLocationOpen(false);
507
+ };
508
+ const locationSummary = target === "project-dsh" ? t("locationSummaryProjectDsh", { title: workspace?.title ?? "" }) : target === "project-agents" ? t("locationSummaryProjectAgents", { title: workspace?.title ?? "" }) : target === "user-dsh" ? t("locationSummaryUserDsh") : t("locationSummaryUserAgents");
509
+ const filteredWorkspaces = workspaces.items.filter((item) => {
510
+ const query = workspaceQuery.trim().toLowerCase();
511
+ return query.length === 0 || item.title.toLowerCase().includes(query) || item.path.toLowerCase().includes(query);
512
+ });
513
+ const canSubmit = busy || mode === "file" && fileDraft === void 0 || mode === "url" && (url.trim().length === 0 || !/^https?:\/\//i.test(url.trim())) || mode === "batch" && batchPath === void 0 || target.startsWith("project-") && workspace === void 0;
448
514
  const runPrimaryAction = () => {
449
515
  if (mode !== "batch") {
450
516
  submit();
451
517
  return;
452
518
  }
453
519
  if (batchScan === void 0) scanBatchDirectory();
454
- else commitBatchDirectory();
520
+ else requestBatchCommit();
455
521
  };
456
522
  const primaryLabel = mode !== "batch" ? busy ? t("importing") : t("import") : busy ? t("batchWorking") : batchScan === void 0 ? t("batchScan") : t("batchCommit");
457
523
  const flags = (skill) => {
458
524
  if (!skill.modelInvocable && skill.userInvocable) return t("userOnly");
459
525
  if (skill.modelInvocable && !skill.userInvocable) return t("modelOnly");
460
526
  };
461
- const toggleSource = (source) => {
462
- setCollapsedSources((current) => {
527
+ const toggleGroup = (groupKey) => {
528
+ setExpandedGroups((current) => {
463
529
  const next = new Set(current);
464
- if (!next.delete(source)) next.add(source);
530
+ if (!next.delete(groupKey)) next.add(groupKey);
465
531
  return next;
466
532
  });
467
533
  };
534
+ const skillGroups = [
535
+ ...workspaces.items.flatMap((item) => ["project-dsh", "project-agents"].map((source) => ({
536
+ key: `${item.workspaceId}:${source}`,
537
+ source,
538
+ workspacePath: item.path,
539
+ name: source === "project-dsh" ? t("targetProjectDsh", { title: item.title }) : t("targetProjectAgents", { title: item.title })
540
+ }))),
541
+ {
542
+ key: "user-dsh",
543
+ source: "user-dsh",
544
+ workspacePath: void 0,
545
+ name: t("targetUserDsh")
546
+ },
547
+ {
548
+ key: "user-agents",
549
+ source: "user-agents",
550
+ workspacePath: void 0,
551
+ name: t("targetUserAgents")
552
+ }
553
+ ];
468
554
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
469
555
  style: {
470
556
  display: "flex",
@@ -519,23 +605,33 @@ window.__ModuleLoader__.load({
519
605
  listError
520
606
  ]
521
607
  }) : null,
608
+ listState !== "error" && skillIssues.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
609
+ style: {
610
+ ...descriptionStyle,
611
+ color: "var(--dsw-alias-state-warn-label)"
612
+ },
613
+ children: [
614
+ t("installedPartial", { count: skillIssues.length }),
615
+ " ",
616
+ skillIssues[0]?.message
617
+ ]
618
+ }) : null,
522
619
  listState === "idle" && skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
523
620
  style: descriptionStyle,
524
621
  children: t("installedEmpty")
525
622
  }) : null,
526
623
  skills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
527
624
  style: skillGroupsStyle,
528
- children: ["project-agents", "user"].map((source) => {
529
- const rows = skills.filter((skill) => skill.source === source);
625
+ children: skillGroups.map((group) => {
626
+ const rows = skills.filter((skill) => skill.source === group.source && (!group.source.startsWith("project-") || skill.workspacePath === group.workspacePath));
530
627
  if (rows.length === 0) return null;
531
- const sourceName = source === "user" ? t("targetUser") : workspace === void 0 ? t("targetProjectNoWorkspace") : t("targetProjectAgents", { title: workspace.title });
532
- const open = !collapsedSources.has(source);
628
+ const open = expandedGroups.has(group.key);
533
629
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
534
630
  style: skillGroupCardStyle,
535
631
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
536
632
  type: "button",
537
633
  "aria-expanded": open,
538
- onClick: () => toggleSource(source),
634
+ onClick: () => toggleGroup(group.key),
539
635
  style: {
540
636
  ...skillGroupHeaderStyle,
541
637
  borderBottom: open ? skillGroupHeaderStyle.borderBottom : "none"
@@ -549,7 +645,7 @@ window.__ModuleLoader__.load({
549
645
  style: skillGroupIdentityStyle,
550
646
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
551
647
  style: skillGroupNameStyle,
552
- children: sourceName
648
+ children: group.name
553
649
  })
554
650
  }),
555
651
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -592,17 +688,15 @@ window.__ModuleLoader__.load({
592
688
  type: "button",
593
689
  "aria-label": `${t("delete")} ${skill.name}`,
594
690
  title: t("delete"),
595
- onClick: () => {
596
- removeSkill(skill);
597
- },
691
+ onClick: () => setPendingDelete(skill),
598
692
  style: deleteIconButtonStyle,
599
693
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, {})
600
694
  })
601
695
  ]
602
- }, skill.source + ":" + skill.name);
696
+ }, skill.source + ":" + (skill.workspacePath ?? "") + ":" + skill.name);
603
697
  })
604
698
  }) : null]
605
- }, source);
699
+ }, group.key);
606
700
  })
607
701
  }) : null
608
702
  ]
@@ -1006,17 +1100,28 @@ window.__ModuleLoader__.load({
1006
1100
  style: fieldLabelStyle,
1007
1101
  children: t("targetLabel")
1008
1102
  }),
1009
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1010
- value: target,
1011
- onChange: (event) => changeTarget(event.target.value),
1012
- style: selectStyle,
1013
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1014
- value: "project-agents",
1015
- disabled: workspace === void 0,
1016
- children: workspace === void 0 ? t("targetProjectNoWorkspace") : t("targetProjectAgents", { title: workspace.title })
1017
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1018
- value: "user",
1019
- children: t("targetUser")
1103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1104
+ type: "button",
1105
+ onClick: () => {
1106
+ setWorkspaceQuery("");
1107
+ setDraftTarget(target);
1108
+ setDraftWorkspaceId(selectedWorkspaceId);
1109
+ setLocationOpen(true);
1110
+ },
1111
+ style: locationSummaryStyle,
1112
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1113
+ style: {
1114
+ minWidth: 0,
1115
+ flex: 1,
1116
+ overflow: "hidden",
1117
+ textOverflow: "ellipsis",
1118
+ whiteSpace: "nowrap",
1119
+ textAlign: "left"
1120
+ },
1121
+ children: locationSummary
1122
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1123
+ style: secondaryButtonStyle,
1124
+ children: t("locationChange")
1020
1125
  })]
1021
1126
  }),
1022
1127
  workspace === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1048,6 +1153,263 @@ window.__ModuleLoader__.load({
1048
1153
  })
1049
1154
  ]
1050
1155
  })
1156
+ }),
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1158
+ style: versionFooterStyle,
1159
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1160
+ style: versionCopyStyle,
1161
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1162
+ style: versionLabelStyle,
1163
+ children: updateStatus === void 0 ? t("versionChecking") : t("versionCurrent", { version: updateStatus.currentVersion })
1164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1165
+ style: descriptionStyle,
1166
+ children: updateState === "checking" ? t("versionChecking") : updateState === "error" ? t("versionCheckFailed") : updateStatus?.updateAvailable === true ? t("versionAvailable", { version: updateStatus.latestVersion ?? "" }) : t("versionLatest")
1167
+ })]
1168
+ }), updateState === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1169
+ type: "button",
1170
+ onClick: checkForUpdates,
1171
+ style: pillStyle,
1172
+ children: t("versionRetry")
1173
+ }) : updateStatus?.updateAvailable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1174
+ type: "button",
1175
+ onClick: () => {
1176
+ setCommandCopied(false);
1177
+ setUpdateDialogOpen(true);
1178
+ },
1179
+ style: updateButtonStyle,
1180
+ children: t("versionUpdate")
1181
+ }) : null]
1182
+ }),
1183
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1184
+ open: locationOpen,
1185
+ onClose: () => setLocationOpen(false),
1186
+ title: t("locationTitle"),
1187
+ closeLabel: t("close"),
1188
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1189
+ variant: "outline",
1190
+ onClick: () => setLocationOpen(false),
1191
+ children: t("cancel")
1192
+ }),
1193
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1194
+ style: locationPanelStyle,
1195
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1196
+ style: locationScopeStyle,
1197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1198
+ type: "button",
1199
+ onClick: () => {
1200
+ if (!draftTarget.startsWith("project-")) setDraftTarget("project-dsh");
1201
+ },
1202
+ style: {
1203
+ ...locationScopeButtonStyle,
1204
+ ...draftTarget.startsWith("project-") ? locationScopeButtonActiveStyle : {}
1205
+ },
1206
+ children: t("locationProject")
1207
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1208
+ type: "button",
1209
+ onClick: () => {
1210
+ if (draftTarget.startsWith("project-")) setDraftTarget("user-dsh");
1211
+ },
1212
+ style: {
1213
+ ...locationScopeButtonStyle,
1214
+ ...!draftTarget.startsWith("project-") ? locationScopeButtonActiveStyle : {}
1215
+ },
1216
+ children: t("locationGlobal")
1217
+ })]
1218
+ }), draftTarget.startsWith("project-") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1219
+ type: "search",
1220
+ value: workspaceQuery,
1221
+ onChange: (event) => setWorkspaceQuery(event.target.value),
1222
+ placeholder: t("workspaceSearch"),
1223
+ style: controlStyle
1224
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1225
+ style: locationWorkspaceListStyle,
1226
+ children: [filteredWorkspaces.map((item) => {
1227
+ const selected = item.workspaceId === draftWorkspaceId;
1228
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1229
+ style: {
1230
+ ...locationWorkspaceGroupStyle,
1231
+ ...selected ? locationWorkspaceActiveStyle : {}
1232
+ },
1233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1234
+ type: "button",
1235
+ onClick: (event) => {
1236
+ const group = event.currentTarget.parentElement;
1237
+ setDraftWorkspaceId(selected ? void 0 : item.workspaceId);
1238
+ if (!selected) window.requestAnimationFrame(() => group?.scrollIntoView({
1239
+ block: "nearest",
1240
+ behavior: "smooth"
1241
+ }));
1242
+ },
1243
+ style: locationWorkspaceStyle,
1244
+ "aria-expanded": selected,
1245
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1246
+ style: {
1247
+ minWidth: 0,
1248
+ flex: 1,
1249
+ display: "flex",
1250
+ flexDirection: "column",
1251
+ gap: 2
1252
+ },
1253
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1254
+ style: skillNameStyle,
1255
+ children: item.title
1256
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1257
+ style: skillDescriptionStyle,
1258
+ children: item.path
1259
+ })]
1260
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1261
+ style: skillGroupChevronStyle,
1262
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronIcon, { open: selected })
1263
+ })]
1264
+ }), selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1265
+ style: locationExpandedRootsStyle,
1266
+ children: [
1267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1268
+ style: locationRootHintStyle,
1269
+ children: t("chooseRootHint")
1270
+ }),
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1272
+ type: "button",
1273
+ onClick: () => chooseDestination("project-dsh", item.workspaceId),
1274
+ style: locationOptionStyle,
1275
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1276
+ style: locationOptionCopyStyle,
1277
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("rootProjectDsh") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
1278
+ style: descriptionStyle,
1279
+ children: ".dsh/skills"
1280
+ })]
1281
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1282
+ style: priorityHighStyle,
1283
+ children: t("priorityHighest")
1284
+ })]
1285
+ }),
1286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1287
+ type: "button",
1288
+ onClick: () => chooseDestination("project-agents", item.workspaceId),
1289
+ style: locationOptionStyle,
1290
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1291
+ style: locationOptionCopyStyle,
1292
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("rootProjectAgents") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
1293
+ style: descriptionStyle,
1294
+ children: ".agents/skills"
1295
+ })]
1296
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1297
+ style: priorityMediumStyle,
1298
+ children: t("priorityHigher")
1299
+ })]
1300
+ })
1301
+ ]
1302
+ }) : null]
1303
+ }, item.workspaceId);
1304
+ }), filteredWorkspaces.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1305
+ style: descriptionStyle,
1306
+ children: t("workspaceNoMatch")
1307
+ }) : null]
1308
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1309
+ style: locationRootsStyle,
1310
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1311
+ type: "button",
1312
+ onClick: () => chooseDestination("user-dsh"),
1313
+ style: locationOptionStyle,
1314
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1315
+ style: locationOptionCopyStyle,
1316
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("rootUserDsh") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
1317
+ style: descriptionStyle,
1318
+ children: "~/.dsh/skills"
1319
+ })]
1320
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1321
+ style: priorityLowStyle,
1322
+ children: t("priorityLower")
1323
+ })]
1324
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1325
+ type: "button",
1326
+ onClick: () => chooseDestination("user-agents"),
1327
+ style: locationOptionStyle,
1328
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1329
+ style: locationOptionCopyStyle,
1330
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("rootUserAgents") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
1331
+ style: descriptionStyle,
1332
+ children: "~/.agents/skills"
1333
+ })]
1334
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1335
+ style: priorityLowStyle,
1336
+ children: t("priorityLowest")
1337
+ })]
1338
+ })]
1339
+ })]
1340
+ })
1341
+ }),
1342
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1343
+ open: pendingDelete !== void 0,
1344
+ onClose: () => {
1345
+ if (!busy) setPendingDelete(void 0);
1346
+ },
1347
+ title: t("deleteTitle"),
1348
+ closeLabel: t("close"),
1349
+ description: pendingDelete === void 0 ? void 0 : t("deleteConfirm", { name: pendingDelete.name }),
1350
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1351
+ variant: "outline",
1352
+ disabled: busy,
1353
+ onClick: () => setPendingDelete(void 0),
1354
+ children: t("cancel")
1355
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1356
+ variant: "primary",
1357
+ disabled: busy,
1358
+ onClick: () => {
1359
+ removeSkill();
1360
+ },
1361
+ children: busy ? t("deleting") : t("delete")
1362
+ })] })
1363
+ }),
1364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1365
+ open: batchConfirmation !== void 0,
1366
+ onClose: () => {
1367
+ if (!busy) setBatchConfirmation(void 0);
1368
+ },
1369
+ title: t("batchConfirmTitle"),
1370
+ closeLabel: t("close"),
1371
+ description: batchConfirmation === void 0 ? void 0 : t("batchConfirm", batchConfirmation),
1372
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1373
+ variant: "outline",
1374
+ disabled: busy,
1375
+ onClick: () => setBatchConfirmation(void 0),
1376
+ children: t("cancel")
1377
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1378
+ variant: "primary",
1379
+ disabled: busy,
1380
+ onClick: () => {
1381
+ commitBatchDirectory();
1382
+ },
1383
+ children: busy ? t("batchWorking") : t("batchCommit")
1384
+ })] })
1385
+ }),
1386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1387
+ open: updateDialogOpen,
1388
+ onClose: () => setUpdateDialogOpen(false),
1389
+ title: t("updateTitle"),
1390
+ closeLabel: t("close"),
1391
+ description: t("updateDescription"),
1392
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1393
+ variant: "outline",
1394
+ onClick: () => setUpdateDialogOpen(false),
1395
+ children: t("cancel")
1396
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1397
+ variant: "primary",
1398
+ onClick: () => {
1399
+ copyUpdateCommand();
1400
+ },
1401
+ children: commandCopied ? t("commandCopied") : t("copyCommand")
1402
+ })] }),
1403
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1404
+ style: updateDialogBodyStyle,
1405
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1406
+ style: updateCommandStyle,
1407
+ children: updateStatus?.command
1408
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1409
+ style: descriptionStyle,
1410
+ children: t("restartHint")
1411
+ })]
1412
+ })
1051
1413
  })
1052
1414
  ]
1053
1415
  });
@@ -1076,6 +1438,58 @@ window.__ModuleLoader__.load({
1076
1438
  lineHeight: "18px",
1077
1439
  color: "var(--dsw-alias-label-caption)"
1078
1440
  };
1441
+ const versionFooterStyle = {
1442
+ minHeight: 58,
1443
+ padding: "12px 0",
1444
+ display: "flex",
1445
+ alignItems: "center",
1446
+ gap: 12,
1447
+ borderTop: "1px solid var(--dsw-alias-border-l2)"
1448
+ };
1449
+ const versionCopyStyle = {
1450
+ flex: 1,
1451
+ minWidth: 0,
1452
+ display: "flex",
1453
+ flexDirection: "column",
1454
+ gap: 2
1455
+ };
1456
+ const versionLabelStyle = {
1457
+ fontSize: 12,
1458
+ lineHeight: "18px",
1459
+ fontWeight: 500,
1460
+ color: "var(--dsw-alias-label-secondary)"
1461
+ };
1462
+ const updateButtonStyle = {
1463
+ display: "inline-flex",
1464
+ alignItems: "center",
1465
+ height: 28,
1466
+ padding: "0 12px",
1467
+ border: "1px solid var(--dsw-alias-interactive-border-primary)",
1468
+ borderRadius: 14,
1469
+ background: "transparent",
1470
+ font: "inherit",
1471
+ fontSize: 13,
1472
+ borderColor: "var(--dsw-alias-interactive-border-primary)",
1473
+ color: "var(--dsw-alias-interactive-label-primary)",
1474
+ cursor: "pointer"
1475
+ };
1476
+ const updateDialogBodyStyle = {
1477
+ display: "flex",
1478
+ flexDirection: "column",
1479
+ gap: 10
1480
+ };
1481
+ const updateCommandStyle = {
1482
+ display: "block",
1483
+ padding: "10px 12px",
1484
+ border: "1px solid var(--dsw-alias-border-l2)",
1485
+ borderRadius: 8,
1486
+ background: "var(--dsw-alias-bg-module-platform)",
1487
+ color: "var(--dsw-alias-label-primary)",
1488
+ fontSize: 12,
1489
+ lineHeight: "18px",
1490
+ whiteSpace: "pre-wrap",
1491
+ wordBreak: "break-all"
1492
+ };
1079
1493
  const pillStyle = {
1080
1494
  display: "inline-flex",
1081
1495
  alignItems: "center",
@@ -1253,18 +1667,7 @@ window.__ModuleLoader__.load({
1253
1667
  color: "var(--dsw-alias-label-primary)",
1254
1668
  outline: "none"
1255
1669
  };
1256
- /** Select variant: replace the OS arrow with the shared 12px chevron inset
1257
- * (same treatment as the composer chips / models page selects). */
1258
- const selectStyle = {
1259
- ...controlStyle,
1260
- appearance: "none",
1261
- paddingRight: 32,
1262
- backgroundImage: "url(\"data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2712%27 height=%2712%27 viewBox=%270 0 12 12%27 fill=%27none%27%3E%3Cpath d=%27M3 4.5L6 7.5L9 4.5%27 stroke=%27%2381858C%27 stroke-width=%271.5%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27/%3E%3C/svg%3E\")",
1263
- backgroundRepeat: "no-repeat",
1264
- backgroundPosition: "right 12px center",
1265
- backgroundSize: "12px 12px",
1266
- cursor: "pointer"
1267
- };
1670
+ ({ ...controlStyle });
1268
1671
  const entryGridStyle = {
1269
1672
  display: "grid",
1270
1673
  gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
@@ -1365,6 +1768,127 @@ window.__ModuleLoader__.load({
1365
1768
  fontSize: 12,
1366
1769
  lineHeight: "18px"
1367
1770
  };
1771
+ const locationSummaryStyle = {
1772
+ ...controlStyle,
1773
+ display: "flex",
1774
+ alignItems: "center",
1775
+ gap: 10,
1776
+ cursor: "pointer"
1777
+ };
1778
+ const locationPanelStyle = {
1779
+ display: "flex",
1780
+ flexDirection: "column",
1781
+ gap: 14,
1782
+ minWidth: 0
1783
+ };
1784
+ const locationScopeStyle = {
1785
+ display: "grid",
1786
+ gridTemplateColumns: "1fr 1fr",
1787
+ gap: 4,
1788
+ padding: 4,
1789
+ borderRadius: 10,
1790
+ background: "var(--dsw-alias-bg-module-platform)"
1791
+ };
1792
+ const locationScopeButtonStyle = {
1793
+ border: "none",
1794
+ borderRadius: 7,
1795
+ padding: "8px 12px",
1796
+ background: "transparent",
1797
+ color: "var(--dsw-alias-label-secondary)",
1798
+ cursor: "pointer",
1799
+ fontWeight: 500
1800
+ };
1801
+ const locationScopeButtonActiveStyle = {
1802
+ background: "var(--dsw-alias-bg-layer-1)",
1803
+ color: "var(--dsw-alias-label-primary)",
1804
+ boxShadow: "0 1px 3px rgba(0,0,0,.08)"
1805
+ };
1806
+ const locationWorkspaceListStyle = {
1807
+ display: "flex",
1808
+ flexDirection: "column",
1809
+ gap: 4,
1810
+ maxHeight: "min(440px, 52vh)",
1811
+ overflowY: "auto",
1812
+ scrollPadding: "8px 0"
1813
+ };
1814
+ const locationWorkspaceGroupStyle = {
1815
+ overflow: "hidden",
1816
+ borderRadius: 10,
1817
+ transition: "background 140ms ease"
1818
+ };
1819
+ const locationWorkspaceStyle = {
1820
+ width: "100%",
1821
+ display: "flex",
1822
+ alignItems: "center",
1823
+ gap: 8,
1824
+ border: "none",
1825
+ borderRadius: 8,
1826
+ padding: "9px 10px",
1827
+ background: "transparent",
1828
+ cursor: "pointer",
1829
+ textAlign: "left"
1830
+ };
1831
+ const locationWorkspaceActiveStyle = { background: "var(--dsw-alias-bg-module-platform)" };
1832
+ const locationExpandedRootsStyle = {
1833
+ display: "flex",
1834
+ flexDirection: "column",
1835
+ gap: 8,
1836
+ padding: "2px 10px 10px 18px"
1837
+ };
1838
+ const locationRootHintStyle = {
1839
+ fontSize: 11,
1840
+ lineHeight: "16px",
1841
+ fontWeight: 500,
1842
+ color: "var(--dsw-alias-label-tertiary)"
1843
+ };
1844
+ const locationRootsStyle = {
1845
+ display: "flex",
1846
+ flexDirection: "column",
1847
+ gap: 8
1848
+ };
1849
+ const locationOptionStyle = {
1850
+ display: "flex",
1851
+ alignItems: "center",
1852
+ gap: 12,
1853
+ width: "100%",
1854
+ border: "1px solid var(--dsw-alias-border-l2)",
1855
+ borderRadius: 10,
1856
+ padding: "10px 12px",
1857
+ background: "var(--dsw-alias-bg-layer-1)",
1858
+ color: "var(--dsw-alias-label-primary)",
1859
+ cursor: "pointer",
1860
+ textAlign: "left"
1861
+ };
1862
+ const locationOptionCopyStyle = {
1863
+ minWidth: 0,
1864
+ flex: 1,
1865
+ display: "flex",
1866
+ flexDirection: "column",
1867
+ gap: 2
1868
+ };
1869
+ const priorityBaseStyle = {
1870
+ flex: "none",
1871
+ borderRadius: 999,
1872
+ padding: "2px 7px",
1873
+ fontSize: 11,
1874
+ lineHeight: "16px",
1875
+ fontWeight: 600
1876
+ };
1877
+ const priorityHighStyle = {
1878
+ ...priorityBaseStyle,
1879
+ color: "var(--dsw-alias-state-success-primary)",
1880
+ background: "var(--dsw-alias-bg-module-platform)"
1881
+ };
1882
+ const priorityMediumStyle = {
1883
+ ...priorityBaseStyle,
1884
+ color: "var(--dsw-alias-label-primary)",
1885
+ background: "var(--dsw-alias-bg-module-platform)"
1886
+ };
1887
+ const priorityLowStyle = {
1888
+ ...priorityBaseStyle,
1889
+ color: "var(--dsw-alias-label-secondary)",
1890
+ background: "var(--dsw-alias-bg-module-platform)"
1891
+ };
1368
1892
  const previewStyle = {
1369
1893
  display: "flex",
1370
1894
  flexDirection: "column",
@@ -1516,6 +2040,58 @@ window.__ModuleLoader__.load({
1516
2040
  cursor: "default"
1517
2041
  };
1518
2042
  //#endregion
2043
+ //#region src/client/atomicSkillDelete.ts
2044
+ function leadingSkillToken(draft, names) {
2045
+ const ordered = [...new Set(names)].sort((a, b) => b.length - a.length);
2046
+ for (const name of ordered) {
2047
+ if (name.length === 0) continue;
2048
+ const token = `/${name}`;
2049
+ if (!draft.startsWith(token)) continue;
2050
+ const following = draft[token.length];
2051
+ if (following !== void 0 && !/\s/u.test(following)) continue;
2052
+ return {
2053
+ tokenEnd: token.length,
2054
+ boundaryEnd: token.length + (following === " " ? 1 : 0)
2055
+ };
2056
+ }
2057
+ }
2058
+ /**
2059
+ * Delete one exact, leading, installed `/name` token as a single edit.
2060
+ *
2061
+ * This deliberately handles only the common boundary gesture. Selections,
2062
+ * token-internal edits, unknown names, leading whitespace, and non-boundary
2063
+ * carets fall through to the textarea's normal behavior.
2064
+ */
2065
+ function atomicSkillBackspace(draft, caret, names) {
2066
+ if (!Number.isInteger(caret) || caret < 0 || caret > draft.length) return void 0;
2067
+ const token = leadingSkillToken(draft, names);
2068
+ if (token === void 0) return void 0;
2069
+ if (caret !== token.tokenEnd && caret !== token.boundaryEnd) return void 0;
2070
+ return {
2071
+ draft: draft.slice(token.boundaryEnd),
2072
+ caret: 0
2073
+ };
2074
+ }
2075
+ /**
2076
+ * Resolve one whole-token horizontal caret jump.
2077
+ * Left from anywhere inside/just after the leading skill goes before it;
2078
+ * right from before/inside it goes after its single separator.
2079
+ */
2080
+ function atomicSkillArrow(draft, caret, direction, names) {
2081
+ if (!Number.isInteger(caret) || caret < 0 || caret > draft.length) return void 0;
2082
+ const token = leadingSkillToken(draft, names);
2083
+ if (token === void 0) return void 0;
2084
+ if (direction === "left" && caret > 0 && caret <= token.boundaryEnd) return 0;
2085
+ if (direction === "right" && caret >= 0 && caret < token.boundaryEnd) return token.boundaryEnd;
2086
+ }
2087
+ //#endregion
2088
+ //#region src/client/composerFocus.ts
2089
+ const COMPOSER_FOCUS_EVENT = "dsh-skill-importer/composer-focus";
2090
+ /** Ask the mounted picker in one session to restore its composer focus. */
2091
+ function requestComposerFocus(request) {
2092
+ window.dispatchEvent(new CustomEvent(COMPOSER_FOCUS_EVENT, { detail: request }));
2093
+ }
2094
+ //#endregion
1519
2095
  //#region src/client/SkillsPicker.tsx
1520
2096
  /**
1521
2097
  * Composer skill picker: a compact pill trigger in the composer's tool row
@@ -1526,16 +2102,53 @@ window.__ModuleLoader__.load({
1526
2102
  * with `/name ` — the native text gesture — highlighted via the plugin's
1527
2103
  * lexicon contribution.
1528
2104
  */
2105
+ /** Find the one composer textarea owned by the picker nearest in the slot tree. */
2106
+ function composerTextareaFor(root) {
2107
+ let node = root?.parentElement;
2108
+ while (node !== null && node !== void 0) {
2109
+ const textareas = node.querySelectorAll("textarea");
2110
+ if (textareas.length > 0) return textareas.length === 1 ? textareas[0] : void 0;
2111
+ node = node.parentElement;
2112
+ }
2113
+ }
1529
2114
  /** One row of the composer skill picker. */
1530
- function SkillsPicker({ t, useSkills, useInput, inputActions, actions }) {
1531
- const skills = useSkills((value) => value);
2115
+ function SkillsPicker({ t, sessionId, useInput, inputActions, actions }) {
1532
2116
  const input = useInput((value) => value.draft);
2117
+ const [skills, setSkills] = (0, react.useState)([]);
1533
2118
  const [open, setOpen] = (0, react.useState)(false);
2119
+ const [loading, setLoading] = (0, react.useState)(true);
1534
2120
  const [query, setQuery] = (0, react.useState)("");
1535
2121
  const rootRef = (0, react.useRef)(null);
2122
+ const draftRef = (0, react.useRef)(input);
2123
+ const skillNamesRef = (0, react.useRef)([]);
2124
+ const caretFrameRef = (0, react.useRef)();
1536
2125
  (0, react.useEffect)(() => {
1537
- actions.refreshSkills().catch(() => {});
1538
- }, [actions]);
2126
+ let cancelled = false;
2127
+ const refresh = (showLoading) => {
2128
+ if (showLoading) setLoading(true);
2129
+ actions.refreshSkillsForSession(sessionId).then((rows) => {
2130
+ if (!cancelled) {
2131
+ setSkills(rows);
2132
+ setLoading(false);
2133
+ }
2134
+ }, () => {
2135
+ if (!cancelled) {
2136
+ setSkills([]);
2137
+ setLoading(false);
2138
+ }
2139
+ });
2140
+ };
2141
+ refresh(true);
2142
+ const timer = open ? window.setInterval(() => refresh(false), 2e3) : void 0;
2143
+ return () => {
2144
+ cancelled = true;
2145
+ if (timer !== void 0) window.clearInterval(timer);
2146
+ };
2147
+ }, [
2148
+ actions,
2149
+ sessionId,
2150
+ open
2151
+ ]);
1539
2152
  (0, react.useEffect)(() => {
1540
2153
  if (!open) return;
1541
2154
  const onDown = (event) => {
@@ -1548,11 +2161,76 @@ window.__ModuleLoader__.load({
1548
2161
  if (open) setQuery("");
1549
2162
  }, [open]);
1550
2163
  const usable = skills.filter((skill) => skill.userInvocable);
1551
- if (usable.length === 0) return null;
2164
+ draftRef.current = input;
2165
+ skillNamesRef.current = usable.map((skill) => skill.name);
2166
+ const scheduleComposerFocus = (expectedDraft, caret) => {
2167
+ const textarea = composerTextareaFor(rootRef.current);
2168
+ if (caretFrameRef.current !== void 0) window.cancelAnimationFrame(caretFrameRef.current);
2169
+ caretFrameRef.current = window.requestAnimationFrame(() => {
2170
+ caretFrameRef.current = void 0;
2171
+ if (textarea === void 0 || !textarea.isConnected || textarea.value !== expectedDraft) return;
2172
+ textarea.focus({ preventScroll: true });
2173
+ textarea.setSelectionRange(caret, caret);
2174
+ });
2175
+ };
2176
+ (0, react.useEffect)(() => {
2177
+ const onFocusRequest = (event) => {
2178
+ const request = event.detail;
2179
+ if (request.sessionId !== sessionId) return;
2180
+ scheduleComposerFocus(request.expectedDraft, request.caret);
2181
+ };
2182
+ window.addEventListener(COMPOSER_FOCUS_EVENT, onFocusRequest);
2183
+ return () => window.removeEventListener(COMPOSER_FOCUS_EVENT, onFocusRequest);
2184
+ }, [sessionId]);
2185
+ (0, react.useEffect)(() => {
2186
+ const onKeyDown = (event) => {
2187
+ if (![
2188
+ "Backspace",
2189
+ "ArrowLeft",
2190
+ "ArrowRight"
2191
+ ].includes(event.key)) return;
2192
+ if (event.defaultPrevented || event.isComposing || event.keyCode === 229) return;
2193
+ if (event.metaKey || event.ctrlKey || event.altKey) return;
2194
+ if (event.shiftKey && event.key !== "Backspace") return;
2195
+ const textarea = event.target;
2196
+ if (!(textarea instanceof HTMLTextAreaElement) || document.activeElement !== textarea) return;
2197
+ if (composerTextareaFor(rootRef.current) !== textarea || textarea.value !== draftRef.current) return;
2198
+ const start = textarea.selectionStart;
2199
+ const end = textarea.selectionEnd;
2200
+ if (start === null || end === null || start !== end) return;
2201
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
2202
+ const caret = atomicSkillArrow(draftRef.current, start, event.key === "ArrowLeft" ? "left" : "right", skillNamesRef.current);
2203
+ if (caret === void 0) return;
2204
+ event.preventDefault();
2205
+ event.stopPropagation();
2206
+ textarea.setSelectionRange(caret, caret);
2207
+ return;
2208
+ }
2209
+ const edit = atomicSkillBackspace(draftRef.current, start, skillNamesRef.current);
2210
+ if (edit === void 0) return;
2211
+ event.preventDefault();
2212
+ event.stopPropagation();
2213
+ inputActions.setDraft(edit.draft);
2214
+ if (caretFrameRef.current !== void 0) window.cancelAnimationFrame(caretFrameRef.current);
2215
+ caretFrameRef.current = window.requestAnimationFrame(() => {
2216
+ caretFrameRef.current = void 0;
2217
+ if (!textarea.isConnected || document.activeElement !== textarea || textarea.value !== edit.draft) return;
2218
+ textarea.setSelectionRange(edit.caret, edit.caret);
2219
+ });
2220
+ };
2221
+ document.addEventListener("keydown", onKeyDown, true);
2222
+ return () => {
2223
+ document.removeEventListener("keydown", onKeyDown, true);
2224
+ if (caretFrameRef.current !== void 0) window.cancelAnimationFrame(caretFrameRef.current);
2225
+ };
2226
+ }, [inputActions]);
1552
2227
  const filtered = query.trim().length === 0 ? usable : usable.filter((skill) => skill.name.toLowerCase().includes(query.trim().toLowerCase()));
1553
2228
  const onSelect = (name) => {
2229
+ const prefix = `/${name} `;
2230
+ const nextDraft = `${prefix}${input}`;
1554
2231
  setOpen(false);
1555
- inputActions.setDraft(`/${name} ${input}`);
2232
+ inputActions.setDraft(nextDraft);
2233
+ scheduleComposerFocus(nextDraft, prefix.length);
1556
2234
  };
1557
2235
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1558
2236
  ref: rootRef,
@@ -1561,7 +2239,10 @@ window.__ModuleLoader__.load({
1561
2239
  type: "button",
1562
2240
  "aria-label": t("pickerLabel"),
1563
2241
  "aria-expanded": open,
1564
- onClick: () => setOpen((value) => !value),
2242
+ onClick: () => {
2243
+ if (!open) setLoading(true);
2244
+ setOpen((value) => !value);
2245
+ },
1565
2246
  style: triggerStyle,
1566
2247
  children: [
1567
2248
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1593,7 +2274,10 @@ window.__ModuleLoader__.load({
1593
2274
  placeholder: t("pickerSearchPlaceholder"),
1594
2275
  style: searchStyle
1595
2276
  }),
1596
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2277
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2278
+ style: emptyStyle,
2279
+ children: t("pickerLoading")
2280
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1597
2281
  style: listStyle,
1598
2282
  children: filtered.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1599
2283
  type: "button",
@@ -1603,13 +2287,19 @@ window.__ModuleLoader__.load({
1603
2287
  style: avatarStyle,
1604
2288
  "aria-hidden": "true",
1605
2289
  children: skill.name.charAt(0).toUpperCase()
1606
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1607
- style: rowLabelStyle,
1608
- children: skill.name
2290
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2291
+ style: rowCopyStyle,
2292
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2293
+ style: rowLabelStyle,
2294
+ children: skill.name
2295
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2296
+ style: rowDetailStyle,
2297
+ children: skill.source === "project-dsh" ? t("pickerSourceProjectDsh") : skill.source === "project-agents" ? t("pickerSourceProjectAgents") : skill.source === "user-dsh" ? t("pickerSourceUserDsh") : t("pickerSourceUserAgents")
2298
+ })]
1609
2299
  })]
1610
2300
  }) }, skill.name))
1611
2301
  }),
1612
- filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2302
+ !loading && filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1613
2303
  style: emptyStyle,
1614
2304
  children: t("pickerEmpty")
1615
2305
  }) : null
@@ -1724,12 +2414,41 @@ window.__ModuleLoader__.load({
1724
2414
  minWidth: 0,
1725
2415
  overflow: "hidden"
1726
2416
  };
2417
+ const rowCopyStyle = {
2418
+ display: "flex",
2419
+ flexDirection: "column",
2420
+ minWidth: 0,
2421
+ flex: 1,
2422
+ lineHeight: "18px"
2423
+ };
2424
+ const rowDetailStyle = {
2425
+ overflow: "hidden",
2426
+ textOverflow: "ellipsis",
2427
+ whiteSpace: "nowrap",
2428
+ color: "var(--dsw-alias-label-caption)",
2429
+ fontSize: 11,
2430
+ lineHeight: "16px"
2431
+ };
1727
2432
  const emptyStyle = {
1728
2433
  padding: "8px 10px",
1729
2434
  fontSize: 12,
1730
2435
  color: "var(--dsw-alias-label-caption)"
1731
2436
  };
1732
2437
  //#endregion
2438
+ //#region src/client/effectiveSkills.ts
2439
+ const SOURCE_RANK = {
2440
+ "project-dsh": 100,
2441
+ "project-agents": 200,
2442
+ "user-dsh": 400,
2443
+ "user-agents": 500
2444
+ };
2445
+ /** Mirror DSH's name-based winner selection for one session-scoped catalog. */
2446
+ function effectiveSkills(rows) {
2447
+ const winners = /* @__PURE__ */ new Map();
2448
+ for (const row of [...rows].sort((a, b) => SOURCE_RANK[a.source] - SOURCE_RANK[b.source])) if (!winners.has(row.name)) winners.set(row.name, row);
2449
+ return [...winners.values()].sort((a, b) => a.name.localeCompare(b.name));
2450
+ }
2451
+ //#endregion
1733
2452
  //#region src/client/locales.ts
1734
2453
  /**
1735
2454
  * Locale dictionaries for the skill importer settings section.
@@ -1746,7 +2465,12 @@ window.__ModuleLoader__.load({
1746
2465
  pickerLabel: "技能",
1747
2466
  pickerPlaceholder: "技能",
1748
2467
  pickerSearchPlaceholder: "筛选技能…",
2468
+ pickerLoading: "正在同步当前项目技能…",
1749
2469
  pickerEmpty: "没有匹配的技能",
2470
+ pickerSourceProjectDsh: "当前项目 · .dsh",
2471
+ pickerSourceProjectAgents: "当前项目 · .agents",
2472
+ pickerSourceUserDsh: "全局 · .dsh",
2473
+ pickerSourceUserAgents: "全局 · .agents",
1750
2474
  commandDescription: "选择技能:弹出技能列表,选中后填入 /<名称>",
1751
2475
  entryTitle: "技能入口",
1752
2476
  entryDescription: "通过输入框工具行的技能下拉或 /skills 命令选择技能;也可直接输入 /<名称>。选中的技能会高亮并注入指令。",
@@ -1754,10 +2478,15 @@ window.__ModuleLoader__.load({
1754
2478
  refresh: "刷新",
1755
2479
  installedEmpty: "暂无技能。导入新技能或手动放入技能目录后会自动出现。",
1756
2480
  delete: "删除",
2481
+ deleting: "删除中…",
2482
+ deleteTitle: "删除技能",
1757
2483
  deleteConfirm: "确定删除技能 {name} 吗?将删除对应目录下的 SKILL.md 及资源文件。",
2484
+ cancel: "取消",
2485
+ close: "关闭",
1758
2486
  deleteFailed: "删除失败",
1759
2487
  deleted: "已删除。",
1760
2488
  installedFailed: "无法读取技能列表",
2489
+ installedPartial: "有 {count} 个技能目录暂时无法读取,其他技能已正常显示。",
1761
2490
  userOnly: "仅斜杠可用",
1762
2491
  modelOnly: "仅模型可用",
1763
2492
  importTitle: "导入技能",
@@ -1800,6 +2529,7 @@ window.__ModuleLoader__.load({
1800
2529
  batchConflict: "名称重复",
1801
2530
  batchReplace: "替换",
1802
2531
  batchConfirm: "将新增 {importing} 个、替换 {replacing} 个、跳过 {skipped} 个技能。确认继续吗?",
2532
+ batchConfirmTitle: "确认批量导入",
1803
2533
  batchResultTitle: "本次导入结果",
1804
2534
  batchResultSummary: "新增 {imported} · 替换 {replaced} · 跳过 {skipped} · 失败 {failed}",
1805
2535
  batchStatusImported: "已导入",
@@ -1807,15 +2537,48 @@ window.__ModuleLoader__.load({
1807
2537
  batchStatusSkipped: "已跳过",
1808
2538
  batchStatusError: "失败",
1809
2539
  targetLabel: "安装到",
1810
- targetUser: "全局技能(~/.dsh/skills)",
1811
- targetProjectAgents: "项目技能({title} · .agents/skills)",
2540
+ targetProjectDsh: "DSH 项目技能({title})",
2541
+ targetProjectAgents: "通用项目技能({title})",
2542
+ targetUserDsh: "DSH 全局技能",
2543
+ targetUserAgents: "通用全局技能",
2544
+ locationChange: "更改",
2545
+ locationTitle: "选择安装位置",
2546
+ locationProject: "项目技能",
2547
+ locationGlobal: "全局技能",
2548
+ workspaceSearch: "搜索工作区…",
2549
+ workspaceNoMatch: "没有匹配的工作区",
2550
+ chooseRootHint: "选择技能目录",
2551
+ rootProjectDsh: "DSH 项目技能",
2552
+ rootProjectAgents: "通用项目技能",
2553
+ rootUserDsh: "DSH 全局技能",
2554
+ rootUserAgents: "通用全局技能",
2555
+ priorityHighest: "最高",
2556
+ priorityHigher: "较高",
2557
+ priorityLower: "较低",
2558
+ priorityLowest: "最低",
2559
+ locationSummaryProjectDsh: "项目技能 · {title} · .dsh/skills",
2560
+ locationSummaryProjectAgents: "项目技能 · {title} · .agents/skills",
2561
+ locationSummaryUserDsh: "全局技能 · ~/.dsh/skills",
2562
+ locationSummaryUserAgents: "全局技能 · ~/.agents/skills",
1812
2563
  targetProjectNoWorkspace: "项目技能(需先添加工作区)",
1813
2564
  noWorkspace: "未注册工作区:请先在会话页选择/创建工作区,或改用用户技能目录。",
1814
2565
  import: "导入",
1815
2566
  importing: "导入中…",
1816
2567
  imported: "已写入:",
1817
2568
  importFailed: "导入失败",
1818
- hostUnreachable: "无法连接 dsh 服务(插件路由未生效)。请确认插件已安装,并重启 dsh web 后刷新页面。"
2569
+ hostUnreachable: "无法连接 dsh 服务(插件路由未生效)。请确认插件已安装,并重启 dsh web 后刷新页面。",
2570
+ versionChecking: "正在检查更新…",
2571
+ versionCurrent: "当前版本 v{version}",
2572
+ versionLatest: "已是最新版本",
2573
+ versionAvailable: "发现新版本 v{version}",
2574
+ versionCheckFailed: "暂时无法检查更新",
2575
+ versionRetry: "重试",
2576
+ versionUpdate: "更新",
2577
+ updateTitle: "更新 dsh-skill-importer",
2578
+ updateDescription: "复制下面的精确版本命令,在终端运行后重启 dsh web。",
2579
+ copyCommand: "复制更新命令",
2580
+ commandCopied: "已复制",
2581
+ restartHint: "更新完成后需要重启 dsh web 才会生效。"
1819
2582
  };
1820
2583
  /** English dictionary, checked complete against the zh key set. */
1821
2584
  const en = {
@@ -1823,7 +2586,12 @@ window.__ModuleLoader__.load({
1823
2586
  pickerLabel: "Skills",
1824
2587
  pickerPlaceholder: "Skills",
1825
2588
  pickerSearchPlaceholder: "Filter skills…",
2589
+ pickerLoading: "Syncing skills for the current project…",
1826
2590
  pickerEmpty: "No matching skills",
2591
+ pickerSourceProjectDsh: "Current project · .dsh",
2592
+ pickerSourceProjectAgents: "Current project · .agents",
2593
+ pickerSourceUserDsh: "Global · .dsh",
2594
+ pickerSourceUserAgents: "Global · .agents",
1827
2595
  commandDescription: "Pick a skill: opens a popup list; selecting fills /<name>",
1828
2596
  entryTitle: "Skill entry points",
1829
2597
  entryDescription: "Pick a skill from the composer tool-row dropdown or the /skills command; or type /<name> directly. The picked skill is highlighted and its instructions are injected.",
@@ -1831,10 +2599,15 @@ window.__ModuleLoader__.load({
1831
2599
  refresh: "Refresh",
1832
2600
  installedEmpty: "No skills yet. Import one or drop files into a skill directory and they will appear automatically.",
1833
2601
  delete: "Delete",
2602
+ deleting: "Deleting…",
2603
+ deleteTitle: "Delete skill",
1834
2604
  deleteConfirm: "Delete the skill {name}? This removes its SKILL.md and resources from that directory.",
2605
+ cancel: "Cancel",
2606
+ close: "Close",
1835
2607
  deleteFailed: "Failed to delete",
1836
2608
  deleted: "Deleted.",
1837
2609
  installedFailed: "Unable to read the skill list",
2610
+ installedPartial: "{count} skill directories are temporarily unreadable; other skills are still shown.",
1838
2611
  userOnly: "Slash only",
1839
2612
  modelOnly: "Model only",
1840
2613
  importTitle: "Import a skill",
@@ -1877,6 +2650,7 @@ window.__ModuleLoader__.load({
1877
2650
  batchConflict: "Name conflict",
1878
2651
  batchReplace: "Replace",
1879
2652
  batchConfirm: "Add {importing}, replace {replacing}, and skip {skipped} skills. Continue?",
2653
+ batchConfirmTitle: "Confirm batch import",
1880
2654
  batchResultTitle: "Import results",
1881
2655
  batchResultSummary: "Added {imported} · Replaced {replaced} · Skipped {skipped} · Failed {failed}",
1882
2656
  batchStatusImported: "Imported",
@@ -1884,15 +2658,48 @@ window.__ModuleLoader__.load({
1884
2658
  batchStatusSkipped: "Skipped",
1885
2659
  batchStatusError: "Failed",
1886
2660
  targetLabel: "Install into",
1887
- targetUser: "Global skills (~/.dsh/skills)",
1888
- targetProjectAgents: "Project skills ({title} · .agents/skills)",
2661
+ targetProjectDsh: "DSH project skills ({title})",
2662
+ targetProjectAgents: "Shared project skills ({title})",
2663
+ targetUserDsh: "DSH global skills",
2664
+ targetUserAgents: "Shared global skills",
2665
+ locationChange: "Change",
2666
+ locationTitle: "Choose install location",
2667
+ locationProject: "Project skills",
2668
+ locationGlobal: "Global skills",
2669
+ workspaceSearch: "Search workspaces…",
2670
+ workspaceNoMatch: "No matching workspaces",
2671
+ chooseRootHint: "Choose a skill directory",
2672
+ rootProjectDsh: "DSH project skills",
2673
+ rootProjectAgents: "Shared project skills",
2674
+ rootUserDsh: "DSH global skills",
2675
+ rootUserAgents: "Shared global skills",
2676
+ priorityHighest: "Highest",
2677
+ priorityHigher: "Higher",
2678
+ priorityLower: "Lower",
2679
+ priorityLowest: "Lowest",
2680
+ locationSummaryProjectDsh: "Project · {title} · .dsh/skills",
2681
+ locationSummaryProjectAgents: "Project · {title} · .agents/skills",
2682
+ locationSummaryUserDsh: "Global · ~/.dsh/skills",
2683
+ locationSummaryUserAgents: "Global · ~/.agents/skills",
1889
2684
  targetProjectNoWorkspace: "Project skills (add a workspace first)",
1890
2685
  noWorkspace: "No registered workspace: create/select one in a session, or use the user skill directory.",
1891
2686
  import: "Import",
1892
2687
  importing: "Importing…",
1893
2688
  imported: "Written to: ",
1894
2689
  importFailed: "Import failed",
1895
- hostUnreachable: "Cannot reach the dsh service (plugin routes are not active). Make sure the plugin is installed, restart dsh web, and refresh the page."
2690
+ hostUnreachable: "Cannot reach the dsh service (plugin routes are not active). Make sure the plugin is installed, restart dsh web, and refresh the page.",
2691
+ versionChecking: "Checking for updates…",
2692
+ versionCurrent: "Current version v{version}",
2693
+ versionLatest: "Up to date",
2694
+ versionAvailable: "Version v{version} is available",
2695
+ versionCheckFailed: "Unable to check for updates",
2696
+ versionRetry: "Retry",
2697
+ versionUpdate: "Update",
2698
+ updateTitle: "Update dsh-skill-importer",
2699
+ updateDescription: "Copy this exact-version command, run it in a terminal, then restart dsh web.",
2700
+ copyCommand: "Copy update command",
2701
+ commandCopied: "Copied",
2702
+ restartHint: "Restart dsh web after the update for it to take effect."
1896
2703
  };
1897
2704
  //#endregion
1898
2705
  //#region src/client/index.ts
@@ -1909,7 +2716,8 @@ window.__ModuleLoader__.load({
1909
2716
  importUrl: "/skill-importer/import-url",
1910
2717
  delete: "/skill-importer/delete",
1911
2718
  batchScan: "/skill-importer/batch/scan",
1912
- batchCommit: "/skill-importer/batch/commit"
2719
+ batchCommit: "/skill-importer/batch/commit",
2720
+ updateStatus: "/skill-importer/update-status"
1913
2721
  };
1914
2722
  /** Fetch one route and unwrap the JSON envelope; throws on transport/HTTP errors. */
1915
2723
  async function call(path, body) {
@@ -1942,7 +2750,10 @@ window.__ModuleLoader__.load({
1942
2750
  en
1943
2751
  }), "dsh-skill-importer: dictionaries");
1944
2752
  const t = ctx.locale.bind(NS);
2753
+ const sourceLabel = (source) => source === "project-dsh" ? t("pickerSourceProjectDsh") : source === "project-agents" ? t("pickerSourceProjectAgents") : source === "user-dsh" ? t("pickerSourceUserDsh") : t("pickerSourceUserAgents");
1945
2754
  let catalog = [];
2755
+ let issues = [];
2756
+ const sessionCatalogs = /* @__PURE__ */ new Map();
1946
2757
  const listeners = /* @__PURE__ */ new Set();
1947
2758
  const notify = () => {
1948
2759
  for (const listener of [...listeners]) try {
@@ -1957,7 +2768,7 @@ window.__ModuleLoader__.load({
1957
2768
  skillsApi.list = ((_request, _signal) => {
1958
2769
  return Promise.reject(/* @__PURE__ */ new Error("skill group hidden: use the composer skill picker or /skills"));
1959
2770
  });
1960
- const usableNames = () => catalog.filter((skill) => skill.userInvocable).map((skill) => skill.name);
2771
+ const usableNames = () => [...new Set([...sessionCatalogs.values()].flat().filter((skill) => skill.userInvocable).map((skill) => skill.name))];
1961
2772
  ctx.effect(() => {
1962
2773
  return ctx.get("inputTriggers").registerSource({
1963
2774
  trigger: "/",
@@ -1989,10 +2800,10 @@ window.__ModuleLoader__.load({
1989
2800
  available: () => true,
1990
2801
  ui: {
1991
2802
  kind: "popupSelect",
1992
- async options(_session, _signal) {
1993
- return catalog.filter((skill) => skill.userInvocable).map((skill) => ({
2803
+ async options(session, _signal) {
2804
+ return (await refreshSkillsForSession(session.sessionId)).filter((skill) => skill.userInvocable).map((skill) => ({
1994
2805
  id: skill.name,
1995
- label: skill.name
2806
+ label: `${skill.name} · ${sourceLabel(skill.source)}`
1996
2807
  }));
1997
2808
  },
1998
2809
  onSelect(option, session) {
@@ -2000,16 +2811,32 @@ window.__ModuleLoader__.load({
2000
2811
  if (actx === void 0) return;
2001
2812
  const input = conversation.input.for(actx);
2002
2813
  const rest = input.state.getSnapshot().draft.replace(/^\s*\/[a-zA-Z][a-zA-Z0-9-]*\s*/, "");
2003
- input.setDraft(`/${option.id} ${rest}`);
2814
+ const prefix = `/${option.id} `;
2815
+ const nextDraft = `${prefix}${rest}`;
2816
+ input.setDraft(nextDraft);
2817
+ requestComposerFocus({
2818
+ sessionId: session.sessionId,
2819
+ expectedDraft: nextDraft,
2820
+ caret: prefix.length
2821
+ });
2004
2822
  }
2005
2823
  }
2006
2824
  });
2007
2825
  }, "dsh-skill-importer: /skills popupSelect command");
2008
- const refreshSkills = async () => {
2009
- catalog = (await call(ROUTES.list)).skills;
2826
+ const refreshSkills = async (workspacePath) => {
2827
+ const result = await call(workspacePath === void 0 ? ROUTES.list : `${ROUTES.list}?workspacePath=${encodeURIComponent(workspacePath)}`);
2828
+ catalog = result.skills;
2829
+ issues = result.issues;
2010
2830
  notify();
2011
2831
  return catalog;
2012
2832
  };
2833
+ const refreshSkillsForSession = async (sessionId) => {
2834
+ const cwd = sessions.list.getSnapshot().byId[sessionId]?.cwd;
2835
+ const effective = effectiveSkills((await call(cwd === void 0 ? `${ROUTES.list}?scope=global` : `${ROUTES.list}?workspacePath=${encodeURIComponent(cwd)}`)).skills);
2836
+ sessionCatalogs.set(sessionId, effective);
2837
+ notify();
2838
+ return effective;
2839
+ };
2013
2840
  const importSkill = async (request) => {
2014
2841
  return (await call(ROUTES.import, request)).path;
2015
2842
  };
@@ -2026,24 +2853,38 @@ window.__ModuleLoader__.load({
2026
2853
  };
2027
2854
  const scanBatch = (request) => call(ROUTES.batchScan, request);
2028
2855
  const commitBatch = (request) => call(ROUTES.batchCommit, request);
2856
+ const checkForUpdates = () => call(ROUTES.updateStatus);
2029
2857
  const face = () => ({
2030
- hooks: { skills: {
2031
- getSnapshot: () => catalog,
2032
- subscribe: (listener) => {
2033
- listeners.add(listener);
2034
- return () => {
2035
- listeners.delete(listener);
2036
- };
2858
+ hooks: {
2859
+ skills: {
2860
+ getSnapshot: () => catalog,
2861
+ subscribe: (listener) => {
2862
+ listeners.add(listener);
2863
+ return () => {
2864
+ listeners.delete(listener);
2865
+ };
2866
+ }
2867
+ },
2868
+ skillIssues: {
2869
+ getSnapshot: () => issues,
2870
+ subscribe: (listener) => {
2871
+ listeners.add(listener);
2872
+ return () => {
2873
+ listeners.delete(listener);
2874
+ };
2875
+ }
2037
2876
  }
2038
- } },
2877
+ },
2039
2878
  actions: {
2040
2879
  refreshSkills,
2880
+ refreshSkillsForSession,
2041
2881
  importSkill,
2042
2882
  importUrl,
2043
2883
  deleteSkill,
2044
2884
  pickDirectory,
2045
2885
  scanBatch,
2046
- commitBatch
2886
+ commitBatch,
2887
+ checkForUpdates
2047
2888
  }
2048
2889
  });
2049
2890
  ctx.slots.inject("settings.section", () => ctx.slots.register({