dsh-skill-importer 0.1.2 → 0.2.0

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
@@ -110,6 +110,110 @@ window.__ModuleLoader__.load({
110
110
  * through the four props shares.
111
111
  */
112
112
  const MAX_FILE_BYTES = 262144;
113
+ function FileIcon() {
114
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
115
+ width: "18",
116
+ height: "18",
117
+ viewBox: "0 0 18 18",
118
+ fill: "none",
119
+ "aria-hidden": "true",
120
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
121
+ d: "M5.25 2.25h4.2l3.3 3.3v9.2a1 1 0 0 1-1 1h-6.5a1 1 0 0 1-1-1V3.25a1 1 0 0 1 1-1Z",
122
+ stroke: "currentColor",
123
+ strokeWidth: "1.35",
124
+ strokeLinejoin: "round"
125
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
126
+ d: "M9.25 2.5v3.25h3.25M6.75 9h4.5M6.75 11.75h3.25",
127
+ stroke: "currentColor",
128
+ strokeWidth: "1.35",
129
+ strokeLinecap: "round",
130
+ strokeLinejoin: "round"
131
+ })]
132
+ });
133
+ }
134
+ function LinkIcon() {
135
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
136
+ width: "18",
137
+ height: "18",
138
+ viewBox: "0 0 18 18",
139
+ fill: "none",
140
+ "aria-hidden": "true",
141
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
142
+ d: "m7.15 10.85 3.7-3.7M6.05 12.55l-1.1 1.1a2.6 2.6 0 0 1-3.68-3.68l2.3-2.3a2.6 2.6 0 0 1 3.68 0M11.95 5.45l1.1-1.1a2.6 2.6 0 1 1 3.68 3.68l-2.3 2.3a2.6 2.6 0 0 1-3.68 0",
143
+ stroke: "currentColor",
144
+ strokeWidth: "1.35",
145
+ strokeLinecap: "round"
146
+ })
147
+ });
148
+ }
149
+ function BatchIcon() {
150
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
151
+ width: "18",
152
+ height: "18",
153
+ viewBox: "0 0 18 18",
154
+ fill: "none",
155
+ "aria-hidden": "true",
156
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
157
+ d: "M3 4.25h12v9.5H3zM3 7.25h12M6.25 2.5v3.25M11.75 2.5v3.25M6 10.5h2M10 10.5h2",
158
+ stroke: "currentColor",
159
+ strokeWidth: "1.3",
160
+ strokeLinecap: "round",
161
+ strokeLinejoin: "round"
162
+ })
163
+ });
164
+ }
165
+ function CheckIcon() {
166
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
167
+ width: "14",
168
+ height: "14",
169
+ viewBox: "0 0 14 14",
170
+ fill: "none",
171
+ "aria-hidden": "true",
172
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
173
+ d: "m3 7.25 2.4 2.4L11 4.5",
174
+ stroke: "currentColor",
175
+ strokeWidth: "1.5",
176
+ strokeLinecap: "round",
177
+ strokeLinejoin: "round"
178
+ })
179
+ });
180
+ }
181
+ function TrashIcon() {
182
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
183
+ width: "14",
184
+ height: "14",
185
+ viewBox: "0 0 14 14",
186
+ fill: "none",
187
+ "aria-hidden": "true",
188
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
189
+ d: "M2.5 3.75h9M5.25 3.75V2.5h3.5v1.25M4 3.75l.55 7.15a.75.75 0 0 0 .75.7h3.4a.75.75 0 0 0 .75-.7L10 3.75M5.75 6v3.25M8.25 6v3.25",
190
+ stroke: "currentColor",
191
+ strokeWidth: "1.2",
192
+ strokeLinecap: "round",
193
+ strokeLinejoin: "round"
194
+ })
195
+ });
196
+ }
197
+ function ChevronIcon({ open }) {
198
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
199
+ width: "14",
200
+ height: "14",
201
+ viewBox: "0 0 14 14",
202
+ fill: "none",
203
+ "aria-hidden": "true",
204
+ style: {
205
+ transform: open ? "rotate(90deg)" : void 0,
206
+ transition: "transform 120ms ease"
207
+ },
208
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
209
+ d: "m5 3.25 3.75 3.75L5 10.75",
210
+ stroke: "currentColor",
211
+ strokeWidth: "1.3",
212
+ strokeLinecap: "round",
213
+ strokeLinejoin: "round"
214
+ })
215
+ });
216
+ }
113
217
  /** Read a picked file's text (browser-local; nothing crosses the wire except the import itself). */
