skai 0.0.5 → 0.0.7

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,146 @@ 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
+ }
743
+ var DISABLED_SUFFIX = ".disabled";
744
+ function listManagedSkills(agent, options = {}) {
745
+ const skills = [];
746
+ const checkPath = (basePath, scope) => {
747
+ if (!fs5.existsSync(basePath)) return;
748
+ try {
749
+ const entries = fs5.readdirSync(basePath, { withFileTypes: true });
750
+ for (const entry of entries) {
751
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
752
+ const isDisabled = entry.name.endsWith(DISABLED_SUFFIX);
753
+ const name = isDisabled ? entry.name.slice(0, -DISABLED_SUFFIX.length) : entry.name;
754
+ skills.push({
755
+ name,
756
+ path: path5.join(basePath, entry.name),
757
+ agent,
758
+ scope,
759
+ enabled: !isDisabled
760
+ });
761
+ }
762
+ }
763
+ } catch {
764
+ }
765
+ };
766
+ if (!options.global) {
767
+ const projectPath = path5.join(process.cwd(), agent.projectPath);
768
+ checkPath(projectPath, "project");
769
+ }
770
+ if (!options.projectOnly) {
771
+ checkPath(agent.globalPath, "global");
772
+ }
773
+ return skills;
774
+ }
775
+ function toggleSkill(skill) {
776
+ const currentPath = skill.path;
777
+ const basePath = path5.dirname(currentPath);
778
+ const currentName = path5.basename(currentPath);
779
+ let newName;
780
+ let newEnabled;
781
+ if (skill.enabled) {
782
+ newName = currentName + DISABLED_SUFFIX;
783
+ newEnabled = false;
784
+ } else {
785
+ newName = currentName.slice(0, -DISABLED_SUFFIX.length);
786
+ newEnabled = true;
787
+ }
788
+ const newPath = path5.join(basePath, newName);
789
+ if (!isPathSafe(newPath, basePath)) {
790
+ return {
791
+ skillName: skill.name,
792
+ agent: skill.agent,
793
+ success: false,
794
+ enabled: skill.enabled,
795
+ error: "Invalid skill path"
796
+ };
797
+ }
798
+ try {
799
+ fs5.renameSync(currentPath, newPath);
800
+ return {
801
+ skillName: skill.name,
802
+ agent: skill.agent,
803
+ success: true,
804
+ enabled: newEnabled
805
+ };
806
+ } catch (error) {
807
+ return {
808
+ skillName: skill.name,
809
+ agent: skill.agent,
810
+ success: false,
811
+ enabled: skill.enabled,
812
+ error: error instanceof Error ? error.message : String(error)
813
+ };
814
+ }
815
+ }
654
816
 
655
817
  // src/tree-select.ts
656
818
  import * as p from "@clack/prompts";
@@ -976,34 +1138,7 @@ var S_BAR_END = import_picocolors.default.gray("\u2514");
976
1138
  var S_CHECKBOX_ACTIVE = import_picocolors.default.cyan("\u25FB");
977
1139
  var S_CHECKBOX_SELECTED = import_picocolors.default.green("\u25FC");
978
1140
  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;
