skills 1.5.18 → 1.5.19

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.
@@ -627,60 +627,6 @@ var r = class extends V {
627
627
  });
628
628
  }
629
629
  };
630
- let u$2 = class u extends V {
631
- options;
632
- cursor = 0;
633
- #t;
634
- getGroupItems(t) {
635
- return this.options.filter((r) => r.group === t);
636
- }
637
- isGroupSelected(t) {
638
- const r = this.getGroupItems(t), e = this.value;
639
- return e === void 0 ? false : r.every((s) => e.includes(s.value));
640
- }
641
- toggleValue() {
642
- const t = this.options[this.cursor];
643
- if (t !== void 0) if (this.value === void 0 && (this.value = []), t.group === true) {
644
- const r = t.value, e = this.getGroupItems(r);
645
- this.isGroupSelected(r) ? this.value = this.value.filter((s) => e.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...e.map((s) => s.value)], this.value = Array.from(new Set(this.value));
646
- } else {
647
- const r = this.value.includes(t.value);
648
- this.value = r ? this.value.filter((e) => e !== t.value) : [...this.value, t.value];
649
- }
650
- }
651
- constructor(t) {
652
- super(t, false);
653
- const { options: r } = t;
654
- this.#t = t.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [{
655
- value: e,
656
- group: true,
657
- label: e
658
- }, ...s.map((i) => ({
659
- ...i,
660
- group: e
661
- }))]), this.value = [...t.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
662
- switch (e) {
663
- case "left":
664
- case "up": {
665
- this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
666
- const s = this.options[this.cursor]?.group === true;
667
- !this.#t && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
668
- break;
669
- }
670
- case "down":
671
- case "right": {
672
- this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
673
- const s = this.options[this.cursor]?.group === true;
674
- !this.#t && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
675
- break;
676
- }
677
- case "space":
678
- this.toggleValue();
679
- break;
680
- }
681
- });
682
- }
683
- };
684
630
  var a = class extends V {
685
631
  options;
686
632
  cursor = 0;
@@ -755,4 +701,4 @@ let n$1 = class n extends V {
755
701
  });
756
702
  }
757
703
  };
758
- export { a, block, fastStringWidth, getColumns, getRows, isCancel, n$1, r, require_src, settings, u$2, wrapAnsi, wrapTextWithPrefix };
704
+ export { a, block, fastStringWidth, getColumns, getRows, isCancel, n$1, r, require_src, settings, wrapAnsi, wrapTextWithPrefix };
@@ -1,4 +1,4 @@
1
- import { a, block, fastStringWidth, getColumns, getRows, n$1, r, require_src, settings, u$2, wrapAnsi, wrapTextWithPrefix } from "./core.mjs";
1
+ import { a, block, fastStringWidth, getColumns, getRows, n$1, r, require_src, settings, wrapAnsi, wrapTextWithPrefix } from "./core.mjs";
2
2
  import { styleText } from "node:util";
3
3
  import process$1 from "node:process";
4
4
  var import_src = require_src();
@@ -213,100 +213,6 @@ ${h}
213
213
  }
214
214
  }).prompt();
215
215
  };