114
218
  function readFileText(file) {
115
219
  return new Promise((resolve, reject) => {
@@ -127,6 +231,7 @@ window.__ModuleLoader__.load({
127
231
  const [listState, setListState] = (0, react.useState)("idle");
128
232
  const [listError, setListError] = (0, react.useState)();
129
233
  const [listEpoch, setListEpoch] = (0, react.useState)(0);
234
+ const [collapsedSources, setCollapsedSources] = (0, react.useState)(() => /* @__PURE__ */ new Set());
130
235
  (0, react.useEffect)(() => {
131
236
  let cancelled = false;
132
237
  setListState("loading");
@@ -151,6 +256,10 @@ window.__ModuleLoader__.load({
151
256
  const [busy, setBusy] = (0, react.useState)(false);
152
257
  const [message, setMessage] = (0, react.useState)();
153
258
  const [messageError, setMessageError] = (0, react.useState)(false);
259
+ const [batchPath, setBatchPath] = (0, react.useState)();
260
+ const [batchScan, setBatchScan] = (0, react.useState)();
261
+ const [batchResults, setBatchResults] = (0, react.useState)();
262
+ const [replaceNames, setReplaceNames] = (0, react.useState)(() => /* @__PURE__ */ new Set());
154
263
  const fileInput = (0, react.useRef)(null);
155
264
  const onFilePicked = async (file) => {
156
265
  setFileDraft(void 0);
@@ -226,7 +335,7 @@ window.__ModuleLoader__.load({
226
335
  workspacePath: workspace?.path
227
336
  });
228
337
  pollForSkill(fileDraft.name, 10);
229
- } else {
338
+ } else if (mode === "url") {
230
339
  path = await actions.importUrl({
231
340
  name: effectiveUrlName,
232
341
  target,
@@ -234,7 +343,7 @@ window.__ModuleLoader__.load({
234
343
  workspacePath: workspace?.path
235
344
  });
236
345
  pollForSkill(effectiveUrlName, 10);
237
- }
346
+ } else return;
238
347
  setMessage(`${t("imported")}${path}`);
239
348
  setFileDraft(void 0);
240
349
  setFileError(void 0);
@@ -249,16 +358,118 @@ window.__ModuleLoader__.load({
249
358
  setBusy(false);
250
359
  }
251
360
  };
252
- const canSubmit = busy || mode === "file" && fileDraft === void 0 || mode === "url" && (url.trim().length === 0 || !/^https?:\/\//i.test(url.trim())) || target !== "user" && workspace === void 0;
361
+ const resetBatchPreflight = () => {
362
+ setBatchScan(void 0);
363
+ setBatchResults(void 0);
364
+ setReplaceNames(/* @__PURE__ */ new Set());
365
+ setMessage(void 0);
366
+ };
367
+ const chooseBatchDirectory = async () => {
368
+ setBusy(true);
369
+ setMessage(void 0);
370
+ setMessageError(false);
371
+ try {
372
+ const path = await actions.pickDirectory();
373
+ if (path === null) return;
374
+ setBatchPath(path);
375
+ resetBatchPreflight();
376
+ } catch (error) {
377
+ setMessageError(true);
378
+ setMessage(`${t("batchPickFailed")}:${String(error)}`);
379
+ } finally {
380
+ setBusy(false);
381
+ }
382
+ };
383
+ const scanBatchDirectory = async () => {
384
+ if (batchPath === void 0) return;
385
+ setBusy(true);
386
+ setMessage(void 0);
387
+ setMessageError(false);
388
+ setBatchResults(void 0);
389
+ setReplaceNames(/* @__PURE__ */ new Set());
390
+ try {
391
+ const result = await actions.scanBatch({
392
+ sourcePath: batchPath,
393
+ target,
394
+ workspacePath: workspace?.path
395
+ });
396
+ setBatchScan(result);
397
+ } catch (error) {
398
+ setBatchScan(void 0);
399
+ setMessageError(true);
400
+ setMessage(`${t("batchScanFailed")}:${String(error)}`);
401
+ } finally {
402
+ setBusy(false);
403
+ }
404
+ };
405
+ const commitBatchDirectory = async () => {
406
+ if (batchScan === void 0) return;
407
+ const ready = batchScan.entries.filter((entry) => entry.status === "ready");
408
+ const replacing = ready.filter((entry) => replaceNames.has(entry.name)).length;
409
+ const importing = ready.filter((entry) => !entry.conflict).length;
410
+ const skipped = ready.filter((entry) => entry.conflict && !replaceNames.has(entry.name)).length;
411
+ if (!window.confirm(t("batchConfirm", {
412
+ importing,
413
+ replacing,
414
+ skipped
415
+ }))) return;
416
+ setBusy(true);
417
+ setMessage(void 0);
418
+ setMessageError(false);
419
+ try {
420
+ const result = await actions.commitBatch({
421
+ scanId: batchScan.scanId,
422
+ replace: [...replaceNames]
423
+ });
424
+ setBatchResults(result.results);
425
+ setBatchScan(void 0);
426
+ setReplaceNames(/* @__PURE__ */ new Set());
427
+ setListEpoch((epoch) => epoch + 1);
428
+ } catch (error) {
429
+ setMessageError(true);
430
+ setMessage(`${t("batchImportFailed")}:${String(error)}`);
431
+ } finally {
432
+ setBusy(false);
433
+ }
434
+ };
435
+ const toggleReplace = (name) => {
436
+ setReplaceNames((current) => {
437
+ const next = new Set(current);
438
+ if (!next.delete(name)) next.add(name);
439
+ return next;
440
+ });
441
+ };
442
+ const changeTarget = (next) => {
443
+ setTarget(next);
444
+ if (mode === "batch") resetBatchPreflight();
445
+ };
446
+ 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;
447
+ const runPrimaryAction = () => {
448
+ if (mode !== "batch") {
449
+ submit();
450
+ return;
451
+ }
452
+ if (batchScan === void 0) scanBatchDirectory();
453
+ else commitBatchDirectory();
454
+ };
455
+ const primaryLabel = mode !== "batch" ? busy ? t("importing") : t("import") : busy ? t("batchWorking") : batchScan === void 0 ? t("batchScan") : t("batchCommit");
253
456
  const flags = (skill) => {
254
457
  if (!skill.modelInvocable && skill.userInvocable) return t("userOnly");
255
458
  if (skill.modelInvocable && !skill.userInvocable) return t("modelOnly");
256
459
  };
460
+ const toggleSource = (source) => {
461
+ setCollapsedSources((current) => {
462
+ const next = new Set(current);
463
+ if (!next.delete(source)) next.add(source);
464
+ return next;
465
+ });
466
+ };
257
467
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
258
468
  style: {
259
469
  display: "flex",
260
470
  flexDirection: "column",
261
- width: "100%"
471
+ width: "100%",
472
+ maxWidth: 720
262
473
  },
263
474
  children: [
264
475
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -312,104 +523,94 @@ window.__ModuleLoader__.load({
312
523
  children: t("installedEmpty")
313
524
  }) : null,
314
525
  skills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
315
- style: {
316
- display: "flex",
317
- flexDirection: "column"
318
- },
319
- children: [
320
- "project-agents",
321
- "project-dsh",
322
- "user"
323
- ].map((source) => {
526
+ style: skillGroupsStyle,
527
+ children: ["project-agents", "user"].map((source) => {
324
528
  const rows = skills.filter((skill) => skill.source === source);
325
529
  if (rows.length === 0) return null;
326
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
327
- style: {
328
- display: "flex",
329
- flexDirection: "column",
330
- gap: 4
331
- },
332
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
530
+ const sourceName = source === "user" ? t("targetUser") : workspace === void 0 ? t("targetProjectNoWorkspace") : t("targetProjectAgents", { title: workspace.title });
531
+ const open = !collapsedSources.has(source);
532
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
533
+ style: skillGroupCardStyle,
534
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
535
+ type: "button",
536
+ "aria-expanded": open,
537
+ onClick: () => toggleSource(source),
333
538
  style: {
334
- ...descriptionStyle,
335
- fontWeight: 600,
336
- textTransform: "uppercase",
337
- letterSpacing: "0.04em",
338
- marginTop: 8
539
+ ...skillGroupHeaderStyle,
540
+ borderBottom: open ? skillGroupHeaderStyle.borderBottom : "none"
339
541
  },
340
- children: t(`source${source === "project-agents" ? "Agents" : source === "project-dsh" ? "Dsh" : "User"}`)
341
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
342
- style: {
343
- listStyle: "none",
344
- margin: 0,
345
- padding: 0,
346
- display: "flex",
347
- flexDirection: "column"
348
- },
349
- children: rows.map((skill) => {
542
+ children: [
543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
544
+ style: skillGroupChevronStyle,
545
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronIcon, { open })
546
+ }),
547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
548
+ style: skillGroupIdentityStyle,
549
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
550
+ style: skillGroupNameStyle,
551
+ children: sourceName
552
+ })
553
+ }),
554
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
555
+ style: countBadgeStyle,
556
+ children: rows.length
557
+ })
558
+ ]
559
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
560
+ style: skillListStyle,
561
+ children: rows.map((skill, index) => {
350
562
  const flag = flags(skill);
351
563
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
352
564
  style: {
353
- display: "flex",
354
- alignItems: "center",
355
- gap: 8,
356
- padding: "6px 0"
565
+ ...skillRowStyle,
566
+ borderBottom: index === rows.length - 1 ? "none" : skillRowStyle.borderBottom
357
567
  },
358
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
359
- style: {
360
- flex: 1,
361
- minWidth: 0,
362
- display: "flex",
363
- flexDirection: "column",
364
- gap: 2
365
- },
366
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
367
- style: {
368
- fontSize: 14,
369
- fontWeight: 600,
370
- color: "var(--dsw-alias-label-primary)"
371
- },
372
- children: [
373
- "/",
374
- skill.name,
375
- flag !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
376
- style: {
377
- opacity: .6,
378
- fontWeight: 400,
379
- marginLeft: 8
380
- },
568
+ children: [
569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
570
+ style: skillAvatarStyle,
571
+ "aria-hidden": "true",
572
+ children: skill.name.charAt(0).toUpperCase()
573
+ }),
574
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
575
+ style: skillContentStyle,
576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
577
+ style: skillNameRowStyle,
578
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
579
+ style: skillNameStyle,
580
+ children: skill.name
581
+ }), flag !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
582
+ style: skillFlagStyle,
381
583
  children: flag
382
- }) : null
383
- ]
384
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
385
- style: {
386
- fontSize: 13,
387
- color: "var(--dsw-alias-label-secondary)"
584
+ }) : null]
585
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
586
+ style: skillDescriptionStyle,
587
+ children: skill.description
588
+ })]
589
+ }),
590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
591
+ type: "button",
592
+ "aria-label": `${t("delete")} ${skill.name}`,
593
+ title: t("delete"),
594
+ onClick: () => {
595
+ removeSkill(skill);
388
596
  },