1141
+ var MAX_LABEL_WIDTH = 30;
1007
1142
  function buildSearchableOptions(options) {
1008
1143
  return options.map((opt) => ({
1009
1144
  option: opt,
@@ -1191,21 +1326,11 @@ var SearchableMultiSelectPrompt = class extends x {
1191
1326
  } else {
1192
1327
  checkbox = S_CHECKBOX_INACTIVE;
1193
1328
  }
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)}`;
1329
+ const hint = opt.option.hint || "";
1330
+ const paddedLabel = opt.option.label.padEnd(MAX_LABEL_WIDTH);
1331
+ const highlightedPaddedLabel = this.searchTerm ? highlightMatch(paddedLabel, this.searchTerm) : paddedLabel;
1332
+ const line = isActive ? `${checkbox} ${highlightedPaddedLabel} ${import_picocolors.default.dim(hint)}` : `${checkbox} ${import_picocolors.default.dim(paddedLabel)} ${import_picocolors.default.dim(hint)}`;
1200
1333
  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
1334
  }
1210
1335
  if (belowCount > 0) {
1211
1336
  lines.push(
@@ -1454,24 +1579,13 @@ var SearchableGroupMultiSelectPrompt = class extends x {
1454
1579
  } else {
1455
1580
  checkbox = S_CHECKBOX_INACTIVE;
1456
1581
  }
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})`) : "";
1582
+ const hint = item.option.option.hint || "";
1462
1583
  const isLastInGroup = i + 1 >= visibleItems.length || visibleItems[i + 1].type === "group";
1463
1584
  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)}`;
1585
+ const paddedLabel = item.option.option.label.padEnd(MAX_LABEL_WIDTH);
1586
+ const highlightedPaddedLabel = this.searchTerm ? highlightMatch(paddedLabel, this.searchTerm) : paddedLabel;
1587
+ const line = isActive ? `${indent}${checkbox} ${highlightedPaddedLabel} ${import_picocolors.default.dim(hint)}` : `${indent}${checkbox} ${import_picocolors.default.dim(paddedLabel)} ${import_picocolors.default.dim(hint)}`;
1465
1588
  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
1589
  }
1476
1590
  }
1477
1591
  if (belowCount > 0) {
@@ -1615,6 +1729,227 @@ async function treeSelect(nodes) {
1615
1729
  return [];
1616
1730
  }
1617
1731
 
1732
+ // src/skill-manager.ts
1733
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
1734
+ var MAX_VISIBLE_ITEMS2 = 12;
1735
+ var MAX_NAME_WIDTH = 25;
1736
+ var MAX_AGENT_WIDTH = 14;
1737
+ var S_STEP_ACTIVE2 = import_picocolors2.default.green("\u25C6");
1738
+ var S_STEP_CANCEL2 = import_picocolors2.default.red("\u25A0");
1739
+ var S_STEP_SUBMIT2 = import_picocolors2.default.green("\u25C7");
1740
+ var S_BAR2 = import_picocolors2.default.gray("\u2502");
1741
+ var S_BAR_END2 = import_picocolors2.default.gray("\u2514");
1742
+ var S_TOGGLE_ON = import_picocolors2.default.green("\u25CF");
1743
+ var S_TOGGLE_OFF = import_picocolors2.default.dim("\u25CB");
1744
+ var S_TOGGLE_ACTIVE_ON = import_picocolors2.default.green("\u25C9");
1745
+ var S_TOGGLE_ACTIVE_OFF = import_picocolors2.default.cyan("\u25CE");
1746
+ function symbol2(state) {
1747
+ switch (state) {
1748
+ case "active":
1749
+ return S_STEP_ACTIVE2;
1750
+ case "cancel":
1751
+ return S_STEP_CANCEL2;
1752
+ case "submit":
1753
+ return S_STEP_SUBMIT2;
1754
+ default:
1755
+ return import_picocolors2.default.cyan("\u25C6");
1756
+ }
1757
+ }
1758
+ function getSkillKey(skill) {
1759
+ return `${skill.agent.name}:${skill.scope}:${skill.name}`;
1760
+ }
1761
+ var SkillManagerPrompt = class extends x {
1762
+ state_data;
1763
+ maxItems;
1764
+ constructor(skills) {
1765
+ super(
1766
+ {
1767
+ render: () => this.renderPrompt()
1768
+ },
1769
+ false
1770
+ );
1771
+ this.state_data = {
1772
+ skills,
1773
+ cursor: 0,
1774
+ scrollOffset: 0,
1775
+ changes: /* @__PURE__ */ new Map()
1776
+ };
1777
+ this.maxItems = MAX_VISIBLE_ITEMS2;
1778
+ this.on("cursor", (action) => this.handleCursor(action ?? "up"));
1779
+ }
1780
+ handleCursor(action) {
1781
+ switch (action) {
1782
+ case "up":
1783
+ this.state_data.cursor = Math.max(0, this.state_data.cursor - 1);
1784
+ this.adjustScroll();
1785
+ break;
1786
+ case "down":
1787
+ this.state_data.cursor = Math.min(
1788
+ this.state_data.skills.length - 1,
1789
+ this.state_data.cursor + 1
1790
+ );
1791
+ this.adjustScroll();
1792
+ break;
1793
+ case "space":
1794
+ this.toggleCurrent();
1795
+ break;
1796
+ }
1797
+ }
1798
+ adjustScroll() {
1799
+ if (this.state_data.cursor < this.state_data.scrollOffset) {
1800
+ this.state_data.scrollOffset = this.state_data.cursor;
1801
+ } else if (this.state_data.cursor >= this.state_data.scrollOffset + this.maxItems) {
1802
+ this.state_data.scrollOffset = this.state_data.cursor - this.maxItems + 1;
1803
+ }
1804
+ }
1805
+ toggleCurrent() {
1806
+ const skill = this.state_data.skills[this.state_data.cursor];
1807
+ if (!skill) return;
1808
+ const key = getSkillKey(skill);
1809
+ const currentState = this.state_data.changes.has(key) ? this.state_data.changes.get(key) : skill.enabled;
1810
+ this.state_data.changes.set(key, !currentState);
1811
+ }
1812
+ getEffectiveState(skill) {
1813
+ const key = getSkillKey(skill);
1814
+ return this.state_data.changes.has(key) ? this.state_data.changes.get(key) : skill.enabled;
1815
+ }
1816
+ renderPrompt() {
1817
+ const lines = [];
1818
+ const { skills, cursor, scrollOffset, changes } = this.state_data;
1819
+ lines.push(`${import_picocolors2.default.gray(S_BAR2)}`);
1820
+ lines.push(`${symbol2(this.state)} Manage installed skills`);
1821
+ if (this.state === "submit") {
1822
+ const changeCount2 = changes.size;
1823
+ if (changeCount2 === 0) {
1824
+ lines.push(`${import_picocolors2.default.gray(S_BAR2)} ${import_picocolors2.default.dim("No changes")}`);
1825
+ } else {
1826
+ lines.push(`${import_picocolors2.default.gray(S_BAR2)} ${import_picocolors2.default.dim(`${changeCount2} change(s) applied`)}`);
1827
+ }
1828
+ return lines.join("\n");
1829
+ }
1830
+ if (this.state === "cancel") {
1831
+ lines.push(`${import_picocolors2.default.gray(S_BAR2)} ${import_picocolors2.default.dim("Cancelled")}`);
1832
+ lines.push(`${import_picocolors2.default.gray(S_BAR2)}`);
1833
+ return lines.join("\n");
1834
+ }
1835
+ if (skills.length === 0) {
1836
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)}`);
1837
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim("No skills installed")}`);
1838
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim('Use "skai <source>" to install skills')}`);
1839
+ lines.push(`${import_picocolors2.default.cyan(S_BAR_END2)}`);
1840
+ return lines.join("\n");
1841
+ }
1842
+ const changeCount = changes.size;
1843
+ const changeText = changeCount > 0 ? import_picocolors2.default.yellow(` \u2022 ${changeCount} pending change(s)`) : "";
1844
+ lines.push(
1845
+ `${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim("\u2191/\u2193 navigate \u2022 space toggle \u2022 enter apply")}${changeText}`
1846
+ );
1847
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim("\u2500".repeat(50))}`);
1848
+ const headerName = "SKILL".padEnd(MAX_NAME_WIDTH);
1849
+ const headerAgent = "AGENT".padEnd(MAX_AGENT_WIDTH);
1850
+ const headerScope = "SCOPE".padEnd(8);
1851
+ const headerStatus = "STATUS";
1852
+ lines.push(
1853
+ `${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim(" " + headerName + headerAgent + headerScope + headerStatus)}`
1854
+ );
1855
+ const aboveCount = scrollOffset;
1856
+ const belowCount = Math.max(0, skills.length - scrollOffset - this.maxItems);
1857
+ if (aboveCount > 0) {
1858
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim(`\u2191 ${aboveCount} more above`)}`);
1859
+ }
1860
+ const visibleSkills = skills.slice(scrollOffset, scrollOffset + this.maxItems);
1861
+ for (let i = 0; i < visibleSkills.length; i++) {
1862
+ const skill = visibleSkills[i];
1863
+ const globalIndex = scrollOffset + i;
1864
+ const isActive = globalIndex === cursor;
1865
+ const enabled = this.getEffectiveState(skill);
1866
+ const wasChanged = changes.has(getSkillKey(skill));
1867
+ let toggle;
1868
+ if (isActive && enabled) {
1869
+ toggle = S_TOGGLE_ACTIVE_ON;
1870
+ } else if (isActive && !enabled) {
1871
+ toggle = S_TOGGLE_ACTIVE_OFF;
1872
+ } else if (enabled) {
1873
+ toggle = S_TOGGLE_ON;
1874
+ } else {
1875
+ toggle = S_TOGGLE_OFF;
1876
+ }
1877
+ const name = skill.name.length > MAX_NAME_WIDTH ? skill.name.slice(0, MAX_NAME_WIDTH - 2) + ".." : skill.name.padEnd(MAX_NAME_WIDTH);
1878
+ const agent = skill.agent.displayName.length > MAX_AGENT_WIDTH ? skill.agent.displayName.slice(0, MAX_AGENT_WIDTH - 2) + ".." : skill.agent.displayName.padEnd(MAX_AGENT_WIDTH);
1879
+ const scope = skill.scope.padEnd(8);
1880
+ const status = enabled ? import_picocolors2.default.green("enabled") : import_picocolors2.default.dim("disabled");
1881
+ const changedMarker = wasChanged ? import_picocolors2.default.yellow(" *") : "";
1882
+ const line = isActive ? `${toggle} ${name}${agent}${scope}${status}${changedMarker}` : `${toggle} ${import_picocolors2.default.dim(name)}${import_picocolors2.default.dim(agent)}${import_picocolors2.default.dim(scope)}${status}${changedMarker}`;
1883
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${line}`);
1884
+ }
1885
+ if (belowCount > 0) {
1886
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim(`\u2193 ${belowCount} more below`)}`);
1887
+ }
1888
+ lines.push(`${import_picocolors2.default.cyan(S_BAR_END2)}`);
1889
+ return lines.join("\n");
1890
+ }
1891
+ async run() {
1892
+ const result = await this.prompt();
1893
+ if (BD(result)) {
1894
+ return result;
1895
+ }
1896
+ return {
1897
+ skills: this.state_data.skills,
1898
+ changes: this.state_data.changes
1899
+ };
1900
+ }
1901
+ };
1902
+ async function manageSkills() {
1903
+ let targetAgents = detectInstalledAgents();
1904
+ if (targetAgents.length === 0) {
1905
+ targetAgents = getAllAgents();
1906
+ }
1907
+ const allSkills = [];
1908
+ for (const agent of targetAgents) {
1909
+ const skills2 = listManagedSkills(agent);
1910
+ allSkills.push(...skills2);
1911
+ }
1912
+ allSkills.sort((a, b2) => {
1913
+ const agentCmp = a.agent.displayName.localeCompare(b2.agent.displayName);
1914
+ if (agentCmp !== 0) return agentCmp;
1915
+ const scopeCmp = a.scope.localeCompare(b2.scope);
1916
+ if (scopeCmp !== 0) return scopeCmp;
1917
+ return a.name.localeCompare(b2.name);
1918
+ });
1919
+ const prompt = new SkillManagerPrompt(allSkills);
1920
+ const result = await prompt.run();
1921
+ if (BD(result)) {
1922
+ return null;
1923
+ }
1924
+ const { skills, changes } = result;
1925
+ if (changes.size === 0) {
1926
+ return { enabled: 0, disabled: 0, failed: 0, errors: [] };
1927
+ }
1928
+ const results = { enabled: 0, disabled: 0, failed: 0, errors: [] };
1929
+ for (const skill of skills) {
1930
+ const key = getSkillKey(skill);
1931
+ if (!changes.has(key)) continue;
1932
+ const newState = changes.get(key);
1933
+ if (newState === skill.enabled) continue;
1934
+ const toggleResult = toggleSkill(skill);
1935
+ if (toggleResult.success) {
1936
+ if (toggleResult.enabled) {
1937
+ results.enabled++;
1938
+ } else {
1939
+ results.disabled++;
1940
+ }
1941
+ } else {
1942
+ results.failed++;
1943
+ results.errors.push({
1944
+ skill: skill.name,
1945
+ agent: skill.agent.displayName,
1946
+ error: toggleResult.error || "Unknown error"
1947
+ });
1948
+ }
1949
+ }
1950
+ return results;
1951
+ }
1952
+
1618
1953
  // src/dependencies.ts
