skillspp 0.2.0 → 1.3.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/dist/cli.js CHANGED
@@ -20,9 +20,7 @@ function parseSource(input) {
20
20
  if (isLocalPath(input)) {
21
21
  return { type: "local", localPath: path.resolve(input) };
22
22
  }
23
- const githubTreeWithPath = input.match(
24
- /github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/
25
- );
23
+ const githubTreeWithPath = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
26
24
  if (githubTreeWithPath) {
27
25
  const [, owner, repo, ref, subpath] = githubTreeWithPath;
28
26
  return {
@@ -32,9 +30,7 @@ function parseSource(input) {
32
30
  subpath
33
31
  };
34
32
  }
35
- const githubTree = input.match(
36
- /github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/
37
- );
33
+ const githubTree = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
38
34
  if (githubTree) {
39
35
  const [, owner, repo, ref] = githubTree;
40
36
  return {
@@ -81,13 +77,7 @@ function parseSource(input) {
81
77
  import fs from "node:fs";
82
78
  import path2 from "node:path";
83
79
  import matter from "gray-matter";
84
- var SKIP_DIRS = /* @__PURE__ */ new Set([
85
- ".git",
86
- "node_modules",
87
- "dist",
88
- "build",
89
- "__pycache__"
90
- ]);
80
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "__pycache__"]);
91
81
  function resolveSourceLabel(parsedSource) {
92
82
  switch (parsedSource.type) {
93
83
  case "local":
@@ -136,12 +126,7 @@ function findSkillDirsRecursive(dir, depth, maxDepth, out) {
136
126
  if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
137
127
  continue;
138
128
  }
139
- findSkillDirsRecursive(
140
- path2.join(dir, entry.name),
141
- depth + 1,
142
- maxDepth,
143
- out
144
- );
129
+ findSkillDirsRecursive(path2.join(dir, entry.name), depth + 1, maxDepth, out);
145
130
  }
146
131
  }
147
132
  function discoverSkills(basePath) {
@@ -498,9 +483,7 @@ function normalizeAgentSelectionInput(values, cwd = process.cwd()) {
498
483
  if (unknown.length === 0) {
499
484
  return values;
500
485
  }
501
- const expandedFromGlob = unknown.every(
502
- (value) => fs2.existsSync(path3.resolve(cwd, value))
503
- );
486
+ const expandedFromGlob = unknown.every((value) => fs2.existsSync(path3.resolve(cwd, value)));
504
487
  if (!expandedFromGlob) {
505
488
  return values;
506
489
  }
@@ -544,22 +527,20 @@ function isAgentInstalled(agent, cwd) {
544
527
  return false;
545
528
  }
546
529
 
547
- // ../../packages/core/src/runtime/installer.ts
548
- function sanitizeSkillName(name) {
549
- const sanitized = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "");
550
- return sanitized || "unnamed-skill";
551
- }
530
+ // ../../packages/cli-shared/src/ui/selection-step.tsx
531
+ import { useMemo, useState as useState2 } from "react";
532
+ import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
552
533
 
553
- // src/ui/screens.tsx
534
+ // ../../packages/cli-shared/src/ui/screens.tsx
554
535
  import { stripVTControlCharacters } from "node:util";
555
536
  import React, { useEffect, useState } from "react";
556
537
  import { Box, Text, render, useInput, useStdout } from "ink";
557
538
 
558
- // src/ui/format.ts
539
+ // ../../packages/cli-shared/src/ui/format.ts
559
540
  import os2 from "node:os";
560
541
  import path4 from "node:path";
561
542
 
562
- // src/ui/colors.ts
543
+ // ../../packages/cli-shared/src/ui/colors.ts
563
544
  var ANSI_ESCAPE = "\x1B[";
564
545
  var ANSI_RESET = "\x1B[0m";
565
546
  var COLOR_TOKENS = {
@@ -593,7 +574,7 @@ function dim(text, colorEnabled) {
593
574
  return ansiStyle(text, "2", colorEnabled);
594
575
  }
595
576
 
596
- // src/ui/format.ts
577
+ // ../../packages/cli-shared/src/ui/format.ts
597
578
  function shortenHomePath(value, homeDir = os2.homedir()) {
598
579
  if (value === homeDir || value.startsWith(`${homeDir}${path4.sep}`)) {
599
580
  return `~${value.slice(homeDir.length)}`;
@@ -620,25 +601,31 @@ function formatDriftChips(options) {
620
601
  )}`;
621
602
  }
622
603
 
623
- // src/ui/logo.ts
604
+ // ../../packages/cli-shared/src/ui/logo.ts
624
605
  import fs3 from "node:fs";
625
- import path5 from "node:path";
626
- import { fileURLToPath } from "node:url";
627
606
  var DEFAULT_LOGO_FPS = 12;
628
607
  var EMPTY_TEXT_ONLY_LOGO = [];
629
608
  var animatedCache;
630
609
  var staticCache;
631
- function resolveLogoDir() {
632
- const dirname = path5.dirname(fileURLToPath(import.meta.url));
633
- return path5.resolve(dirname, "../assets/ascii/logo");
610
+ var configuredSessionPath = null;
611
+ var configuredStaticPath = null;
612
+ function normalizeConfiguredPath(filePath) {
613
+ if (typeof filePath !== "string") {
614
+ return null;
615
+ }
616
+ const trimmed = filePath.trim();
617
+ return trimmed.length > 0 ? trimmed : null;
618
+ }
619
+ function configureLogoAssetPaths(paths) {
620
+ configuredSessionPath = normalizeConfiguredPath(paths.sessionPath);
621
+ configuredStaticPath = normalizeConfiguredPath(paths.textPath);
622
+ resetLogoCache();
634
623
  }
635
624
  function resolveSessionPath() {
636
- const customPath = process.env.SKILLSPP_LOGO_SESSION_PATH;
637
- return customPath || path5.join(resolveLogoDir(), "skillspp-logo.session.json");
625
+ return normalizeConfiguredPath(process.env.SKILLSPP_LOGO_SESSION_PATH) ?? configuredSessionPath;
638
626
  }
639
627
  function resolveStaticPath() {
640
- const customPath = process.env.SKILLSPP_LOGO_TEXT_PATH;
641
- return customPath || path5.join(resolveLogoDir(), "skillspp-logo.txt");
628
+ return normalizeConfiguredPath(process.env.SKILLSPP_LOGO_TEXT_PATH) ?? configuredStaticPath;
642
629
  }
643
630
  function trimOuterEmptyRows(lines) {
644
631
  let start = 0;
@@ -747,6 +734,10 @@ function readFileSafe(filePath) {
747
734
  return null;
748
735
  }
749
736
  }
737
+ function resetLogoCache() {
738
+ animatedCache = void 0;
739
+ staticCache = void 0;
740
+ }
750
741
  function getAnimatedLogoFrames() {
751
742
  if (animatedCache !== void 0) {
752
743
  return animatedCache;
@@ -783,7 +774,7 @@ function getBannerLogoLines() {
783
774
  return getStaticLogoLines() ?? EMPTY_TEXT_ONLY_LOGO;
784
775
  }
785
776
 
786
- // src/ui/screens.tsx
777
+ // ../../packages/cli-shared/src/ui/screens.tsx
787
778
  import { jsx, jsxs } from "react/jsx-runtime";
788
779
  var DEFAULT_BANNER_WIDTH = 78;
789
780
  var DEFAULT_PANEL_MIN_WIDTH = 58;
@@ -906,10 +897,7 @@ function resolveTerminalColumns() {
906
897
  return Math.max(MIN_TERMINAL_WIDTH, process.stdout.columns || 80);
907
898
  }
908
899
  function resolveMaxInnerWidth(indent = " ") {
909
- return Math.max(
910
- MIN_INNER_WIDTH,
911
- resolveTerminalColumns() - indent.length - FRAME_OVERHEAD
912
- );
900
+ return Math.max(MIN_INNER_WIDTH, resolveTerminalColumns() - indent.length - FRAME_OVERHEAD);
913
901
  }
914
902
  function clampToTerminalInnerWidth(idealWidth, minWidth, indent = " ") {
915
903
  const maxInnerWidth = resolveMaxInnerWidth(indent);
@@ -917,9 +905,7 @@ function clampToTerminalInnerWidth(idealWidth, minWidth, indent = " ") {
917
905
  return Math.max(lowerBound, Math.min(idealWidth, maxInnerWidth));
918
906
  }
919
907
  function resolvePanelWidth(options) {
920
- const lineLengths = options.lines.map(
921
- (line) => visibleLength(singleLine(line))
922
- );
908
+ const lineLengths = options.lines.map((line) => visibleLength(singleLine(line)));
923
909
  return Math.max(
924
910
  options.minWidth ?? DEFAULT_PANEL_MIN_WIDTH,
925
911
  singleLine(options.title).length + 4,
@@ -928,10 +914,7 @@ function resolvePanelWidth(options) {
928
914
  }
929
915
  function resolveSelectionColumnWidths(innerWidth, targetLabelWidth, targetDescWidth) {
930
916
  const availableColumns = Math.max(8, innerWidth - SELECTION_ROW_OVERHEAD);
931
- const targetTotal = Math.max(
932
- 8,
933
- targetLabelWidth + Math.max(0, targetDescWidth)
934
- );
917
+ const targetTotal = Math.max(8, targetLabelWidth + Math.max(0, targetDescWidth));
935
918
  if (availableColumns >= targetTotal) {
936
919
  return {
937
920
  labelWidth: targetLabelWidth,
@@ -957,25 +940,13 @@ function renderFramedPanel(options) {
957
940
  const bottomLeft = options.style === "rounded" ? "\u2570" : "\u2514";
958
941
  const bottomRight = options.style === "rounded" ? "\u256F" : "\u2518";
959
942
  const out = [];
960
- out.push(
961
- `${options.indent}${dim(
962
- `${topLeft}\u2500 ${title} ${"\u2500".repeat(topTail)}${topRight}`
963
- )}`
964
- );
943
+ out.push(`${options.indent}${dim(`${topLeft}\u2500 ${title} ${"\u2500".repeat(topTail)}${topRight}`)}`);
965
944
  for (const line of options.lines) {
966
945
  for (const wrappedLine of wrapVisibleLine(line, width)) {
967
- out.push(
968
- `${options.indent}${dim("\u2502")} ${padRight(wrappedLine, width)} ${dim(
969
- "\u2502"
970
- )}`
971
- );
946
+ out.push(`${options.indent}${dim("\u2502")} ${padRight(wrappedLine, width)} ${dim("\u2502")}`);
972
947
  }
973
948
  }
974
- out.push(
975
- `${options.indent}${dim(
976
- `${bottomLeft}${"\u2500".repeat(bottomWidth)}${bottomRight}`
977
- )}`
978
- );
949
+ out.push(`${options.indent}${dim(`${bottomLeft}${"\u2500".repeat(bottomWidth)}${bottomRight}`)}`);
979
950
  return out.join("\n");
980
951
  }
981
952
  function toSelectedRows(rows, selectedIds) {
@@ -1022,11 +993,7 @@ function resolveSelectionPanelLayout(options) {
1022
993
  options.minWidth ?? DEFAULT_PANEL_MIN_WIDTH,
1023
994
  indent
1024
995
  );
1025
- const columns = resolveSelectionColumnWidths(
1026
- width,
1027
- options.labelWidth,
1028
- options.descWidth
1029
- );
996
+ const columns = resolveSelectionColumnWidths(width, options.labelWidth, options.descWidth);
1030
997
  return {
1031
998
  width,
1032
999
  labelWidth: columns.labelWidth,
@@ -1045,10 +1012,7 @@ function renderSectionToText(section) {
1045
1012
  switch (section.type) {
1046
1013
  case "banner": {
1047
1014
  const logoLines = getBannerLogoLines();
1048
- const widestLogoLine = logoLines.reduce(
1049
- (max, line) => Math.max(max, visibleLength(line)),
1050
- 0
1051
- );
1015
+ const widestLogoLine = logoLines.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
1052
1016
  const resolvedWidth = clampToTerminalInnerWidth(
1053
1017
  Math.max(
1054
1018
  section.width ?? DEFAULT_BANNER_WIDTH,
@@ -1075,7 +1039,7 @@ function renderSectionToText(section) {
1075
1039
  return renderPanelText(section);
1076
1040
  }
1077
1041
  case "source":
1078
- return finalizeUiBlock(` Skills source: ${section.source}`);
1042
+ return finalizeUiBlock(` ${section.source}`);
1079
1043
  case "lines":
1080
1044
  return finalizeUiBlock(section.lines.join("\n"));
1081
1045
  case "text":
@@ -1096,10 +1060,7 @@ function HistoryText({ text }) {
1096
1060
  return /* @__PURE__ */ jsx(Text, { children: text.replace(/\n$/, "") });
1097
1061
  }
1098
1062
  function renderPinnedLogoHeaderText(lines) {
1099
- const widestLogoLine = lines.reduce(
1100
- (max, line) => Math.max(max, visibleLength(line)),
1101
- 0
1102
- );
1063
+ const widestLogoLine = lines.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
1103
1064
  const resolvedWidth = clampToTerminalInnerWidth(
1104
1065
  Math.max(PINNED_LOGO_HEADER_WIDTH, widestLogoLine),
1105
1066
  Math.max(1, widestLogoLine)
@@ -1271,10 +1232,7 @@ function renderSectionToTextWithLogoLines(section, logoLinesOverride) {
1271
1232
  return renderSectionToText(section);
1272
1233
  }
1273
1234
  const logoLines = logoLinesOverride ?? getBannerLogoLines();
1274
- const widestLogoLine = logoLines.reduce(
1275
- (max, line) => Math.max(max, visibleLength(line)),
1276
- 0
1277
- );
1235
+ const widestLogoLine = logoLines.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
1278
1236
  const resolvedWidth = clampToTerminalInnerWidth(
1279
1237
  Math.max(
1280
1238
  section.width ?? DEFAULT_BANNER_WIDTH,
@@ -1290,11 +1248,7 @@ function renderSectionToTextWithLogoLines(section, logoLinesOverride) {
1290
1248
  center(section.title, resolvedWidth)
1291
1249
  ];
1292
1250
  return finalizeUiBlock(
1293
- [
1294
- `\u256D${border}\u256E`,
1295
- ...bodyLines.map((line) => `\u2502${line}\u2502`),
1296
- `\u2570${border}\u256F`
1297
- ].join("\n")
1251
+ [`\u256D${border}\u256E`, ...bodyLines.map((line) => `\u2502${line}\u2502`), `\u2570${border}\u256F`].join("\n")
1298
1252
  );
1299
1253
  }
1300
1254
  function freezeBannerSections(sections, logoLines) {
@@ -1350,17 +1304,13 @@ function statusStepsSection(steps) {
1350
1304
  );
1351
1305
  }
1352
1306
  function completedStepsSection(steps) {
1353
- return statusStepsSection(
1354
- steps.map((step) => ({ status: "completed", label: step }))
1355
- );
1307
+ return statusStepsSection(steps.map((step) => ({ status: "completed", label: step })));
1356
1308
  }
1357
1309
  function failedStepsSection(steps) {
1358
- return statusStepsSection(
1359
- steps.map((step) => ({ status: "failed", label: step }))
1360
- );
1310
+ return statusStepsSection(steps.map((step) => ({ status: "failed", label: step })));
1361
1311
  }
1362
- function sourceSection(source) {
1363
- return { type: "source", source };
1312
+ function sourceSection(source, label = "Skills source") {
1313
+ return { type: "source", source: `${label}: ${source}` };
1364
1314
  }
1365
1315
  function completionSummarySection(options) {
1366
1316
  const skillLabel = options.skillCount === 1 ? "skill" : "skills";
@@ -1383,10 +1333,7 @@ function installationSummarySection(options) {
1383
1333
  )} ${bold("Agents:")} ${colorToken(
1384
1334
  options.agentCount.toString(),
1385
1335
  "primary"
1386
- )} ${bold("Targets:")} ${colorToken(
1387
- options.targetCount.toString(),
1388
- "primary"
1389
- )}`,
1336
+ )} ${bold("Targets:")} ${colorToken(options.targetCount.toString(), "primary")}`,
1390
1337
  "",
1391
1338
  bold("Targets:")
1392
1339
  ];
@@ -1411,13 +1358,14 @@ function installationSummarySection(options) {
1411
1358
  });
1412
1359
  }
1413
1360
  function uninstallSummarySection(options) {
1361
+ const itemLabel = options.itemLabel || "Skills";
1414
1362
  return panelSection({
1415
1363
  title: "Uninstall Summary",
1416
1364
  lines: [
1417
1365
  `Scope: ${options.globalInstall ? "global" : "current project"}`,
1418
1366
  "",
1419
- `Skills (${options.skillNames.length}):`,
1420
- ...options.skillNames.map((name) => ` - ${name}`),
1367
+ `${itemLabel} (${options.itemNames.length}):`,
1368
+ ...options.itemNames.map((name) => ` - ${name}`),
1421
1369
  "",
1422
1370
  `Agents (${options.agentDisplayNames.length}): ${compactAgentDisplayNames(
1423
1371
  [...options.agentDisplayNames].sort((a, b) => a.localeCompare(b)),
@@ -1534,9 +1482,7 @@ function Spinner({ label }) {
1534
1482
  return /* @__PURE__ */ jsx(Text, { children: ` ${colorToken(frames[frameIndex], "primary")} ${label}` });
1535
1483
  }
1536
1484
 
1537
- // src/ui/selection-step.tsx
1538
- import { useMemo, useState as useState2 } from "react";
1539
- import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
1485
+ // ../../packages/cli-shared/src/ui/selection-step.tsx
1540
1486
  import { jsx as jsx2 } from "react/jsx-runtime";
1541
1487
  var DEFAULT_REQUIRED_MESSAGE = "At least one choice must be selected";
1542
1488
  function assertPromptAllowed(shouldPrompt, interactive) {
@@ -1550,9 +1496,7 @@ function filterSelectionRowIndexes(rows, searchTerm) {
1550
1496
  return rows.map((_, index) => index);
1551
1497
  }
1552
1498
  return rows.reduce((acc, row, index) => {
1553
- const haystack = `${row.label} ${row.description || ""}`.toLocaleLowerCase(
1554
- "en-US"
1555
- );
1499
+ const haystack = `${row.label} ${row.description || ""}`.toLocaleLowerCase("en-US");
1556
1500
  if (haystack.includes(normalized)) {
1557
1501
  acc.push(index);
1558
1502
  }
@@ -1603,6 +1547,31 @@ function toVisibleRows(model) {
1603
1547
  };
1604
1548
  });
1605
1549
  }
1550
+ function resolveVisibleSelectionViewport(rows, activeIndex, maxVisibleRows) {
1551
+ const limit = Math.max(0, Math.floor(maxVisibleRows ?? 0));
1552
+ if (limit === 0 || rows.length <= limit) {
1553
+ return {
1554
+ rows,
1555
+ truncatedTop: false,
1556
+ truncatedBottom: false
1557
+ };
1558
+ }
1559
+ const clampedActiveIndex = clampActiveVisibleIndex(activeIndex, rows.length);
1560
+ const maxStart = rows.length - limit;
1561
+ let start = clampedActiveIndex - Math.floor(limit / 2);
1562
+ if (start < 0) {
1563
+ start = 0;
1564
+ }
1565
+ if (start > maxStart) {
1566
+ start = maxStart;
1567
+ }
1568
+ const end = start + limit;
1569
+ return {
1570
+ rows: rows.slice(start, end),
1571
+ truncatedTop: start > 0,
1572
+ truncatedBottom: end < rows.length
1573
+ };
1574
+ }
1606
1575
  function buildRenderModel(options) {
1607
1576
  return {
1608
1577
  title: options.request.title,
@@ -1611,9 +1580,7 @@ function buildRenderModel(options) {
1611
1580
  label: row.label,
1612
1581
  description: row.description
1613
1582
  })),
1614
- visibleRowIds: options.visibleIndexes.map(
1615
- (index) => options.rows[index].id
1616
- ),
1583
+ visibleRowIds: options.visibleIndexes.map((index) => options.rows[index].id),
1617
1584
  activeVisibleIndex: options.activeVisibleIndex,
1618
1585
  selectedIds: options.selectedIds ?? selectedRowIds(options.rows),
1619
1586
  searchable: Boolean(options.request.searchable),
@@ -1630,7 +1597,11 @@ function appendSearchChar(current, text) {
1630
1597
  return normalizeSearchTerm(`${current}${text}`);
1631
1598
  }
1632
1599
  function renderManySelectionOpenPanel(config, model) {
1633
- const visibleRows = toVisibleRows(model);
1600
+ const viewport = resolveVisibleSelectionViewport(
1601
+ toVisibleRows(model),
1602
+ model.activeVisibleIndex,
1603
+ config.maxVisibleRows
1604
+ );
1634
1605
  const hintLine = selectionHintsText(
1635
1606
  model.keyHints.length > 0 ? model.keyHints : config.defaultHints
1636
1607
  );
@@ -1658,10 +1629,13 @@ function renderManySelectionOpenPanel(config, model) {
1658
1629
  lines.push(`Search: ${model.searchTerm}`);
1659
1630
  lines.push("");
1660
1631
  }
1661
- if (visibleRows.length === 0) {
1632
+ if (viewport.rows.length === 0) {
1662
1633
  lines.push(" (no matches)");
1663
1634
  } else {
1664
- for (const row of visibleRows) {
1635
+ if (viewport.truncatedTop) {
1636
+ lines.push(" ...");
1637
+ }
1638
+ for (const row of viewport.rows) {
1665
1639
  const prefix = ` ${row.active ? "\u203A" : " "} `;
1666
1640
  const marker = row.selected ? colorToken("\u25CF", "primary") : "\u25CB";
1667
1641
  const content = formatSelectionDisplayLine({
@@ -1674,6 +1648,9 @@ function renderManySelectionOpenPanel(config, model) {
1674
1648
  const renderedRow = `${prefix}${marker} ${row.active || row.selected ? content : dim(content)}`;
1675
1649
  lines.push(renderedRow);
1676
1650
  }
1651
+ if (viewport.truncatedBottom) {
1652
+ lines.push(" ...");
1653
+ }
1677
1654
  }
1678
1655
  lines.push("");
1679
1656
  lines.push(` ${hintLine}`);
@@ -1689,14 +1666,8 @@ function renderManySelectionOpenPanel(config, model) {
1689
1666
  }
1690
1667
  function renderSingleSelectionOpenPanel(config, model) {
1691
1668
  const visibleRows = toVisibleRows(model);
1692
- const maxLabelWidth = visibleRows.reduce(
1693
- (max, row) => Math.max(max, row.label.length),
1694
- 8
1695
- );
1696
- const maxDescWidth = visibleRows.reduce(
1697
- (max, row) => Math.max(max, row.description.length),
1698
- 0
1699
- );
1669
+ const maxLabelWidth = visibleRows.reduce((max, row) => Math.max(max, row.label.length), 8);
1670
+ const maxDescWidth = visibleRows.reduce((max, row) => Math.max(max, row.description.length), 0);
1700
1671
  const layout = resolveSelectionPanelLayout({
1701
1672
  title: config.title,
1702
1673
  staticLines: [config.instructionLine, "\u2191\u2193 navigate enter confirm"],
@@ -1731,9 +1702,7 @@ function SelectionRenderer({ content }) {
1731
1702
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(Text2, { children: content.replace(/\n$/, "") }) });
1732
1703
  }
1733
1704
  function MultiSelectPrompt(props) {
1734
- const initialSelectedIds = new Set(
1735
- props.options.initialSelectedIds || props.selectedIds || []
1736
- );
1705
+ const initialSelectedIds = new Set(props.options.initialSelectedIds || props.selectedIds || []);
1737
1706
  const [rows, setRows] = useState2(
1738
1707
  props.rows.map((row) => ({
1739
1708
  ...row,
@@ -1744,16 +1713,10 @@ function MultiSelectPrompt(props) {
1744
1713
  const [activeVisibleIndex, setActiveVisibleIndex] = useState2(0);
1745
1714
  const [errorMessage, setErrorMessage] = useState2();
1746
1715
  const visibleIndexes = useMemo(
1747
- () => filterSelectionRowIndexes(
1748
- rows,
1749
- props.options.searchable !== false ? searchTerm : ""
1750
- ),
1716
+ () => filterSelectionRowIndexes(rows, props.options.searchable !== false ? searchTerm : ""),
1751
1717
  [rows, searchTerm, props.options.searchable]
1752
1718
  );
1753
- const clampedIndex = clampActiveVisibleIndex(
1754
- activeVisibleIndex,
1755
- visibleIndexes.length
1756
- );
1719
+ const clampedIndex = clampActiveVisibleIndex(activeVisibleIndex, visibleIndexes.length);
1757
1720
  useInput2((input, key) => {
1758
1721
  if (key.ctrl && input === "c") {
1759
1722
  props.onCancel(new PromptCancelledError());
@@ -1765,9 +1728,7 @@ function MultiSelectPrompt(props) {
1765
1728
  }
1766
1729
  if (key.upArrow) {
1767
1730
  if (visibleIndexes.length > 0) {
1768
- setActiveVisibleIndex(
1769
- (clampedIndex - 1 + visibleIndexes.length) % visibleIndexes.length
1770
- );
1731
+ setActiveVisibleIndex((clampedIndex - 1 + visibleIndexes.length) % visibleIndexes.length);
1771
1732
  }
1772
1733
  setErrorMessage(void 0);
1773
1734
  return;
@@ -1780,9 +1741,7 @@ function MultiSelectPrompt(props) {
1780
1741
  return;
1781
1742
  }
1782
1743
  if (input === " ") {
1783
- setRows(
1784
- (prev) => toggleSelectionAtVisibleIndex(prev, visibleIndexes, clampedIndex)
1785
- );
1744
+ setRows((prev) => toggleSelectionAtVisibleIndex(prev, visibleIndexes, clampedIndex));
1786
1745
  setErrorMessage(void 0);
1787
1746
  return;
1788
1747
  }
@@ -1810,9 +1769,7 @@ function MultiSelectPrompt(props) {
1810
1769
  if (key.return) {
1811
1770
  const selectedIds = selectedRowIds(rows);
1812
1771
  if ((props.options.required ?? true) && selectedIds.length === 0) {
1813
- setErrorMessage(
1814
- props.options.requiredMessage || DEFAULT_REQUIRED_MESSAGE
1815
- );
1772
+ setErrorMessage(props.options.requiredMessage || DEFAULT_REQUIRED_MESSAGE);
1816
1773
  return;
1817
1774
  }
1818
1775
  props.onSubmit(selectedIds);
@@ -1875,9 +1832,7 @@ function SingleSelectPrompt(props) {
1875
1832
  if (key.return) {
1876
1833
  const selectedId2 = props.rows[activeVisibleIndex]?.id || "";
1877
1834
  if ((props.options.required ?? true) && !selectedId2) {
1878
- setErrorMessage(
1879
- props.options.requiredMessage || DEFAULT_REQUIRED_MESSAGE
1880
- );
1835
+ setErrorMessage(props.options.requiredMessage || DEFAULT_REQUIRED_MESSAGE);
1881
1836
  return;
1882
1837
  }
1883
1838
  props.onSubmit(selectedId2);
@@ -1959,7 +1914,148 @@ async function runOneSelectionStep(options) {
1959
1914
  return selectedId;
1960
1915
  }
1961
1916
 
1962
- // src/interactive.ts
1917
+ // ../../packages/cli-shared/src/add-command.ts
1918
+ function parseAddLockFormatValue(value) {
1919
+ if (!value) {
1920
+ return void 0;
1921
+ }
1922
+ if (value !== "json" && value !== "yaml") {
1923
+ throw new Error(`Invalid --lock-format value: ${value}`);
1924
+ }
1925
+ return value;
1926
+ }
1927
+ function buildBaseAddOptions(input, presence = {}) {
1928
+ const maxDownloadBytes = input.maxDownloadBytes ? Number(input.maxDownloadBytes) : void 0;
1929
+ if (typeof maxDownloadBytes === "number" && (!Number.isFinite(maxDownloadBytes) || maxDownloadBytes <= 0)) {
1930
+ throw new Error(`Invalid --max-download-bytes value: ${input.maxDownloadBytes}`);
1931
+ }
1932
+ const parsed = {
1933
+ global: Boolean(input.global),
1934
+ symlink: Boolean(input.symlink),
1935
+ yaml: Boolean(input.yaml),
1936
+ list: Boolean(input.list),
1937
+ all: Boolean(input.all),
1938
+ nonInteractive: Boolean(input.nonInteractive),
1939
+ trustWellKnown: Boolean(input.trustWellKnown),
1940
+ agent: normalizeAgentSelectionInput(input.agent),
1941
+ skill: input.selectedNames,
1942
+ allowHost: input.allowHost?.map((item) => item.toLowerCase()),
1943
+ denyHost: input.denyHost?.map((item) => item.toLowerCase()),
1944
+ maxDownloadBytes,
1945
+ policyMode: input.policyMode,
1946
+ lockFormat: parseAddLockFormatValue(input.lockFormat),
1947
+ experimental: input.experimental ?? false
1948
+ };
1949
+ if (parsed.agent && parsed.agent.length > 0) {
1950
+ parsed.agentFlagProvided = true;
1951
+ }
1952
+ if (presence.agentProvided) {
1953
+ parsed.agentFlagProvided = true;
1954
+ }
1955
+ if (presence.globalProvided) {
1956
+ parsed.globalFlagProvided = true;
1957
+ }
1958
+ if (presence.symlinkProvided) {
1959
+ parsed.symlinkFlagProvided = true;
1960
+ }
1961
+ if (parsed.all) {
1962
+ parsed.skill = ["*"];
1963
+ parsed.agent = ["*"];
1964
+ }
1965
+ return parsed;
1966
+ }
1967
+ function buildNamedAddSelectionRows(items) {
1968
+ return items.map((item) => ({
1969
+ id: item.name,
1970
+ label: item.name,
1971
+ description: item.description
1972
+ }));
1973
+ }
1974
+ function filterNamedAddSelection(items, requested) {
1975
+ if (!requested || requested.length === 0) {
1976
+ return [...items];
1977
+ }
1978
+ if (requested.includes("*")) {
1979
+ return [...items];
1980
+ }
1981
+ const wanted = new Set(requested.map((item) => item.toLowerCase()));
1982
+ return items.filter((item) => wanted.has(item.name.toLowerCase()));
1983
+ }
1984
+ async function resolveNamedAddSelection(options) {
1985
+ const {
1986
+ available,
1987
+ interactive,
1988
+ listMode,
1989
+ requested,
1990
+ rows,
1991
+ keyHints,
1992
+ view,
1993
+ promptTitle,
1994
+ requiredMessage,
1995
+ emptyMessage,
1996
+ multipleInNonInteractiveMessage,
1997
+ renderClosed
1998
+ } = options;
1999
+ if (listMode) {
2000
+ return filterNamedAddSelection(available, requested);
2001
+ }
2002
+ const prompt = {
2003
+ title: promptTitle,
2004
+ required: true,
2005
+ requiredMessage,
2006
+ searchable: true,
2007
+ keyHints: [...keyHints],
2008
+ view
2009
+ };
2010
+ if (requested) {
2011
+ const selected = filterNamedAddSelection(available, requested);
2012
+ if (selected.length > 0) {
2013
+ await runManySelectionStep({
2014
+ interactive,
2015
+ rows: [...rows],
2016
+ selectedIds: selected.map((item) => item.name),
2017
+ shouldPrompt: false,
2018
+ prompt,
2019
+ renderClosed
2020
+ });
2021
+ }
2022
+ return selected;
2023
+ }
2024
+ if (available.length === 0) {
2025
+ throw new Error(emptyMessage);
2026
+ }
2027
+ if (available.length === 1) {
2028
+ await runManySelectionStep({
2029
+ interactive,
2030
+ rows: [...rows],
2031
+ selectedIds: [available[0].name],
2032
+ shouldPrompt: false,
2033
+ prompt,
2034
+ renderClosed
2035
+ });
2036
+ return [...available];
2037
+ }
2038
+ if (!interactive) {
2039
+ throw new Error(multipleInNonInteractiveMessage);
2040
+ }
2041
+ const selectedNames = await runManySelectionStep({
2042
+ interactive,
2043
+ rows: [...rows],
2044
+ shouldPrompt: true,
2045
+ prompt,
2046
+ renderClosed
2047
+ });
2048
+ const wanted = new Set(selectedNames);
2049
+ return available.filter((item) => wanted.has(item.name));
2050
+ }
2051
+
2052
+ // ../../packages/core/src/runtime/installer.ts
2053
+ function sanitizeSkillName(name) {
2054
+ const sanitized = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "");
2055
+ return sanitized || "unnamed-skill";
2056
+ }
2057
+
2058
+ // ../../packages/cli-shared/src/interactive.ts
1963
2059
  function isPromptCancelledError(error) {
1964
2060
  return error instanceof PromptCancelledError;
1965
2061
  }
@@ -1997,7 +2093,7 @@ function parsePolicyMode(value) {
1997
2093
  throw new Error(`Invalid --policy-mode value: ${value}`);
1998
2094
  }
1999
2095
 
2000
- // src/command-builder.ts
2096
+ // ../../packages/cli-shared/src/command-builder.ts
2001
2097
  import { CommanderError } from "commander";
2002
2098
 
2003
2099
  // ../../packages/core/src/runtime/telemetry.ts
@@ -2034,8 +2130,8 @@ function emitLifecycleEvent(emitter, event) {
2034
2130
  emitter.publish(row);
2035
2131
  }
2036
2132
 
2037
- // src/command-builder.ts
2038
- function createCliCommandContext(emitter, options) {
2133
+ // ../../packages/cli-shared/src/command-builder.ts
2134
+ function createCliCommandContext(emitter, options = {}) {
2039
2135
  const emitCommandEvent = (command, event) => {
2040
2136
  emitLifecycleEvent(emitter, {
2041
2137
  eventType: event.eventType,
@@ -2048,7 +2144,7 @@ function createCliCommandContext(emitter, options) {
2048
2144
  });
2049
2145
  };
2050
2146
  return {
2051
- experimental: options.experimental,
2147
+ experimental: Boolean(options.experimental),
2052
2148
  emitCommandEvent,
2053
2149
  wrapAction: (command, action) => async (...args) => {
2054
2150
  emitCommandEvent(command, {
@@ -2075,12 +2171,52 @@ function createCliCommandContext(emitter, options) {
2075
2171
  }
2076
2172
  };
2077
2173
  }
2174
+ function applyExitOverride(command) {
2175
+ command.exitOverride((error) => {
2176
+ throw error;
2177
+ });
2178
+ for (const subcommand of command.commands) {
2179
+ applyExitOverride(subcommand);
2180
+ }
2181
+ }
2182
+ function isGracefulCommanderExit(error) {
2183
+ return error instanceof CommanderError && (error.code === "commander.helpDisplayed" || error.code === "commander.version");
2184
+ }
2185
+ function inferCommandSource(argv, options = {}) {
2186
+ const valueFlags = new Set(options.valueFlags ?? []);
2187
+ for (let i = 0; i < argv.length; i += 1) {
2188
+ const arg = argv[i];
2189
+ if (valueFlags.has(arg)) {
2190
+ i += 1;
2191
+ continue;
2192
+ }
2193
+ if (arg.startsWith("-")) {
2194
+ continue;
2195
+ }
2196
+ return arg;
2197
+ }
2198
+ return options.fallbackSource ?? "cli";
2199
+ }
2200
+ function emitCommanderParseErrorTelemetry(emitter, argv, error, options = {}) {
2201
+ const source = inferCommandSource(argv, options);
2202
+ emitLifecycleEvent(emitter, {
2203
+ eventType: `${source}_failed`,
2204
+ source,
2205
+ reason: "commander_parse_error",
2206
+ command: source,
2207
+ status: "error",
2208
+ error: error.message,
2209
+ metadata: {
2210
+ commanderCode: error.code
2211
+ }
2212
+ });
2213
+ }
2078
2214
 
2079
2215
  // ../../packages/platform-node/src/background-runner.ts
2080
2216
  import fs4 from "node:fs";
2081
- import path6 from "node:path";
2217
+ import path5 from "node:path";
2082
2218
  import { spawn } from "node:child_process";
2083
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2219
+ import { fileURLToPath } from "node:url";
2084
2220
  var activeChildren = /* @__PURE__ */ new Set();
2085
2221
  var cleanupHandlersInstalled = false;
2086
2222
  function cleanupActiveChildren() {
@@ -2110,12 +2246,12 @@ function installCleanupHandlers() {
2110
2246
  }
2111
2247
  }
2112
2248
  function resolveWorkerEntry() {
2113
- const dir = path6.dirname(fileURLToPath2(import.meta.url));
2114
- const tsPath = path6.join(dir, "background-worker.ts");
2249
+ const dir = path5.dirname(fileURLToPath(import.meta.url));
2250
+ const tsPath = path5.join(dir, "background-worker.ts");
2115
2251
  if (fs4.existsSync(tsPath)) {
2116
2252
  return tsPath;
2117
2253
  }
2118
- return path6.join(dir, "background-worker.js");
2254
+ return path5.join(dir, "background-worker.js");
2119
2255
  }
2120
2256
  function appendOutputChunk(chunks, chunk) {
2121
2257
  const next = chunk.toString();
@@ -2131,18 +2267,14 @@ async function runBackgroundTask(request, options) {
2131
2267
  installCleanupHandlers();
2132
2268
  return new Promise((resolve, reject) => {
2133
2269
  const childCwd = typeof request === "object" && request !== null && "payload" in request && typeof request.payload?.cwd === "string" ? request.payload.cwd : process.cwd();
2134
- const child = spawn(
2135
- process.execPath,
2136
- [...process.execArgv, resolveWorkerEntry()],
2137
- {
2138
- cwd: childCwd,
2139
- env: {
2140
- ...process.env,
2141
- SKILLSPP_BG_EXECUTOR: options.executorModule
2142
- },
2143
- stdio: ["ignore", "pipe", "pipe", "ipc"]
2144
- }
2145
- );
2270
+ const child = spawn(process.execPath, [...process.execArgv, resolveWorkerEntry()], {
2271
+ cwd: childCwd,
2272
+ env: {
2273
+ ...process.env,
2274
+ SKILLSPP_BG_EXECUTOR: options.executorModule
2275
+ },
2276
+ stdio: ["ignore", "pipe", "pipe", "ipc"]
2277
+ });
2146
2278
  activeChildren.add(child);
2147
2279
  const stdoutChunks = [];
2148
2280
  const stderrChunks = [];
@@ -2204,13 +2336,34 @@ async function runBackgroundTask(request, options) {
2204
2336
  });
2205
2337
  }
2206
2338
 
2207
- // src/runtime/background-runner.ts
2208
- async function runBackgroundTask2(request, options = {}) {
2209
- return runBackgroundTask(request, {
2210
- onProgress: options.onProgress,
2211
- executorModule: "@skillspp/core/runtime/background-tasks"
2212
- });
2339
+ // ../../packages/cli-shared/src/runtime/background-runner.ts
2340
+ import fs5 from "node:fs";
2341
+ import path6 from "node:path";
2342
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
2343
+ function resolveExecutorModule(importMetaUrl) {
2344
+ const runtimeDir = path6.dirname(fileURLToPath2(importMetaUrl));
2345
+ const localCandidates = [
2346
+ path6.join(runtimeDir, "background-executor.js"),
2347
+ path6.join(runtimeDir, "background-executor.ts")
2348
+ ];
2349
+ for (const candidate of localCandidates) {
2350
+ if (fs5.existsSync(candidate)) {
2351
+ return pathToFileURL(candidate).href;
2352
+ }
2353
+ }
2354
+ return "@skillspp/core/runtime/background-tasks";
2213
2355
  }
2356
+ function createBackgroundTaskRunner(importMetaUrl) {
2357
+ return async function runBackgroundTask3(request, options = {}) {
2358
+ return runBackgroundTask(request, {
2359
+ onProgress: options.onProgress,
2360
+ executorModule: resolveExecutorModule(importMetaUrl)
2361
+ });
2362
+ };
2363
+ }
2364
+
2365
+ // src/runtime/background-runner.ts
2366
+ var runBackgroundTask2 = createBackgroundTaskRunner(import.meta.url);
2214
2367
 
2215
2368
  // src/commands/add.ts
2216
2369
  var SKILL_NAME_WIDTH = 32;
@@ -2238,6 +2391,7 @@ var ADD_SKILLS_SELECTION_VIEW = {
2238
2391
  instructionLine: "Select skills (space to toggle)",
2239
2392
  labelWidth: SKILL_NAME_WIDTH,
2240
2393
  descWidth: SKILL_DESC_WIDTH,
2394
+ maxVisibleRows: 10,
2241
2395
  minWidth: 74,
2242
2396
  defaultHints: ADD_SKILLS_KEY_HINTS
2243
2397
  };
@@ -2247,6 +2401,7 @@ var ADD_AGENTS_SELECTION_VIEW = {
2247
2401
  instructionLine: "Select agents (space to toggle)",
2248
2402
  labelWidth: AGENT_NAME_WIDTH,
2249
2403
  descWidth: AGENT_DESC_WIDTH,
2404
+ maxVisibleRows: 10,
2250
2405
  minWidth: 74,
2251
2406
  defaultHints: ADD_AGENTS_KEY_HINTS
2252
2407
  };
@@ -2267,17 +2422,10 @@ var ADD_SCOPE_SELECTION_ROWS = [
2267
2422
  description: "Install into home-directory skills directories"
2268
2423
  }
2269
2424
  ];
2270
- function buildAddSkillSelectionRows(skills) {
2271
- return skills.map((skill) => ({
2272
- id: skill.name,
2273
- label: skill.name,
2274
- description: skill.description
2275
- }));
2276
- }
2277
2425
  function renderAddSkillsSection(options) {
2278
2426
  return manySelectionClosedSection(
2279
2427
  ADD_SKILLS_SELECTION_VIEW,
2280
- buildAddSkillSelectionRows(options.skills),
2428
+ buildNamedAddSelectionRows(options.skills),
2281
2429
  options.selectedNames
2282
2430
  );
2283
2431
  }
@@ -2294,11 +2442,7 @@ function buildInstallationSummaryRows(options) {
2294
2442
  if (!canonicalAgent) {
2295
2443
  return rows;
2296
2444
  }
2297
- const canonicalBase = getAgentSkillsDir(
2298
- canonicalAgent,
2299
- options.globalInstall,
2300
- options.cwd
2301
- );
2445
+ const canonicalBase = getAgentSkillsDir(canonicalAgent, options.globalInstall, options.cwd);
2302
2446
  for (const rawSkillName of options.skillNames) {
2303
2447
  const skillName = sanitizeSkillName(rawSkillName);
2304
2448
  const canonicalDir = path7.join(canonicalBase, skillName);
@@ -2354,95 +2498,33 @@ async function printAddListScreen(options) {
2354
2498
  ]);
2355
2499
  }
2356
2500
  async function resolveAddSkills(available, merged, interactive) {
2357
- const filterByRequestedName = (items, requested) => {
2358
- if (!requested || requested.length === 0) {
2359
- return items;
2360
- }
2361
- if (requested.includes("*")) {
2362
- return items;
2363
- }
2364
- const wanted2 = new Set(requested.map((item) => item.toLowerCase()));
2365
- return items.filter((item) => wanted2.has(item.name.toLowerCase()));
2366
- };
2367
- if (merged.list) {
2368
- return filterByRequestedName(available, merged.skill);
2369
- }
2370
- const skillRows = buildAddSkillSelectionRows(
2501
+ const skillRows = buildNamedAddSelectionRows(
2371
2502
  available.map((item) => ({
2372
2503
  name: item.name,
2373
2504
  description: item.description
2374
2505
  }))
2375
2506
  );
2376
- const renderClosed = (selectedNames2) => renderAddSkillsSection({
2507
+ const renderClosed = (selectedNames) => renderAddSkillsSection({
2377
2508
  skills: available.map((item) => ({
2378
2509
  name: item.name,
2379
2510
  description: item.description
2380
2511
  })),
2381
- selectedNames: selectedNames2
2512
+ selectedNames
2382
2513
  });
2383
- if (merged.skill) {
2384
- const selected = filterByRequestedName(available, merged.skill);
2385
- if (selected.length > 0) {
2386
- await runManySelectionStep({
2387
- interactive,
2388
- rows: skillRows,
2389
- selectedIds: selected.map((item) => item.name),
2390
- shouldPrompt: false,
2391
- prompt: {
2392
- title: "Choose Skills",
2393
- required: true,
2394
- requiredMessage: "At least one skill must be selected",
2395
- searchable: true,
2396
- keyHints: ADD_SKILLS_KEY_HINTS,
2397
- view: ADD_SKILLS_SELECTION_VIEW
2398
- },
2399
- renderClosed
2400
- });
2401
- }
2402
- return selected;
2403
- }
2404
- if (available.length === 0) {
2405
- throw new Error("No skills available");
2406
- }
2407
- if (available.length === 1) {
2408
- await runManySelectionStep({
2409
- interactive,
2410
- rows: skillRows,
2411
- selectedIds: [available[0].name],
2412
- shouldPrompt: false,
2413
- prompt: {
2414
- title: "Choose Skills",
2415
- required: true,
2416
- requiredMessage: "At least one skill must be selected",
2417
- searchable: true,
2418
- keyHints: ADD_SKILLS_KEY_HINTS,
2419
- view: ADD_SKILLS_SELECTION_VIEW
2420
- },
2421
- renderClosed
2422
- });
2423
- return available;
2424
- }
2425
- if (!interactive) {
2426
- throw new Error(
2427
- "Multiple skills found. Use --skill <name> or run in TTY without --non-interactive."
2428
- );
2429
- }
2430
- const selectedNames = await runManySelectionStep({
2514
+ return resolveNamedAddSelection({
2515
+ available,
2431
2516
  interactive,
2517
+ listMode: merged.list,
2518
+ requested: merged.skill,
2432
2519
  rows: skillRows,
2433
- shouldPrompt: true,
2434
- prompt: {
2435
- title: "Choose Skills",
2436
- required: true,
2437
- requiredMessage: "At least one skill must be selected",
2438
- searchable: true,
2439
- keyHints: ADD_SKILLS_KEY_HINTS,
2440
- view: ADD_SKILLS_SELECTION_VIEW
2441
- },
2520
+ keyHints: ADD_SKILLS_KEY_HINTS,
2521
+ view: ADD_SKILLS_SELECTION_VIEW,
2522
+ promptTitle: "Choose Skills",
2523
+ requiredMessage: "At least one skill must be selected",
2524
+ emptyMessage: "No skills available",
2525
+ multipleInNonInteractiveMessage: "Multiple skills found. Use --skill <name> or run in TTY without --non-interactive.",
2442
2526
  renderClosed
2443
2527
  });
2444
- const wanted = new Set(selectedNames);
2445
- return available.filter((item) => wanted.has(item.name));
2446
2528
  }
2447
2529
  async function resolveAddAgents(merged, globalInstall, interactive) {
2448
2530
  const rows = resolveAddAgentSelectionRows(globalInstall ? "global" : "local");
@@ -2490,9 +2572,7 @@ async function resolveAddAgents(merged, globalInstall, interactive) {
2490
2572
  return selectedAgents;
2491
2573
  }
2492
2574
  if (!interactive) {
2493
- throw new Error(
2494
- "Missing --agent in non-interactive mode. Provide at least one agent."
2495
- );
2575
+ throw new Error("Missing --agent in non-interactive mode. Provide at least one agent.");
2496
2576
  }
2497
2577
  if (allForScope.length === 1) {
2498
2578
  await runManySelectionStep({
@@ -2563,56 +2643,27 @@ function resolveAddInstallMode(merged) {
2563
2643
  }
2564
2644
  return "copy";
2565
2645
  }
2566
- function parseLockFormatValue(value) {
2567
- if (!value) {
2568
- return void 0;
2569
- }
2570
- if (value !== "json" && value !== "yaml") {
2571
- throw new Error(`Invalid --lock-format value: ${value}`);
2572
- }
2573
- return value;
2574
- }
2575
2646
  function toAddOptions(options, presence = {}) {
2576
- const maxDownloadBytes = options.maxDownloadBytes ? Number(options.maxDownloadBytes) : void 0;
2577
- if (typeof maxDownloadBytes === "number" && (!Number.isFinite(maxDownloadBytes) || maxDownloadBytes <= 0)) {
2578
- throw new Error(
2579
- `Invalid --max-download-bytes value: ${options.maxDownloadBytes}`
2580
- );
2581
- }
2582
- const parsed = {
2583
- global: Boolean(options.global),
2584
- symlink: Boolean(options.symlink),
2585
- yaml: Boolean(options.yaml),
2586
- list: Boolean(options.list),
2587
- all: Boolean(options.all),
2588
- nonInteractive: Boolean(options.nonInteractive),
2589
- trustWellKnown: Boolean(options.trustWellKnown),
2590
- agent: normalizeAgentSelectionInput(options.agent),
2591
- skill: options.skill,
2592
- allowHost: options.allowHost?.map((item) => item.toLowerCase()),
2593
- denyHost: options.denyHost?.map((item) => item.toLowerCase()),
2594
- maxDownloadBytes,
2595
- policyMode: parsePolicyMode(options.policyMode),
2596
- lockFormat: parseLockFormatValue(options.lockFormat),
2597
- experimental: false
2598
- };
2599
- if (parsed.agent && parsed.agent.length > 0) {
2600
- parsed.agentFlagProvided = true;
2601
- }
2602
- if (presence.agentProvided) {
2603
- parsed.agentFlagProvided = true;
2604
- }
2605
- if (presence.globalProvided) {
2606
- parsed.globalFlagProvided = true;
2607
- }
2608
- if (presence.symlinkProvided) {
2609
- parsed.symlinkFlagProvided = true;
2610
- }
2611
- if (parsed.all) {
2612
- parsed.skill = ["*"];
2613
- parsed.agent = ["*"];
2614
- }
2615
- return parsed;
2647
+ return buildBaseAddOptions(
2648
+ {
2649
+ global: options.global,
2650
+ symlink: options.symlink,
2651
+ yaml: options.yaml,
2652
+ list: options.list,
2653
+ all: options.all,
2654
+ nonInteractive: options.nonInteractive,
2655
+ trustWellKnown: options.trustWellKnown,
2656
+ agent: options.agent,
2657
+ selectedNames: options.skill,
2658
+ allowHost: options.allowHost,
2659
+ denyHost: options.denyHost,
2660
+ maxDownloadBytes: options.maxDownloadBytes,
2661
+ policyMode: parsePolicyMode(options.policyMode),
2662
+ lockFormat: options.lockFormat,
2663
+ experimental: false
2664
+ },
2665
+ presence
2666
+ );
2616
2667
  }
2617
2668
  async function executeAdd(sourceInput, merged) {
2618
2669
  const interactive = canUseInteractive(merged.nonInteractive);
@@ -2625,9 +2676,7 @@ async function executeAdd(sourceInput, merged) {
2625
2676
  sourceLabel = resolveSourceLabel(parsedSource);
2626
2677
  } catch (error) {
2627
2678
  hideLoader();
2628
- await renderStaticScreen([
2629
- failedStepsSection(["failed to parse source"])
2630
- ]);
2679
+ await renderStaticScreen([failedStepsSection(["failed to parse source"])]);
2631
2680
  throw error;
2632
2681
  }
2633
2682
  hideLoader();
@@ -2653,26 +2702,17 @@ async function executeAdd(sourceInput, merged) {
2653
2702
  );
2654
2703
  } catch (error) {
2655
2704
  hideLoader();
2656
- await renderStaticScreen([
2657
- failedStepsSection(["failed to fetch skill index"])
2658
- ]);
2705
+ await renderStaticScreen([failedStepsSection(["failed to fetch skill index"])]);
2659
2706
  throw error;
2660
2707
  }
2661
2708
  hideLoader();
2662
2709
  await renderStaticScreen([
2663
- completedStepsSection([
2664
- "skill index fetched",
2665
- "interactive session ready"
2666
- ])
2710
+ completedStepsSection(["skill index fetched", "interactive session ready"])
2667
2711
  ]);
2668
2712
  if (!merged.list) {
2669
2713
  await renderStaticScreen([sourceSection(shortenHomePath(sourceLabel))]);
2670
2714
  }
2671
- const selected = await resolveAddSkills(
2672
- discovered.skills,
2673
- merged,
2674
- interactive
2675
- );
2715
+ const selected = await resolveAddSkills(discovered.skills, merged, interactive);
2676
2716
  if (selected.length === 0) {
2677
2717
  if (parsedSource.type === "well-known" || parsedSource.type === "catalog") {
2678
2718
  throw new Error("No matching well-known skills found in source");
@@ -2695,11 +2735,7 @@ async function executeAdd(sourceInput, merged) {
2695
2735
  global: globalInstall,
2696
2736
  globalFlagProvided: true
2697
2737
  };
2698
- const agents = await resolveAddAgents(
2699
- installOptions,
2700
- globalInstall,
2701
- interactive
2702
- );
2738
+ const agents = await resolveAddAgents(installOptions, globalInstall, interactive);
2703
2739
  const mode = resolveAddInstallMode(merged);
2704
2740
  await printInstallationSummary({
2705
2741
  skillNames: selected.map((item) => item.name),
@@ -2731,9 +2767,7 @@ async function executeAdd(sourceInput, merged) {
2731
2767
  );
2732
2768
  } catch (error) {
2733
2769
  hideLoader();
2734
- await renderStaticScreen([
2735
- failedStepsSection(["failed to install skills"])
2736
- ]);
2770
+ await renderStaticScreen([failedStepsSection(["failed to install skills"])]);
2737
2771
  throw error;
2738
2772
  }
2739
2773
  hideLoader();
@@ -2754,13 +2788,7 @@ async function executeAdd(sourceInput, merged) {
2754
2788
  }
2755
2789
  }
2756
2790
  function configureAddCommand(command, action) {
2757
- return command.description("Install skills from local path or git source").argument("<source>", "Source path or URL").option("-a, --agent <agents...>", "Target agent(s) for installation").option("-s, --skill <skills...>", "Install only selected skill(s)").option("-l, --list", "List skills from source without installing").option("--symlink", "Install by symlinking files to all agents").option(
2758
- "--yaml",
2759
- "Create skill-installer.yaml when scaffolding missing installer config"
2760
- ).option("-g, --global", "Install globally").option("--trust-well-known", "Allow hook commands for well-known source").option("--allow-host <hosts...>", "Restrict well-known hosts to allowlist").option("--deny-host <hosts...>", "Block specific well-known hosts").option("--max-download-bytes <n>", "Set well-known download budget").option("--policy-mode <mode>", "Policy mode (enforce|warn)").option("--lock-format <format>", "Lockfile format output (json|yaml)").option(
2761
- "--non-interactive",
2762
- "Disable prompts and require explicit selection"
2763
- ).option("--all", "Install all skills and known agents").action(action);
2791
+ return command.description("Install skills from local path or git source").argument("<source>", "Source path or URL").option("-a, --agent <agents...>", "Target agent(s) for installation").option("-s, --skill <skills...>", "Install only selected skill(s)").option("-l, --list", "List skills from source without installing").option("--symlink", "Install by symlinking files to all agents").option("--yaml", "Create skill-installer.yaml when scaffolding missing installer config").option("-g, --global", "Install globally").option("--trust-well-known", "Allow hook commands for well-known source").option("--allow-host <hosts...>", "Restrict well-known hosts to allowlist").option("--deny-host <hosts...>", "Block specific well-known hosts").option("--max-download-bytes <n>", "Set well-known download budget").option("--policy-mode <mode>", "Policy mode (enforce|warn)").option("--lock-format <format>", "Lockfile format output (json|yaml)").option("--non-interactive", "Disable prompts and require explicit selection").option("--all", "Install all skills and known agents").action(action);
2764
2792
  }
2765
2793
  function registerAddCommand(program, ctx) {
2766
2794
  configureAddCommand(
@@ -2786,7 +2814,7 @@ function registerAddCommand(program, ctx) {
2786
2814
  import { Command as Command3 } from "commander";
2787
2815
 
2788
2816
  // ../../packages/core/src/sources/git.ts
2789
- import fs5 from "node:fs";
2817
+ import fs6 from "node:fs";
2790
2818
  import os3 from "node:os";
2791
2819
  import path8 from "node:path";
2792
2820
  import { spawn as spawn2, spawnSync } from "node:child_process";
@@ -2812,12 +2840,12 @@ function applyCheckoutRefSync(repoDir, ref) {
2812
2840
  }
2813
2841
  function prepareSourceDir(parsed) {
2814
2842
  if (parsed.type === "local") {
2815
- if (!fs5.existsSync(parsed.localPath)) {
2843
+ if (!fs6.existsSync(parsed.localPath)) {
2816
2844
  throw new Error(`Local source not found: ${parsed.localPath}`);
2817
2845
  }
2818
2846
  return { basePath: parsed.localPath };
2819
2847
  }
2820
- const tmp = fs5.mkdtempSync(path8.join(os3.tmpdir(), "skillspp-cli-"));
2848
+ const tmp = fs6.mkdtempSync(path8.join(os3.tmpdir(), "skillspp-cli-"));
2821
2849
  runGit(["clone", "--depth", "1", parsed.repoUrl, tmp]);
2822
2850
  const ref = parsed.type === "github" ? parsed.ref : void 0;
2823
2851
  applyCheckoutRefSync(tmp, ref);
@@ -2825,7 +2853,7 @@ function prepareSourceDir(parsed) {
2825
2853
  return {
2826
2854
  basePath,
2827
2855
  cleanup: () => {
2828
- fs5.rmSync(tmp, { recursive: true, force: true });
2856
+ fs6.rmSync(tmp, { recursive: true, force: true });
2829
2857
  }
2830
2858
  };
2831
2859
  }
@@ -2872,11 +2900,33 @@ var DEFAULT_OPTIONS = {
2872
2900
  maxFilesPerSkill: 128,
2873
2901
  maxSkillFileBytes: 512 * 1024
2874
2902
  };
2875
- var EXCLUDED_HOSTS = /* @__PURE__ */ new Set([
2876
- "github.com",
2877
- "gitlab.com",
2878
- "raw.githubusercontent.com"
2879
- ]);
2903
+ var EXCLUDED_HOSTS = /* @__PURE__ */ new Set(["github.com", "gitlab.com", "raw.githubusercontent.com"]);
2904
+ var SKILL_CONFIG = {
2905
+ kind: "skills",
2906
+ displayLabel: "well-known skills",
2907
+ indexPath: "/.well-known/skills/index.json",
2908
+ entryLabel: "skill",
2909
+ requireDescription: true,
2910
+ missingManifestMessage: (name) => `Well-known skill '${name}' is missing SKILL.md`,
2911
+ validateName(name) {
2912
+ if (!/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(name)) {
2913
+ throw new Error(`Invalid well-known skill name: ${name}`);
2914
+ }
2915
+ },
2916
+ hasRequiredManifest(filePath) {
2917
+ return filePath.toLowerCase() === "skill.md";
2918
+ },
2919
+ buildRemoteResult({ entry, files, sourceUrl }) {
2920
+ return {
2921
+ name: entry.name,
2922
+ description: entry.description || "",
2923
+ installName: entry.name,
2924
+ sourceUrl,
2925
+ sourceType: "well-known",
2926
+ files
2927
+ };
2928
+ }
2929
+ };
2880
2930
  var SecureWellKnownProvider = class {
2881
2931
  id = "well-known";
2882
2932
  displayName = "Secure Well-Known Skills";
@@ -2896,10 +2946,13 @@ var SecureWellKnownProvider = class {
2896
2946
  }
2897
2947
  getSourceIdentifier(url) {
2898
2948
  const parsed = new URL(url);
2899
- const path16 = parsed.pathname.replace(/\/$/, "");
2900
- return path16 && path16 !== "/" ? `wellknown/${parsed.hostname}${path16}` : `wellknown/${parsed.hostname}`;
2949
+ const pathname = parsed.pathname.replace(/\/$/, "");
2950
+ return pathname && pathname !== "/" ? `wellknown/${parsed.hostname}${pathname}` : `wellknown/${parsed.hostname}`;
2901
2951
  }
2902
2952
  async fetchAllSkills(url, options = {}) {
2953
+ return this.fetchAllResources(url, options, SKILL_CONFIG);
2954
+ }
2955
+ async fetchAllResources(url, options, config) {
2903
2956
  const normalized = this.normalizeOptions(options);
2904
2957
  const budget = { remaining: normalized.maxDownloadBytes };
2905
2958
  const parsed = new URL(url);
@@ -2907,29 +2960,19 @@ var SecureWellKnownProvider = class {
2907
2960
  throw new Error("Well-known provider requires HTTPS URLs");
2908
2961
  }
2909
2962
  await this.assertHostAllowed(parsed.hostname, normalized);
2910
- const { index, resolvedBase } = await this.fetchIndex(
2911
- parsed,
2912
- normalized,
2913
- budget
2914
- );
2915
- const skills = [];
2916
- for (const entry of index.skills) {
2917
- const skill = await this.fetchSkillByEntry(
2918
- resolvedBase,
2919
- entry,
2920
- normalized,
2921
- budget
2963
+ const { index, resolvedBase } = await this.fetchIndex(parsed, normalized, budget, config);
2964
+ const resources = [];
2965
+ for (const entry of index) {
2966
+ resources.push(
2967
+ await this.fetchResourceByEntry(resolvedBase, entry, normalized, budget, config)
2922
2968
  );
2923
- if (skill) {
2924
- skills.push(skill);
2925
- }
2926
2969
  }
2927
- return skills;
2970
+ return resources;
2928
2971
  }
2929
2972
  normalizeOptions(options) {
2930
2973
  return {
2931
- allowHosts: (options.allowHosts || []).map((x) => x.trim().toLowerCase()).filter(Boolean),
2932
- denyHosts: (options.denyHosts || []).map((x) => x.trim().toLowerCase()).filter(Boolean),
2974
+ allowHosts: (options.allowHosts || []).map((value) => value.trim().toLowerCase()).filter(Boolean),
2975
+ denyHosts: (options.denyHosts || []).map((value) => value.trim().toLowerCase()).filter(Boolean),
2933
2976
  maxDownloadBytes: options.maxDownloadBytes ?? DEFAULT_OPTIONS.maxDownloadBytes,
2934
2977
  timeoutMs: options.timeoutMs ?? DEFAULT_OPTIONS.timeoutMs,
2935
2978
  maxRedirects: options.maxRedirects ?? DEFAULT_OPTIONS.maxRedirects,
@@ -2937,10 +2980,10 @@ var SecureWellKnownProvider = class {
2937
2980
  maxSkillFileBytes: options.maxSkillFileBytes ?? DEFAULT_OPTIONS.maxSkillFileBytes
2938
2981
  };
2939
2982
  }
2940
- async fetchIndex(parsedUrl, options, budget) {
2941
- const candidates = this.buildBaseCandidates(parsedUrl);
2983
+ async fetchIndex(parsedUrl, options, budget, config) {
2984
+ const candidates = this.buildBaseCandidates(parsedUrl, config.indexPath);
2942
2985
  for (const base of candidates) {
2943
- const indexUrl = `${base}/.well-known/skills/index.json`;
2986
+ const indexUrl = `${base}${config.indexPath}`;
2944
2987
  try {
2945
2988
  const jsonText = await this.fetchTextWithLimit(
2946
2989
  indexUrl,
@@ -2949,20 +2992,18 @@ var SecureWellKnownProvider = class {
2949
2992
  budget
2950
2993
  );
2951
2994
  const parsed = JSON.parse(jsonText);
2952
- const validated = this.validateIndex(parsed, options.maxFilesPerSkill);
2995
+ const validated = this.validateIndex(parsed, options.maxFilesPerSkill, config);
2953
2996
  return { index: validated, resolvedBase: base };
2954
2997
  } catch {
2955
2998
  continue;
2956
2999
  }
2957
3000
  }
2958
- throw new Error(
2959
- "No valid well-known skills index found at /.well-known/skills/index.json"
2960
- );
3001
+ throw new Error(`No valid ${config.displayLabel} index found at ${config.indexPath}`);
2961
3002
  }
2962
- buildBaseCandidates(parsed) {
3003
+ buildBaseCandidates(parsed, indexPath) {
2963
3004
  const origin = parsed.origin;
2964
3005
  const pathname = parsed.pathname.replace(/\/$/, "");
2965
- const marker = "/.well-known/skills";
3006
+ const marker = indexPath.replace(/\/index\.json$/, "");
2966
3007
  const out = [];
2967
3008
  if (pathname.includes(marker)) {
2968
3009
  const prefix = pathname.slice(0, pathname.indexOf(marker));
@@ -2977,53 +3018,52 @@ var SecureWellKnownProvider = class {
2977
3018
  }
2978
3019
  }
2979
3020
  return [
2980
- ...new Set(out.map((x) => x.endsWith("/") ? x.slice(0, -1) : x))
3021
+ ...new Set(out.map((value) => value.endsWith("/") ? value.slice(0, -1) : value))
2981
3022
  ].filter(Boolean);
2982
3023
  }
2983
- validateIndex(raw, maxFilesPerSkill) {
3024
+ validateIndex(raw, maxFilesPerSkill, config) {
2984
3025
  if (!raw || typeof raw !== "object") {
2985
3026
  throw new Error("Invalid well-known index: expected object");
2986
3027
  }
2987
3028
  const data = raw;
2988
- if (!Array.isArray(data.skills)) {
2989
- throw new Error("Invalid well-known index: 'skills' must be an array");
3029
+ const rows = data[config.kind];
3030
+ if (!Array.isArray(rows)) {
3031
+ throw new Error(`Invalid well-known index: '${config.kind}' must be an array`);
2990
3032
  }
2991
- const skills = data.skills.map((item, idx) => {
3033
+ return rows.map((item, idx) => {
2992
3034
  if (!item || typeof item !== "object") {
2993
3035
  throw new Error(`Invalid well-known index entry[${idx}]`);
2994
3036
  }
2995
3037
  const row = item;
2996
3038
  const name = String(row.name || "").trim();
2997
- const description = String(row.description || "").trim();
2998
- const files = Array.isArray(row.files) ? row.files.map((x) => String(x)) : [];
2999
- if (!name || !description || files.length === 0) {
3000
- throw new Error(
3001
- `Invalid well-known index entry[${idx}]: missing required fields`
3002
- );
3039
+ const description = typeof row.description === "string" ? row.description.trim() : void 0;
3040
+ const files = Array.isArray(row.files) ? row.files.map((value) => String(value)) : [];
3041
+ if (!name || files.length === 0) {
3042
+ throw new Error(`Invalid well-known index entry[${idx}]: missing required fields`);
3003
3043
  }
3004
- if (!/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(name)) {
3005
- throw new Error(`Invalid well-known skill name: ${name}`);
3044
+ if (config.requireDescription && !description) {
3045
+ throw new Error(`Invalid well-known index entry[${idx}]: missing required fields`);
3006
3046
  }
3047
+ config.validateName?.(name);
3007
3048
  if (files.length > maxFilesPerSkill) {
3008
- throw new Error(`Too many files in well-known skill '${name}'`);
3049
+ throw new Error(`Too many files in well-known ${config.entryLabel} '${name}'`);
3009
3050
  }
3010
- if (!files.some((f) => f.toLowerCase() === "skill.md")) {
3011
- throw new Error(`Well-known skill '${name}' is missing SKILL.md`);
3051
+ if (!files.some((filePath) => config.hasRequiredManifest(filePath))) {
3052
+ throw new Error(config.missingManifestMessage(name));
3012
3053
  }
3013
3054
  for (const file of files) {
3014
3055
  this.assertSafeRelativePath(file);
3015
3056
  }
3016
3057
  return { name, description, files };
3017
3058
  });
3018
- return { skills };
3019
3059
  }
3020
3060
  assertSafeRelativePath(filePath) {
3021
3061
  if (!filePath || filePath.startsWith("/") || filePath.startsWith("\\") || filePath.includes("..") || filePath.includes("\\")) {
3022
3062
  throw new Error(`Unsafe well-known file path: ${filePath}`);
3023
3063
  }
3024
3064
  }
3025
- async fetchSkillByEntry(resolvedBase, entry, options, budget) {
3026
- const baseUrl = `${resolvedBase}/.well-known/skills/${entry.name}`;
3065
+ async fetchResourceByEntry(resolvedBase, entry, options, budget, config) {
3066
+ const baseUrl = `${resolvedBase}/.well-known/${config.kind}/${entry.name}`;
3027
3067
  const files = /* @__PURE__ */ new Map();
3028
3068
  for (const filePath of entry.files) {
3029
3069
  this.assertSafeRelativePath(filePath);
@@ -3035,24 +3075,31 @@ var SecureWellKnownProvider = class {
3035
3075
  budget
3036
3076
  );
3037
3077
  if (text.includes("\0")) {
3038
- throw new Error(
3039
- `Binary content is not allowed in well-known file: ${filePath}`
3040
- );
3078
+ throw new Error(`Binary content is not allowed in well-known file: ${filePath}`);
3041
3079
  }
3042
3080
  files.set(filePath, text);
3043
3081
  }
3044
- const skillContent = files.get("SKILL.md") || files.get("skill.md");
3045
- if (!skillContent) {
3046
- return null;
3082
+ const manifestPath = this.pickPrimaryManifestPath(entry.files, config);
3083
+ return config.buildRemoteResult({
3084
+ entry,
3085
+ files,
3086
+ sourceUrl: `${baseUrl}/${manifestPath}`
3087
+ });
3088
+ }
3089
+ pickPrimaryManifestPath(filePaths, config) {
3090
+ const manifests = filePaths.filter((filePath) => config.hasRequiredManifest(filePath)).sort((left, right) => {
3091
+ const leftDepth = left.split("/").length;
3092
+ const rightDepth = right.split("/").length;
3093
+ if (leftDepth !== rightDepth) {
3094
+ return leftDepth - rightDepth;
3095
+ }
3096
+ return left.localeCompare(right);
3097
+ });
3098
+ const manifestPath = manifests[0];
3099
+ if (!manifestPath) {
3100
+ throw new Error("Missing required manifest in well-known index entry");
3047
3101
  }
3048
- return {
3049
- name: entry.name,
3050
- description: entry.description,
3051
- installName: entry.name,
3052
- sourceUrl: `${baseUrl}/SKILL.md`,
3053
- sourceType: "well-known",
3054
- files
3055
- };
3102
+ return manifestPath;
3056
3103
  }
3057
3104
  async fetchTextWithLimit(url, maxPerRequestBytes, options, budget) {
3058
3105
  let currentUrl = url;
@@ -3087,111 +3134,61 @@ var SecureWellKnownProvider = class {
3087
3134
  continue;
3088
3135
  }
3089
3136
  if (!response.ok) {
3090
- throw new Error(
3091
- `Fetch failed (${response.status} ${response.statusText}) for ${currentUrl}`
3092
- );
3093
- }
3094
- const contentLengthHeader = response.headers.get("content-length");
3095
- if (contentLengthHeader) {
3096
- const declared = Number(contentLengthHeader);
3097
- if (Number.isFinite(declared) && declared > maxPerRequestBytes) {
3098
- throw new Error(
3099
- `Response exceeds per-file size limit for ${currentUrl}`
3100
- );
3101
- }
3102
- if (Number.isFinite(declared) && declared > budget.remaining) {
3103
- throw new Error(
3104
- `Response exceeds remaining download budget for ${currentUrl}`
3105
- );
3106
- }
3107
- }
3108
- const reader = response.body?.getReader();
3109
- if (!reader) {
3110
- return "";
3137
+ throw new Error(`Failed to fetch ${currentUrl}: ${response.status} ${response.statusText}`);
3111
3138
  }
3112
- let received = 0;
3113
- const chunks = [];
3114
- while (true) {
3115
- const result = await reader.read();
3116
- if (result.done) {
3117
- break;
3118
- }
3119
- const chunk = result.value;
3120
- received += chunk.byteLength;
3121
- if (received > maxPerRequestBytes) {
3122
- throw new Error(
3123
- `Response exceeded per-file size limit for ${currentUrl}`
3124
- );
3125
- }
3126
- if (received > budget.remaining) {
3127
- throw new Error(
3128
- `Response exceeded remaining download budget for ${currentUrl}`
3129
- );
3130
- }
3131
- chunks.push(chunk);
3139
+ const bytes = new Uint8Array(await response.arrayBuffer());
3140
+ if (bytes.byteLength > maxPerRequestBytes) {
3141
+ throw new Error(`Exceeded per-file download limit for ${currentUrl}`);
3132
3142
  }
3133
- budget.remaining -= received;
3134
- const total = new Uint8Array(received);
3135
- let offset = 0;
3136
- for (const chunk of chunks) {
3137
- total.set(chunk, offset);
3138
- offset += chunk.byteLength;
3143
+ budget.remaining -= bytes.byteLength;
3144
+ if (budget.remaining < 0) {
3145
+ throw new Error(`Exceeded total download budget while fetching ${url}`);
3139
3146
  }
3140
- return new TextDecoder("utf-8", { fatal: false }).decode(total);
3147
+ return new TextDecoder("utf8").decode(bytes);
3141
3148
  }
3142
3149
  }
3143
3150
  async assertHostAllowed(hostname, options) {
3144
- const host = hostname.toLowerCase();
3145
- if (options.denyHosts.includes(host)) {
3146
- throw new Error(`Well-known host denied by policy: ${hostname}`);
3151
+ const lowerHost = hostname.toLowerCase();
3152
+ if (options.denyHosts.includes(lowerHost)) {
3153
+ throw new Error(`Host '${hostname}' is explicitly denied`);
3147
3154
  }
3148
- if (options.allowHosts.length > 0 && !options.allowHosts.includes(host)) {
3149
- throw new Error(`Well-known host is not in allowlist: ${hostname}`);
3155
+ if (options.allowHosts.length > 0 && !options.allowHosts.includes(lowerHost)) {
3156
+ throw new Error(`Host '${hostname}' is not in allowed host list`);
3150
3157
  }
3151
- if (this.isLocalHostname(host)) {
3152
- throw new Error(`Well-known host is not allowed: ${hostname}`);
3158
+ if (isLocalHostname(lowerHost)) {
3159
+ throw new Error(`Local or loopback host '${hostname}' is not allowed`);
3153
3160
  }
3154
- const records = await this.resolveHostIps(host);
3155
- for (const ip of records) {
3156
- if (this.isPrivateOrLocalIp(ip)) {
3157
- throw new Error(
3158
- `Well-known host resolves to private/local address: ${hostname}`
3159
- );
3160
- }
3161
+ const ips = await resolveHostIps(hostname);
3162
+ if (ips.some((ip) => isPrivateOrLocalIp(ip))) {
3163
+ throw new Error(`Host '${hostname}' resolves to a private/local address`);
3161
3164
  }
3162
3165
  }
3163
- isLocalHostname(host) {
3164
- return host === "localhost" || host.endsWith(".local") || host.endsWith(".internal") || host === "0.0.0.0";
3165
- }
3166
- async resolveHostIps(hostname) {
3167
- const out = /* @__PURE__ */ new Set();
3168
- try {
3169
- const records = await dns.lookup(hostname, { all: true });
3170
- for (const record of records) {
3171
- out.add(record.address);
3172
- }
3173
- } catch {
3166
+ };
3167
+ function isLocalHostname(hostname) {
3168
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local");
3169
+ }
3170
+ async function resolveHostIps(hostname) {
3171
+ const out = /* @__PURE__ */ new Set();
3172
+ try {
3173
+ const entries = await dns.lookup(hostname, { all: true });
3174
+ for (const entry of entries) {
3175
+ out.add(entry.address);
3174
3176
  }
3175
- return [...out];
3177
+ } catch {
3176
3178
  }
3177
- isPrivateOrLocalIp(ip) {
3178
- if (!net.isIP(ip)) {
3179
- return false;
3180
- }
3181
- if (net.isIPv4(ip)) {
3182
- const parts = ip.split(".").map((x) => Number(x));
3183
- const [a, b] = parts;
3184
- if (a === 10 || a === 127 || a === 0) return true;
3185
- if (a === 169 && b === 254) return true;
3186
- if (a === 172 && b >= 16 && b <= 31) return true;
3187
- if (a === 192 && b === 168) return true;
3188
- if (a >= 224) return true;
3189
- return false;
3190
- }
3191
- const value = ip.toLowerCase();
3192
- return value === "::1" || value === "::" || value.startsWith("fc") || value.startsWith("fd") || value.startsWith("fe80:");
3179
+ return [...out];
3180
+ }
3181
+ function isPrivateOrLocalIp(ip) {
3182
+ const family = net.isIP(ip);
3183
+ if (family === 4) {
3184
+ return ip.startsWith("10.") || ip.startsWith("127.") || ip.startsWith("169.254.") || ip.startsWith("192.168.") || /^172\.(1[6-9]|2\d|3[0-1])\./.test(ip);
3193
3185
  }
3194
- };
3186
+ if (family === 6) {
3187
+ const normalized = ip.toLowerCase();
3188
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:");
3189
+ }
3190
+ return false;
3191
+ }
3195
3192
  var wellKnownProvider = new SecureWellKnownProvider();
3196
3193
 
3197
3194
  // ../../packages/core/src/providers/catalog.ts
@@ -3220,6 +3217,33 @@ var HttpCatalogProvider = class {
3220
3217
  return `catalog/${parsed.host}${parsed.pathname.replace(/\/+$/, "")}`;
3221
3218
  }
3222
3219
  async fetchAllSkills(url, options = {}) {
3220
+ return this.fetchAllResources(url, options, {
3221
+ kind: "skills",
3222
+ indexLabel: "catalog skills",
3223
+ resolveIndexUrl(parsed) {
3224
+ return parsed.pathname.endsWith(".json") ? parsed.toString() : new URL(
3225
+ "index.json",
3226
+ parsed.toString().endsWith("/") ? parsed.toString() : `${parsed.toString()}/`
3227
+ ).toString();
3228
+ },
3229
+ requireDescription: true,
3230
+ missingManifestMessage: (name) => `Catalog skill '${name}' is missing SKILL.md`,
3231
+ hasRequiredManifest(filePath) {
3232
+ return filePath.toLowerCase() === "skill.md";
3233
+ },
3234
+ buildRemoteResult({ entry, files, sourceUrl }) {
3235
+ return {
3236
+ name: entry.name,
3237
+ description: entry.description || "",
3238
+ installName: entry.name,
3239
+ sourceUrl,
3240
+ sourceType: "catalog",
3241
+ files
3242
+ };
3243
+ }
3244
+ });
3245
+ }
3246
+ async fetchAllResources(url, options, config) {
3223
3247
  const parsed = new URL(url);
3224
3248
  if (parsed.protocol !== "https:") {
3225
3249
  throw new Error("Catalog provider requires HTTPS URLs");
@@ -3228,23 +3252,17 @@ var HttpCatalogProvider = class {
3228
3252
  const timeoutMs = options.timeoutMs ?? DEFAULT_OPTIONS2.timeoutMs;
3229
3253
  const maxFilesPerSkill = options.maxFilesPerSkill ?? DEFAULT_OPTIONS2.maxFilesPerSkill;
3230
3254
  const maxSkillFileBytes = options.maxSkillFileBytes ?? DEFAULT_OPTIONS2.maxSkillFileBytes;
3231
- const indexUrl = parsed.pathname.endsWith(".json") ? parsed.toString() : new URL(
3232
- "index.json",
3233
- parsed.toString().endsWith("/") ? parsed.toString() : `${parsed.toString()}/`
3234
- ).toString();
3255
+ const indexUrl = config.resolveIndexUrl(parsed);
3235
3256
  const indexText = await this.fetchTextWithLimit(
3236
3257
  indexUrl,
3237
3258
  Math.min(maxDownloadBytes, maxSkillFileBytes),
3238
3259
  timeoutMs
3239
3260
  );
3240
- const index = this.validateIndex(
3241
- JSON.parse(indexText),
3242
- maxFilesPerSkill
3243
- );
3261
+ const index = this.validateIndex(JSON.parse(indexText), maxFilesPerSkill, config);
3244
3262
  const out = [];
3245
3263
  let remaining = maxDownloadBytes - indexText.length;
3246
3264
  const indexBase = indexUrl.slice(0, indexUrl.lastIndexOf("/") + 1);
3247
- for (const row of index.skills) {
3265
+ for (const row of index) {
3248
3266
  const files = /* @__PURE__ */ new Map();
3249
3267
  for (const rel of row.files) {
3250
3268
  this.assertSafeRelativePath(rel);
@@ -3260,14 +3278,16 @@ var HttpCatalogProvider = class {
3260
3278
  remaining -= text.length;
3261
3279
  files.set(rel, text);
3262
3280
  }
3263
- out.push({
3264
- name: row.name,
3265
- description: row.description,
3266
- installName: row.name,
3267
- sourceUrl: new URL(`${row.name}/SKILL.md`, indexBase).toString(),
3268
- sourceType: "catalog",
3269
- files
3270
- });
3281
+ out.push(
3282
+ config.buildRemoteResult({
3283
+ entry: row,
3284
+ files,
3285
+ sourceUrl: new URL(
3286
+ `${row.name}/${this.pickPrimaryManifestPath(row.files, config)}`,
3287
+ indexBase
3288
+ ).toString()
3289
+ })
3290
+ );
3271
3291
  }
3272
3292
  return out;
3273
3293
  }
@@ -3290,39 +3310,55 @@ var HttpCatalogProvider = class {
3290
3310
  clearTimeout(timeout);
3291
3311
  }
3292
3312
  }
3293
- validateIndex(raw, maxFilesPerSkill) {
3313
+ validateIndex(raw, maxFilesPerSkill, config) {
3294
3314
  if (!raw || typeof raw !== "object") {
3295
3315
  throw new Error("Invalid catalog index: expected object");
3296
3316
  }
3297
3317
  const data = raw;
3298
- if (!Array.isArray(data.skills)) {
3299
- throw new Error("Invalid catalog index: 'skills' must be an array");
3318
+ const rows = data[config.kind];
3319
+ if (!Array.isArray(rows)) {
3320
+ throw new Error(`Invalid catalog index: '${config.kind}' must be an array`);
3300
3321
  }
3301
- const skills = data.skills.map((item, idx) => {
3322
+ return rows.map((item, idx) => {
3302
3323
  if (!item || typeof item !== "object") {
3303
3324
  throw new Error(`Invalid catalog index entry[${idx}]`);
3304
3325
  }
3305
3326
  const row = item;
3306
3327
  const name = String(row.name || "").trim();
3307
- const description = String(row.description || "").trim();
3328
+ const description = typeof row.description === "string" ? row.description.trim() : void 0;
3308
3329
  const files = Array.isArray(row.files) ? row.files.map((x) => String(x)) : [];
3309
- if (!name || !description || files.length === 0) {
3310
- throw new Error(
3311
- `Invalid catalog index entry[${idx}]: missing required fields`
3312
- );
3330
+ if (!name || files.length === 0) {
3331
+ throw new Error(`Invalid catalog index entry[${idx}]: missing required fields`);
3332
+ }
3333
+ if (config.requireDescription && !description) {
3334
+ throw new Error(`Invalid catalog index entry[${idx}]: missing required fields`);
3313
3335
  }
3314
3336
  if (files.length > maxFilesPerSkill) {
3315
- throw new Error(`Too many files in catalog skill '${name}'`);
3337
+ throw new Error(`Too many files in catalog ${config.kind.slice(0, -1)} '${name}'`);
3316
3338
  }
3317
- if (!files.some((f) => f.toLowerCase() === "skill.md")) {
3318
- throw new Error(`Catalog skill '${name}' is missing SKILL.md`);
3339
+ if (!files.some((filePath) => config.hasRequiredManifest(filePath))) {
3340
+ throw new Error(config.missingManifestMessage(name));
3319
3341
  }
3320
3342
  for (const file of files) {
3321
3343
  this.assertSafeRelativePath(file);
3322
3344
  }
3323
3345
  return { name, description, files };
3324
3346
  });
3325
- return { skills };
3347
+ }
3348
+ pickPrimaryManifestPath(filePaths, config) {
3349
+ const manifests = filePaths.filter((filePath) => config.hasRequiredManifest(filePath)).sort((left, right) => {
3350
+ const leftDepth = left.split("/").length;
3351
+ const rightDepth = right.split("/").length;
3352
+ if (leftDepth !== rightDepth) {
3353
+ return leftDepth - rightDepth;
3354
+ }
3355
+ return left.localeCompare(right);
3356
+ });
3357
+ const manifestPath = manifests[0];
3358
+ if (!manifestPath) {
3359
+ throw new Error("Catalog entry is missing required manifest");
3360
+ }
3361
+ return manifestPath;
3326
3362
  }
3327
3363
  assertSafeRelativePath(filePath) {
3328
3364
  if (!filePath || filePath.startsWith("/") || filePath.startsWith("\\") || filePath.includes("..") || filePath.includes("\\")) {
@@ -3349,9 +3385,7 @@ function assertExperimentalFeatureEnabled(feature, enabled) {
3349
3385
  return;
3350
3386
  }
3351
3387
  if (feature === "catalog") {
3352
- throw new Error(
3353
- "Catalog source is experimental and requires explicit experimental mode."
3354
- );
3388
+ throw new Error("Catalog source is experimental and requires explicit experimental mode.");
3355
3389
  }
3356
3390
  }
3357
3391
 
@@ -3385,7 +3419,7 @@ async function resolveCatalogSkills(sourceUrl, options) {
3385
3419
  }
3386
3420
 
3387
3421
  // ../../packages/core/src/runtime/lockfile.ts
3388
- import fs6 from "node:fs";
3422
+ import fs7 from "node:fs";
3389
3423
  import path9 from "node:path";
3390
3424
  import YAML from "yaml";
3391
3425
  function perSkillLockfilePath(canonicalDir, format = "json") {
@@ -3404,7 +3438,7 @@ function parseLockPayload(text, format) {
3404
3438
  function readPerSkillLockfile(canonicalDir) {
3405
3439
  const jsonPath = perSkillLockfilePath(canonicalDir, "json");
3406
3440
  const yamlPath = perSkillLockfilePath(canonicalDir, "yaml");
3407
- const raw = fs6.existsSync(jsonPath) ? parseLockPayload(fs6.readFileSync(jsonPath, "utf8"), "json") : fs6.existsSync(yamlPath) ? parseLockPayload(fs6.readFileSync(yamlPath, "utf8"), "yaml") : null;
3441
+ const raw = fs7.existsSync(jsonPath) ? parseLockPayload(fs7.readFileSync(jsonPath, "utf8"), "json") : fs7.existsSync(yamlPath) ? parseLockPayload(fs7.readFileSync(yamlPath, "utf8"), "yaml") : null;
3408
3442
  if (!raw) {
3409
3443
  return null;
3410
3444
  }
@@ -3419,18 +3453,18 @@ function readPerSkillLockfile(canonicalDir) {
3419
3453
  function isSkillDirEntry(entry) {
3420
3454
  return entry.isDirectory() || entry.isSymbolicLink();
3421
3455
  }
3422
- function listInstalledSkillDirs(globalInstall, cwd) {
3456
+ function listInstalledResourceDirs(_kind, globalInstall, cwd) {
3423
3457
  const out = /* @__PURE__ */ new Set();
3424
3458
  for (const agent of Object.keys(AGENTS)) {
3425
- const skillsRoot = getAgentSkillsDir(agent, globalInstall, cwd);
3426
- if (!fs6.existsSync(skillsRoot) || !fs6.statSync(skillsRoot).isDirectory()) {
3459
+ const resourceRoot = getAgentSkillsDir(agent, globalInstall, cwd);
3460
+ if (!fs7.existsSync(resourceRoot) || !fs7.statSync(resourceRoot).isDirectory()) {
3427
3461
  continue;
3428
3462
  }
3429
- for (const entry of fs6.readdirSync(skillsRoot, { withFileTypes: true })) {
3463
+ for (const entry of fs7.readdirSync(resourceRoot, { withFileTypes: true })) {
3430
3464
  if (!isSkillDirEntry(entry)) {
3431
3465
  continue;
3432
3466
  }
3433
- out.add(path9.join(skillsRoot, entry.name));
3467
+ out.add(path9.join(resourceRoot, entry.name));
3434
3468
  }
3435
3469
  }
3436
3470
  return [...out];
@@ -3440,8 +3474,11 @@ function lockEntrySortTime(entry) {
3440
3474
  return Number.isFinite(parsed) ? parsed : 0;
3441
3475
  }
3442
3476
  function readLockfile(globalInstall, cwd) {
3477
+ return readResourceLockfile("skill", globalInstall, cwd);
3478
+ }
3479
+ function readResourceLockfile(kind, globalInstall, cwd) {
3443
3480
  const entriesBySkill = /* @__PURE__ */ new Map();
3444
- for (const skillDir of listInstalledSkillDirs(globalInstall, cwd)) {
3481
+ for (const skillDir of listInstalledResourceDirs(kind, globalInstall, cwd)) {
3445
3482
  const entry = readPerSkillLockfile(skillDir);
3446
3483
  if (!entry || typeof entry.skillName !== "string") {
3447
3484
  continue;
@@ -3453,9 +3490,7 @@ function readLockfile(globalInstall, cwd) {
3453
3490
  }
3454
3491
  return {
3455
3492
  version: 1,
3456
- entries: [...entriesBySkill.values()].sort(
3457
- (a, b) => a.skillName.localeCompare(b.skillName)
3458
- )
3493
+ entries: [...entriesBySkill.values()].sort((a, b) => a.skillName.localeCompare(b.skillName))
3459
3494
  };
3460
3495
  }
3461
3496
 
@@ -3492,9 +3527,7 @@ function buildCheckDriftSummaryLines(options) {
3492
3527
  function toCheckOptions(options) {
3493
3528
  const maxDownloadBytes = options.maxDownloadBytes ? Number(options.maxDownloadBytes) : void 0;
3494
3529
  if (typeof maxDownloadBytes === "number" && (!Number.isFinite(maxDownloadBytes) || maxDownloadBytes <= 0)) {
3495
- throw new Error(
3496
- `Invalid --max-download-bytes value: ${options.maxDownloadBytes}`
3497
- );
3530
+ throw new Error(`Invalid --max-download-bytes value: ${options.maxDownloadBytes}`);
3498
3531
  }
3499
3532
  return {
3500
3533
  global: Boolean(options.global),
@@ -3599,9 +3632,7 @@ async function executeCheck(options) {
3599
3632
  detailLines.push("");
3600
3633
  }
3601
3634
  detailLines.push(` ${kind}`);
3602
- for (const row of [...rows].sort(
3603
- (a, b) => a.skillName.localeCompare(b.skillName)
3604
- )) {
3635
+ for (const row of [...rows].sort((a, b) => a.skillName.localeCompare(b.skillName))) {
3605
3636
  detailLines.push(` ${row.skillName}: ${row.detail}`);
3606
3637
  }
3607
3638
  }
@@ -3619,9 +3650,7 @@ async function executeCheck(options) {
3619
3650
  if (conflicts.length > 0) {
3620
3651
  conflictLines.push("Local/global conflicts (local preferred):");
3621
3652
  for (const conflict of conflicts) {
3622
- conflictLines.push(
3623
- ` ${conflict.skillName}: winner=${conflict.winner}`
3624
- );
3653
+ conflictLines.push(` ${conflict.skillName}: winner=${conflict.winner}`);
3625
3654
  }
3626
3655
  }
3627
3656
  if (transitiveConflicts.length > 0) {
@@ -3651,9 +3680,7 @@ async function executeCheck(options) {
3651
3680
  }
3652
3681
  const updateSkillNames = [
3653
3682
  ...new Set(
3654
- drift.filter(
3655
- (item) => item.kind === "changed-source" || item.kind === "local-modified"
3656
- ).map((item) => item.skillName)
3683
+ drift.filter((item) => item.kind === "changed-source" || item.kind === "local-modified").map((item) => item.skillName)
3657
3684
  )
3658
3685
  ].sort((a, b) => a.localeCompare(b));
3659
3686
  const migrateSkillNames = [
@@ -3666,9 +3693,7 @@ async function executeCheck(options) {
3666
3693
  if (migrateSkillNames.length > 0) {
3667
3694
  lines.push("Migration required:");
3668
3695
  for (const skillName of migrateSkillNames) {
3669
- lines.push(
3670
- `skillspp update ${skillName} --migrate <new-skill-source>`
3671
- );
3696
+ lines.push(`skillspp update ${skillName} --migrate <new-skill-source>`);
3672
3697
  }
3673
3698
  }
3674
3699
  if (updateSkillNames.length > 0) {
@@ -3717,9 +3742,7 @@ import { Command as Command4 } from "commander";
3717
3742
  function toFindOptions(options) {
3718
3743
  const maxDownloadBytes = options.maxDownloadBytes ? Number(options.maxDownloadBytes) : void 0;
3719
3744
  if (typeof maxDownloadBytes === "number" && (!Number.isFinite(maxDownloadBytes) || maxDownloadBytes <= 0)) {
3720
- throw new Error(
3721
- `Invalid --max-download-bytes value: ${options.maxDownloadBytes}`
3722
- );
3745
+ throw new Error(`Invalid --max-download-bytes value: ${options.maxDownloadBytes}`);
3723
3746
  }
3724
3747
  return {
3725
3748
  allowHost: options.allowHost?.map((item) => item.toLowerCase()),
@@ -3793,18 +3816,12 @@ async function executeFind(source, query, options) {
3793
3816
  filtered = inventory.skills.filter((item) => matchesQuery(item.name, item.description, query)).sort((a, b) => a.name.localeCompare(b.name));
3794
3817
  } catch (error) {
3795
3818
  hideLoader();
3796
- await renderStaticScreen([
3797
- failedStepsSection(["failed to apply query filter"])
3798
- ]);
3819
+ await renderStaticScreen([failedStepsSection(["failed to apply query filter"])]);
3799
3820
  throw error;
3800
3821
  }
3801
3822
  hideLoader();
3802
3823
  const flowSections = [
3803
- completedStepsSection([
3804
- "source parsed",
3805
- "skill inventory fetched",
3806
- "query filter applied"
3807
- ]),
3824
+ completedStepsSection(["source parsed", "skill inventory fetched", "query filter applied"]),
3808
3825
  sourceSection(shortenHomePath(inventory.sourceLabel))
3809
3826
  ];
3810
3827
  const queryTrimmed = query && query.trim().length > 0 ? query : "";
@@ -3905,7 +3922,7 @@ function registerFindCommand(program, ctx) {
3905
3922
  }
3906
3923
 
3907
3924
  // src/commands/init.ts
3908
- import fs8 from "node:fs";
3925
+ import fs9 from "node:fs";
3909
3926
  import path11 from "node:path";
3910
3927
  import { Command as Command5 } from "commander";
3911
3928
 
@@ -3961,7 +3978,7 @@ function buildAgentConfigScaffoldPlan(agents, input) {
3961
3978
  }
3962
3979
 
3963
3980
  // ../../packages/core/src/runtime/installer-scaffold.ts
3964
- import fs7 from "node:fs";
3981
+ import fs8 from "node:fs";
3965
3982
  import path10 from "node:path";
3966
3983
  import YAML3 from "yaml";
3967
3984
  function installerConfigSkeleton() {
@@ -3973,7 +3990,7 @@ function installerConfigSkeleton() {
3973
3990
  };
3974
3991
  }
3975
3992
  function isFile(filePath) {
3976
- return fs7.existsSync(filePath) && fs7.statSync(filePath).isFile();
3993
+ return fs8.existsSync(filePath) && fs8.statSync(filePath).isFile();
3977
3994
  }
3978
3995
  function getInstallerConfigState(skillDir) {
3979
3996
  const yamlPath = path10.join(skillDir, "skill-installer.yaml");
@@ -4000,7 +4017,7 @@ function scaffoldInstallerConfigFile(skillDir, format) {
4000
4017
  }
4001
4018
  const content = format === "yaml" ? YAML3.stringify(installerConfigSkeleton()) : JSON.stringify(installerConfigSkeleton(), null, 2);
4002
4019
  const destinationPath = format === "yaml" ? state.yamlPath : state.jsonPath;
4003
- fs7.writeFileSync(destinationPath, `${content}
4020
+ fs8.writeFileSync(destinationPath, `${content}
4004
4021
  `, "utf8");
4005
4022
  return { created: true, filePath: destinationPath };
4006
4023
  }
@@ -4021,6 +4038,7 @@ var INIT_AGENTS_SELECTION_VIEW = {
4021
4038
  instructionLine: "Select agents (space to toggle)",
4022
4039
  labelWidth: 30,
4023
4040
  descWidth: 40,
4041
+ maxVisibleRows: 10,
4024
4042
  minWidth: 74,
4025
4043
  defaultHints: INIT_AGENTS_KEY_HINTS
4026
4044
  };
@@ -4068,9 +4086,7 @@ async function chooseInitOne(options) {
4068
4086
  description: choice.description
4069
4087
  }))
4070
4088
  );
4071
- const labelByValue = new Map(
4072
- options.choices.map((choice) => [choice.value, choice.label])
4073
- );
4089
+ const labelByValue = new Map(options.choices.map((choice) => [choice.value, choice.label]));
4074
4090
  const selected = await runOneSelectionStep({
4075
4091
  interactive: true,
4076
4092
  rows,
@@ -4160,11 +4176,7 @@ async function collectAnswers(options) {
4160
4176
  const answers = { ...defaults };
4161
4177
  for (const question of initQuestions) {
4162
4178
  if (!question.when(interactive)) {
4163
- setAnswer(
4164
- answers,
4165
- question.id,
4166
- question.normalize(answers[question.id], defaults)
4167
- );
4179
+ setAnswer(answers, question.id, question.normalize(answers[question.id], defaults));
4168
4180
  continue;
4169
4181
  }
4170
4182
  const value = await question.ask(defaults);
@@ -4198,9 +4210,7 @@ async function resolveInitAgents(options) {
4198
4210
  return selectedIds;
4199
4211
  }
4200
4212
  if (!interactive) {
4201
- throw new Error(
4202
- "Missing --agent in non-interactive mode. Provide at least one agent."
4203
- );
4213
+ throw new Error("Missing --agent in non-interactive mode. Provide at least one agent.");
4204
4214
  }
4205
4215
  const selected = await runManySelectionStep({
4206
4216
  interactive,
@@ -4302,9 +4312,7 @@ async function executeInit(options, hooks) {
4302
4312
  ];
4303
4313
  if (agentConfigPlan.unmapped.length > 0) {
4304
4314
  summaryLines.push(
4305
- `Unmapped agents (skipped): ${agentConfigPlan.unmapped.join(
4306
- ", "
4307
- )} (no scaffold mapping)`
4315
+ `Unmapped agents (skipped): ${agentConfigPlan.unmapped.join(", ")} (no scaffold mapping)`
4308
4316
  );
4309
4317
  }
4310
4318
  summaryLines.push("");
@@ -4333,16 +4341,16 @@ async function executeInit(options, hooks) {
4333
4341
  const completedSteps = [];
4334
4342
  let failedLabel = "failed to create directory";
4335
4343
  try {
4336
- const createDirectory = !fs8.existsSync(skillDir);
4337
- fs8.mkdirSync(skillDir, { recursive: true });
4344
+ const createDirectory = !fs9.existsSync(skillDir);
4345
+ fs9.mkdirSync(skillDir, { recursive: true });
4338
4346
  if (createDirectory) {
4339
4347
  completedSteps.push("directory created");
4340
4348
  }
4341
4349
  failedLabel = "failed to write SKILL.md";
4342
- if (fs8.existsSync(skillFile)) {
4350
+ if (fs9.existsSync(skillFile)) {
4343
4351
  throw new Error(`SKILL.md already exists at: ${skillFile}`);
4344
4352
  }
4345
- fs8.writeFileSync(skillFile, `${renderSkillContent(answers)}
4353
+ fs9.writeFileSync(skillFile, `${renderSkillContent(answers)}
4346
4354
  `, "utf8");
4347
4355
  completedSteps.push("SKILL.md written");
4348
4356
  failedLabel = "failed to scaffold installer config";
@@ -4351,11 +4359,11 @@ async function executeInit(options, hooks) {
4351
4359
  failedLabel = "failed to scaffold agent config";
4352
4360
  for (const row of agentConfigPlan.mapped) {
4353
4361
  const destination = ensureInsideSkillDir(skillDir, row.path);
4354
- if (fs8.existsSync(destination)) {
4362
+ if (fs9.existsSync(destination)) {
4355
4363
  throw new Error(`Agent config already exists at: ${destination}`);
4356
4364
  }
4357
- fs8.mkdirSync(path11.dirname(destination), { recursive: true });
4358
- fs8.writeFileSync(destination, row.content, "utf8");
4365
+ fs9.mkdirSync(path11.dirname(destination), { recursive: true });
4366
+ fs9.writeFileSync(destination, row.content, "utf8");
4359
4367
  }
4360
4368
  if (agentConfigPlan.mapped.length > 0) {
4361
4369
  completedSteps.push("agent configs scaffolded");
@@ -4372,27 +4380,24 @@ async function executeInit(options, hooks) {
4372
4380
  await renderStaticScreen(sections);
4373
4381
  }
4374
4382
  function configureInitCommand(command, action) {
4375
- return command.description("Create a new SKILL.md template").argument("[name]", "Optional skill directory/name").option("-a, --agent <agents...>", "Target agent(s) for config scaffolding").option(
4376
- "--yaml",
4377
- "Create skill-installer.yaml when scaffolding installer config"
4378
- ).option("--non-interactive", "Disable prompts").action(action);
4383
+ return command.description("Create a new SKILL.md template").argument("[name]", "Optional skill directory/name").option("-a, --agent <agents...>", "Target agent(s) for config scaffolding").option("--yaml", "Create skill-installer.yaml when scaffolding installer config").option("--non-interactive", "Disable prompts").action(action);
4379
4384
  }
4380
4385
  function registerInitCommand(program, ctx) {
4381
4386
  configureInitCommand(
4382
4387
  program.command("init"),
4383
- ctx.wrapAction(
4384
- "init",
4385
- async (name, options) => {
4386
- await executeInit({
4388
+ ctx.wrapAction("init", async (name, options) => {
4389
+ await executeInit(
4390
+ {
4387
4391
  nameArg: name,
4388
4392
  nonInteractive: Boolean(options.nonInteractive),
4389
4393
  yaml: Boolean(options.yaml),
4390
4394
  agent: options.agent
4391
- }, {
4395
+ },
4396
+ {
4392
4397
  emitCommandEvent: (event) => ctx.emitCommandEvent("init", event)
4393
- });
4394
- }
4395
- )
4398
+ }
4399
+ );
4400
+ })
4396
4401
  );
4397
4402
  }
4398
4403
 
@@ -4419,6 +4424,7 @@ var LIST_AGENTS_SELECTION_VIEW = {
4419
4424
  instructionLine: "Select agents to list (space to toggle)",
4420
4425
  labelWidth: AGENT_LABEL_WIDTH,
4421
4426
  descWidth: AGENT_DESC_WIDTH2,
4427
+ maxVisibleRows: 10,
4422
4428
  minWidth: 74,
4423
4429
  defaultHints: LIST_AGENTS_KEY_HINTS
4424
4430
  };
@@ -4450,10 +4456,7 @@ function renderListScopePanel(options) {
4450
4456
  function renderListInventorySummary(skillCount) {
4451
4457
  return panelSection({
4452
4458
  title: "Inventory Summary",
4453
- lines: [
4454
- `${skillCount} unique skills found`,
4455
- "Grouped by (skill name + resolved path)"
4456
- ],
4459
+ lines: [`${skillCount} unique skills found`, "Grouped by (skill name + resolved path)"],
4457
4460
  style: "square",
4458
4461
  minWidth: 74
4459
4462
  });
@@ -4467,17 +4470,12 @@ function renderListInstalledSkillsPanel(rows) {
4467
4470
  return a.resolvedPath.localeCompare(b.resolvedPath);
4468
4471
  });
4469
4472
  const uniqueSkillCount = new Set(sortedRows.map((row) => row.name)).size;
4470
- const targetCount = sortedRows.reduce(
4471
- (count, row) => count + row.agents.length,
4472
- 0
4473
- );
4473
+ const targetCount = sortedRows.reduce((count, row) => count + row.agents.length, 0);
4474
4474
  const lines = [];
4475
4475
  lines.push(
4476
4476
  `${bold("Skills:")} ${dim(uniqueSkillCount.toString())} ${bold(
4477
4477
  "Entries:"
4478
- )} ${dim(sortedRows.length.toString())} ${bold("Targets:")} ${dim(
4479
- targetCount.toString()
4480
- )}`
4478
+ )} ${dim(sortedRows.length.toString())} ${bold("Targets:")} ${dim(targetCount.toString())}`
4481
4479
  );
4482
4480
  lines.push("");
4483
4481
  lines.push("Targets");
@@ -4490,11 +4488,7 @@ function renderListInstalledSkillsPanel(rows) {
4490
4488
  }
4491
4489
  const agents = [...row.agents].sort((a, b) => a.localeCompare(b));
4492
4490
  for (const agent of agents) {
4493
- lines.push(
4494
- ` - ${agent.padEnd(16, " ")} ${dim(
4495
- shortenHomePath(row.resolvedPath)
4496
- )}`
4497
- );
4491
+ lines.push(` - ${agent.padEnd(16, " ")} ${dim(shortenHomePath(row.resolvedPath))}`);
4498
4492
  }
4499
4493
  }
4500
4494
  return panelSection({
@@ -4616,7 +4610,7 @@ function registerListCommand(program, ctx) {
4616
4610
  }
4617
4611
 
4618
4612
  // src/commands/remove.ts
4619
- import fs9 from "node:fs";
4613
+ import fs10 from "node:fs";
4620
4614
  import path12 from "node:path";
4621
4615
  import { Command as Command7 } from "commander";
4622
4616
  function toRemoveOptions(options) {
@@ -4658,6 +4652,7 @@ var REMOVE_SKILLS_SELECTION_VIEW = {
4658
4652
  instructionLine: "Select skills to uninstall (space to toggle)",
4659
4653
  labelWidth: SKILL_NAME_WIDTH2,
4660
4654
  descWidth: SKILL_DESC_WIDTH2,
4655
+ maxVisibleRows: 10,
4661
4656
  minWidth: 74,
4662
4657
  defaultHints: REMOVE_SKILLS_KEY_HINTS
4663
4658
  };
@@ -4667,6 +4662,7 @@ var REMOVE_AGENTS_SELECTION_VIEW = {
4667
4662
  instructionLine: "Select agents to remove from (space to toggle)",
4668
4663
  labelWidth: AGENT_NAME_WIDTH2,
4669
4664
  descWidth: AGENT_DESC_WIDTH3,
4665
+ maxVisibleRows: 10,
4670
4666
  minWidth: 74,
4671
4667
  defaultHints: REMOVE_AGENTS_KEY_HINTS
4672
4668
  };
@@ -4717,7 +4713,12 @@ function renderRemoveConfirmPanel(options) {
4717
4713
  );
4718
4714
  }
4719
4715
  function renderRemoveUninstallSummaryBox(options) {
4720
- return uninstallSummarySection(options);
4716
+ return uninstallSummarySection({
4717
+ globalInstall: options.globalInstall,
4718
+ itemNames: options.skillNames,
4719
+ itemLabel: "Skills",
4720
+ agentDisplayNames: options.agentDisplayNames
4721
+ });
4721
4722
  }
4722
4723
  function isSkillEntry(entry) {
4723
4724
  return entry.isDirectory() || entry.isSymbolicLink();
@@ -4728,11 +4729,11 @@ function buildInstallIndex(globalInstall, cwd) {
4728
4729
  const allAgents = Object.keys(AGENTS);
4729
4730
  for (const agent of allAgents) {
4730
4731
  const dir = getAgentSkillsDir(agent, globalInstall, cwd);
4731
- if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
4732
+ if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
4732
4733
  continue;
4733
4734
  }
4734
4735
  const names = /* @__PURE__ */ new Set();
4735
- for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
4736
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
4736
4737
  if (!isSkillEntry(entry)) {
4737
4738
  continue;
4738
4739
  }
@@ -4763,9 +4764,7 @@ function orderAgentsForDisplay(agents) {
4763
4764
  }
4764
4765
  function resolveCandidateAgentsForSkills(skillNames, index) {
4765
4766
  return orderAgentsForDisplay(
4766
- skillNames.flatMap(
4767
- (name) => Array.from(index.agentsBySkill.get(name) ?? [])
4768
- )
4767
+ skillNames.flatMap((name) => Array.from(index.agentsBySkill.get(name) ?? []))
4769
4768
  );
4770
4769
  }
4771
4770
  async function executeRemove(positional, options) {
@@ -4777,9 +4776,7 @@ async function executeRemove(positional, options) {
4777
4776
  try {
4778
4777
  index = buildInstallIndex(globalInstall, cwd);
4779
4778
  } catch (error) {
4780
- await renderStaticScreen([
4781
- failedStepsSection(["failed to index installed skills"])
4782
- ]);
4779
+ await renderStaticScreen([failedStepsSection(["failed to index installed skills"])]);
4783
4780
  throw error;
4784
4781
  }
4785
4782
  if (index.agentsBySkill.size === 0) {
@@ -4816,20 +4813,14 @@ async function executeRemove(positional, options) {
4816
4813
  try {
4817
4814
  if (options.agent && options.agent.length > 0) {
4818
4815
  if (options.agent.includes("*")) {
4819
- candidateAgents = orderAgentsForDisplay(
4820
- Array.from(index.skillsByAgent.keys())
4821
- );
4816
+ candidateAgents = orderAgentsForDisplay(Array.from(index.skillsByAgent.keys()));
4822
4817
  agents = candidateAgents;
4823
4818
  } else {
4824
4819
  const resolved = resolveAgents(options.agent);
4825
- const outOfScopeAgents = resolved.filter(
4826
- (agent) => !scopeAllowedAgents.has(agent)
4827
- );
4820
+ const outOfScopeAgents = resolved.filter((agent) => !scopeAllowedAgents.has(agent));
4828
4821
  if (outOfScopeAgents.length > 0) {
4829
4822
  throw new Error(
4830
- `Agent(s) not available in ${scope} scope: ${outOfScopeAgents.join(
4831
- ", "
4832
- )}`
4823
+ `Agent(s) not available in ${scope} scope: ${outOfScopeAgents.join(", ")}`
4833
4824
  );
4834
4825
  }
4835
4826
  candidateAgents = resolved;
@@ -4848,16 +4839,12 @@ async function executeRemove(positional, options) {
4848
4839
  );
4849
4840
  }
4850
4841
  }
4851
- candidateAgents = candidateAgents.filter(
4852
- (agent) => scopeAllowedAgents.has(agent)
4853
- );
4842
+ candidateAgents = candidateAgents.filter((agent) => scopeAllowedAgents.has(agent));
4854
4843
  agents = agents.filter((agent) => scopeAllowedAgents.has(agent));
4855
4844
  candidateAgents = orderAgentsForDisplay(candidateAgents);
4856
4845
  agents = orderAgentsForDisplay(agents);
4857
4846
  } catch (error) {
4858
- await renderStaticScreen([
4859
- failedStepsSection(["failed to resolve target candidates"])
4860
- ]);
4847
+ await renderStaticScreen([failedStepsSection(["failed to resolve target candidates"])]);
4861
4848
  throw error;
4862
4849
  }
4863
4850
  await renderStaticScreen([
@@ -4893,9 +4880,7 @@ async function executeRemove(positional, options) {
4893
4880
  if (!options.agent || options.agent.length === 0) {
4894
4881
  if (!options.all) {
4895
4882
  candidateAgents = resolveCandidateAgentsForSkills(finalSkills, index);
4896
- candidateAgents = candidateAgents.filter(
4897
- (agent) => scopeAllowedAgents.has(agent)
4898
- );
4883
+ candidateAgents = candidateAgents.filter((agent) => scopeAllowedAgents.has(agent));
4899
4884
  candidateAgents = orderAgentsForDisplay(candidateAgents);
4900
4885
  }
4901
4886
  if (candidateAgents.length === 0) {
@@ -4908,15 +4893,10 @@ async function executeRemove(positional, options) {
4908
4893
  }
4909
4894
  }
4910
4895
  const shouldPromptAgents = interactive && (!options.agent || options.agent.length === 0);
4911
- const visibleCandidateAgents = candidateAgents.filter(
4912
- (agent) => scopeAllowedAgents.has(agent)
4913
- );
4896
+ const visibleCandidateAgents = candidateAgents.filter((agent) => scopeAllowedAgents.has(agent));
4914
4897
  const selectedAgentIds = await runManySelectionStep({
4915
4898
  interactive,
4916
- rows: buildRemoveAgentSelectionRows(
4917
- visibleCandidateAgents,
4918
- scopedAgentRowsById
4919
- ),
4899
+ rows: buildRemoveAgentSelectionRows(visibleCandidateAgents, scopedAgentRowsById),
4920
4900
  selectedIds: agents,
4921
4901
  shouldPrompt: shouldPromptAgents,
4922
4902
  prompt: {
@@ -4934,9 +4914,7 @@ async function executeRemove(positional, options) {
4934
4914
  })
4935
4915
  });
4936
4916
  const selectedAgentSet = new Set(selectedAgentIds);
4937
- agents = visibleCandidateAgents.filter(
4938
- (agent) => selectedAgentSet.has(agent)
4939
- );
4917
+ agents = visibleCandidateAgents.filter((agent) => selectedAgentSet.has(agent));
4940
4918
  if (options.all) {
4941
4919
  const all = /* @__PURE__ */ new Set();
4942
4920
  for (const agent of agents) {
@@ -5022,10 +5000,10 @@ async function executeRemove(positional, options) {
5022
5000
  try {
5023
5001
  for (const agent of agents) {
5024
5002
  const dir = getAgentSkillsDir(agent, globalInstall, cwd);
5025
- if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
5003
+ if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
5026
5004
  continue;
5027
5005
  }
5028
- for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
5006
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
5029
5007
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
5030
5008
  continue;
5031
5009
  }
@@ -5034,7 +5012,7 @@ async function executeRemove(positional, options) {
5034
5012
  }
5035
5013
  const fullPath = path12.join(dir, entry.name);
5036
5014
  failedLabel = `failed to remove ${entry.name} from ${agent}`;
5037
- fs9.rmSync(fullPath, { recursive: true, force: true });
5015
+ fs10.rmSync(fullPath, { recursive: true, force: true });
5038
5016
  removedCount += 1;
5039
5017
  completedRemovalSteps.push(`removed ${entry.name} from ${agent}`);
5040
5018
  }
@@ -5056,12 +5034,9 @@ function configureRemoveCommand(command, action) {
5056
5034
  function registerRemoveCommand(program, ctx) {
5057
5035
  configureRemoveCommand(
5058
5036
  program.command("remove").alias("rm"),
5059
- ctx.wrapAction(
5060
- "remove",
5061
- async (skills, options) => {
5062
- await executeRemove(skills, toRemoveOptions(options));
5063
- }
5064
- )
5037
+ ctx.wrapAction("remove", async (skills, options) => {
5038
+ await executeRemove(skills, toRemoveOptions(options));
5039
+ })
5065
5040
  );
5066
5041
  }
5067
5042
 
@@ -5070,9 +5045,7 @@ import { Command as Command8 } from "commander";
5070
5045
  function toUpdateOptions(options) {
5071
5046
  const maxDownloadBytes = options.maxDownloadBytes ? Number(options.maxDownloadBytes) : void 0;
5072
5047
  if (typeof maxDownloadBytes === "number" && (!Number.isFinite(maxDownloadBytes) || maxDownloadBytes <= 0)) {
5073
- throw new Error(
5074
- `Invalid --max-download-bytes value: ${options.maxDownloadBytes}`
5075
- );
5048
+ throw new Error(`Invalid --max-download-bytes value: ${options.maxDownloadBytes}`);
5076
5049
  }
5077
5050
  const lockFormat = options.lockFormat;
5078
5051
  if (lockFormat && lockFormat !== "json" && lockFormat !== "yaml") {
@@ -5106,6 +5079,7 @@ var UPDATE_SKILLS_SELECTION_VIEW = {
5106
5079
  instructionLine: "Select skills (space to toggle)",
5107
5080
  labelWidth: 32,
5108
5081
  descWidth: 28,
5082
+ maxVisibleRows: 10,
5109
5083
  minWidth: 74,
5110
5084
  defaultHints: UPDATE_SKILLS_KEY_HINTS
5111
5085
  };
@@ -5120,11 +5094,7 @@ function buildUpdateRows(assessments) {
5120
5094
  });
5121
5095
  }
5122
5096
  function renderUpdateSkillsClosedPanel(rows, selectedIds) {
5123
- return manySelectionClosedSection(
5124
- UPDATE_SKILLS_SELECTION_VIEW,
5125
- rows,
5126
- selectedIds
5127
- );
5097
+ return manySelectionClosedSection(UPDATE_SKILLS_SELECTION_VIEW, rows, selectedIds);
5128
5098
  }
5129
5099
  function buildUpdateDriftSummaryLines(options) {
5130
5100
  return [
@@ -5201,10 +5171,7 @@ async function executeMigrateUpdate(options) {
5201
5171
  }
5202
5172
  hideLoader();
5203
5173
  await renderStaticScreen([
5204
- completedStepsSection([
5205
- `migrated ${options.skillName}`,
5206
- "lockfile written"
5207
- ]),
5174
+ completedStepsSection([`migrated ${options.skillName}`, "lockfile written"]),
5208
5175
  linesSection(["Migration complete.", `Updated ${options.skillName}.`])
5209
5176
  ]);
5210
5177
  }
@@ -5216,9 +5183,7 @@ async function executeUpdate(options, positionalSkill) {
5216
5183
  ...options,
5217
5184
  skill: mergedSkills
5218
5185
  };
5219
- const requestedSkills = (effectiveOptions.skill || []).filter(
5220
- (skill) => skill !== "*"
5221
- );
5186
+ const requestedSkills = (effectiveOptions.skill || []).filter((skill) => skill !== "*");
5222
5187
  if (effectiveOptions.migrate) {
5223
5188
  if (requestedSkills.length !== 1) {
5224
5189
  throw new Error(
@@ -5264,9 +5229,7 @@ async function executeUpdate(options, positionalSkill) {
5264
5229
  );
5265
5230
  } catch (error) {
5266
5231
  hideLoader();
5267
- await renderStaticScreen([
5268
- failedStepsSection(["failed to assess drift"])
5269
- ]);
5232
+ await renderStaticScreen([failedStepsSection(["failed to assess drift"])]);
5270
5233
  throw error;
5271
5234
  }
5272
5235
  hideLoader();
@@ -5279,9 +5242,7 @@ async function executeUpdate(options, positionalSkill) {
5279
5242
  (item) => item.kind === "changed-source" || item.kind === "local-modified"
5280
5243
  );
5281
5244
  });
5282
- const migrateRequired = assessments.filter(
5283
- (assessment) => assessment.drift.some((item) => item.kind === "migrate-required")
5284
- ).map((assessment) => assessment.entry.skillName).sort((a, b) => a.localeCompare(b));
5245
+ const migrateRequired = assessments.filter((assessment) => assessment.drift.some((item) => item.kind === "migrate-required")).map((assessment) => assessment.entry.skillName).sort((a, b) => a.localeCompare(b));
5285
5246
  let changedSourceCount = 0;
5286
5247
  let localModifiedCount = 0;
5287
5248
  for (const assessment of candidateAssessments) {
@@ -5320,7 +5281,7 @@ async function executeUpdate(options, positionalSkill) {
5320
5281
  panelSection({
5321
5282
  title: "Migration Required",
5322
5283
  lines: migrateRequired.map(
5323
- (skillName) => `skillspp update ${skillName} --migrate <new-skill-source>`
5284
+ (skillName) => `skillspp update ${colorToken(skillName, "primary")} --migrate <new-skill-source>`
5324
5285
  ),
5325
5286
  style: "square",
5326
5287
  minWidth: 74
@@ -5375,9 +5336,7 @@ async function executeUpdate(options, positionalSkill) {
5375
5336
  })
5376
5337
  ]);
5377
5338
  if (effectiveOptions.dryRun) {
5378
- await renderStaticScreen([
5379
- linesSection(["Dry-run mode: no changes applied."])
5380
- ]);
5339
+ await renderStaticScreen([linesSection(["Dry-run mode: no changes applied."])]);
5381
5340
  return;
5382
5341
  }
5383
5342
  showLoader("updating selected skills");
@@ -5391,9 +5350,7 @@ async function executeUpdate(options, positionalSkill) {
5391
5350
  payload: {
5392
5351
  cwd,
5393
5352
  options: effectiveOptions,
5394
- selectedSkillNames: selectedAssessments.map(
5395
- (assessment) => assessment.entry.skillName
5396
- ),
5353
+ selectedSkillNames: selectedAssessments.map((assessment) => assessment.entry.skillName),
5397
5354
  lockFormat
5398
5355
  }
5399
5356
  },
@@ -5402,9 +5359,7 @@ async function executeUpdate(options, positionalSkill) {
5402
5359
  if (label === "writing lockfile") {
5403
5360
  failedLabel = "failed to write lockfile";
5404
5361
  } else if (label.startsWith("updating ")) {
5405
- failedLabel = `failed to update ${label.slice(
5406
- "updating ".length
5407
- )}`;
5362
+ failedLabel = `failed to update ${label.slice("updating ".length)}`;
5408
5363
  } else {
5409
5364
  failedLabel = "failed to assess selected skills";
5410
5365
  }
@@ -5424,10 +5379,7 @@ async function executeUpdate(options, positionalSkill) {
5424
5379
  ...applied.updatedSkillNames.map((skillName) => `updated ${skillName}`),
5425
5380
  "lockfile written"
5426
5381
  ]),
5427
- linesSection([
5428
- "Update complete.",
5429
- `Updated ${applied.updatedSkillNames.length} skills.`
5430
- ])
5382
+ linesSection(["Update complete.", `Updated ${applied.updatedSkillNames.length} skills.`])
5431
5383
  ]);
5432
5384
  } finally {
5433
5385
  hideLoader();
@@ -5439,23 +5391,20 @@ function configureUpdateCommand(command, action) {
5439
5391
  function registerUpdateCommand(program, ctx) {
5440
5392
  configureUpdateCommand(
5441
5393
  program.command("update"),
5442
- ctx.wrapAction(
5443
- "update",
5444
- async (skill, options) => {
5445
- await executeUpdate(
5446
- {
5447
- ...toUpdateOptions(options),
5448
- experimental: ctx.experimental
5449
- },
5450
- skill
5451
- );
5452
- }
5453
- )
5394
+ ctx.wrapAction("update", async (skill, options) => {
5395
+ await executeUpdate(
5396
+ {
5397
+ ...toUpdateOptions(options),
5398
+ experimental: ctx.experimental
5399
+ },
5400
+ skill
5401
+ );
5402
+ })
5454
5403
  );
5455
5404
  }
5456
5405
 
5457
5406
  // src/commands/validate.ts
5458
- import fs12 from "node:fs";
5407
+ import fs13 from "node:fs";
5459
5408
  import path15 from "node:path";
5460
5409
  import os5 from "node:os";
5461
5410
  import { Command as Command9 } from "commander";
@@ -5463,14 +5412,14 @@ import matter2 from "gray-matter";
5463
5412
 
5464
5413
  // ../../packages/core/src/runtime/skill-installer.ts
5465
5414
  import { spawnSync as spawnSync2 } from "node:child_process";
5466
- import fs11 from "node:fs";
5415
+ import fs12 from "node:fs";
5467
5416
  import os4 from "node:os";
5468
5417
  import path14 from "node:path";
5469
5418
  import YAML4 from "yaml";
5470
5419
  import { z } from "zod";
5471
5420
 
5472
5421
  // ../../packages/core/src/runtime/installer-security.ts
5473
- import fs10 from "node:fs";
5422
+ import fs11 from "node:fs";
5474
5423
  import path13 from "node:path";
5475
5424
  function isInsideRoot(rootDir, candidatePath) {
5476
5425
  const relative = path13.relative(rootDir, candidatePath);
@@ -5504,9 +5453,9 @@ function evaluateInstallerLocalDependency(input, _options = {}) {
5504
5453
  violation: toEscapeViolation(input.source)
5505
5454
  };
5506
5455
  }
5507
- if (fs10.existsSync(resolvedSourcePath)) {
5508
- const realRoot = fs10.realpathSync.native ? fs10.realpathSync.native(resolvedRoot) : fs10.realpathSync(resolvedRoot);
5509
- const realSource = fs10.realpathSync.native ? fs10.realpathSync.native(resolvedSourcePath) : fs10.realpathSync(resolvedSourcePath);
5456
+ if (fs11.existsSync(resolvedSourcePath)) {
5457
+ const realRoot = fs11.realpathSync.native ? fs11.realpathSync.native(resolvedRoot) : fs11.realpathSync(resolvedRoot);
5458
+ const realSource = fs11.realpathSync.native ? fs11.realpathSync.native(resolvedSourcePath) : fs11.realpathSync(resolvedSourcePath);
5510
5459
  if (!isInsideRoot(realRoot, realSource)) {
5511
5460
  return {
5512
5461
  ok: false,
@@ -5539,10 +5488,7 @@ function applyMode(result, mode) {
5539
5488
  };
5540
5489
  }
5541
5490
  function evaluateInstallerLocalDependencyPolicy(input, mode) {
5542
- return applyMode(
5543
- evaluateInstallerLocalDependency(input, { policyMode: "fixed" }),
5544
- mode
5545
- );
5491
+ return applyMode(evaluateInstallerLocalDependency(input, { policyMode: "fixed" }), mode);
5546
5492
  }
5547
5493
  function evaluateHookTrustPolicy(input) {
5548
5494
  if (input.sourceType !== "well-known") {
@@ -5628,9 +5574,7 @@ function sourceLooksLikeRepoShorthand(source) {
5628
5574
  }
5629
5575
  function parseRepoSource(source) {
5630
5576
  const withoutProtocol = source.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
5631
- const match = withoutProtocol.match(
5632
- /^(github\.com|gitlab\.com)\/([^/]+)\/([^/]+?)(?:\.git)?$/
5633
- );
5577
+ const match = withoutProtocol.match(/^(github\.com|gitlab\.com)\/([^/]+)\/([^/]+?)(?:\.git)?$/);
5634
5578
  if (!match) {
5635
5579
  throw new Error(`Unsupported repository dependency source: ${source}`);
5636
5580
  }
@@ -5648,9 +5592,7 @@ function deriveDestinationNameFromSource(source) {
5648
5592
  const parts = parsed.pathname.split("/").filter(Boolean);
5649
5593
  const leaf2 = parts[parts.length - 1];
5650
5594
  if (!leaf2) {
5651
- throw new Error(
5652
- `Cannot derive dependency name from URL source: ${source}`
5653
- );
5595
+ throw new Error(`Cannot derive dependency name from URL source: ${source}`);
5654
5596
  }
5655
5597
  return leaf2;
5656
5598
  }
@@ -5674,9 +5616,7 @@ function classifyDependencySource(source) {
5674
5616
  }
5675
5617
  function parseConfigObject(raw) {
5676
5618
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
5677
- throw new Error(
5678
- "Invalid skill-installer config: expected an object with top-level keys"
5679
- );
5619
+ throw new Error("Invalid skill-installer config: expected an object with top-level keys");
5680
5620
  }
5681
5621
  const parsed = raw;
5682
5622
  if (typeof parsed["skill-installer"] !== "undefined") {
@@ -5717,10 +5657,10 @@ function parseConfigObject(raw) {
5717
5657
  }
5718
5658
  function assertNoLegacyInstallerBlock(skillDir) {
5719
5659
  const openAiYamlPath = path14.join(skillDir, "agents", "openai.yaml");
5720
- if (!fs11.existsSync(openAiYamlPath) || !fs11.statSync(openAiYamlPath).isFile()) {
5660
+ if (!fs12.existsSync(openAiYamlPath) || !fs12.statSync(openAiYamlPath).isFile()) {
5721
5661
  return;
5722
5662
  }
5723
- const parsed = YAML4.parse(fs11.readFileSync(openAiYamlPath, "utf8"));
5663
+ const parsed = YAML4.parse(fs12.readFileSync(openAiYamlPath, "utf8"));
5724
5664
  if (parsed && typeof parsed === "object" && typeof parsed["skill-installer"] !== "undefined") {
5725
5665
  throw new Error(
5726
5666
  "Legacy skill-installer config detected in agents/openai.yaml. Move it to skill-installer.yaml or skill-installer.json."
@@ -5731,8 +5671,8 @@ function loadInstallerConfig(skillDir) {
5731
5671
  assertNoLegacyInstallerBlock(skillDir);
5732
5672
  const yamlPath = path14.join(skillDir, "skill-installer.yaml");
5733
5673
  const jsonPath = path14.join(skillDir, "skill-installer.json");
5734
- const hasYaml = fs11.existsSync(yamlPath) && fs11.statSync(yamlPath).isFile();
5735
- const hasJson = fs11.existsSync(jsonPath) && fs11.statSync(jsonPath).isFile();
5674
+ const hasYaml = fs12.existsSync(yamlPath) && fs12.statSync(yamlPath).isFile();
5675
+ const hasJson = fs12.existsSync(jsonPath) && fs12.statSync(jsonPath).isFile();
5736
5676
  if (hasYaml && hasJson) {
5737
5677
  throw new Error(
5738
5678
  "Both skill-installer.yaml and skill-installer.json exist. Keep only one installer config file."
@@ -5747,10 +5687,10 @@ function loadInstallerConfig(skillDir) {
5747
5687
  };
5748
5688
  }
5749
5689
  if (hasYaml) {
5750
- const parsed = YAML4.parse(fs11.readFileSync(yamlPath, "utf8"));
5690
+ const parsed = YAML4.parse(fs12.readFileSync(yamlPath, "utf8"));
5751
5691
  return parseConfigObject(parsed);
5752
5692
  }
5753
- const raw = JSON.parse(fs11.readFileSync(jsonPath, "utf8"));
5693
+ const raw = JSON.parse(fs12.readFileSync(jsonPath, "utf8"));
5754
5694
  return parseConfigObject(raw);
5755
5695
  }
5756
5696
  function resolveDependencySourceAndTarget(dep) {
@@ -5766,19 +5706,13 @@ function resolveDependencySourceAndTarget(dep) {
5766
5706
  };
5767
5707
  }
5768
5708
  function runGitClone(repoUrl, targetDir) {
5769
- const result = spawnSync2(
5770
- "git",
5771
- ["clone", "--depth", "1", repoUrl, targetDir],
5772
- {
5773
- encoding: "utf8",
5774
- stdio: "pipe"
5775
- }
5776
- );
5709
+ const result = spawnSync2("git", ["clone", "--depth", "1", repoUrl, targetDir], {
5710
+ encoding: "utf8",
5711
+ stdio: "pipe"
5712
+ });
5777
5713
  if (result.status !== 0) {
5778
5714
  const message = (result.stderr || result.stdout || "git clone failed").trim().split(/\r?\n/)[0] || "git clone failed";
5779
- throw new Error(
5780
- `Repository dependency clone failed: ${repoUrl} (${message})`
5781
- );
5715
+ throw new Error(`Repository dependency clone failed: ${repoUrl} (${message})`);
5782
5716
  }
5783
5717
  }
5784
5718
  async function prepareDependency(source, targetPath, index, sourceCwd, stagingDir, policyMode, events) {
@@ -5794,9 +5728,7 @@ async function prepareDependency(source, targetPath, index, sourceCwd, stagingDi
5794
5728
  if (!evaluation.ok) {
5795
5729
  const violation = "violation" in evaluation ? evaluation.violation : void 0;
5796
5730
  if (!violation) {
5797
- throw new Error(
5798
- `Dependency[${index}] policy evaluation failed for source: ${source}`
5799
- );
5731
+ throw new Error(`Dependency[${index}] policy evaluation failed for source: ${source}`);
5800
5732
  }
5801
5733
  if (violation.blocking) {
5802
5734
  throw new InstallerSecurityError(violation);
@@ -5807,12 +5739,10 @@ async function prepareDependency(source, targetPath, index, sourceCwd, stagingDi
5807
5739
  });
5808
5740
  }
5809
5741
  const sourcePath = evaluation.ok ? evaluation.resolvedPath : path14.resolve(sourceCwd, source);
5810
- if (!fs11.existsSync(sourcePath)) {
5811
- throw new Error(
5812
- `Dependency[${index}] (local) source not found: ${source}`
5813
- );
5742
+ if (!fs12.existsSync(sourcePath)) {
5743
+ throw new Error(`Dependency[${index}] (local) source not found: ${source}`);
5814
5744
  }
5815
- fs11.accessSync(sourcePath, fs11.constants.R_OK);
5745
+ fs12.accessSync(sourcePath, fs12.constants.R_OK);
5816
5746
  events.push({
5817
5747
  level: "info",
5818
5748
  message: `[skills] dependency[${index}] preflight-validated: ${source}`
@@ -5846,9 +5776,7 @@ async function prepareDependency(source, targetPath, index, sourceCwd, stagingDi
5846
5776
  }
5847
5777
  const target = new URL(source);
5848
5778
  if (target.protocol !== "http:" && target.protocol !== "https:") {
5849
- throw new Error(
5850
- `Dependency[${index}] (remote) unsupported protocol: ${target.protocol}`
5851
- );
5779
+ throw new Error(`Dependency[${index}] (remote) unsupported protocol: ${target.protocol}`);
5852
5780
  }
5853
5781
  const response = await fetch(target.toString());
5854
5782
  if (!response.ok) {
@@ -5860,8 +5788,8 @@ async function prepareDependency(source, targetPath, index, sourceCwd, stagingDi
5860
5788
  stagingDir,
5861
5789
  `remote-${index}-${path14.basename(targetPath)}`
5862
5790
  );
5863
- fs11.mkdirSync(path14.dirname(stagedFilePath), { recursive: true });
5864
- fs11.writeFileSync(stagedFilePath, Buffer.from(await response.arrayBuffer()));
5791
+ fs12.mkdirSync(path14.dirname(stagedFilePath), { recursive: true });
5792
+ fs12.writeFileSync(stagedFilePath, Buffer.from(await response.arrayBuffer()));
5865
5793
  events.push({
5866
5794
  level: "info",
5867
5795
  message: `[skills] dependency[${index}] staged: ${source}`
@@ -5910,9 +5838,7 @@ async function prepareInstallerArtifacts(skillDir, sourceCwd, options = {}) {
5910
5838
  events
5911
5839
  };
5912
5840
  }
5913
- const stagingDir = fs11.mkdtempSync(
5914
- path14.join(os4.tmpdir(), "skillspp-installer-stage-")
5915
- );
5841
+ const stagingDir = fs12.mkdtempSync(path14.join(os4.tmpdir(), "skillspp-installer-stage-"));
5916
5842
  const preparedDependencies = [];
5917
5843
  const seenTargetPaths = /* @__PURE__ */ new Set();
5918
5844
  try {
@@ -5921,13 +5847,11 @@ async function prepareInstallerArtifacts(skillDir, sourceCwd, options = {}) {
5921
5847
  const { source, targetPath } = resolveDependencySourceAndTarget(dep);
5922
5848
  const normalizedTarget = targetPath.replace(/\\/g, "/");
5923
5849
  if (seenTargetPaths.has(normalizedTarget)) {
5924
- throw new Error(
5925
- `Dependency[${i}] duplicate target path: ${targetPath}`
5926
- );
5850
+ throw new Error(`Dependency[${i}] duplicate target path: ${targetPath}`);
5927
5851
  }
5928
5852
  seenTargetPaths.add(normalizedTarget);
5929
5853
  const destinationInSkill = ensureInsideRoot(skillDir, targetPath);
5930
- if (fs11.existsSync(destinationInSkill)) {
5854
+ if (fs12.existsSync(destinationInSkill)) {
5931
5855
  throw new Error(
5932
5856
  `Dependency[${i}] destination already exists: ${path14.relative(
5933
5857
  skillDir,
@@ -5954,13 +5878,13 @@ async function prepareInstallerArtifacts(skillDir, sourceCwd, options = {}) {
5954
5878
  events
5955
5879
  };
5956
5880
  } catch (error) {
5957
- fs11.rmSync(stagingDir, { recursive: true, force: true });
5881
+ fs12.rmSync(stagingDir, { recursive: true, force: true });
5958
5882
  throw error;
5959
5883
  }
5960
5884
  }
5961
5885
  function cleanupPreparedInstallerArtifacts(prepared) {
5962
5886
  if (prepared.stagingDir) {
5963
- fs11.rmSync(prepared.stagingDir, { recursive: true, force: true });
5887
+ fs12.rmSync(prepared.stagingDir, { recursive: true, force: true });
5964
5888
  }
5965
5889
  }
5966
5890
 
@@ -5974,15 +5898,11 @@ function toValidateOptions(source, options) {
5974
5898
  }
5975
5899
  const maxDescriptionChars = options.maxDescriptionChars ? Number(options.maxDescriptionChars) : void 0;
5976
5900
  if (typeof maxDescriptionChars === "number" && (!Number.isFinite(maxDescriptionChars) || maxDescriptionChars <= 0)) {
5977
- throw new Error(
5978
- `Invalid --max-description-chars value: ${options.maxDescriptionChars}`
5979
- );
5901
+ throw new Error(`Invalid --max-description-chars value: ${options.maxDescriptionChars}`);
5980
5902
  }
5981
5903
  const maxDownloadBytes = options.maxDownloadBytes ? Number(options.maxDownloadBytes) : void 0;
5982
5904
  if (typeof maxDownloadBytes === "number" && (!Number.isFinite(maxDownloadBytes) || maxDownloadBytes <= 0)) {
5983
- throw new Error(
5984
- `Invalid --max-download-bytes value: ${options.maxDownloadBytes}`
5985
- );
5905
+ throw new Error(`Invalid --max-download-bytes value: ${options.maxDownloadBytes}`);
5986
5906
  }
5987
5907
  return {
5988
5908
  source,
@@ -6001,10 +5921,10 @@ function toValidateOptions(source, options) {
6001
5921
  }
6002
5922
  function loadRepoValidateConfig(cwd) {
6003
5923
  const configPath = path15.join(cwd, ".skillspp-cli.json");
6004
- if (!fs12.existsSync(configPath) || !fs12.statSync(configPath).isFile()) {
5924
+ if (!fs13.existsSync(configPath) || !fs13.statSync(configPath).isFile()) {
6005
5925
  return {};
6006
5926
  }
6007
- const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
5927
+ const parsed = JSON.parse(fs13.readFileSync(configPath, "utf8"));
6008
5928
  return parsed.validate || {};
6009
5929
  }
6010
5930
  function resolveThresholds(options, cwd) {
@@ -6036,7 +5956,7 @@ var typeRules = [
6036
5956
  when: (frontmatter) => frontmatter.type === "framework",
6037
5957
  validate: ({ skillDir, skillName, diagnostics }) => {
6038
5958
  const referencesDir = path15.join(skillDir, "references");
6039
- if (!fs12.existsSync(referencesDir) || !fs12.statSync(referencesDir).isDirectory()) {
5959
+ if (!fs13.existsSync(referencesDir) || !fs13.statSync(referencesDir).isDirectory()) {
6040
5960
  addDiagnostic(diagnostics, {
6041
5961
  severity: "error",
6042
5962
  skill: skillName,
@@ -6049,9 +5969,7 @@ var typeRules = [
6049
5969
  }
6050
5970
  ];
6051
5971
  async function validateInstallerDependencies(skillDir, sourceRoot, diagnostics, skillName) {
6052
- const installerConfigPath = fs12.existsSync(
6053
- path15.join(skillDir, "skill-installer.yaml")
6054
- ) ? path15.join(skillDir, "skill-installer.yaml") : path15.join(skillDir, "skill-installer.json");
5972
+ const installerConfigPath = fs13.existsSync(path15.join(skillDir, "skill-installer.yaml")) ? path15.join(skillDir, "skill-installer.yaml") : path15.join(skillDir, "skill-installer.json");
6055
5973
  try {
6056
5974
  const installer = loadInstallerConfig(skillDir);
6057
5975
  const fallbackRoots = Array.from(
@@ -6095,24 +6013,18 @@ async function validateInstallerDependencies(skillDir, sourceRoot, diagnostics,
6095
6013
  if (hasSecurityViolation) {
6096
6014
  return;
6097
6015
  }
6098
- const tempSkillDir = fs12.mkdtempSync(
6099
- path15.join(os5.tmpdir(), "skillspp-validate-installer-")
6100
- );
6016
+ const tempSkillDir = fs13.mkdtempSync(path15.join(os5.tmpdir(), "skillspp-validate-installer-"));
6101
6017
  try {
6102
- const filesToCopy = [
6103
- "SKILL.md",
6104
- "skill-installer.yaml",
6105
- "skill-installer.json"
6106
- ];
6018
+ const filesToCopy = ["SKILL.md", "skill-installer.yaml", "skill-installer.json"];
6107
6019
  for (const fileName of filesToCopy) {
6108
6020
  const src = path15.join(skillDir, fileName);
6109
- if (fs12.existsSync(src) && fs12.statSync(src).isFile()) {
6110
- fs12.copyFileSync(src, path15.join(tempSkillDir, fileName));
6021
+ if (fs13.existsSync(src) && fs13.statSync(src).isFile()) {
6022
+ fs13.copyFileSync(src, path15.join(tempSkillDir, fileName));
6111
6023
  }
6112
6024
  }
6113
6025
  const agentsDir = path15.join(skillDir, "agents");
6114
- if (fs12.existsSync(agentsDir) && fs12.statSync(agentsDir).isDirectory()) {
6115
- fs12.cpSync(agentsDir, path15.join(tempSkillDir, "agents"), {
6026
+ if (fs13.existsSync(agentsDir) && fs13.statSync(agentsDir).isDirectory()) {
6027
+ fs13.cpSync(agentsDir, path15.join(tempSkillDir, "agents"), {
6116
6028
  recursive: true,
6117
6029
  force: true
6118
6030
  });
@@ -6154,7 +6066,7 @@ async function validateInstallerDependencies(skillDir, sourceRoot, diagnostics,
6154
6066
  throw lastMissingSourceError;
6155
6067
  }
6156
6068
  } finally {
6157
- fs12.rmSync(tempSkillDir, { recursive: true, force: true });
6069
+ fs13.rmSync(tempSkillDir, { recursive: true, force: true });
6158
6070
  }
6159
6071
  } catch (error) {
6160
6072
  if (isInstallerSecurityError(error)) {
@@ -6189,7 +6101,7 @@ async function validateInstallerDependencies(skillDir, sourceRoot, diagnostics,
6189
6101
  async function validateSkillDir(skillDir, sourceRoot, diagnostics, strict, thresholds) {
6190
6102
  const skillMd = path15.join(skillDir, "SKILL.md");
6191
6103
  const skillName = path15.basename(skillDir);
6192
- if (!fs12.existsSync(skillMd)) {
6104
+ if (!fs13.existsSync(skillMd)) {
6193
6105
  addDiagnostic(diagnostics, {
6194
6106
  severity: "error",
6195
6107
  skill: skillName,
@@ -6199,7 +6111,7 @@ async function validateSkillDir(skillDir, sourceRoot, diagnostics, strict, thres
6199
6111
  });
6200
6112
  return;
6201
6113
  }
6202
- const content = fs12.readFileSync(skillMd, "utf8");
6114
+ const content = fs13.readFileSync(skillMd, "utf8");
6203
6115
  const lines = content.split(/\r?\n/);
6204
6116
  if (lines.length > thresholds.maxLines) {
6205
6117
  addDiagnostic(diagnostics, {
@@ -6270,7 +6182,7 @@ async function validateSkillDir(skillDir, sourceRoot, diagnostics, strict, thres
6270
6182
  if (!rel || rel.startsWith("..") || path15.isAbsolute(rel)) {
6271
6183
  continue;
6272
6184
  }
6273
- if (!fs12.existsSync(resolved)) {
6185
+ if (!fs13.existsSync(resolved)) {
6274
6186
  addDiagnostic(diagnostics, {
6275
6187
  severity: "error",
6276
6188
  skill: skillName,
@@ -6285,18 +6197,13 @@ async function validateSkillDir(skillDir, sourceRoot, diagnostics, strict, thres
6285
6197
  rule.validate({ skillDir, skillName, diagnostics });
6286
6198
  }
6287
6199
  }
6288
- await validateInstallerDependencies(
6289
- skillDir,
6290
- sourceRoot,
6291
- diagnostics,
6292
- skillName
6293
- );
6200
+ await validateInstallerDependencies(skillDir, sourceRoot, diagnostics, skillName);
6294
6201
  }
6295
6202
  function discoverFallbackRoots(cwd) {
6296
6203
  const candidates = [cwd, path15.join(cwd, "skills")];
6297
6204
  const packagesDir = path15.join(cwd, "packages");
6298
- if (fs12.existsSync(packagesDir) && fs12.statSync(packagesDir).isDirectory()) {
6299
- for (const entry of fs12.readdirSync(packagesDir, { withFileTypes: true })) {
6205
+ if (fs13.existsSync(packagesDir) && fs13.statSync(packagesDir).isDirectory()) {
6206
+ for (const entry of fs13.readdirSync(packagesDir, { withFileTypes: true })) {
6300
6207
  if (!entry.isDirectory()) {
6301
6208
  continue;
6302
6209
  }
@@ -6306,7 +6213,7 @@ function discoverFallbackRoots(cwd) {
6306
6213
  const seen = /* @__PURE__ */ new Set();
6307
6214
  const out = [];
6308
6215
  for (const item of candidates) {
6309
- if (!fs12.existsSync(item)) {
6216
+ if (!fs13.existsSync(item)) {
6310
6217
  continue;
6311
6218
  }
6312
6219
  const resolved = path15.resolve(item);
@@ -6321,7 +6228,7 @@ function discoverFallbackRoots(cwd) {
6321
6228
  function discoverCiRoots(cwd) {
6322
6229
  const config = loadRepoValidateConfig(cwd);
6323
6230
  if (config.ciRoots && config.ciRoots.length > 0) {
6324
- const roots = config.ciRoots.map((item) => path15.resolve(cwd, item)).filter((item) => fs12.existsSync(item) && fs12.statSync(item).isDirectory());
6231
+ const roots = config.ciRoots.map((item) => path15.resolve(cwd, item)).filter((item) => fs13.existsSync(item) && fs13.statSync(item).isDirectory());
6325
6232
  if (roots.length > 0) {
6326
6233
  return roots;
6327
6234
  }
@@ -6330,7 +6237,7 @@ function discoverCiRoots(cwd) {
6330
6237
  }
6331
6238
  async function stageAndValidateLocalRoot(rootPath, dependencyRoot, seenSkillPaths, diagnostics, strict, thresholds, emitProgress) {
6332
6239
  const resolvedRoot = path15.resolve(rootPath);
6333
- if (!fs12.existsSync(resolvedRoot) || !fs12.statSync(resolvedRoot).isDirectory()) {
6240
+ if (!fs13.existsSync(resolvedRoot) || !fs13.statSync(resolvedRoot).isDirectory()) {
6334
6241
  addDiagnostic(diagnostics, {
6335
6242
  severity: "error",
6336
6243
  skill: path15.basename(resolvedRoot),
@@ -6359,13 +6266,7 @@ async function stageAndValidateLocalRoot(rootPath, dependencyRoot, seenSkillPath
6359
6266
  }
6360
6267
  seenSkillPaths.add(resolvedSkillPath);
6361
6268
  await emitProgress?.(`validating ${skill.name}`);
6362
- await validateSkillDir(
6363
- skill.path,
6364
- dependencyRoot,
6365
- diagnostics,
6366
- strict,
6367
- thresholds
6368
- );
6269
+ await validateSkillDir(skill.path, dependencyRoot, diagnostics, strict, thresholds);
6369
6270
  }
6370
6271
  }
6371
6272
  async function stageAndValidateSource(options, diagnostics, thresholds, emitProgress) {
@@ -6400,27 +6301,19 @@ async function stageAndValidateSource(options, diagnostics, thresholds, emitProg
6400
6301
  const tempRoots = [];
6401
6302
  try {
6402
6303
  for (const remoteSkill of remote) {
6403
- const tmp = fs12.mkdtempSync(
6404
- path15.join(os5.tmpdir(), "skillspp-validate-")
6405
- );
6304
+ const tmp = fs13.mkdtempSync(path15.join(os5.tmpdir(), "skillspp-validate-"));
6406
6305
  tempRoots.push(tmp);
6407
6306
  for (const [relativePath, content] of remoteSkill.files.entries()) {
6408
6307
  const target = path15.join(tmp, relativePath);
6409
- fs12.mkdirSync(path15.dirname(target), { recursive: true });
6410
- fs12.writeFileSync(target, content, "utf8");
6308
+ fs13.mkdirSync(path15.dirname(target), { recursive: true });
6309
+ fs13.writeFileSync(target, content, "utf8");
6411
6310
  }
6412
6311
  await emitProgress?.(`validating ${remoteSkill.installName}`);
6413
- await validateSkillDir(
6414
- tmp,
6415
- tmp,
6416
- diagnostics,
6417
- Boolean(options.strict),
6418
- thresholds
6419
- );
6312
+ await validateSkillDir(tmp, tmp, diagnostics, Boolean(options.strict), thresholds);
6420
6313
  }
6421
6314
  } finally {
6422
6315
  for (const tmp of tempRoots) {
6423
- fs12.rmSync(tmp, { recursive: true, force: true });
6316
+ fs13.rmSync(tmp, { recursive: true, force: true });
6424
6317
  }
6425
6318
  }
6426
6319
  return;
@@ -6466,12 +6359,7 @@ async function runValidateAnalysis(options, emitProgress) {
6466
6359
  );
6467
6360
  }
6468
6361
  } else {
6469
- await stageAndValidateSource(
6470
- options,
6471
- diagnostics,
6472
- thresholds,
6473
- emitProgress
6474
- );
6362
+ await stageAndValidateSource(options, diagnostics, thresholds, emitProgress);
6475
6363
  }
6476
6364
  await emitProgress?.("collecting diagnostics");
6477
6365
  return { diagnostics };
@@ -6499,9 +6387,7 @@ function buildDiagnosticsPanelLines(diagnostics) {
6499
6387
  return [colorToken("No diagnostics found.", "success")];
6500
6388
  }
6501
6389
  const sorted = [...diagnostics].sort(
6502
- (a, b) => `${a.severity}:${a.skill}:${a.rule}`.localeCompare(
6503
- `${b.severity}:${b.skill}:${b.rule}`
6504
- )
6390
+ (a, b) => `${a.severity}:${a.skill}:${a.rule}`.localeCompare(`${b.severity}:${b.skill}:${b.rule}`)
6505
6391
  );
6506
6392
  return sorted.map((row) => {
6507
6393
  const marker = row.severity === "error" ? colorToken("[error]", "danger") : colorToken("[warning]", "warning");
@@ -6572,38 +6458,23 @@ async function executeValidate(options) {
6572
6458
  const errors = diagnostics.filter((item) => item.severity === "error");
6573
6459
  const warnings = diagnostics.filter((item) => item.severity === "warning");
6574
6460
  const validatingSteps = [...runtimeSteps].filter((step) => step.startsWith("validating ")).sort((a, b) => a.localeCompare(b));
6575
- const runStepLines = [
6576
- "staging source",
6577
- ...validatingSteps,
6578
- "collecting diagnostics"
6579
- ];
6461
+ const runStepLines = ["staging source", ...validatingSteps, "collecting diagnostics"];
6580
6462
  await renderStaticScreen([
6581
6463
  completedStepsSection([
6582
6464
  "source parsed",
6583
6465
  "candidate skills discovered",
6584
6466
  "validation session ready"
6585
6467
  ]),
6586
- linesSection([
6587
- ` Validation target: ${resolveValidateTargetLabel(options)}`
6588
- ]),
6468
+ linesSection([` Validation target: ${resolveValidateTargetLabel(options)}`]),
6589
6469
  panelSection({
6590
6470
  title: "Checks Included",
6591
6471
  lines: [
6592
- ` ${colorToken(
6593
- "\u25CF",
6594
- "primary"
6595
- )} SKILL.md exists + frontmatter parseability`,
6472
+ ` ${colorToken("\u25CF", "primary")} SKILL.md exists + frontmatter parseability`,
6596
6473
  ` ${colorToken("\u25CF", "primary")} required fields: name, description`,
6597
6474
  ` ${colorToken("\u25CF", "primary")} name \u2194 directory consistency`,
6598
6475
  ` ${colorToken("\u25CF", "primary")} markdown reference existence`,
6599
- ` ${colorToken(
6600
- "\u25CF",
6601
- "primary"
6602
- )} type-specific rules (framework references dir)`,
6603
- ` ${colorToken(
6604
- "\u25CF",
6605
- "primary"
6606
- )} installer dependency security + preflight`,
6476
+ ` ${colorToken("\u25CF", "primary")} type-specific rules (framework references dir)`,
6477
+ ` ${colorToken("\u25CF", "primary")} installer dependency security + preflight`,
6607
6478
  ` ${colorToken("\u25CF", "primary")} line/description budget thresholds`
6608
6479
  ],
6609
6480
  style: "square",
@@ -6637,10 +6508,7 @@ async function executeValidate(options) {
6637
6508
  }
6638
6509
  }
6639
6510
  function configureValidateCommand(command, action) {
6640
- return command.description("Validate skill source structure and references").argument("[source]", "Source path or URL").option("--ci", "Validate multiple local roots for CI").option("--root <paths...>", "Root paths for CI mode").option("--strict", "Escalate warnings to errors").option("--json", "Emit JSON output").option("--max-lines <n>", "SKILL.md line budget threshold").option(
6641
- "--max-description-chars <n>",
6642
- "Description length budget threshold"
6643
- ).option("--allow-host <hosts...>", "Restrict well-known hosts to allowlist").option("--deny-host <hosts...>", "Block specific well-known hosts").option("--max-download-bytes <n>", "Set well-known download budget").option("--policy-mode <mode>", "Policy mode (enforce|warn)").action(action);
6511
+ return command.description("Validate skill source structure and references").argument("[source]", "Source path or URL").option("--ci", "Validate multiple local roots for CI").option("--root <paths...>", "Root paths for CI mode").option("--strict", "Escalate warnings to errors").option("--json", "Emit JSON output").option("--max-lines <n>", "SKILL.md line budget threshold").option("--max-description-chars <n>", "Description length budget threshold").option("--allow-host <hosts...>", "Restrict well-known hosts to allowlist").option("--deny-host <hosts...>", "Block specific well-known hosts").option("--max-download-bytes <n>", "Set well-known download budget").option("--policy-mode <mode>", "Policy mode (enforce|warn)").action(action);
6644
6512
  }
6645
6513
  function registerValidateCommand(program, ctx) {
6646
6514
  configureValidateCommand(
@@ -6680,9 +6548,22 @@ function createCliTelemetryEmitter(sink) {
6680
6548
 
6681
6549
  // src/cli.ts
6682
6550
  import picocolors from "picocolors";
6551
+ import path16 from "node:path";
6552
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
6683
6553
  var require2 = createRequire(import.meta.url);
6684
6554
  var packageJson = require2("../package.json");
6685
6555
  var CLI_VERSION = typeof packageJson.version === "string" ? packageJson.version : "0.1.0";
6556
+ function resolveSkillsppLogoDir() {
6557
+ const moduleDir = path16.dirname(fileURLToPath3(import.meta.url));
6558
+ return path16.resolve(moduleDir, "../src/assets/ascii/logo");
6559
+ }
6560
+ function configureSkillsppLogoAssetPaths() {
6561
+ const logoDir = resolveSkillsppLogoDir();
6562
+ configureLogoAssetPaths({
6563
+ sessionPath: path16.join(logoDir, "skillspp-logo.session.json"),
6564
+ textPath: path16.join(logoDir, "skillspp-logo.txt")
6565
+ });
6566
+ }
6686
6567
  function formatCliError(error) {
6687
6568
  if (error && typeof error === "object" && "code" in error) {
6688
6569
  const code = String(error.code);
@@ -6706,14 +6587,6 @@ function createProgram(emitter, experimental) {
6706
6587
  applyExitOverride(program);
6707
6588
  return program;
6708
6589
  }
6709
- function applyExitOverride(command) {
6710
- command.exitOverride((error) => {
6711
- throw error;
6712
- });
6713
- for (const subcommand of command.commands) {
6714
- applyExitOverride(subcommand);
6715
- }
6716
- }
6717
6590
  function parseTelemetryFromArgv(argv) {
6718
6591
  for (let i = 0; i < argv.length; i += 1) {
6719
6592
  if (argv[i] !== "--telemetry") {
@@ -6723,35 +6596,8 @@ function parseTelemetryFromArgv(argv) {
6723
6596
  }
6724
6597
  return void 0;
6725
6598
  }
6726
- function inferCommandSource(argv) {
6727
- for (let i = 0; i < argv.length; i += 1) {
6728
- const arg = argv[i];
6729
- if (arg === "--telemetry") {
6730
- i += 1;
6731
- continue;
6732
- }
6733
- if (arg.startsWith("-")) {
6734
- continue;
6735
- }
6736
- return arg;
6737
- }
6738
- return "cli";
6739
- }
6740
- function emitCommanderParseErrorTelemetry(emitter, argv, error) {
6741
- const source = inferCommandSource(argv);
6742
- emitLifecycleEvent(emitter, {
6743
- eventType: `${source}_failed`,
6744
- source,
6745
- reason: "commander_parse_error",
6746
- command: source,
6747
- status: "error",
6748
- error: error.message,
6749
- metadata: {
6750
- commanderCode: error.code
6751
- }
6752
- });
6753
- }
6754
6599
  async function runCli(argv) {
6600
+ configureSkillsppLogoAssetPaths();
6755
6601
  const telemetrySink = parseTelemetrySink(parseTelemetryFromArgv(argv));
6756
6602
  const emitter = createCliTelemetryEmitter(telemetrySink);
6757
6603
  const experimental = argv.includes("--experimental");
@@ -6764,11 +6610,13 @@ async function runCli(argv) {
6764
6610
  await program.parseAsync(argv, { from: "user" });
6765
6611
  return 0;
6766
6612
  } catch (error) {
6767
- if (error instanceof CommanderError2 && (error.code === "commander.helpDisplayed" || error.code === "commander.version")) {
6613
+ if (isGracefulCommanderExit(error)) {
6768
6614
  return 0;
6769
6615
  }
6770
6616
  if (error instanceof CommanderError2) {
6771
- emitCommanderParseErrorTelemetry(emitter, argv, error);
6617
+ emitCommanderParseErrorTelemetry(emitter, argv, error, {
6618
+ valueFlags: ["--telemetry"]
6619
+ });
6772
6620
  throw new Error(error.message);
6773
6621
  }
6774
6622
  throw error;
@@ -6797,6 +6645,7 @@ runCli(process.argv.slice(2)).then(
6797
6645
  }
6798
6646
  );
6799
6647
  export {
6648
+ configureSkillsppLogoAssetPaths,
6800
6649
  runCli
6801
6650
  };
6802
6651
  //# sourceMappingURL=cli.js.map