389
- children: skill.description
390
- })]
391
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
392
- type: "button",
393
- onClick: () => {
394
- removeSkill(skill);
395
- },
396
- style: {
397
- ...pillStyle,
398
- flex: "none",
399
- color: "var(--dsw-alias-state-error-primary)"
400
- },
401
- children: t("delete")
402
- })]
597
+ style: deleteIconButtonStyle,
598
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, {})
599
+ })
600
+ ]
403
601
  }, skill.source + ":" + skill.name);
404
602
  })
405
- })]
603
+ }) : null]
406
604
  }, source);
407
605
  })
408
606
  }) : null
409
607
  ]
410
608
  }),
411
609
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
412
- style: rowStyle$1,
610
+ style: {
611
+ ...rowStyle$1,
612
+ borderBottom: "none"
613
+ },
413
614
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
414
615
  style: rowTextStyle,
415
616
  children: [
@@ -417,227 +618,432 @@ window.__ModuleLoader__.load({
417
618
  style: titleStyle,
418
619
  children: t("importTitle")
419
620
  }),
621
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
622
+ style: descriptionStyle,
623
+ children: t("importDescription")
624
+ }),
420
625
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
421
626
  role: "tablist",
422
- style: {
423
- display: "flex",
424
- gap: 8,
425
- marginTop: 4
426
- },
427
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
428
- type: "button",
429
- role: "tab",
430
- "aria-selected": mode === "file",
431
- onClick: () => setMode("file"),
432
- style: segmentStyle(mode === "file"),
433
- children: t("fileTab")
434
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
435
- type: "button",
436
- role: "tab",
437
- "aria-selected": mode === "url",
438
- onClick: () => setMode("url"),
439
- style: segmentStyle(mode === "url"),
440
- children: t("urlTab")
441
- })]
442
- }),
443
- mode === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
444
- style: {
445
- display: "flex",
446
- flexDirection: "column",
447
- gap: 8,
448
- marginTop: 12
449
- },
627
+ style: entryGridStyle,
450
628
  children: [
451
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
452
- ref: fileInput,
453
- type: "file",
454
- accept: ".md,.markdown,text/markdown,text/plain",
455
- onChange: (event) => {
456
- onFilePicked(event.target.files?.[0]);
457
- },
458
- style: {
459
- ...controlStyle,
460
- padding: 8
461
- }
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
630
+ type: "button",
631
+ role: "tab",
632
+ "aria-selected": mode === "file",
633
+ onClick: () => setMode("file"),
634
+ style: entryButtonStyle(mode === "file"),
635
+ children: [
636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
637
+ style: entryIconStyle,
638
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileIcon, {})
639
+ }),
640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
641
+ style: entryCopyStyle,
642
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
643
+ style: entryTitleStyle,
644
+ children: t("fileTab")
645
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
646
+ style: entryDescriptionStyle,
647
+ children: t("fileEntryDescription")
648
+ })]
649
+ }),
650
+ mode === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
651
+ style: entryCheckStyle,
652
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {})
653
+ }) : null
654
+ ]
462
655
  }),
463
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
464
- style: descriptionStyle,
465
- children: t("fileHelp")
656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
657
+ type: "button",
658
+ role: "tab",
659
+ "aria-selected": mode === "url",
660
+ onClick: () => setMode("url"),
661
+ style: entryButtonStyle(mode === "url"),
662
+ children: [
663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
664
+ style: entryIconStyle,
665
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkIcon, {})
666
+ }),
667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
668
+ style: entryCopyStyle,
669
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
670
+ style: entryTitleStyle,
671
+ children: t("urlTab")
672
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
673
+ style: entryDescriptionStyle,
674
+ children: t("urlEntryDescription")
675
+ })]
676
+ }),
677
+ mode === "url" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
678
+ style: entryCheckStyle,
679
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {})
680
+ }) : null
681
+ ]
466
682
  }),
467
- fileError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
468
- style: {
469
- ...descriptionStyle,
470
- color: "var(--dsw-alias-state-error-primary)"
471
- },
472
- children: fileError
473
- }) : null,
474
- fileDraft !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
684
+ type: "button",
685
+ role: "tab",
686
+ "aria-selected": mode === "batch",
687
+ onClick: () => setMode("batch"),
688
+ style: entryButtonStyle(mode === "batch"),
689
+ children: [
690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
691
+ style: entryIconStyle,
692
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatchIcon, {})
693
+ }),
694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
695
+ style: entryCopyStyle,
696
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
697
+ style: entryTitleStyle,
698
+ children: t("batchTab")
699
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
700
+ style: entryDescriptionStyle,
701
+ children: t("batchEntryDescription")
702
+ })]
703
+ }),
704
+ mode === "batch" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
705
+ style: entryCheckStyle,
706
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {})
707
+ }) : null
708
+ ]
709
+ })
710
+ ]
711
+ }),
712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
713
+ style: importEditorStyle,
714
+ children: [
715
+ mode === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
475
716
  style: {
476
717
  display: "flex",
477
718
  flexDirection: "column",
478
- gap: 4,
479
- fontSize: 13,
480
- border: "1px solid var(--dsw-alias-border-l2)",
481
- borderRadius: 8,
482
- padding: 10,
483
- color: "var(--dsw-alias-label-secondary)"
719
+ gap: 10
484
720
  },
485
721
  children: [
486
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
487
- style: {
488
- fontWeight: 600,
489
- color: "var(--dsw-alias-label-primary)"
722
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
723
+ ref: fileInput,
724
+ type: "file",
725
+ accept: ".md,.markdown,text/markdown,text/plain",
726
+ onChange: (event) => {
727
+ onFilePicked(event.target.files?.[0]);
490
728
  },
491
- children: t("previewTitle")
729
+ style: hiddenInputStyle
492
730
  }),
493
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
494
- t("previewName"),
495
- ": ",
496
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: fileDraft.name })
497
- ] }),
498
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
499
- t("previewDescription"),
500
- ": ",
501
- fileDraft.description
502
- ] }),
503
- fileDraft.whenToUse !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
504
- t("previewWhenToUse"),
505
- ": ",
506
- fileDraft.whenToUse
507
- ] }) : null,
508
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
509
- style: { opacity: .8 },
731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
732
+ type: "button",
733
+ onClick: () => fileInput.current?.click(),
734
+ style: filePickerStyle,
510
735
  children: [
511
- fileDraft.fileName,
512
- " · ",
513
- t("previewOk")
736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
737
+ style: filePickerIconStyle,
738
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileIcon, {})
739
+ }),
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
741
+ style: {
742
+ minWidth: 0,
743
+ flex: 1,
744
+ display: "flex",
745
+ flexDirection: "column",
746
+ gap: 2,
747
+ textAlign: "left"
748
+ },
749
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
750
+ style: {
751
+ fontSize: 13,
752
+ lineHeight: "20px",
753
+ fontWeight: 500,
754
+ color: "var(--dsw-alias-label-primary)",
755
+ overflow: "hidden",
756
+ textOverflow: "ellipsis",
757
+ whiteSpace: "nowrap"
758
+ },
759
+ children: fileDraft?.fileName ?? t("chooseFile")
760
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
761
+ style: descriptionStyle,
762
+ children: fileDraft === void 0 ? t("filePickerHint") : t("replaceFile")
763
+ })]
764
+ }),
765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
766
+ style: secondaryButtonStyle,
767
+ children: fileDraft === void 0 ? t("browse") : t("change")
768
+ })
514
769
  ]