216
- const groupMultiselect = (o) => {
217
- const { selectableGroups: h = true, groupSpacing: x = 0 } = o, m = (n, l, g = []) => {
218
- const a = n.label ?? String(n.value), t = typeof n.group == "string", s = t && (g[g.indexOf(n) + 1] ?? { group: true }), u = t && s && s.group === true;
219
- let r = "", c = "";
220
- t && (h ? (r = u ? `${S_BAR_END} ` : `${S_BAR} `, c = u ? " " : `${S_BAR} `) : r = " ");
221
- let i = "";
222
- if (x > 0 && !t && (i = `
223
- `.repeat(x)), l === "active") return wrapTextWithPrefix(o.output, `${a}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${styleText("cyan", S_CHECKBOX_ACTIVE)} `, `${i}${styleText("dim", c)} `);
224
- if (l === "group-active") return wrapTextWithPrefix(o.output, a, `${i}${r} `, `${i}${r}${styleText("cyan", S_CHECKBOX_ACTIVE)} `, `${i}${c} `, (d) => styleText("dim", d));
225
- if (l === "group-active-selected") return wrapTextWithPrefix(o.output, a, `${i}${r} `, `${i}${r}${styleText("green", S_CHECKBOX_SELECTED)} `, `${i}${c} `, (d) => styleText("dim", d));
226
- if (l === "selected") {
227
- const d = t || h ? styleText("green", S_CHECKBOX_SELECTED) : "";
228
- return wrapTextWithPrefix(o.output, `${a}${n.hint ? ` (${n.hint})` : ""}`, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${d} `, `${i}${styleText("dim", c)} `, (S) => styleText("dim", S));
229
- }
230
- if (l === "cancelled") return `${styleText(["strikethrough", "dim"], a)}`;
231
- if (l === "active-selected") return wrapTextWithPrefix(o.output, `${a}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${styleText("green", S_CHECKBOX_SELECTED)} `, `${i}${styleText("dim", c)} `);
232
- if (l === "submitted") return `${styleText("dim", a)}`;
233
- const f = t || h ? styleText("dim", S_CHECKBOX_INACTIVE) : "";
234
- return wrapTextWithPrefix(o.output, a, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${f} `, `${i}${styleText("dim", c)} `, (d) => styleText("dim", d));
235
- }, y = o.required ?? true, I = o.showInstructions ?? true;
236
- return new u$2({
237
- options: o.options,
238
- signal: o.signal,
239
- input: o.input,
240
- output: o.output,
241
- initialValues: o.initialValues,
242
- required: y,
243
- cursorAt: o.cursorAt,
244
- selectableGroups: h,
245
- validate(n) {
246
- if (y && (n === void 0 || n.length === 0)) return `Please select at least one option.
247
- ${styleText("reset", styleText("dim", `Press ${styleText([
248
- "gray",
249
- "bgWhite",
250
- "inverse"
251
- ], " space ")} to select, ${styleText("gray", styleText(["bgWhite", "inverse"], " enter "))} to submit`))}`;
252
- },
253
- render() {
254
- const n = o.withGuide ?? settings.withGuide, l = `${n ? `${styleText("gray", S_BAR)}
255
- ` : ""}${symbol(this.state)} ${o.message}
256
- `, g = this.value ?? [], a = (t, s) => {
257
- const u = this.options, r = g.includes(t.value) || t.group === true && this.isGroupSelected(`${t.value}`);
258
- return !s && typeof t.group == "string" && this.options[this.cursor]?.value === t.group ? m(t, r ? "group-active-selected" : "group-active", u) : s && r ? m(t, "active-selected", u) : r ? m(t, "selected", u) : m(t, s ? "active" : "inactive", u);
259
- };
260
- switch (this.state) {
261
- case "submit": {
262
- const t = this.options.filter(({ value: u }) => g.includes(u)).map((u) => m(u, "submitted")), s = t.length === 0 ? "" : ` ${t.join(styleText("dim", ", "))}`;
263
- return `${l}${n ? styleText("gray", S_BAR) : ""}${s}`;
264
- }
265
- case "cancel": {
266
- const t = this.options.filter(({ value: s }) => g.includes(s)).map((s) => m(s, "cancelled")).join(styleText("dim", ", "));
267
- return `${l}${n ? `${styleText("gray", S_BAR)} ` : ""}${t.trim() ? `${t}${n ? `
268
- ${styleText("gray", S_BAR)}` : ""}` : ""}`;
269
- }
270
- case "error": {
271
- const t = n ? `${styleText("yellow", S_BAR)} ` : "", s = this.error.split(`
272
- `).map((i, f) => f === 0 ? `${n ? `${styleText("yellow", S_BAR_END)} ` : ""}${styleText("yellow", i)}` : ` ${i}`).join(`
273
- `), u = l.split(`
274
- `).length, r = s.split(`
275
- `).length + 1;
276
- return `${l}${t}${limitOptions({
277
- output: o.output,
278
- options: this.options,
279
- cursor: this.cursor,
280
- maxItems: o.maxItems,
281
- columnPadding: t.length,
282
- rowPadding: u + r,
283
- style: a
284
- }).join(`
285
- ${t}`)}
286
- ${s}
287
- `;
288
- }
289
- default: {
290
- const t = n ? `${styleText("cyan", S_BAR)} ` : "", s = l.split(`
291
- `).length, u = I ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], r = u.join(`
292
- `), c = u.length + 1;
293
- return `${l}${t}${limitOptions({
294
- output: o.output,
295
- options: this.options,
296
- cursor: this.cursor,
297
- maxItems: o.maxItems,
298
- columnPadding: t.length,
299
- rowPadding: s + c,
300
- style: a
301
- }).join(`
302
- ${t}`)}
303
- ${r}
304
- `;
305
- }
306
- }
307
- }
308
- }).prompt();
309
- };
310
216
  const log = {
311
217
  message: (s = [], { symbol: e = styleText("gray", S_BAR), secondarySymbol: r = styleText("gray", S_BAR), output: m = process.stdout, spacing: l = 1, withGuide: c } = {}) => {
312
218
  const t = [], o = c ?? settings.withGuide, f = o ? r : "", O = o ? `${e} ` : "", u = o ? `${r} ` : "";
@@ -536,4 +442,4 @@ ${b}
536
442
  }).prompt();
