skai 0.0.6 → 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
@@ -740,6 +740,79 @@ function getSkillInstallPath(skillName, agent, options) {
740
740
  const basePath = options.global ? agent.globalPath : path5.join(process.cwd(), agent.projectPath);
741
741
  return path5.join(basePath, sanitizeName(skillName));
742
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
+ }
743
816
 
744
817
  // src/tree-select.ts
745
818
  import * as p from "@clack/prompts";
@@ -1656,6 +1729,227 @@ async function treeSelect(nodes) {
1656
1729
  return [];
1657
1730
  }
1658
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
+
1659
1953
  // src/dependencies.ts
1660
1954
  import * as fs6 from "fs";
1661
1955
  import * as path6 from "path";
@@ -1929,31 +2223,31 @@ function formatInstallStatus(statuses, isDryRun) {
1929
2223
  ${agent}:`));
1930
2224
  for (const skill of skills) {
1931
2225
  let icon;
1932
- let color2;
2226
+ let color3;
1933
2227
  let suffix = "";
1934
2228
  switch (skill.status) {
1935
2229
  case "installed":
1936
2230
  icon = "\u2713";
1937
- color2 = chalk.green;
2231
+ color3 = chalk.green;
1938
2232
  suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
1939
2233
  break;
1940
2234
  case "would-install":
1941
2235
  icon = "\u25CB";
1942
- color2 = chalk.cyan;
2236
+ color3 = chalk.cyan;
1943
2237
  suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
1944
2238
  break;
1945
2239
  case "skipped":
1946
2240
  icon = "\u2013";
1947
- color2 = chalk.yellow;
2241
+ color3 = chalk.yellow;
1948
2242
  suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
1949
2243
  break;
1950
2244
  case "failed":
1951
2245
  icon = "\u2717";
1952
- color2 = chalk.red;
2246
+ color3 = chalk.red;
1953
2247
  suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
1954
2248
  break;
1955
2249
  }
1956
- console.log(` ${color2(icon)} ${skill.skillName}${suffix}`);
2250
+ console.log(` ${color3(icon)} ${skill.skillName}${suffix}`);
1957
2251
  }
1958
2252
  }
1959
2253
  if (isDryRun) {
@@ -1977,12 +2271,6 @@ async function runInstall(source, options) {
1977
2271
  clack.intro(chalk.cyan("skai - AI Agent Skills Package Manager"));
1978
2272
  if (!source) {
1979
2273
  clack.log.error("Please provide a source (GitHub repo, URL, or local path)");
1980
- clack.log.info("Usage: skai <source> [options]");
1981
- clack.log.info("");
1982
- clack.log.info("Examples:");
1983
- clack.log.info(" skai pproenca/dot-skills");
1984
- clack.log.info(" skai https://github.com/org/repo");
1985
- clack.log.info(" skai ./local/skills");
1986
2274
  clack.outro(chalk.red("No source provided"));
1987
2275
  process.exit(EXIT_ERROR);
1988
2276
  }
@@ -2602,10 +2890,42 @@ async function runUpdate(_skillNames, _options) {
2602
2890
  clack.log.info(" 2. Reinstall from the source: skai <source>");
2603
2891
  clack.outro(chalk.yellow("Update not yet implemented"));
2604
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
+ }
2605
2921
  async function main() {
2606
2922
  const program = new Command();
2607
2923
  program.name("skai").description("The package manager for AI agent skills").version(getVersion(), "-V, --version", "Display version");
2608
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
+ }
2609
2929
  await runInstall(source, options);
2610
2930
  });
2611
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) => {
@@ -2620,24 +2940,50 @@ async function main() {
2620
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) => {
2621
2941
  await runUpdate(skills, options);
2622
2942
  });
2943
+ program.command("manage").description("Interactively enable/disable installed skills").action(async () => {
2944
+ await runManage();
2945
+ });
2623
2946
  program.addHelpText(
2624
2947
  "after",
2625
2948
  `
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
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
2637
2955
 
2638
- Supported Agents:
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")}
2639
2982
  claude-code, cursor, copilot, windsurf, codex, opencode, amp,
2640
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")}
2641
2987
  `
2642
2988
  );
2643
2989
  await program.parseAsync(process.argv);