515
- })
770
+ }),
771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
772
+ style: descriptionStyle,
773
+ children: t("fileHelp")
774
+ }),
775
+ fileError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
776
+ style: {
777
+ ...descriptionStyle,
778
+ color: "var(--dsw-alias-state-error-primary)"
779
+ },
780
+ children: fileError
781
+ }) : null,
782
+ fileDraft !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
783
+ style: previewStyle,
784
+ children: [
785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
+ style: {
787
+ display: "flex",
788
+ alignItems: "center",
789
+ gap: 6,
790
+ color: "var(--dsw-alias-state-success-primary)"
791
+ },
792
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
793
+ style: {
794
+ fontSize: 12,
795
+ fontWeight: 500
796
+ },
797
+ children: t("previewOk")
798
+ })]
799
+ }),
800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: previewDividerStyle }),
801
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
802
+ style: previewKeyStyle,
803
+ children: t("previewName")
804
+ }), fileDraft.name] }),
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
806
+ style: previewKeyStyle,
807
+ children: t("previewDescription")
808
+ }), fileDraft.description] }),
809
+ fileDraft.whenToUse !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
810
+ style: previewKeyStyle,
811
+ children: t("previewWhenToUse")
812
+ }), fileDraft.whenToUse] }) : null
813
+ ]
814
+ }) : null
516
815
  ]
517
- }) : null
518
- ]
519
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
520
- style: {
521
- display: "flex",
522
- flexDirection: "column",
523
- gap: 8,
524
- marginTop: 12
525
- },
526
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
527
- style: {
528
- display: "flex",
529
- flexDirection: "column",
530
- gap: 4,
531
- fontSize: 13
532
- },
533
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
816
+ }) : mode === "url" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
534
817
  style: {
535
- fontWeight: 600,
536
- color: "var(--dsw-alias-label-primary)"
818
+ display: "flex",
819
+ flexDirection: "column",
820
+ gap: 12
537
821
  },
538
- children: t("urlLabel")
539
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
540
- type: "url",
541
- value: url,
542
- placeholder: "https://example.com/my-skill.md",
543
- onChange: (event) => setUrl(event.target.value),
544
- style: controlStyle
545
- })]
546
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
547
- style: {
548
- display: "flex",
549
- flexDirection: "column",
550
- gap: 4,
551
- fontSize: 13
552
- },
553
- children: [
554
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
555
- style: {
556
- fontWeight: 600,
557
- color: "var(--dsw-alias-label-primary)"
558
- },
559
- children: t("urlNameLabel")
560
- }),
561
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
562
- type: "text",
563
- value: urlName,
564
- placeholder: nameFromUrl(url) || "my-skill",
565
- onChange: (event) => setUrlName(event.target.value),
566
- style: controlStyle
567
- }),
568
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
569
- style: descriptionStyle,
570
- children: t("urlNameHelp")
571
- })
572
- ]
573
- })]
574
- }),
575
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
576
- style: {
577
- display: "flex",
578
- flexDirection: "column",
579
- gap: 4,
580
- fontSize: 13,
581
- marginTop: 12
582
- },
583
- children: [
584
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
822
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
823
+ style: fieldStyle,
824
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
825
+ style: fieldLabelStyle,
826
+ children: t("urlLabel")
827
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
828
+ type: "url",
829
+ value: url,
830
+ placeholder: "https://example.com/my-skill.md",
831
+ onChange: (event) => setUrl(event.target.value),
832
+ style: controlStyle
833
+ })]
834
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
835
+ style: fieldStyle,
836
+ children: [
837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
838
+ style: fieldLabelStyle,
839
+ children: t("urlNameLabel")
840
+ }),
841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
842
+ type: "text",
843
+ value: urlName,
844
+ placeholder: nameFromUrl(url) || "my-skill",
845
+ onChange: (event) => setUrlName(event.target.value),
846
+ style: controlStyle
847
+ }),
848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
849
+ style: descriptionStyle,
850
+ children: t("urlNameHelp")
851
+ })
852
+ ]
853
+ })]
854
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
585
855
  style: {
586
- fontWeight: 600,
587
- color: "var(--dsw-alias-label-primary)"
856
+ display: "flex",
857
+ flexDirection: "column",
858
+ gap: 12
588
859
  },
589
- children: t("targetLabel")
860
+ children: [
861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
862
+ type: "button",
863
+ onClick: () => {
864
+ chooseBatchDirectory();
865
+ },
866
+ style: filePickerStyle,
867
+ disabled: busy,
868
+ children: [
869
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
870
+ style: filePickerIconStyle,
871
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatchIcon, {})
872
+ }),
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
874
+ style: {
875
+ minWidth: 0,
876
+ flex: 1,
877
+ display: "flex",
878
+ flexDirection: "column",
879
+ gap: 2,
880
+ textAlign: "left"
881
+ },
882
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
883
+ style: {
884
+ fontSize: 13,
885
+ lineHeight: "20px",
886
+ fontWeight: 500,
887
+ color: "var(--dsw-alias-label-primary)",
888
+ overflow: "hidden",
889
+ textOverflow: "ellipsis",
890
+ whiteSpace: "nowrap"
891
+ },
892
+ children: batchPath ?? t("batchChooseDirectory")
893
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
894
+ style: descriptionStyle,
895
+ children: batchPath === void 0 ? t("batchDirectoryHint") : t("batchDirectorySelected")
896
+ })]
897
+ }),
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
899
+ style: secondaryButtonStyle,
900
+ children: batchPath === void 0 ? t("browse") : t("change")
901
+ })
902
+ ]
903
+ }),
904
+ batchScan !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
905
+ style: batchPanelStyle,
906
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
907
+ style: batchSummaryStyle,
908
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("batchPreflight") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
909
+ style: countBadgeStyle,
910
+ children: batchScan.entries.length
911
+ })]
912
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
913
+ style: batchListStyle,
914
+ children: batchScan.entries.map((entry, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
915
+ style: {
916
+ ...batchRowStyle,
917
+ borderBottom: index === batchScan.entries.length - 1 ? "none" : batchRowStyle.borderBottom
918
+ },
919
+ children: [
920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
921
+ ...batchStatusDotStyle,
922
+ background: entry.status === "error" ? "var(--dsw-alias-state-error-primary)" : entry.conflict ? "var(--dsw-alias-state-warn-label)" : "var(--dsw-alias-state-success-primary)"
923
+ } }),
924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
925
+ style: skillContentStyle,
926
+ children: [
927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
928
+ style: skillNameRowStyle,
929
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
930
+ style: skillNameStyle,
931
+ children: entry.name
932
+ }), entry.conflict && entry.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
933
+ style: batchConflictTagStyle,
934
+ children: t("batchConflict")
935
+ }) : null]
936
+ }),
937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
938
+ style: skillDescriptionStyle,
939
+ children: entry.error ?? entry.description ?? entry.relativePath
940
+ }),
941
+ entry.warnings?.map((warning) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
942
+ style: batchWarningStyle,
943
+ children: warning
944
+ }, warning))
945
+ ]
946
+ }),
947
+ entry.conflict && entry.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
948
+ style: replaceLabelStyle,
949
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
950
+ type: "checkbox",
951
+ checked: replaceNames.has(entry.name),
952
+ onChange: () => toggleReplace(entry.name)
953
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("batchReplace") })]
954
+ }) : null
955
+ ]
956
+ }, entry.id))
957
+ })]
958
+ }) : null,
959
+ batchResults !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
960
+ style: batchPanelStyle,
961
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
962
+ style: batchSummaryStyle,
963
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("batchResultTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
964
+ style: descriptionStyle,
965
+ children: t("batchResultSummary", {
966
+ imported: batchResults.filter((entry) => entry.status === "imported").length,
967
+ replaced: batchResults.filter((entry) => entry.status === "replaced").length,
968
+ skipped: batchResults.filter((entry) => entry.status === "skipped").length,
969
+ failed: batchResults.filter((entry) => entry.status === "error").length
970
+ })
971
+ })]
972
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
973
+ style: batchListStyle,
974
+ children: batchResults.map((entry, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
975
+ style: {
976
+ ...batchResultRowStyle,
977
+ borderBottom: index === batchResults.length - 1 ? "none" : batchResultRowStyle.borderBottom
978
+ },
979
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
980
+ style: {
981
+ ...batchResultBadgeStyle,
982
+ color: entry.status === "error" ? "var(--dsw-alias-state-error-primary)" : entry.status === "skipped" ? "var(--dsw-alias-state-warn-label)" : "var(--dsw-alias-state-success-primary)"
983
+ },
984
+ children: t(entry.status === "imported" ? "batchStatusImported" : entry.status === "replaced" ? "batchStatusReplaced" : entry.status === "skipped" ? "batchStatusSkipped" : "batchStatusError")
985
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
986
+ style: skillContentStyle,
987
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
988
+ style: skillNameStyle,
989
+ children: entry.name
990
+ }), entry.message !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
991
+ style: skillDescriptionStyle,
992
+ children: entry.message
993
+ }) : null]
994
+ })]
995
+ }, `${entry.status}:${entry.name}:${index}`))
996
+ })]
997
+ }) : null
998
+ ]
590
999
  }),