537
443
  };
538
444
  `${styleText("gray", S_BAR)}`;
539
- export { cancel, confirm, groupMultiselect, intro, log, multiselect, note, outro, select, spinner };
445
+ export { cancel, confirm, intro, log, multiselect, note, outro, select, spinner };
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { __toESM } from "./_chunks/rolldown-runtime.mjs";
3
3
  import { isCancel } from "./_chunks/libs/@clack/core.mjs";
4
- import { cancel, confirm, groupMultiselect, intro, log, multiselect as multiselect$1, note, outro, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
4
+ import { cancel, confirm, intro, log, multiselect as multiselect$1, note, outro, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
5
5
  import { require_picocolors } from "./_chunks/libs/picocolors.mjs";
6
6
  import { esm_default } from "./_chunks/libs/simple-git.mjs";
7
7
  import { xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
@@ -71,7 +71,10 @@ function sanitizeSubpath(subpath) {
71
71
  function isLocalPath(input) {
72
72
  return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
73
73
  }
74
- const SOURCE_ALIASES = { "coinbase/agentWallet": "coinbase/agentic-wallet-skills" };
74
+ const SOURCE_ALIASES = {
75
+ "coinbase/agentWallet": "coinbase/agentic-wallet-skills",
76
+ "vercel-labs/vercel-skills": "vercel-labs/agent-skills"
77
+ };
75
78
  function decodeFragmentValue(value) {
76
79
  try {
77
80
  return decodeURIComponent(value);
@@ -277,8 +280,88 @@ function countVisualRowsForLines(lines, columns) {
277
280
  const cols = columns !== void 0 && columns > 0 ? columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
278
281
  return lines.reduce((sum, line) => sum + visualRowsForLine(line, cols), 0);
279
282
  }
283
+ function truncateToWidth(text, width) {
284
+ let truncated = "";
285
+ for (const char of text) {
286
+ if (approxStringWidth(truncated + char) > width) break;
287
+ truncated += char;
288
+ }
289
+ return truncated;
290
+ }
291
+ function formatDetailLines(detail, width, maxLines) {
292
+ const safeWidth = Math.max(1, width);
293
+ const normalized = detail?.replace(/\s+/g, " ").trim() ?? "";
294
+ const lines = [];
295
+ let remaining = normalized;
296
+ while (remaining && lines.length < maxLines) {
297
+ if (approxStringWidth(remaining) <= safeWidth) {
298
+ lines.push(remaining);
299
+ remaining = "";
300
+ break;
301
+ }
302
+ const candidate = truncateToWidth(remaining, safeWidth);
303
+ const breakAt = candidate.lastIndexOf(" ");
304
+ if (breakAt > 0) {
305
+ lines.push(candidate.slice(0, breakAt).trimEnd());
306
+ remaining = remaining.slice(breakAt).trimStart();
307
+ } else {
308
+ lines.push(candidate);
309
+ remaining = remaining.slice(candidate.length).trimStart();
310
+ }
311
+ }
312
+ if (remaining && lines.length > 0) {
313
+ const last = lines.length - 1;
314
+ lines[last] = `${truncateToWidth(lines[last], Math.max(0, safeWidth - 1)).trimEnd()}…`;
315
+ }
316
+ while (lines.length < maxLines) lines.push("");
317
+ return lines;
318
+ }
319
+ function buildSearchEntries(items, selectGroups, collapsedGroups = /* @__PURE__ */ new Set()) {
320
+ if (!selectGroups) return items.map((item) => ({
321
+ type: "item",
322
+ item
323
+ }));
324
+ const entries = [];
325
+ let index = 0;
326
+ while (index < items.length) {
327
+ const item = items[index];
328
+ if (!item.group) {
329
+ entries.push({
330
+ type: "item",
331
+ item
332
+ });
333
+ index += 1;
334
+ continue;
335
+ }
336
+ const groupItems = [];
337
+ while (index < items.length && items[index].group === item.group) {
338
+ groupItems.push(items[index]);
339
+ index += 1;
340
+ }
341
+ const collapsed = collapsedGroups.has(item.group);
342
+ entries.push({
343
+ type: "group",
344
+ group: item.group,
345
+ items: groupItems,
346
+ collapsed
347
+ });
348
+ if (!collapsed) entries.push(...groupItems.map((groupItem) => ({
349
+ type: "item",
350
+ item: groupItem
351
+ })));
352
+ }
353
+ return entries;
354
+ }
355
+ function toggleSearchEntry(selected, entry) {
356
+ if (entry?.type === "group") {
357
+ const allSelected = entry.items.every((item) => selected.has(item.value));
358
+ for (const item of entry.items) if (allSelected) selected.delete(item.value);
359
+ else selected.add(item.value);
360
+ } else if (entry?.type === "item") if (selected.has(entry.item.value)) selected.delete(entry.item.value);
361
+ else selected.add(entry.item.value);
362
+ }
280
363
  async function searchMultiselect(options) {
281
- const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection } = options;
364
+ const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, searchable = true, showDetail = false, detailLines = 2, showSelectedSummary = true, selectGroups = false } = options;
282
365
  return new Promise((resolve) => {
283
366
  const rl = readline.createInterface({
284
367
  input: process.stdin,
@@ -290,6 +373,7 @@ async function searchMultiselect(options) {
290
373
  let query = "";
291
374
  let cursor = 0;
292
375
  const selected = new Set(initialSelected);
376
+ const collapsedGroups = /* @__PURE__ */ new Set();
293
377
  let lastRenderHeight = 0;
294
378
  const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
295
379
  const filter = (item, q) => {
@@ -300,17 +384,10 @@ async function searchMultiselect(options) {
300
384
  const getFiltered = () => {
301
385
  return items.filter((item) => filter(item, query));
302
386
  };
303
- const clearRender = () => {
304
- if (lastRenderHeight > 0) {
305
- process.stdout.write(`\x1b[${lastRenderHeight}A`);
306
- for (let i = 0; i < lastRenderHeight; i++) process.stdout.write("\x1B[2K\x1B[1B");
307
- process.stdout.write(`\x1b[${lastRenderHeight}A`);
308
- }
309
- };
310
387
  const render = (state = "active") => {
311
- clearRender();
312
388
  const lines = [];
313
389
  const filtered = getFiltered();
390
+ const entries = buildSearchEntries(filtered, selectGroups, collapsedGroups);
314
391
  const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
315
392
  lines.push(`${icon} ${import_picocolors.default.bold(message)}`);
316
393
  if (state === "active") {
@@ -323,28 +400,41 @@ async function searchMultiselect(options) {
323
400
  lines.push(`${S_BAR}`);
324
401
  lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
325
402
  }
326
- const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
327
- lines.push(searchLine);
328
- lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
329
- lines.push(`${S_BAR}`);
330
- const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), filtered.length - maxVisible));
331
- const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
332
- const visibleItems = filtered.slice(visibleStart, visibleEnd);
403
+ if (searchable) {
404
+ const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
405
+ lines.push(searchLine);
406
+ lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
407
+ lines.push(`${S_BAR}`);
408
+ }
409
+ const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), entries.length - maxVisible));
410
+ const visibleEnd = Math.min(entries.length, visibleStart + maxVisible);
411
+ const visibleEntries = entries.slice(visibleStart, visibleEnd);
333
412
  if (filtered.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("No matches found")}`);
334
413
  else {
335
- for (let i = 0; i < visibleItems.length; i++) {
336
- const item = visibleItems[i];
337
- const actualIndex = visibleStart + i;
338
- const isSelected = selected.has(item.value);
339
- const isCursor = actualIndex === cursor;
340
- const radio = isSelected ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
414
+ for (let i = 0; i < visibleEntries.length; i++) {
415
+ const entry = visibleEntries[i];
416
+ const isCursor = visibleStart + i === cursor;
417
+ if (entry.type === "group") {
418
+ const selectedCount = entry.items.filter((item) => selected.has(item.value)).length;
419
+ const radio = selectedCount === entry.items.length ? S_RADIO_ACTIVE : selectedCount > 0 ? import_picocolors.default.yellow("◐") : S_RADIO_INACTIVE;
420
+ const label = isCursor ? import_picocolors.default.underline(import_picocolors.default.bold(entry.group)) : import_picocolors.default.bold(entry.group);
421
+ const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
422
+ const disclosure = import_picocolors.default.dim(entry.collapsed ? "▸" : "▾");
423
+ lines.push(`${S_BAR} ${prefix} ${disclosure} ${radio} ${label}`);
424
+ continue;
425
+ }
426
+ const item = entry.item;
427
+ const radio = selected.has(item.value) ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
341
428
  const label = isCursor ? import_picocolors.default.underline(item.label) : item.label;
342
429
  const hint = item.hint ? import_picocolors.default.dim(` (${item.hint})`) : "";
343
430
  const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
344
- lines.push(`${S_BAR} ${prefix} ${radio} ${label}${hint}`);
431
+ const groupItems = selectGroups && item.group ? filtered.filter((i) => i.group === item.group) : [];
432
+ const isLastInGroup = groupItems.at(-1) === item;
433
+ const tree = groupItems.length > 0 ? `${import_picocolors.default.dim(isLastInGroup ? "└─" : "├─")} ` : "";
434
+ lines.push(`${S_BAR} ${prefix} ${tree}${radio} ${label}${hint}`);
345
435
  }
346
436
  const hiddenBefore = visibleStart;
347
- const hiddenAfter = filtered.length - visibleEnd;
437
+ const hiddenAfter = entries.length - visibleEnd;
348
438
  if (hiddenBefore > 0 || hiddenAfter > 0) {
349
439
  const parts = [];
350
440
  if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
@@ -352,19 +442,35 @@ async function searchMultiselect(options) {
352
442
  lines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
353
443
  }
354
444
  }
355
- lines.push(`${S_BAR}`);
356
- const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
357
- if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
358
- else {
359
- const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
360
- lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
445
+ if (showDetail) {
446
+ const entry = entries[cursor];
447
+ const detail = entry?.type === "group" ? `Select all ${entry.items.length} skills in ${entry.group}.` : entry?.item.detail;
448
+ const columns = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
449
+ const detailWidth = Math.max(1, columns - 5);
450
+ lines.push(`${S_BAR}`);
451
+ lines.push(`${S_BAR} ${import_picocolors.default.dim("Description")}`);
452
+ for (const line of formatDetailLines(detail, detailWidth, detailLines)) lines.push(`${S_BAR} ${import_picocolors.default.dim(line)}`);
453
+ }
454
+ if (showSelectedSummary) {
455
+ lines.push(`${S_BAR}`);
456
+ const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
457
+ if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
458
+ else {
459
+ const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
460
+ lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
461
+ }
462
+ }
463
+ if (!searchable) {
464
+ lines.push(`${S_BAR}`);
465
+ lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, ←→ collapse/expand, space select, enter confirm")}`);
361
466
  }
362
467
  lines.push(`${import_picocolors.default.dim("└")}`);
363
468
  } else if (state === "submit") {
364
469
  const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
365
470
  lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
366
471
  } else if (state === "cancel") lines.push(`${S_BAR} ${import_picocolors.default.strikethrough(import_picocolors.default.dim("Cancelled"))}`);
367
- process.stdout.write(lines.join("\n") + "\n");
472
+ const clearPreviousFrame = lastRenderHeight > 0 ? `\x1b[${lastRenderHeight}A\x1b[J` : "";
473
+ process.stdout.write(clearPreviousFrame + lines.join("\n") + "\n");
368
474
  lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
369
475
  };
370
476
  const cleanup = () => {
@@ -385,7 +491,7 @@ async function searchMultiselect(options) {
385
491
  };
386
492
  const keypressHandler = (_str, key) => {
387
493
  if (!key) return;
388
- const filtered = getFiltered();
494
+ const entries = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups);
389
495
  if (key.name === "return") {
390
496
  submit();
391
497
  return;
@@ -400,14 +506,31 @@ async function searchMultiselect(options) {
400
506
  return;
401
507
  }
402
508
  if (key.name === "down") {
403
- cursor = Math.min(filtered.length - 1, cursor + 1);
509
+ cursor = Math.min(entries.length - 1, cursor + 1);
404
510
  render();
405
511
  return;
406
512
  }
513
+ if (selectGroups && key.name === "right") {
514
+ const entry = entries[cursor];
515
+ if (entry?.type === "group" && entry.collapsed) {
516
+ collapsedGroups.delete(entry.group);
517
+ render();
518
+ }
519
+ return;
520
+ }
521
+ if (selectGroups && key.name === "left") {
522
+ const entry = entries[cursor];
523
+ const group = entry?.type === "group" ? entry.group : entry?.item.group;
524
+ if (group) {
525
+ collapsedGroups.add(group);
526
+ cursor = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups).findIndex((collapsedEntry) => collapsedEntry.type === "group" && collapsedEntry.group === group);
527
+ render();
528
+ }
529
+ return;
530
+ }
407
531
  if (key.name === "space") {
408
- const item = filtered[cursor];
409
- if (item) if (selected.has(item.value)) selected.delete(item.value);
410
- else selected.add(item.value);
532
+ const entry = entries[cursor];
533
+ toggleSearchEntry(selected, entry);
411
534
  render();
412
535
  return;
413
536
  }
@@ -417,7 +540,7 @@ async function searchMultiselect(options) {
417
540
  render();
418
541
  return;
419
542
  }
420
- if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
543
+ if (searchable && key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
421
544
  query += key.sequence;
422
545
  cursor = 0;
423
546
  render();
@@ -429,6 +552,7 @@ async function searchMultiselect(options) {
429
552
  });
430
553
  }
431
554
  const DEFAULT_CLONE_TIMEOUT_MS = 3e5;
555
+ const ALLOWED_GIT_PROTOCOLS = "https:http:ssh:git:file";
432
556
  const CLONE_TIMEOUT_MS = (() => {
433
557
  const raw = process.env.SKILLS_CLONE_TIMEOUT_MS;
434
558
  if (!raw) return DEFAULT_CLONE_TIMEOUT_MS;
@@ -526,6 +650,7 @@ function createGitClient(sshCommand) {
526
650
  git.env({
527
651
  ...process.env,
528
652
  GIT_TERMINAL_PROMPT: "0",
653
+ GIT_ALLOW_PROTOCOL: ALLOWED_GIT_PROTOCOLS,
529
654
  GIT_LFS_SKIP_SMUDGE: "1",
530
655
  ...sshCommand ? { GIT_SSH_COMMAND: sshCommand } : {}
531
656
  });
@@ -573,7 +698,8 @@ async function tryGhClone(repo, tempDir, ref) {
573
698
  timeout: CLONE_TIMEOUT_MS,
574
699
  env: {
575
700
  ...process.env,
576
- GIT_TERMINAL_PROMPT: "0"
701
+ GIT_TERMINAL_PROMPT: "0",
702
+ GIT_ALLOW_PROTOCOL: ALLOWED_GIT_PROTOCOLS
577
703
  }
578
704
  });
579
705
  return true;
@@ -3208,7 +3334,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
3208
3334
  tree
3209
3335
  };
3210
3336
  }
3211
- var version$1 = "1.5.18";
3337
+ var version$1 = "1.5.19";
3212
3338
  const isCancelled$1 = (value) => typeof value === "symbol";
3213
3339
  const EVE_AGENT_LABEL = "eve agent";
3214
3340
  async function isSourcePrivate(source) {
@@ -3900,34 +4026,24 @@ async function runAdd(args, options = {}) {
3900
4026
  return getSkillDisplayName(a).localeCompare(getSkillDisplayName(b));
3901
4027
  });
3902
4028
  const hasGroups = sortedSkills.some((s) => s.pluginName);
3903
- let selected;
3904
- if (hasGroups) {
3905
- const kebabToTitle = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3906
- const grouped = {};
3907
- for (const s of sortedSkills) {
3908
- const groupName = s.pluginName ? kebabToTitle(s.pluginName) : "Other";
3909
- if (!grouped[groupName]) grouped[groupName] = [];
3910
- grouped[groupName].push({
3911
- value: s,
3912
- label: getSkillDisplayName(s),
3913
- hint: s.description.length > 60 ? s.description.slice(0, 57) + "…" : s.description
3914
- });
3915
- }
3916
- selected = await groupMultiselect({
3917
- message: `Select skills to install ${import_picocolors.default.dim("(space to toggle)")}`,
3918
- options: grouped,
3919
- required: true
3920
- });
3921
- } else selected = await multiselect({
3922
- message: "Select skills to install",
3923
- options: sortedSkills.map((s) => ({
3924
- value: s,
3925
- label: getSkillDisplayName(s),
3926
- hint: s.description.length > 60 ? s.description.slice(0, 57) + "…" : s.description
3927
- })),
3928
- required: true
4029
+ const kebabToTitle = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4030
+ const skillChoices = sortedSkills.map((s) => ({
4031
+ value: s,
4032
+ label: getSkillDisplayName(s),
4033
+ group: hasGroups ? s.pluginName ? kebabToTitle(s.pluginName) : "Other" : void 0,
4034
+ detail: s.description
4035
+ }));
4036
+ const selected = await searchMultiselect({
4037
+ message: hasGroups ? `Select skills to install ${import_picocolors.default.dim("(space to toggle)")}` : "Select skills to install",
4038
+ items: skillChoices,
4039
+ required: true,
4040
+ maxVisible: 20,
4041
+ searchable: !hasGroups,
4042
+ showDetail: true,
4043
+ showSelectedSummary: false,
4044
+ selectGroups: hasGroups
3929
4045
  });
3930
- if (isCancel(selected)) {
4046
+ if (isCancelled$1(selected)) {
3931
4047
  cancel("Installation cancelled");
3932
4048
  await cleanup(tempDir);
3933
4049
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.18",
3
+ "version": "1.5.19",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -136,7 +136,7 @@
136
136
  "xdg-basedir": "^5.1.0"
137
137
  },
138
138
  "engines": {
139
- "node": ">=18"
139
+ "node": ">=22.20.0"
140
140
  },
141
141
  "packageManager": "pnpm@10.17.1",
142
142
  "dependencies": {