skai 0.0.5 → 0.0.6

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/index.js CHANGED
@@ -446,7 +446,7 @@ function skillTreeToTreeNodes(node, parentPath = "") {
446
446
  result.push({
447
447
  id,
448
448
  label: s.name,
449
- hint: s.skill.description,
449
+ hint: extractShortSummary(s.skill.description),
450
450
  skill: s.skill,
451
451
  selected: false
452
452
  });
@@ -475,6 +475,28 @@ function matchesSkillFilter(skill, filter) {
475
475
  }
476
476
  return skill.name.toLowerCase() === filterLower;
477
477
  }
478
+ function extractShortSummary(description, maxLength = 50) {
479
+ if (!description) return "";
480
+ const triggers = [
481
+ "This skill should be used",
482
+ "Triggers on tasks",
483
+ "Use this skill when",
484
+ "Apply when"
485
+ ];
486
+ let summary = description;
487
+ for (const trigger of triggers) {
488
+ const index = summary.indexOf(trigger);
489
+ if (index > 0) {
490
+ summary = summary.slice(0, index).trim();
491
+ break;
492
+ }
493
+ }
494
+ summary = summary.replace(/[.,;:]+$/, "").trim();
495
+ if (summary.length > maxLength) {
496
+ summary = summary.slice(0, maxLength - 1).trim() + "\u2026";
497
+ }
498
+ return summary;
499
+ }
478
500
 
479
501
  // src/agents.ts
480
502
  import * as fs4 from "fs";
@@ -651,6 +673,73 @@ function installSkillForAgent(skill, agent, options) {
651
673
  };
652
674
  }
653
675
  }
676
+ function uninstallSkill(skillName, agent, options) {
677
+ const basePath = options.global ? agent.globalPath : path5.join(process.cwd(), agent.projectPath);
678
+ const sanitizedName = sanitizeName(skillName);
679
+ const targetPath = path5.join(basePath, sanitizedName);
680
+ if (!isPathSafe(targetPath, basePath)) {
681
+ return {
682
+ skillName,
683
+ agent,
684
+ success: false,
685
+ targetPath,
686
+ error: `Invalid skill name: ${skillName}`
687
+ };
688
+ }
689
+ if (fs5.existsSync(targetPath)) {
690
+ try {
691
+ fs5.rmSync(targetPath, { recursive: true, force: true });
692
+ return { skillName, agent, success: true, targetPath };
693
+ } catch (error) {
694
+ return {
695
+ skillName,
696
+ agent,
697
+ success: false,
698
+ targetPath,
699
+ error: error instanceof Error ? error.message : String(error)
700
+ };
701
+ }
702
+ }
703
+ return {
704
+ skillName,
705
+ agent,
706
+ success: false,
707
+ targetPath,
708
+ error: "Skill not found"
709
+ };
710
+ }
711
+ function listInstalledSkills(agent, options = {}) {
712
+ const skills = [];
713
+ const checkPath = (basePath, scope) => {
714
+ if (!fs5.existsSync(basePath)) return;
715
+ try {
716
+ const entries = fs5.readdirSync(basePath, { withFileTypes: true });
717
+ for (const entry of entries) {
718
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
719
+ skills.push({
720
+ name: entry.name,
721
+ path: path5.join(basePath, entry.name),
722
+ agent,
723
+ scope
724
+ });
725
+ }
726
+ }
727
+ } catch {
728
+ }
729
+ };
730
+ if (!options.global) {
731
+ const projectPath = path5.join(process.cwd(), agent.projectPath);
732
+ checkPath(projectPath, "project");
733
+ }
734
+ if (!options.projectOnly) {
735
+ checkPath(agent.globalPath, "global");
736
+ }
737
+ return skills;
738
+ }
739
+ function getSkillInstallPath(skillName, agent, options) {
740
+ const basePath = options.global ? agent.globalPath : path5.join(process.cwd(), agent.projectPath);
741
+ return path5.join(basePath, sanitizeName(skillName));
742
+ }
654
743
 
655
744
  // src/tree-select.ts
656
745
  import * as p from "@clack/prompts";
@@ -976,34 +1065,7 @@ var S_BAR_END = import_picocolors.default.gray("\u2514");
976
1065
  var S_CHECKBOX_ACTIVE = import_picocolors.default.cyan("\u25FB");