591
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
592
- value: target,
593
- onChange: (event) => setTarget(event.target.value),
594
- style: selectStyle,
1000
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: editorDividerStyle }),
1001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1002
+ style: fieldStyle,
595
1003
  children: [
596
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
597
- value: "project-agents",
598
- disabled: workspace === void 0,
599
- children: workspace === void 0 ? t("targetProjectNoWorkspace") : t("targetProjectAgents", { title: workspace.title })
1004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1005
+ style: fieldLabelStyle,
1006
+ children: t("targetLabel")
600
1007
  }),
601
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
602
- value: "project-dsh",
603
- disabled: workspace === void 0,
604
- children: workspace === void 0 ? t("targetProjectNoWorkspace") : t("targetProjectDsh", { title: workspace.title })
1008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1009
+ value: target,
1010
+ onChange: (event) => changeTarget(event.target.value),
1011
+ style: selectStyle,
1012
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1013
+ value: "project-agents",
1014
+ disabled: workspace === void 0,
1015
+ children: workspace === void 0 ? t("targetProjectNoWorkspace") : t("targetProjectAgents", { title: workspace.title })
1016
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1017
+ value: "user",
1018
+ children: t("targetUser")
1019
+ })]
605
1020
  }),
606
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
607
- value: "user",
608
- children: t("targetUser")
609
- })
1021
+ workspace === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1022
+ style: descriptionStyle,
1023
+ children: t("noWorkspace")
1024
+ }) : null
610
1025
  ]
611
1026
  }),
612
- workspace === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
613
- style: descriptionStyle,
614
- children: t("noWorkspace")
615
- }) : null
1027
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1028
+ style: actionStyle,
1029
+ children: [message !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1030
+ style: {
1031
+ flex: 1,
1032
+ fontSize: 12,
1033
+ lineHeight: "18px",
1034
+ color: messageError ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-state-success-primary)",
1035
+ wordBreak: "break-all"
1036
+ },
1037
+ children: message
1038
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1039
+ type: "button",
1040
+ disabled: canSubmit,
1041
+ onClick: runPrimaryAction,
1042
+ style: canSubmit ? primaryDisabledStyle : primaryStyle,
1043
+ children: primaryLabel
1044
+ })]
1045
+ })
616
1046
  ]
617
- }),
618
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
619
- style: {
620
- display: "flex",
621
- alignItems: "center",
622
- gap: 10,
623
- marginTop: 12
624
- },
625
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
626
- type: "button",
627
- disabled: canSubmit,
628
- onClick: () => {
629
- submit();
630
- },
631
- style: canSubmit ? primaryDisabledStyle : primaryStyle,
632
- children: busy ? t("importing") : t("import")
633
- }), message !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
634
- style: {
635
- fontSize: 12,
636
- color: messageError ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-label-secondary)",
637
- wordBreak: "break-all"
638
- },
639
- children: message
640
- }) : null]
641
1047
  })
642
1048
  ]
643
1049
  })
