dsh-skill-importer 0.1.2 → 0.2.1

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