977
1066
  var S_CHECKBOX_SELECTED = import_picocolors.default.green("\u25FC");
978
1067
  var S_CHECKBOX_INACTIVE = import_picocolors.default.dim("\u25FB");
979
- var MAX_HINT_LENGTH = 60;
980
- function truncateHint(hint, maxLen) {
981
- if (!hint) return "";
982
- if (hint.length <= maxLen) return hint;
983
- return hint.slice(0, maxLen - 1) + "\u2026";
984
- }
985
- function wrapText(text, maxWidth) {
986
- if (!text) return [];
987
- const words = text.split(/\s+/);
988
- const lines = [];
989
- let currentLine = "";
990
- for (const word of words) {
991
- if (currentLine.length === 0) {
992
- currentLine = word;
993
- } else if (currentLine.length + 1 + word.length <= maxWidth) {
994
- currentLine += " " + word;
995
- } else {
996
- lines.push(currentLine);
997
- currentLine = word;
998
- }
999
- }
1000
- if (currentLine) {
1001
- lines.push(currentLine);
1002
- }
1003
- return lines;
1004
- }
1005
- var DESCRIPTION_INDENT = " ";
1006
- var DESCRIPTION_MAX_WIDTH = 60;
1068
+ var MAX_LABEL_WIDTH = 30;
1007
1069
  function buildSearchableOptions(options) {
1008
1070
  return options.map((opt) => ({
1009
1071
  option: opt,
@@ -1191,21 +1253,11 @@ var SearchableMultiSelectPrompt = class extends x {
1191
1253
  } else {
1192
1254
  checkbox = S_CHECKBOX_INACTIVE;
1193
1255
  }
1194
- const label = this.searchTerm ? highlightMatch(opt.option.label, this.searchTerm) : opt.option.label;
1195
- const fullHint = opt.option.hint || "";
1196
- const needsTruncation = fullHint.length > MAX_HINT_LENGTH;
1197
- const displayHint = needsTruncation ? truncateHint(fullHint, MAX_HINT_LENGTH) : fullHint;
1198
- const hint = displayHint ? import_picocolors.default.dim(` (${displayHint})`) : "";
1199
- const line = isActive ? `${checkbox} ${label}${hint}` : `${checkbox} ${import_picocolors.default.dim(opt.option.label)}${import_picocolors.default.dim(hint)}`;
1256
+ const hint = opt.option.hint || "";
1257
+ const paddedLabel = opt.option.label.padEnd(MAX_LABEL_WIDTH);
1258
+ const highlightedPaddedLabel = this.searchTerm ? highlightMatch(paddedLabel, this.searchTerm) : paddedLabel;
1259
+ const line = isActive ? `${checkbox} ${highlightedPaddedLabel} ${import_picocolors.default.dim(hint)}` : `${checkbox} ${import_picocolors.default.dim(paddedLabel)} ${import_picocolors.default.dim(hint)}`;
1200
1260
  lines.push(`${import_picocolors.default.cyan(S_BAR)} ${line}`);
1201
- if (isActive && needsTruncation) {
1202
- const wrappedLines = wrapText(fullHint, DESCRIPTION_MAX_WIDTH);
1203
- for (const descLine of wrappedLines) {
1204
- lines.push(
1205
- `${import_picocolors.default.cyan(S_BAR)} ${DESCRIPTION_INDENT}${import_picocolors.default.dim(descLine)}`
1206
- );
1207
- }
1208
- }
1209
1261
  }
1210
1262
  if (belowCount > 0) {
1211
1263
  lines.push(
@@ -1454,24 +1506,13 @@ var SearchableGroupMultiSelectPrompt = class extends x {
1454
1506
  } else {
1455
1507
  checkbox = S_CHECKBOX_INACTIVE;
1456
1508
  }
1457
- const label = this.searchTerm ? highlightMatch(item.option.option.label, this.searchTerm) : item.option.option.label;
1458
- const fullHint = item.option.option.hint || "";
1459
- const needsTruncation = fullHint.length > MAX_HINT_LENGTH;
1460
- const displayHint = needsTruncation ? truncateHint(fullHint, MAX_HINT_LENGTH) : fullHint;
1461
- const hint = displayHint ? import_picocolors.default.dim(` (${displayHint})`) : "";
1509
+ const hint = item.option.option.hint || "";
1462
1510
  const isLastInGroup = i + 1 >= visibleItems.length || visibleItems[i + 1].type === "group";
1463
1511
  const indent = isLastInGroup ? `${import_picocolors.default.gray("\u2514")} ` : `${import_picocolors.default.gray("\u2502")} `;
1464
- const line = isActive ? `${indent}${checkbox} ${label}${hint}` : `${indent}${checkbox} ${import_picocolors.default.dim(item.option.option.label)}${import_picocolors.default.dim(hint)}`;
1512
+ const paddedLabel = item.option.option.label.padEnd(MAX_LABEL_WIDTH);
1513
+ const highlightedPaddedLabel = this.searchTerm ? highlightMatch(paddedLabel, this.searchTerm) : paddedLabel;
1514
+ const line = isActive ? `${indent}${checkbox} ${highlightedPaddedLabel} ${import_picocolors.default.dim(hint)}` : `${indent}${checkbox} ${import_picocolors.default.dim(paddedLabel)} ${import_picocolors.default.dim(hint)}`;
1465
1515
  lines.push(`${import_picocolors.default.cyan(S_BAR)} ${line}`);
1466
- if (isActive && needsTruncation) {
1467
- const descIndent = isLastInGroup ? " " : `${import_picocolors.default.gray("\u2502")} `;
1468
- const wrappedLines = wrapText(fullHint, DESCRIPTION_MAX_WIDTH);
1469
- for (const descLine of wrappedLines) {
1470
- lines.push(
1471
- `${import_picocolors.default.cyan(S_BAR)} ${descIndent}${DESCRIPTION_INDENT}${import_picocolors.default.dim(descLine)}`
1472
- );
1473
- }
1474
- }
1475
1516
  }
1476
1517
  }
1477
1518
  if (belowCount > 0) {
@@ -1856,14 +1897,70 @@ function printSkillTree(node, indent = 0) {
1856
1897
  console.log(chalk.green(`\u2502 ${prefix}\u2022 ${s.name}`) + desc);
1857
1898
  }
1858
1899
  }
1859
- async function main() {
1860
- const program = new Command();
1861
- program.name("skai").description("The package manager for AI agent skills").version(getVersion(), "-V, --version", "Display version").argument("[source]", "GitHub repo, URL, or local path to install skills from").option("-g, --global", "Install to user directory instead of project", false).option("-a, --agent <agents...>", "Target specific agents").option("-s, --skill <skills...>", "Install specific skills by name").option("-l, --list", "List available skills without installing", false).option("-y, --yes", "Skip confirmation prompts", false).option("--json", "Output results in JSON format", false).action(async (source, options) => {
1862
- await run(source, options);
1863
- });
1864
- await program.parseAsync(process.argv);
1900
+ function formatGitError(error, url) {
1901
+ const msg = error.message.toLowerCase();
1902
+ if (msg.includes("authentication") || msg.includes("401") || msg.includes("403")) {
1903
+ return `Authentication failed for ${url}. Check your credentials or ensure the repository is public.`;
1904
+ }
1905
+ if (msg.includes("not found") || msg.includes("404") || msg.includes("does not exist")) {
1906
+ return `Repository not found: ${url}. Check the URL or owner/repo name.`;
1907
+ }
1908
+ if (msg.includes("timeout") || msg.includes("timed out")) {
1909
+ return `Connection timed out while cloning ${url}. Check your network connection.`;
1910
+ }
1911
+ if (msg.includes("could not resolve host") || msg.includes("network")) {
1912
+ return `Network error while cloning ${url}. Check your internet connection.`;
1913
+ }
1914
+ if (msg.includes("permission denied")) {
1915
+ return `Permission denied when accessing ${url}. The repository may be private.`;
1916
+ }
1917
+ return `Failed to clone repository: ${error.message}`;
1918
+ }
1919
+ function formatInstallStatus(statuses, isDryRun) {
1920
+ if (statuses.length === 0) return;
1921
+ const grouped = /* @__PURE__ */ new Map();
1922
+ for (const status of statuses) {
1923
+ const key = status.agentName;
1924
+ if (!grouped.has(key)) grouped.set(key, []);
1925
+ grouped.get(key).push(status);
1926
+ }
1927
+ for (const [agent, skills] of grouped) {
1928
+ console.log(chalk.bold(`
1929
+ ${agent}:`));
1930
+ for (const skill of skills) {
1931
+ let icon;
1932
+ let color2;
1933
+ let suffix = "";
1934
+ switch (skill.status) {
1935
+ case "installed":
1936
+ icon = "\u2713";
1937
+ color2 = chalk.green;
1938
+ suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
1939
+ break;
1940
+ case "would-install":
1941
+ icon = "\u25CB";
1942
+ color2 = chalk.cyan;
1943
+ suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
1944
+ break;
1945
+ case "skipped":
1946
+ icon = "\u2013";
1947
+ color2 = chalk.yellow;
1948
+ suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
1949
+ break;
1950
+ case "failed":
1951
+ icon = "\u2717";
1952
+ color2 = chalk.red;
1953
+ suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
1954
+ break;
1955
+ }
1956
+ console.log(` ${color2(icon)} ${skill.skillName}${suffix}`);
1957
+ }
1958
+ }
1959
+ if (isDryRun) {
1960
+ console.log(chalk.cyan("\n(dry-run mode - no changes made)"));
1961
+ }
1865
1962
  }
1866
- async function run(source, options) {
1963
+ async function runInstall(source, options) {
1867
1964
  let tempDirToClean = null;
1868
1965
  const handleSignal = () => {
1869
1966
  if (tempDirToClean) {
@@ -1881,6 +1978,7 @@ async function run(source, options) {
1881
1978
  if (!source) {
1882
1979
  clack.log.error("Please provide a source (GitHub repo, URL, or local path)");
1883
1980
  clack.log.info("Usage: skai <source> [options]");
1981
+ clack.log.info("");
1884
1982
  clack.log.info("Examples:");
1885
1983
  clack.log.info(" skai pproenca/dot-skills");
1886
1984
  clack.log.info(" skai https://github.com/org/repo");
@@ -1898,6 +1996,9 @@ async function run(source, options) {
1898
1996
  if (!parsed.localPath) {
1899
1997
  throw new Error("Local path not found in parsed source");
1900
1998
  }
1999
+ if (!fs7.existsSync(parsed.localPath)) {
2000
+ throw new Error(`Local path does not exist: ${parsed.localPath}`);
2001
+ }
1901
2002
  skillsBasePath = parsed.localPath;
1902
2003
  clack.log.info(`Using local path: ${skillsBasePath}`);
1903
2004
  } else {
@@ -1914,7 +2015,8 @@ async function run(source, options) {
1914
2015
  spinner2.stop("Repository cloned");
1915
2016
  } catch (error) {
1916
2017
  spinner2.stop("Failed to clone repository");
1917
- throw error;
2018
+ const formattedError = formatGitError(error, parsed.url || source);
2019
+ throw new Error(formattedError);
1918
2020
  }
1919
2021
  }
1920
2022
  const discoverSpinner = clack.spinner();
@@ -1923,6 +2025,7 @@ async function run(source, options) {
1923
2025
  discoverSpinner.stop(`Found ${skills.length} skill(s)`);
1924
2026
  if (skills.length === 0) {
1925
2027
  clack.log.warn("No skills found in the repository");
2028
+ clack.log.info("Skills must have a SKILL.md file with frontmatter metadata.");
1926
2029
  clack.outro(chalk.yellow("No skills to install"));
1927
2030
  return;
1928
2031
  }
@@ -1956,12 +2059,24 @@ async function run(source, options) {
1956
2059
  }
1957
2060
  let targetAgents;
1958
2061
  if (options.agent && options.agent.length > 0) {
1959
- targetAgents = options.agent.map((name) => getAgentByName(name)).filter((a) => a !== void 0);
1960
- if (targetAgents.length === 0) {
1961
- clack.log.error(`No valid agents found for: ${options.agent.join(", ")}`);
2062
+ const invalidAgents = [];
2063
+ targetAgents = [];
2064
+ for (const name of options.agent) {
2065
+ const agent = getAgentByName(name);
2066
+ if (agent) {
2067
+ targetAgents.push(agent);
2068
+ } else {
2069
+ invalidAgents.push(name);
2070
+ }
2071
+ }
2072
+ if (invalidAgents.length > 0) {
2073
+ clack.log.warn(`Unknown agent(s): ${invalidAgents.join(", ")}`);
1962
2074
  clack.log.info(
1963
2075
  `Available agents: ${getAllAgents().map((a) => a.name).join(", ")}`
1964
2076
  );
2077
+ }
2078
+ if (targetAgents.length === 0) {
2079
+ clack.log.error("No valid agents specified");
1965
2080
  clack.outro(chalk.red("No valid agents"));
1966
2081
  return;
1967
2082
  }
@@ -2053,6 +2168,44 @@ async function run(source, options) {
2053
2168
  }
2054
2169
  isGlobal = scope === "global";
2055
2170
  }
2171
+ if (options.dryRun) {
2172
+ const statuses2 = [];
2173
+ const installOptions2 = { global: isGlobal, yes: options.yes };
2174
+ for (const skill of selectedSkills) {
2175
+ for (const agent of selectedAgents) {
2176
+ const targetPath = getSkillInstallPath(skill.name, agent, installOptions2);
2177
+ if (isSkillInstalled(skill, agent, installOptions2)) {
2178
+ statuses2.push({
2179
+ skillName: skill.name,
2180
+ agentName: agent.displayName,
2181
+ status: "skipped",
2182
+ path: targetPath,
2183
+ reason: "already installed"
2184
+ });
2185
+ } else {
2186
+ statuses2.push({
2187
+ skillName: skill.name,
2188
+ agentName: agent.displayName,
2189
+ status: "would-install",
2190
+ path: targetPath
2191
+ });
2192
+ }
2193
+ }
2194
+ }
2195
+ if (options.json) {
2196
+ const jsonOutput = {
2197
+ dry_run: true,
2198
+ would_install: statuses2.filter((s) => s.status === "would-install").map((s) => ({ skill: s.skillName, agent: s.agentName, path: s.path })),
2199
+ would_skip: statuses2.filter((s) => s.status === "skipped").map((s) => ({ skill: s.skillName, agent: s.agentName, reason: s.reason }))
2200
+ };
2201
+ console.log(JSON.stringify(jsonOutput, null, 2));
2202
+ return;
2203
+ }
2204
+ clack.log.info(chalk.bold("\nDry Run - Installation Preview:"));
2205
+ formatInstallStatus(statuses2, true);
2206
+ clack.outro(chalk.cyan("Dry run complete"));
2207
+ return;
2208
+ }
2056
2209
  if (!options.yes) {
2057
2210
  const summary = [
2058
2211
  `Skills: ${selectedSkills.map((s) => s.name).join(", ")}`,
@@ -2079,20 +2232,41 @@ Installation Summary:
2079
2232
  };
2080
2233
  const installOptions = { global: isGlobal, yes: options.yes };
2081
2234
  const installedSkillNames = [];
2235
+ const statuses = [];
2082
2236
  for (const skill of selectedSkills) {
2083
2237
  for (const agent of selectedAgents) {
2238
+ const targetPath = getSkillInstallPath(skill.name, agent, installOptions);
2084
2239
  if (isSkillInstalled(skill, agent, installOptions)) {
2085
2240
  results.skipped++;
2241
+ statuses.push({
2242
+ skillName: skill.name,
2243
+ agentName: agent.displayName,
2244
+ status: "skipped",
2245
+ path: targetPath,
2246
+ reason: "already installed"
2247
+ });
2086
2248
  continue;
2087
2249
  }
2088
2250
  const result = installSkillForAgent(skill, agent, installOptions);
2089
2251
  if (result.success) {
2090
2252
  results.success++;
2253
+ statuses.push({
2254
+ skillName: skill.name,
2255
+ agentName: agent.displayName,
2256
+ status: "installed",
2257
+ path: result.targetPath
2258
+ });
2091
2259
  if (!installedSkillNames.includes(skill.name)) {
2092
2260
  installedSkillNames.push(skill.name);
2093
2261
  }
2094
2262
  } else {
2095
2263
  results.failed++;
2264
+ statuses.push({
2265
+ skillName: skill.name,
2266
+ agentName: agent.displayName,
2267
+ status: "failed",
2268
+ reason: result.error
2269
+ });
2096
2270
  if (!options.json) {
2097
2271
  clack.log.warn(`Failed to install ${skill.name} to ${agent.displayName}: ${result.error}`);
2098
2272
  }
@@ -2100,6 +2274,9 @@ Installation Summary:
2100
2274
  }
2101
2275
  }
2102
2276
  installSpinner.stop(`Installed ${results.success} skill(s)`);
2277
+ if (!options.json && statuses.length > 0) {
2278
+ formatInstallStatus(statuses, false);
2279
+ }
2103
2280
  const depSpinner = clack.spinner();
2104
2281
  depSpinner.start("Scanning for dependencies...");
2105
2282
  const skillDeps = [];
@@ -2251,6 +2428,220 @@ Installation Summary:
2251
2428
  }
2252
2429
  }
2253
2430
  }
2431
+ async function runUninstall(skillNames, options) {
2432
+ clack.intro(chalk.cyan("skai uninstall"));
2433
+ if (skillNames.length === 0) {
2434
+ clack.log.error("Please provide at least one skill name to uninstall");
2435
+ clack.log.info("Usage: skai uninstall <skill> [skill...] [-a <agent>]");
2436
+ clack.outro(chalk.red("No skills specified"));
2437
+ process.exit(EXIT_ERROR);
2438
+ }
2439
+ let targetAgents;
2440
+ if (options.agent && options.agent.length > 0) {
2441
+ targetAgents = options.agent.map((name) => getAgentByName(name)).filter((a) => a !== void 0);
2442
+ if (targetAgents.length === 0) {
2443
+ clack.log.error(`No valid agents found for: ${options.agent.join(", ")}`);
2444
+ clack.log.info(
2445
+ `Available agents: ${getAllAgents().map((a) => a.name).join(", ")}`
2446
+ );
2447
+ clack.outro(chalk.red("No valid agents"));
2448
+ process.exit(EXIT_ERROR);
2449
+ }
2450
+ } else {
2451
+ targetAgents = detectInstalledAgents();
2452
+ if (targetAgents.length === 0) {
2453
+ targetAgents = getAllAgents();
2454
+ }
2455
+ }
2456
+ const toUninstall = [];
2457
+ for (const skillName of skillNames) {
2458
+ for (const agent of targetAgents) {
2459
+ const projectInstalled = listInstalledSkills(agent, { projectOnly: true }).some((s) => s.name === skillName);
2460
+ const globalInstalled = listInstalledSkills(agent, { global: true }).some((s) => s.name === skillName);
2461
+ if (options.global && globalInstalled) {
2462
+ toUninstall.push({ skill: skillName, agent, scope: "global" });
2463
+ } else if (!options.global && projectInstalled) {
2464
+ toUninstall.push({ skill: skillName, agent, scope: "project" });
2465
+ } else if (!options.global && globalInstalled) {
2466
+ toUninstall.push({ skill: skillName, agent, scope: "global" });
2467
+ }
2468
+ }
2469
+ }
2470
+ if (toUninstall.length === 0) {
2471
+ clack.log.warn("No installed skills found matching the specified names");
2472
+ clack.outro(chalk.yellow("Nothing to uninstall"));
2473
+ return;
2474
+ }
2475
+ if (!options.yes) {
2476
+ clack.log.info(chalk.bold("\nSkills to uninstall:"));
2477
+ for (const item of toUninstall) {
2478
+ clack.log.info(` \u2022 ${item.skill} from ${item.agent.displayName} (${item.scope})`);
2479
+ }
2480
+ const confirmed = await clack.confirm({
2481
+ message: `Uninstall ${toUninstall.length} skill(s)?`
2482
+ });
2483
+ if (clack.isCancel(confirmed) || !confirmed) {
2484
+ clack.outro(chalk.yellow("Cancelled"));
2485
+ return;
2486
+ }
2487
+ }
2488
+ const results = { success: 0, failed: 0 };
2489
+ const uninstalledSkills = [];
2490
+ const errors = [];
2491
+ for (const item of toUninstall) {
2492
+ const installOptions = { global: item.scope === "global", yes: options.yes };
2493
+ const result = uninstallSkill(item.skill, item.agent, installOptions);
2494
+ if (result.success) {
2495
+ results.success++;
2496
+ if (!uninstalledSkills.includes(item.skill)) {
2497
+ uninstalledSkills.push(item.skill);
2498
+ }
2499
+ if (!options.json) {
2500
+ clack.log.info(chalk.green(`\u2713 Uninstalled ${item.skill} from ${item.agent.displayName}`));
2501
+ }
2502
+ } else {
2503
+ results.failed++;
2504
+ errors.push({ skill: item.skill, agent: item.agent.displayName, error: result.error || "Unknown error" });
2505
+ if (!options.json) {
2506
+ clack.log.warn(`\u2717 Failed to uninstall ${item.skill} from ${item.agent.displayName}: ${result.error}`);
2507
+ }
2508
+ }
2509
+ }
2510
+ if (options.json) {
2511
+ const jsonOutput = {
2512
+ skills_uninstalled: uninstalledSkills,
2513
+ errors
2514
+ };
2515
+ console.log(JSON.stringify(jsonOutput, null, 2));
2516
+ return;
2517
+ }
2518
+ const resultParts = [];
2519
+ if (results.success > 0) resultParts.push(chalk.green(`${results.success} uninstalled`));
2520
+ if (results.failed > 0) resultParts.push(chalk.red(`${results.failed} failed`));
2521
+ clack.outro(resultParts.join(", ") || chalk.green("Done"));
2522
+ }
2523
+ async function runList(options) {
2524
+ if (!options.json) {
2525
+ clack.intro(chalk.cyan("skai list"));
2526
+ }
2527
+ let targetAgents;
2528
+ if (options.agent && options.agent.length > 0) {
2529
+ targetAgents = options.agent.map((name) => getAgentByName(name)).filter((a) => a !== void 0);
2530
+ if (targetAgents.length === 0 && !options.json) {
2531
+ clack.log.error(`No valid agents found for: ${options.agent.join(", ")}`);
2532
+ clack.log.info(
2533
+ `Available agents: ${getAllAgents().map((a) => a.name).join(", ")}`
2534
+ );
2535
+ clack.outro(chalk.red("No valid agents"));
2536
+ process.exit(EXIT_ERROR);
2537
+ }
2538
+ } else {
2539
+ targetAgents = detectInstalledAgents();
2540
+ if (targetAgents.length === 0) {
2541
+ targetAgents = getAllAgents();
2542
+ }
2543
+ }
2544
+ const allSkills = [];
2545
+ const agentSkillsMap = /* @__PURE__ */ new Map();
2546
+ for (const agent of targetAgents) {
2547
+ const skills = listInstalledSkills(agent, { global: options.global });
2548
+ const projectSkills = [];
2549
+ const globalSkills = [];
2550
+ for (const skill of skills) {
2551
+ allSkills.push({
2552
+ name: skill.name,
2553
+ path: skill.path,
2554
+ agent: agent.displayName,
2555
+ scope: skill.scope
2556
+ });
2557
+ if (skill.scope === "project") {
2558
+ projectSkills.push(skill.name);
2559
+ } else {
2560
+ globalSkills.push(skill.name);
2561
+ }
2562
+ }
2563
+ if (projectSkills.length > 0 || globalSkills.length > 0) {
2564
+ agentSkillsMap.set(agent.displayName, { project: projectSkills, global: globalSkills });
2565
+ }
2566
+ }
2567
+ if (options.json) {
2568
+ const jsonOutput = { skills: allSkills };
2569
+ console.log(JSON.stringify(jsonOutput, null, 2));
2570
+ return;
2571
+ }
2572
+ if (allSkills.length === 0) {
2573
+ clack.log.info("No skills installed");
2574
+ clack.outro(chalk.dim('Use "skai <source>" to install skills'));
2575
+ return;
2576
+ }
2577
+ for (const [agentName, skills] of agentSkillsMap) {
2578
+ console.log(chalk.bold(`
2579
+ ${agentName}:`));
2580
+ if (skills.project.length > 0) {
2581
+ console.log(chalk.dim(" Project:"));
2582
+ for (const skill of skills.project.sort()) {
2583
+ console.log(chalk.green(` \u2022 ${skill}`));
2584
+ }
2585
+ }
2586
+ if (skills.global.length > 0) {
2587
+ console.log(chalk.dim(" Global:"));
2588
+ for (const skill of skills.global.sort()) {
2589
+ console.log(chalk.blue(` \u2022 ${skill}`));
2590
+ }
2591
+ }
2592
+ }
2593
+ clack.outro(chalk.cyan(`${allSkills.length} skill(s) installed`));
2594
+ }
2595
+ async function runUpdate(_skillNames, _options) {
2596
+ clack.intro(chalk.cyan("skai update"));
2597
+ clack.log.warn("The update command requires tracking skill sources.");
2598
+ clack.log.info("Currently, skai does not track where skills were installed from.");
2599
+ clack.log.info("");
2600
+ clack.log.info("To update a skill, you can:");
2601
+ clack.log.info(" 1. Uninstall the existing skill: skai uninstall <skill>");
2602
+ clack.log.info(" 2. Reinstall from the source: skai <source>");
2603
+ clack.outro(chalk.yellow("Update not yet implemented"));
2604
+ }
2605
+ async function main() {
2606
+ const program = new Command();
2607
+ program.name("skai").description("The package manager for AI agent skills").version(getVersion(), "-V, --version", "Display version");
2608
+ program.argument("[source]", "GitHub repo, URL, or local path to install skills from").option("-g, --global", "Install to user directory instead of project", false).option("-a, --agent <agents...>", "Target specific agents").option("-s, --skill <skills...>", "Install specific skills by name").option("-l, --list", "List available skills without installing", false).option("-y, --yes", "Skip confirmation prompts", false).option("--json", "Output results in JSON format", false).option("--dry-run", "Preview installation without making changes", false).action(async (source, options) => {
2609
+ await runInstall(source, options);
2610
+ });
2611
+ program.command("install <source>").description("Install skills from a source").option("-g, --global", "Install to user directory instead of project", false).option("-a, --agent <agents...>", "Target specific agents").option("-s, --skill <skills...>", "Install specific skills by name").option("-l, --list", "List available skills without installing", false).option("-y, --yes", "Skip confirmation prompts", false).option("--json", "Output results in JSON format", false).option("--dry-run", "Preview installation without making changes", false).action(async (source, options) => {
2612
+ await runInstall(source, options);
2613
+ });
2614
+ program.command("uninstall <skills...>").alias("rm").alias("remove").description("Uninstall skills from agents").option("-g, --global", "Uninstall from global directory", false).option("-a, --agent <agents...>", "Target specific agents").option("-y, --yes", "Skip confirmation prompts", false).option("--json", "Output results in JSON format", false).action(async (skills, options) => {
2615
+ await runUninstall(skills, options);
2616
+ });
2617
+ program.command("list").alias("ls").description("List installed skills").option("-g, --global", "List only global skills", false).option("-a, --agent <agents...>", "Target specific agents").option("--json", "Output results in JSON format", false).action(async (options) => {
2618
+ await runList(options);
2619
+ });
2620
+ program.command("update [skills...]").alias("up").description("Update installed skills (coming soon)").option("-g, --global", "Update global skills", false).option("-a, --agent <agents...>", "Target specific agents").option("-y, --yes", "Skip confirmation prompts", false).option("--json", "Output results in JSON format", false).action(async (skills, options) => {
2621
+ await runUpdate(skills, options);
2622
+ });
2623
+ program.addHelpText(
2624
+ "after",
2625
+ `
2626
+ Examples:
2627
+ $ skai pproenca/dot-skills Install skills from GitHub
2628
+ $ skai https://github.com/org/repo Install from full URL
2629
+ $ skai ./local/skills Install from local directory
2630
+ $ skai pproenca/dot-skills -s typescript Install specific skill
2631
+ $ skai pproenca/dot-skills -a claude-code Install to specific agent
2632
+ $ skai pproenca/dot-skills --dry-run Preview installation
2633
+ $ skai list List installed skills
2634
+ $ skai list -a cursor List skills for specific agent
2635
+ $ skai uninstall typescript Uninstall a skill
2636
+ $ skai uninstall typescript -a cursor Uninstall from specific agent
2637
+
2638
+ Supported Agents:
2639
+ claude-code, cursor, copilot, windsurf, codex, opencode, amp,
2640
+ kilo-code, roo-code, goose, gemini, antigravity, clawdbot, droid
2641
+ `
2642
+ );
2643
+ await program.parseAsync(process.argv);
2644
+ }
2254
2645
  main().catch((error) => {
2255
2646
  console.error(chalk.red("Fatal error:"), error);
2256
2647
  process.exit(EXIT_ERROR);