@@ -682,14 +1088,167 @@ window.__ModuleLoader__.load({
682
1088
  color: "var(--dsw-alias-label-primary)",
683
1089
  cursor: "pointer"
684
1090
  };
685
- const controlStyle = {
1091
+ const skillGroupsStyle = {
1092
+ display: "flex",
1093
+ flexDirection: "column",
1094
+ gap: 10,
1095
+ marginTop: 4
1096
+ };
1097
+ const skillGroupCardStyle = {
1098
+ overflow: "hidden",
1099
+ border: "1px solid var(--dsw-alias-border-l2)",
1100
+ borderRadius: 12,
1101
+ background: "transparent"
1102
+ };
1103
+ const skillGroupHeaderStyle = {
1104
+ width: "100%",
1105
+ minHeight: 42,
686
1106
  padding: "7px 12px",
1107
+ boxSizing: "border-box",
1108
+ display: "flex",
1109
+ alignItems: "center",
1110
+ gap: 10,
1111
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
1112
+ borderTop: "none",
1113
+ borderRight: "none",
1114
+ borderLeft: "none",
1115
+ background: "var(--dsw-alias-bg-module-platform)",
1116
+ color: "inherit",
1117
+ font: "inherit",
1118
+ cursor: "pointer",
1119
+ textAlign: "left"
1120
+ };
1121
+ const skillGroupChevronStyle = {
1122
+ flex: "none",
1123
+ width: 18,
1124
+ display: "inline-flex",
1125
+ alignItems: "center",
1126
+ justifyContent: "center",
1127
+ color: "var(--dsw-alias-label-caption)"
1128
+ };
1129
+ const skillGroupIdentityStyle = {
1130
+ flex: 1,
1131
+ minWidth: 0,
1132
+ display: "flex",
1133
+ alignItems: "baseline",
1134
+ gap: 8,
1135
+ flexWrap: "wrap"
1136
+ };
1137
+ const skillGroupNameStyle = {
1138
+ fontSize: 13,
1139
+ lineHeight: "20px",
1140
+ fontWeight: 500,
1141
+ color: "var(--dsw-alias-label-primary)"
1142
+ };
1143
+ const countBadgeStyle = {
1144
+ flex: "none",
1145
+ minWidth: 22,
1146
+ height: 20,
1147
+ padding: "0 6px",
1148
+ boxSizing: "border-box",
1149
+ display: "inline-flex",
1150
+ alignItems: "center",
1151
+ justifyContent: "center",
1152
+ borderRadius: 10,
1153
+ background: "var(--dsw-alias-interactive-bg-hover)",
1154
+ color: "var(--dsw-alias-label-secondary)",
1155
+ fontSize: 11,
1156
+ lineHeight: "16px"
1157
+ };
1158
+ const skillListStyle = {
1159
+ listStyle: "none",
1160
+ margin: 0,
1161
+ padding: "0 12px",
1162
+ display: "flex",
1163
+ flexDirection: "column"
1164
+ };
1165
+ const skillRowStyle = {
1166
+ minHeight: 62,
1167
+ padding: "9px 0",
1168
+ boxSizing: "border-box",
1169
+ display: "flex",
1170
+ alignItems: "center",
1171
+ gap: 10,
1172
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
1173
+ };
1174
+ const skillAvatarStyle = {
1175
+ flex: "none",
1176
+ width: 32,
1177
+ height: 32,
1178
+ display: "inline-flex",
1179
+ alignItems: "center",
1180
+ justifyContent: "center",
1181
+ borderRadius: 10,
1182
+ background: "var(--dsw-alias-bg-module-platform)",
1183
+ color: "var(--dsw-alias-label-secondary)",
1184
+ fontSize: 13,
1185
+ lineHeight: "20px",
1186
+ fontWeight: 600
1187
+ };
1188
+ const skillContentStyle = {
1189
+ flex: 1,
1190
+ minWidth: 0,
1191
+ display: "flex",
1192
+ flexDirection: "column",
1193
+ gap: 2
1194
+ };
1195
+ const skillNameRowStyle = {
1196
+ display: "flex",
1197
+ alignItems: "center",
1198
+ gap: 7,
1199
+ minWidth: 0
1200
+ };
1201
+ const skillNameStyle = {
1202
+ overflow: "hidden",
1203
+ textOverflow: "ellipsis",
1204
+ whiteSpace: "nowrap",
1205
+ fontSize: 13,
1206
+ lineHeight: "20px",
1207
+ fontWeight: 500,
1208
+ color: "var(--dsw-alias-label-primary)"
1209
+ };
1210
+ const skillDescriptionStyle = {
1211
+ overflow: "hidden",
1212
+ textOverflow: "ellipsis",
1213
+ whiteSpace: "nowrap",
1214
+ fontSize: 12,
1215
+ lineHeight: "18px",
1216
+ color: "var(--dsw-alias-label-tertiary)"
1217
+ };
1218
+ const skillFlagStyle = {
1219
+ flex: "none",
1220
+ padding: "1px 6px",
1221
+ border: "1px solid var(--dsw-alias-border-l3)",
1222
+ borderRadius: 4,
1223
+ fontSize: 10,
1224
+ lineHeight: "14px",
1225
+ color: "var(--dsw-alias-label-secondary)"
1226
+ };
1227
+ const deleteIconButtonStyle = {
1228
+ flex: "none",
1229
+ width: 28,
1230
+ height: 28,
1231
+ padding: 0,
1232
+ display: "inline-flex",
1233
+ alignItems: "center",
1234
+ justifyContent: "center",
1235
+ border: "none",
1236
+ borderRadius: 14,
1237
+ background: "transparent",
1238
+ color: "var(--dsw-alias-label-caption)",
1239
+ cursor: "pointer"
1240
+ };
1241
+ const controlStyle = {
1242
+ boxSizing: "border-box",
1243
+ width: "100%",
1244
+ height: 36,
1245
+ padding: "0 12px",
687
1246
  borderRadius: 8,
688
1247
  border: "1px solid var(--dsw-alias-border-l2)",
689
1248
  background: "var(--dsw-alias-bg-layer-1)",
690
1249
  font: "inherit",
691
- fontSize: 14,
692
- lineHeight: "22px",
1250
+ fontSize: 13,
1251
+ lineHeight: "20px",
693
1252
  color: "var(--dsw-alias-label-primary)",
694
1253
  outline: "none"
695
1254
  };
@@ -705,16 +1264,234 @@ window.__ModuleLoader__.load({
705
1264
  backgroundSize: "12px 12px",
706
1265
  cursor: "pointer"
707
1266
  };
708
- const segmentStyle = (active) => ({
709
- padding: "4px 14px",
710
- borderRadius: 14,
711
- border: "1px solid var(--dsw-alias-border-l2)",
1267
+ const entryGridStyle = {
1268
+ display: "grid",
1269
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
1270
+ gap: 10,
1271
+ marginTop: 12
1272
+ };
1273
+ const entryButtonStyle = (active) => ({
1274
+ boxSizing: "border-box",
1275
+ position: "relative",
1276
+ minWidth: 0,
1277
+ minHeight: 68,
1278
+ padding: "11px 12px",
1279
+ display: "flex",
1280
+ alignItems: "center",
1281
+ gap: 10,
1282
+ border: `1px ${active ? "solid" : "dashed"} ${active ? "var(--dsw-alias-border-l3)" : "var(--dsw-alias-border-l2)"}`,
1283
+ borderRadius: 12,
712
1284
  background: active ? "var(--dsw-alias-bg-module-platform)" : "transparent",
713
1285
  color: active ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)",
714
1286
  font: "inherit",
1287
+ cursor: "pointer",
1288
+ textAlign: "left"
1289
+ });
1290
+ const entryIconStyle = {
1291
+ flex: "none",
1292
+ width: 34,
1293
+ height: 34,
1294
+ borderRadius: 10,
1295
+ display: "inline-flex",
1296
+ alignItems: "center",
1297
+ justifyContent: "center",
1298
+ background: "var(--dsw-alias-interactive-bg-hover)",
1299
+ color: "var(--dsw-alias-label-secondary)"
1300
+ };
1301
+ const entryCopyStyle = {
1302
+ minWidth: 0,
1303
+ display: "flex",
1304
+ flexDirection: "column",
1305
+ gap: 1
1306
+ };
1307
+ const entryTitleStyle = {
715
1308
  fontSize: 13,
1309
+ lineHeight: "20px",
1310
+ fontWeight: 500,
1311
+ color: "var(--dsw-alias-label-primary)"
1312
+ };
1313
+ const entryDescriptionStyle = {
1314
+ fontSize: 11,
1315
+ lineHeight: "16px",
1316
+ color: "var(--dsw-alias-label-tertiary)"
1317
+ };
1318
+ const entryCheckStyle = {
1319
+ position: "absolute",
1320
+ top: 8,
1321
+ right: 8,
1322
+ color: "var(--dsw-alias-state-success-primary)"
1323
+ };
1324
+ const importEditorStyle = {
1325
+ marginTop: 10,
1326
+ padding: "14px 16px",
1327
+ display: "flex",
1328
+ flexDirection: "column",
1329
+ gap: 14,
1330
+ borderRadius: 12,
1331
+ background: "var(--dsw-alias-bg-module-platform)"
1332
+ };
1333
+ const hiddenInputStyle = { display: "none" };
1334
+ const filePickerStyle = {
1335
+ boxSizing: "border-box",
1336
+ width: "100%",
1337
+ minHeight: 62,
1338
+ display: "flex",
1339
+ alignItems: "center",
1340
+ gap: 10,
1341
+ padding: "9px 10px",
1342
+ border: "1px dashed var(--dsw-alias-border-l3)",
1343
+ borderRadius: 10,
1344
+ background: "transparent",
1345
+ color: "var(--dsw-alias-label-secondary)",
1346
+ font: "inherit",
716
1347
  cursor: "pointer"
717
- });
1348
+ };
1349
+ const filePickerIconStyle = {
1350
+ ...entryIconStyle,
1351
+ width: 36,
1352
+ height: 36
1353
+ };
1354
+ const secondaryButtonStyle = {
1355
+ flex: "none",
1356
+ boxSizing: "border-box",
1357
+ height: 28,
1358
+ padding: "0 10px",
1359
+ display: "inline-flex",
1360
+ alignItems: "center",
1361
+ border: "1px solid var(--dsw-alias-border-l2)",
1362
+ borderRadius: 14,
1363
+ color: "var(--dsw-alias-label-primary)",
1364
+ fontSize: 12,
1365
+ lineHeight: "18px"
1366
+ };
1367
+ const previewStyle = {
1368
+ display: "flex",
1369
+ flexDirection: "column",
1370
+ gap: 5,
1371
+ padding: "10px 12px",
1372
+ border: "1px solid var(--dsw-alias-border-l2)",
1373
+ borderRadius: 10,
1374
+ background: "var(--dsw-alias-bg-layer-1)",
1375
+ color: "var(--dsw-alias-label-secondary)",
1376
+ fontSize: 12,
1377
+ lineHeight: "18px"
1378
+ };
1379
+ const previewDividerStyle = {
1380
+ height: 1,
1381
+ margin: "2px 0",
1382
+ background: "var(--dsw-alias-border-l2)"
1383
+ };
1384
+ const previewKeyStyle = {
1385
+ display: "inline-block",
1386
+ minWidth: 70,
1387
+ marginRight: 8,
1388
+ color: "var(--dsw-alias-label-tertiary)",
1389
+ fontWeight: 500
1390
+ };
1391
+ const editorDividerStyle = {
1392
+ height: 1,
1393
+ background: "var(--dsw-alias-border-l2)"
1394
+ };
1395
+ const fieldStyle = {
1396
+ display: "flex",
1397
+ flexDirection: "column",
1398
+ gap: 6,
1399
+ fontSize: 13
1400
+ };
1401
+ const fieldLabelStyle = {
1402
+ fontSize: 12,
1403
+ lineHeight: "18px",
1404
+ fontWeight: 500,
1405
+ color: "var(--dsw-alias-label-secondary)"
1406
+ };
1407
+ const actionStyle = {
1408
+ display: "flex",
1409
+ alignItems: "center",
1410
+ gap: 10,
1411
+ justifyContent: "flex-end"
1412
+ };
1413
+ const batchPanelStyle = {
1414
+ overflow: "hidden",
1415
+ border: "1px solid var(--dsw-alias-border-l2)",
1416
+ borderRadius: 10,
1417
+ background: "var(--dsw-alias-bg-layer-1)"
1418
+ };
1419
+ const batchSummaryStyle = {
1420
+ minHeight: 38,
1421
+ boxSizing: "border-box",
1422
+ padding: "7px 10px",
1423
+ display: "flex",
1424
+ alignItems: "center",
1425
+ justifyContent: "space-between",
1426
+ gap: 10,
1427
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
1428
+ fontSize: 12,
1429
+ lineHeight: "18px",
1430
+ fontWeight: 500,
1431
+ color: "var(--dsw-alias-label-primary)"
1432
+ };
1433
+ const batchListStyle = {
1434
+ listStyle: "none",
1435
+ margin: 0,
1436
+ padding: "0 10px",
1437
+ display: "flex",
1438
+ flexDirection: "column",
1439
+ maxHeight: 320,
1440
+ overflowY: "auto"
1441
+ };
1442
+ const batchRowStyle = {
1443
+ minHeight: 58,
1444
+ padding: "8px 0",
1445
+ display: "flex",
1446
+ alignItems: "center",
1447
+ gap: 9,
1448
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
1449
+ };
1450
+ const batchResultRowStyle = {
1451
+ minHeight: 48,
1452
+ padding: "7px 0",
1453
+ display: "flex",
1454
+ alignItems: "center",
1455
+ gap: 9,
1456
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
1457
+ };
1458
+ const batchStatusDotStyle = {
1459
+ flex: "none",
1460
+ width: 8,
1461
+ height: 8,
1462
+ borderRadius: "50%"
1463
+ };
1464
+ const batchConflictTagStyle = {
1465
+ flex: "none",
1466
+ padding: "1px 6px",
1467
+ borderRadius: 4,
1468
+ border: "1px solid var(--dsw-alias-state-warn-label)",
1469
+ color: "var(--dsw-alias-state-warn-label)",
1470
+ fontSize: 10,
1471
+ lineHeight: "14px"
1472
+ };
1473
+ const batchWarningStyle = {
1474
+ fontSize: 11,
1475
+ lineHeight: "16px",
1476
+ color: "var(--dsw-alias-state-warn-label)"
1477
+ };
1478
+ const replaceLabelStyle = {
1479
+ flex: "none",
1480
+ display: "inline-flex",
1481
+ alignItems: "center",
1482
+ gap: 5,
1483
+ fontSize: 11,
1484
+ lineHeight: "16px",
1485
+ color: "var(--dsw-alias-label-secondary)",
1486
+ cursor: "pointer"
1487
+ };
1488
+ const batchResultBadgeStyle = {
1489
+ flex: "none",
1490
+ minWidth: 42,
1491
+ fontSize: 11,
1492
+ lineHeight: "16px",
1493
+ fontWeight: 500
1494
+ };
718
1495
  const primaryStyle = {
719
1496
  display: "inline-flex",
720
1497
  alignItems: "center",
@@ -975,9 +1752,6 @@ window.__ModuleLoader__.load({
975
1752
  installedTitle: "已安装技能",
976
1753
  refresh: "刷新",
977
1754
  installedEmpty: "暂无技能。导入新技能或手动放入技能目录后会自动出现。",
978
- sourceAgents: "工作区 .agents/skills",
979
- sourceDsh: "工作区 .dsh/skills",
980
- sourceUser: "用户技能目录",
981
1755
  delete: "删除",
982
1756
  deleteConfirm: "确定删除技能 {name} 吗?将删除对应目录下的 SKILL.md 及资源文件。",
983
1757
  deleteFailed: "删除失败",
@@ -986,9 +1760,18 @@ window.__ModuleLoader__.load({
986
1760
  userOnly: "仅斜杠可用",
987
1761
  modelOnly: "仅模型可用",
988
1762
  importTitle: "导入技能",
1763
+ importDescription: "选择导入来源,确认技能信息和安装位置后写入。",
989
1764
  fileTab: "从文件",
990
1765
  urlTab: "从 URL",
1766
+ batchTab: "批量导入",
1767
+ fileEntryDescription: "上传本地 Markdown",
1768
+ urlEntryDescription: "从在线地址获取",
1769
+ batchEntryDescription: "迁移其他 Agent 技能",
991
1770
  chooseFile: "选择 Markdown 文件",
1771
+ filePickerHint: "支持 .md 和 .markdown,最大 256 KB",
1772
+ replaceFile: "已读取并校验,可重新选择",
1773
+ browse: "浏览",
1774
+ change: "更换",
992
1775
  fileHelp: "SKILL.md 或 <名称>.md,需带 frontmatter(name、description 必填)。选择后先预览,再点击导入。",
993
1776
  previewTitle: "文件预览",
994
1777
  previewName: "名称",
@@ -1003,11 +1786,29 @@ window.__ModuleLoader__.load({
1003
1786
  urlLabel: "技能 URL",
1004
1787
  urlNameLabel: "技能名称",
1005
1788
  urlNameHelp: "kebab-case;留空则从 URL 自动推导",
1789
+ batchChooseDirectory: "选择 Skills 目录",
1790
+ batchDirectoryHint: "支持 Claude Code、Codex 等标准技能目录",
1791
+ batchDirectorySelected: "已选择目录;扫描前不会写入任何文件",
1792
+ batchPickFailed: "无法选择目录",
1793
+ batchScan: "扫描并校验",
1794
+ batchWorking: "处理中…",
1795
+ batchCommit: "确认导入",
1796
+ batchScanFailed: "扫描失败",
1797
+ batchImportFailed: "批量导入失败",
1798
+ batchPreflight: "扫描结果",
1799
+ batchConflict: "名称重复",
1800
+ batchReplace: "替换",
1801
+ batchConfirm: "将新增 {importing} 个、替换 {replacing} 个、跳过 {skipped} 个技能。确认继续吗?",
1802
+ batchResultTitle: "本次导入结果",
1803
+ batchResultSummary: "新增 {imported} · 替换 {replaced} · 跳过 {skipped} · 失败 {failed}",
1804
+ batchStatusImported: "已导入",
1805
+ batchStatusReplaced: "已替换",
1806
+ batchStatusSkipped: "已跳过",
1807
+ batchStatusError: "失败",
1006
1808
  targetLabel: "安装到",
1007
- targetUser: "用户技能目录(~/.dsh/skills)",
1008
- targetProjectAgents: "当前工作区 {title} 的 .agents/skills",
1009
- targetProjectDsh: "当前工作区 {title} 的 .dsh/skills",
1010
- targetProjectNoWorkspace: "项目目录(需先添加工作区)",
1809
+ targetUser: "全局技能(~/.dsh/skills)",
1810
+ targetProjectAgents: "项目技能({title} · .agents/skills)",
1811
+ targetProjectNoWorkspace: "项目技能(需先添加工作区)",
1011
1812
  noWorkspace: "未注册工作区:请先在会话页选择/创建工作区,或改用用户技能目录。",
1012
1813
  import: "导入",
1013
1814
  importing: "导入中…",
@@ -1028,9 +1829,6 @@ window.__ModuleLoader__.load({
1028
1829
  installedTitle: "Installed skills",
1029
1830
  refresh: "Refresh",
1030
1831
  installedEmpty: "No skills yet. Import one or drop files into a skill directory and they will appear automatically.",
1031
- sourceAgents: "Workspace .agents/skills",
1032
- sourceDsh: "Workspace .dsh/skills",
1033
- sourceUser: "User skill directory",
1034
1832
  delete: "Delete",
1035
1833
  deleteConfirm: "Delete the skill {name}? This removes its SKILL.md and resources from that directory.",
1036
1834
  deleteFailed: "Failed to delete",
@@ -1039,9 +1837,18 @@ window.__ModuleLoader__.load({
1039
1837
  userOnly: "Slash only",
1040
1838
  modelOnly: "Model only",
1041
1839
  importTitle: "Import a skill",
1840
+ importDescription: "Choose a source, review the skill and destination, then import it.",
1042
1841
  fileTab: "From file",
1043
1842
  urlTab: "From URL",
1843
+ batchTab: "Batch import",
1844
+ fileEntryDescription: "Upload local Markdown",
1845
+ urlEntryDescription: "Fetch from a web address",
1846
+ batchEntryDescription: "Migrate other agent skills",
1044
1847
  chooseFile: "Choose a Markdown file",
1848
+ filePickerHint: "Supports .md and .markdown, up to 256 KB",
1849
+ replaceFile: "Read and validated; choose another file if needed",
1850
+ browse: "Browse",
1851
+ change: "Change",
1045
1852
  fileHelp: "SKILL.md or <name>.md with frontmatter (name and description required). Preview first, then import.",
1046
1853
  previewTitle: "File preview",
1047
1854
  previewName: "Name",
@@ -1056,11 +1863,29 @@ window.__ModuleLoader__.load({
1056
1863
  urlLabel: "Skill URL",
1057
1864
  urlNameLabel: "Skill name",
1058
1865
  urlNameHelp: "kebab-case; leave empty to derive from the URL",
1866
+ batchChooseDirectory: "Choose a Skills directory",
1867
+ batchDirectoryHint: "Supports standard Claude Code, Codex, and similar skill roots",
1868
+ batchDirectorySelected: "Directory selected; scanning never writes files",
1869
+ batchPickFailed: "Unable to choose a directory",
1870
+ batchScan: "Scan and validate",
1871
+ batchWorking: "Working…",
1872
+ batchCommit: "Confirm import",
1873
+ batchScanFailed: "Scan failed",
1874
+ batchImportFailed: "Batch import failed",
1875
+ batchPreflight: "Preflight results",
1876
+ batchConflict: "Name conflict",
1877
+ batchReplace: "Replace",
1878
+ batchConfirm: "Add {importing}, replace {replacing}, and skip {skipped} skills. Continue?",
1879
+ batchResultTitle: "Import results",
1880
+ batchResultSummary: "Added {imported} · Replaced {replaced} · Skipped {skipped} · Failed {failed}",
1881
+ batchStatusImported: "Imported",
1882
+ batchStatusReplaced: "Replaced",
1883
+ batchStatusSkipped: "Skipped",
1884
+ batchStatusError: "Failed",
1059
1885
  targetLabel: "Install into",
1060
- targetUser: "User skill directory (~/.dsh/skills)",
1061
- targetProjectAgents: "Workspace {title} .agents/skills",
1062
- targetProjectDsh: "Workspace {title} .dsh/skills",
1063
- targetProjectNoWorkspace: "Project directory (add a workspace first)",
1886
+ targetUser: "Global skills (~/.dsh/skills)",
1887
+ targetProjectAgents: "Project skills ({title} · .agents/skills)",
1888
+ targetProjectNoWorkspace: "Project skills (add a workspace first)",
1064
1889
  noWorkspace: "No registered workspace: create/select one in a session, or use the user skill directory.",
1065
1890
  import: "Import",
1066
1891
  importing: "Importing…",
@@ -1081,7 +1906,9 @@ window.__ModuleLoader__.load({
1081
1906
  list: "/skill-importer/list",
1082
1907
  import: "/skill-importer/import",
1083
1908
  importUrl: "/skill-importer/import-url",
1084
- delete: "/skill-importer/delete"
1909
+ delete: "/skill-importer/delete",
1910
+ batchScan: "/skill-importer/batch/scan",
1911
+ batchCommit: "/skill-importer/batch/commit"
1085
1912
  };
1086
1913
  /** Fetch one route and unwrap the JSON envelope; throws on transport/HTTP errors. */
1087
1914
  async function call(path, body) {
@@ -1123,7 +1950,8 @@ window.__ModuleLoader__.load({
1123
1950
  console.error("[dsh-skill-importer] listener failed:", error);
1124
1951
  }
1125
1952
  };
1126
- const skillsApi = ctx.get("connection").api.skills;
1953
+ const connection = ctx.get("connection");
1954
+ const skillsApi = connection.api.skills;
1127
1955
  skillsApi.list.bind(skillsApi);
1128
1956
  skillsApi.list = ((_request, _signal) => {
1129
1957
  return Promise.reject(/* @__PURE__ */ new Error("skill group hidden: use the composer skill picker or /skills"));
@@ -1190,6 +2018,13 @@ window.__ModuleLoader__.load({
1190
2018
  const deleteSkill = async (request) => {
1191
2019
  return (await call(ROUTES.delete, request)).removed;
1192
2020
  };
2021
+ const pickDirectory = async () => {
2022
+ const response = await connection.api.host.pickDirectory({});
2023
+ if (!response.result.ok) throw new Error(response.result.error.message);
2024
+ return response.result.value.path;
2025
+ };
2026
+ const scanBatch = (request) => call(ROUTES.batchScan, request);
2027
+ const commitBatch = (request) => call(ROUTES.batchCommit, request);
1193
2028
  const face = () => ({
1194
2029
  hooks: { skills: {
1195
2030
  getSnapshot: () => catalog,
@@ -1204,7 +2039,10 @@ window.__ModuleLoader__.load({
1204
2039
  refreshSkills,
1205
2040
  importSkill,
1206
2041
  importUrl,
1207
- deleteSkill
2042
+ deleteSkill,
2043
+ pickDirectory,
2044
+ scanBatch,
2045
+ commitBatch
1208
2046
  }
1209
2047
  });
1210
2048
  ctx.slots.inject("settings.section", () => ctx.slots.register({