1619
1954
  import * as fs6 from "fs";
1620
1955
  import * as path6 from "path";
@@ -1856,14 +2191,70 @@ function printSkillTree(node, indent = 0) {
1856
2191
  console.log(chalk.green(`\u2502 ${prefix}\u2022 ${s.name}`) + desc);
1857
2192
  }
1858
2193
  }
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);
2194
+ function formatGitError(error, url) {
2195
+ const msg = error.message.toLowerCase();
2196
+ if (msg.includes("authentication") || msg.includes("401") || msg.includes("403")) {
2197
+ return `Authentication failed for ${url}. Check your credentials or ensure the repository is public.`;
2198
+ }
2199
+ if (msg.includes("not found") || msg.includes("404") || msg.includes("does not exist")) {
2200
+ return `Repository not found: ${url}. Check the URL or owner/repo name.`;
2201
+ }
2202
+ if (msg.includes("timeout") || msg.includes("timed out")) {
2203
+ return `Connection timed out while cloning ${url}. Check your network connection.`;
2204
+ }
2205
+ if (msg.includes("could not resolve host") || msg.includes("network")) {
2206
+ return `Network error while cloning ${url}. Check your internet connection.`;
2207
+ }
2208
+ if (msg.includes("permission denied")) {
2209
+ return `Permission denied when accessing ${url}. The repository may be private.`;
2210
+ }
2211
+ return `Failed to clone repository: ${error.message}`;
2212
+ }
2213
+ function formatInstallStatus(statuses, isDryRun) {
2214
+ if (statuses.length === 0) return;
2215
+ const grouped = /* @__PURE__ */ new Map();
2216
+ for (const status of statuses) {
2217
+ const key = status.agentName;
2218
+ if (!grouped.has(key)) grouped.set(key, []);
2219
+ grouped.get(key).push(status);
2220
+ }
2221
+ for (const [agent, skills] of grouped) {
2222
+ console.log(chalk.bold(`
2223
+ ${agent}:`));
2224
+ for (const skill of skills) {
2225
+ let icon;
2226
+ let color3;
2227
+ let suffix = "";
2228
+ switch (skill.status) {
2229
+ case "installed":
2230
+ icon = "\u2713";
2231
+ color3 = chalk.green;
2232
+ suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
2233
+ break;
2234
+ case "would-install":
2235
+ icon = "\u25CB";
2236
+ color3 = chalk.cyan;
2237
+ suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
2238
+ break;
2239
+ case "skipped":
2240
+ icon = "\u2013";
2241
+ color3 = chalk.yellow;
2242
+ suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
2243
+ break;
2244
+ case "failed":
2245
+ icon = "\u2717";
2246
+ color3 = chalk.red;
2247
+ suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
2248
+ break;
2249
+ }
2250
+ console.log(` ${color3(icon)} ${skill.skillName}${suffix}`);
2251
+ }
2252
+ }
2253
+ if (isDryRun) {
2254
+ console.log(chalk.cyan("\n(dry-run mode - no changes made)"));
2255
+ }
1865
2256
  }
1866
- async function run(source, options) {
2257
+ async function runInstall(source, options) {
1867
2258
  let tempDirToClean = null;
1868
2259
  const handleSignal = () => {
1869
2260
  if (tempDirToClean) {
@@ -1880,11 +2271,6 @@ async function run(source, options) {
1880
2271
  clack.intro(chalk.cyan("skai - AI Agent Skills Package Manager"));
1881
2272
  if (!source) {
1882
2273
  clack.log.error("Please provide a source (GitHub repo, URL, or local path)");
1883
- clack.log.info("Usage: skai <source> [options]");
1884
- clack.log.info("Examples:");
1885
- clack.log.info(" skai pproenca/dot-skills");
1886
- clack.log.info(" skai https://github.com/org/repo");
1887
- clack.log.info(" skai ./local/skills");
1888
2274
  clack.outro(chalk.red("No source provided"));
1889
2275
  process.exit(EXIT_ERROR);
1890
2276
  }
@@ -1898,6 +2284,9 @@ async function run(source, options) {
1898
2284
  if (!parsed.localPath) {
1899
2285
  throw new Error("Local path not found in parsed source");
1900
2286
  }
2287
+ if (!fs7.existsSync(parsed.localPath)) {
2288
+ throw new Error(`Local path does not exist: ${parsed.localPath}`);
2289
+ }
1901
2290
  skillsBasePath = parsed.localPath;
1902
2291
  clack.log.info(`Using local path: ${skillsBasePath}`);
1903
2292
  } else {
@@ -1914,7 +2303,8 @@ async function run(source, options) {
1914
2303
  spinner2.stop("Repository cloned");
1915
2304
  } catch (error) {
1916
2305
  spinner2.stop("Failed to clone repository");
1917
- throw error;
2306
+ const formattedError = formatGitError(error, parsed.url || source);
2307
+ throw new Error(formattedError);
1918
2308
  }
1919
2309
  }
1920
2310
  const discoverSpinner = clack.spinner();
@@ -1923,6 +2313,7 @@ async function run(source, options) {
1923
2313
  discoverSpinner.stop(`Found ${skills.length} skill(s)`);
1924
2314
  if (skills.length === 0) {
1925
2315
  clack.log.warn("No skills found in the repository");
2316
+ clack.log.info("Skills must have a SKILL.md file with frontmatter metadata.");
1926
2317
  clack.outro(chalk.yellow("No skills to install"));
1927
2318
  return;
1928
2319
  }
@@ -1956,12 +2347,24 @@ async function run(source, options) {
1956
2347
  }
1957
2348
  let targetAgents;
1958
2349
  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(", ")}`);
2350
+ const invalidAgents = [];
2351
+ targetAgents = [];
2352
+ for (const name of options.agent) {
2353
+ const agent = getAgentByName(name);
2354
+ if (agent) {
2355
+ targetAgents.push(agent);
2356
+ } else {
2357
+ invalidAgents.push(name);
2358
+ }
2359
+ }
2360
+ if (invalidAgents.length > 0) {
2361
+ clack.log.warn(`Unknown agent(s): ${invalidAgents.join(", ")}`);
1962
2362
  clack.log.info(
1963
2363
  `Available agents: ${getAllAgents().map((a) => a.name).join(", ")}`
1964
2364
  );
2365
+ }
2366
+ if (targetAgents.length === 0) {
2367
+ clack.log.error("No valid agents specified");
1965
2368
  clack.outro(chalk.red("No valid agents"));
1966
2369
  return;
1967
2370
  }
@@ -2053,6 +2456,44 @@ async function run(source, options) {
2053
2456
  }
2054
2457
  isGlobal = scope === "global";
2055
2458
  }
2459
+ if (options.dryRun) {
2460
+ const statuses2 = [];
2461
+ const installOptions2 = { global: isGlobal, yes: options.yes };
2462
+ for (const skill of selectedSkills) {
2463
+ for (const agent of selectedAgents) {
2464
+ const targetPath = getSkillInstallPath(skill.name, agent, installOptions2);
2465
+ if (isSkillInstalled(skill, agent, installOptions2)) {
2466
+ statuses2.push({
2467
+ skillName: skill.name,
2468
+ agentName: agent.displayName,
2469
+ status: "skipped",
2470
+ path: targetPath,
2471
+ reason: "already installed"
2472
+ });
2473
+ } else {
2474
+ statuses2.push({
2475
+ skillName: skill.name,
2476
+ agentName: agent.displayName,
2477
+ status: "would-install",
2478
+ path: targetPath
2479
+ });
2480
+ }
2481
+ }
2482
+ }
2483
+ if (options.json) {
2484
+ const jsonOutput = {
2485
+ dry_run: true,
2486
+ would_install: statuses2.filter((s) => s.status === "would-install").map((s) => ({ skill: s.skillName, agent: s.agentName, path: s.path })),
2487
+ would_skip: statuses2.filter((s) => s.status === "skipped").map((s) => ({ skill: s.skillName, agent: s.agentName, reason: s.reason }))
2488
+ };
2489
+ console.log(JSON.stringify(jsonOutput, null, 2));
2490
+ return;
2491
+ }
2492
+ clack.log.info(chalk.bold("\nDry Run - Installation Preview:"));
2493
+ formatInstallStatus(statuses2, true);
2494
+ clack.outro(chalk.cyan("Dry run complete"));
2495
+ return;
2496
+ }
2056
2497
  if (!options.yes) {
2057
2498
  const summary = [
2058
2499
  `Skills: ${selectedSkills.map((s) => s.name).join(", ")}`,
@@ -2079,20 +2520,41 @@ Installation Summary:
2079
2520
  };
2080
2521
  const installOptions = { global: isGlobal, yes: options.yes };
2081
2522
  const installedSkillNames = [];
2523
+ const statuses = [];
2082
2524
  for (const skill of selectedSkills) {
2083
2525
  for (const agent of selectedAgents) {
2526
+ const targetPath = getSkillInstallPath(skill.name, agent, installOptions);
2084
2527
  if (isSkillInstalled(skill, agent, installOptions)) {
2085
2528
  results.skipped++;
2529
+ statuses.push({
2530
+ skillName: skill.name,
2531
+ agentName: agent.displayName,
2532
+ status: "skipped",
2533
+ path: targetPath,
2534
+ reason: "already installed"
2535
+ });
2086
2536
  continue;
2087
2537
  }
2088
2538
  const result = installSkillForAgent(skill, agent, installOptions);
2089
2539
  if (result.success) {
2090
2540
  results.success++;
2541
+ statuses.push({
2542
+ skillName: skill.name,
2543
+ agentName: agent.displayName,
2544
+ status: "installed",
2545
+ path: result.targetPath
2546
+ });
2091
2547
  if (!installedSkillNames.includes(skill.name)) {
2092
2548
  installedSkillNames.push(skill.name);
2093
2549
  }
2094
2550
  } else {
2095
2551
  results.failed++;
2552
+ statuses.push({
2553
+ skillName: skill.name,
2554
+ agentName: agent.displayName,
2555
+ status: "failed",
2556
+ reason: result.error
2557
+ });
2096
2558
  if (!options.json) {
2097
2559
  clack.log.warn(`Failed to install ${skill.name} to ${agent.displayName}: ${result.error}`);
2098
2560
  }
@@ -2100,6 +2562,9 @@ Installation Summary:
2100
2562
  }
2101
2563
  }
2102
2564
  installSpinner.stop(`Installed ${results.success} skill(s)`);
2565
+ if (!options.json && statuses.length > 0) {
2566
+ formatInstallStatus(statuses, false);
2567
+ }
2103
2568
  const depSpinner = clack.spinner();
2104
2569
  depSpinner.start("Scanning for dependencies...");
2105
2570
  const skillDeps = [];
@@ -2251,6 +2716,278 @@ Installation Summary:
2251
2716
  }
2252
2717
  }
2253
2718
  }
2719
+ async function runUninstall(skillNames, options) {
2720
+ clack.intro(chalk.cyan("skai uninstall"));
2721
+ if (skillNames.length === 0) {
2722
+ clack.log.error("Please provide at least one skill name to uninstall");
2723
+ clack.log.info("Usage: skai uninstall <skill> [skill...] [-a <agent>]");
2724
+ clack.outro(chalk.red("No skills specified"));
2725
+ process.exit(EXIT_ERROR);
2726
+ }
2727
+ let targetAgents;
2728
+ if (options.agent && options.agent.length > 0) {
2729
+ targetAgents = options.agent.map((name) => getAgentByName(name)).filter((a) => a !== void 0);
2730
+ if (targetAgents.length === 0) {
2731
+ clack.log.error(`No valid agents found for: ${options.agent.join(", ")}`);
2732
+ clack.log.info(
2733
+ `Available agents: ${getAllAgents().map((a) => a.name).join(", ")}`
2734
+ );
2735
+ clack.outro(chalk.red("No valid agents"));
2736
+ process.exit(EXIT_ERROR);
2737
+ }
2738
+ } else {
2739
+ targetAgents = detectInstalledAgents();
2740
+ if (targetAgents.length === 0) {
2741
+ targetAgents = getAllAgents();
2742
+ }
2743
+ }
2744
+ const toUninstall = [];
2745
+ for (const skillName of skillNames) {
2746
+ for (const agent of targetAgents) {
2747
+ const projectInstalled = listInstalledSkills(agent, { projectOnly: true }).some((s) => s.name === skillName);
2748
+ const globalInstalled = listInstalledSkills(agent, { global: true }).some((s) => s.name === skillName);
2749
+ if (options.global && globalInstalled) {
2750
+ toUninstall.push({ skill: skillName, agent, scope: "global" });
2751
+ } else if (!options.global && projectInstalled) {
2752
+ toUninstall.push({ skill: skillName, agent, scope: "project" });
2753
+ } else if (!options.global && globalInstalled) {
2754
+ toUninstall.push({ skill: skillName, agent, scope: "global" });
2755
+ }
2756
+ }
2757
+ }
2758
+ if (toUninstall.length === 0) {
2759
+ clack.log.warn("No installed skills found matching the specified names");
2760
+ clack.outro(chalk.yellow("Nothing to uninstall"));
2761
+ return;
2762
+ }
2763
+ if (!options.yes) {
2764
+ clack.log.info(chalk.bold("\nSkills to uninstall:"));
2765
+ for (const item of toUninstall) {
2766
+ clack.log.info(` \u2022 ${item.skill} from ${item.agent.displayName} (${item.scope})`);
2767
+ }
2768
+ const confirmed = await clack.confirm({
2769
+ message: `Uninstall ${toUninstall.length} skill(s)?`
2770
+ });
2771
+ if (clack.isCancel(confirmed) || !confirmed) {
2772
+ clack.outro(chalk.yellow("Cancelled"));
2773
+ return;
2774
+ }
2775
+ }
2776
+ const results = { success: 0, failed: 0 };
2777
+ const uninstalledSkills = [];
2778
+ const errors = [];
2779
+ for (const item of toUninstall) {
2780
+ const installOptions = { global: item.scope === "global", yes: options.yes };
2781
+ const result = uninstallSkill(item.skill, item.agent, installOptions);
2782
+ if (result.success) {
2783
+ results.success++;
2784
+ if (!uninstalledSkills.includes(item.skill)) {
2785
+ uninstalledSkills.push(item.skill);
2786
+ }
2787
+ if (!options.json) {
2788
+ clack.log.info(chalk.green(`\u2713 Uninstalled ${item.skill} from ${item.agent.displayName}`));
2789
+ }
2790
+ } else {
2791
+ results.failed++;
2792
+ errors.push({ skill: item.skill, agent: item.agent.displayName, error: result.error || "Unknown error" });
2793
+ if (!options.json) {
2794
+ clack.log.warn(`\u2717 Failed to uninstall ${item.skill} from ${item.agent.displayName}: ${result.error}`);
2795
+ }
2796
+ }
2797
+ }
2798
+ if (options.json) {
2799
+ const jsonOutput = {
2800
+ skills_uninstalled: uninstalledSkills,
2801
+ errors
2802
+ };
2803
+ console.log(JSON.stringify(jsonOutput, null, 2));
2804
+ return;
2805
+ }
2806
+ const resultParts = [];
2807
+ if (results.success > 0) resultParts.push(chalk.green(`${results.success} uninstalled`));
2808
+ if (results.failed > 0) resultParts.push(chalk.red(`${results.failed} failed`));
2809
+ clack.outro(resultParts.join(", ") || chalk.green("Done"));
2810
+ }
2811
+ async function runList(options) {
2812
+ if (!options.json) {
2813
+ clack.intro(chalk.cyan("skai list"));
2814
+ }
2815
+ let targetAgents;
2816
+ if (options.agent && options.agent.length > 0) {
2817
+ targetAgents = options.agent.map((name) => getAgentByName(name)).filter((a) => a !== void 0);
2818
+ if (targetAgents.length === 0 && !options.json) {
2819
+ clack.log.error(`No valid agents found for: ${options.agent.join(", ")}`);
2820
+ clack.log.info(
2821
+ `Available agents: ${getAllAgents().map((a) => a.name).join(", ")}`
2822
+ );
2823
+ clack.outro(chalk.red("No valid agents"));
2824
+ process.exit(EXIT_ERROR);
2825
+ }
2826
+ } else {
2827
+ targetAgents = detectInstalledAgents();
2828
+ if (targetAgents.length === 0) {
2829
+ targetAgents = getAllAgents();
2830
+ }
2831
+ }
2832
+ const allSkills = [];
2833
+ const agentSkillsMap = /* @__PURE__ */ new Map();
2834
+ for (const agent of targetAgents) {
2835
+ const skills = listInstalledSkills(agent, { global: options.global });
2836
+ const projectSkills = [];
2837
+ const globalSkills = [];
2838
+ for (const skill of skills) {
2839
+ allSkills.push({
2840
+ name: skill.name,
2841
+ path: skill.path,
2842
+ agent: agent.displayName,
2843
+ scope: skill.scope
2844
+ });
2845
+ if (skill.scope === "project") {
2846
+ projectSkills.push(skill.name);
2847
+ } else {
2848
+ globalSkills.push(skill.name);
2849
+ }
2850
+ }
2851
+ if (projectSkills.length > 0 || globalSkills.length > 0) {
2852
+ agentSkillsMap.set(agent.displayName, { project: projectSkills, global: globalSkills });
2853
+ }
2854
+ }
2855
+ if (options.json) {
2856
+ const jsonOutput = { skills: allSkills };
2857
+ console.log(JSON.stringify(jsonOutput, null, 2));
2858
+ return;
2859
+ }
2860
+ if (allSkills.length === 0) {
2861
+ clack.log.info("No skills installed");
2862
+ clack.outro(chalk.dim('Use "skai <source>" to install skills'));
2863
+ return;
2864
+ }
2865
+ for (const [agentName, skills] of agentSkillsMap) {
2866
+ console.log(chalk.bold(`
2867
+ ${agentName}:`));
2868
+ if (skills.project.length > 0) {
2869
+ console.log(chalk.dim(" Project:"));
2870
+ for (const skill of skills.project.sort()) {
2871
+ console.log(chalk.green(` \u2022 ${skill}`));
2872
+ }
2873
+ }
2874
+ if (skills.global.length > 0) {
2875
+ console.log(chalk.dim(" Global:"));
2876
+ for (const skill of skills.global.sort()) {
2877
+ console.log(chalk.blue(` \u2022 ${skill}`));
2878
+ }
2879
+ }
2880
+ }
2881
+ clack.outro(chalk.cyan(`${allSkills.length} skill(s) installed`));
2882
+ }
2883
+ async function runUpdate(_skillNames, _options) {
2884
+ clack.intro(chalk.cyan("skai update"));
2885
+ clack.log.warn("The update command requires tracking skill sources.");
2886
+ clack.log.info("Currently, skai does not track where skills were installed from.");
2887
+ clack.log.info("");
2888
+ clack.log.info("To update a skill, you can:");
2889
+ clack.log.info(" 1. Uninstall the existing skill: skai uninstall <skill>");
2890
+ clack.log.info(" 2. Reinstall from the source: skai <source>");
2891
+ clack.outro(chalk.yellow("Update not yet implemented"));
2892
+ }
2893
+ async function runManage() {
2894
+ if (!process.stdin.isTTY) {
2895
+ clack.log.error("Interactive mode requires a TTY.");
2896
+ clack.log.info('Use "skai list" to view installed skills.');
2897
+ process.exit(EXIT_ERROR);
2898
+ }
2899
+ clack.intro(chalk.cyan("skai - Skill Manager"));
2900
+ const result = await manageSkills();
2901
+ if (result === null) {
2902
+ clack.outro(chalk.yellow("Cancelled"));
2903
+ return;
2904
+ }
2905
+ if (result.enabled === 0 && result.disabled === 0 && result.failed === 0) {
2906
+ clack.outro(chalk.dim("No changes made"));
2907
+ return;
2908
+ }
2909
+ const parts = [];
2910
+ if (result.enabled > 0) parts.push(chalk.green(`${result.enabled} enabled`));
2911
+ if (result.disabled > 0) parts.push(chalk.yellow(`${result.disabled} disabled`));
2912
+ if (result.failed > 0) parts.push(chalk.red(`${result.failed} failed`));
2913
+ for (const err of result.errors) {
2914
+ clack.log.warn(`Failed to update ${err.skill} (${err.agent}): ${err.error}`);
2915
+ }
2916
+ if (result.enabled > 0 || result.disabled > 0) {
2917
+ clack.note("Restart your AI agent to apply changes.", "Next steps");
2918
+ }
2919
+ clack.outro(parts.join(", "));
2920
+ }
2921
+ async function main() {
2922
+ const program = new Command();
2923
+ program.name("skai").description("The package manager for AI agent skills").version(getVersion(), "-V, --version", "Display version");
2924
+ 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) => {
2925
+ if (!source) {
2926
+ await runManage();
2927
+ return;
2928
+ }
2929
+ await runInstall(source, options);
2930
+ });
2931
+ 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) => {
2932
+ await runInstall(source, options);
2933
+ });
2934
+ 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) => {
2935
+ await runUninstall(skills, options);
2936
+ });
2937
+ 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) => {
2938
+ await runList(options);
2939
+ });
2940
+ 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) => {
2941
+ await runUpdate(skills, options);
2942
+ });
2943
+ program.command("manage").description("Interactively enable/disable installed skills").action(async () => {
2944
+ await runManage();
2945
+ });
2946
+ program.addHelpText(
2947
+ "after",
2948
+ `
2949
+ ${chalk.yellow("EXAMPLES")}
2950
+ ${chalk.dim("# Install skills from GitHub")}
2951
+ $ skai pproenca/dot-skills
2952
+
2953
+ ${chalk.dim("# Install from full URL")}
2954
+ $ skai https://github.com/org/repo
2955
+
2956
+ ${chalk.dim("# Install from local directory")}
2957
+ $ skai ./local/skills
2958
+
2959
+ ${chalk.dim("# Install specific skill to specific agent")}
2960
+ $ skai pproenca/dot-skills -s typescript -a claude-code
2961
+
2962
+ ${chalk.dim("# Preview installation without changes")}
2963
+ $ skai pproenca/dot-skills --dry-run
2964
+
2965
+ ${chalk.dim("# List installed skills")}
2966
+ $ skai list
2967
+
2968
+ ${chalk.dim("# List skills for specific agent")}
2969
+ $ skai list -a cursor
2970
+
2971
+ ${chalk.dim("# Uninstall a skill")}
2972
+ $ skai uninstall typescript
2973
+
2974
+ ${chalk.dim("# Uninstall from specific agent")}
2975
+ $ skai uninstall typescript -a cursor
2976
+
2977
+ ${chalk.dim("# Manage skills (enable/disable)")}
2978
+ $ skai
2979
+ $ skai manage
2980
+
2981
+ ${chalk.yellow("SUPPORTED AGENTS")}
2982
+ claude-code, cursor, copilot, windsurf, codex, opencode, amp,
2983
+ kilo-code, roo-code, goose, gemini, antigravity, clawdbot, droid
2984
+
2985
+ ${chalk.yellow("LEARN MORE")}
2986
+ GitHub: ${chalk.cyan("https://github.com/pproenca/skai")}
2987
+ `
2988
+ );
2989
+ await program.parseAsync(process.argv);
2990
+ }
2254
2991
  main().catch((error) => {
2255
2992
  console.error(chalk.red("Fatal error:"), error);
2256
2993
  process.exit(EXIT_ERROR);