skai 0.0.6 → 0.0.8

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,225 @@ 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("\u25FC");
1743
+ var S_TOGGLE_OFF = import_picocolors2.default.dim("\u25FB");
1744
+ var S_TOGGLE_ACTIVE_ON = import_picocolors2.default.green("\u25FC");
1745
+ var S_TOGGLE_ACTIVE_OFF = import_picocolors2.default.cyan("\u25FB");
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";
1851
+ lines.push(
1852
+ `${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim(" " + headerName + headerAgent + headerScope)}`
1853
+ );
1854
+ const aboveCount = scrollOffset;
1855
+ const belowCount = Math.max(0, skills.length - scrollOffset - this.maxItems);
1856
+ if (aboveCount > 0) {
1857
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim(`\u2191 ${aboveCount} more above`)}`);
1858
+ }
1859
+ const visibleSkills = skills.slice(scrollOffset, scrollOffset + this.maxItems);
1860
+ for (let i = 0; i < visibleSkills.length; i++) {
1861
+ const skill = visibleSkills[i];
1862
+ const globalIndex = scrollOffset + i;
1863
+ const isActive = globalIndex === cursor;
1864
+ const enabled = this.getEffectiveState(skill);
1865
+ const wasChanged = changes.has(getSkillKey(skill));
1866
+ let toggle;
1867
+ if (isActive && enabled) {
1868
+ toggle = S_TOGGLE_ACTIVE_ON;
1869
+ } else if (isActive && !enabled) {
1870
+ toggle = S_TOGGLE_ACTIVE_OFF;
1871
+ } else if (enabled) {
1872
+ toggle = S_TOGGLE_ON;
1873
+ } else {
1874
+ toggle = S_TOGGLE_OFF;
1875
+ }
1876
+ const name = skill.name.length > MAX_NAME_WIDTH ? skill.name.slice(0, MAX_NAME_WIDTH - 2) + ".." : skill.name.padEnd(MAX_NAME_WIDTH);
1877
+ 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);
1878
+ const scope = skill.scope;
1879
+ const changedMarker = wasChanged ? import_picocolors2.default.yellow(" *") : "";
1880
+ const line = isActive ? `${toggle} ${name}${agent}${scope}${changedMarker}` : `${toggle} ${import_picocolors2.default.dim(name)}${import_picocolors2.default.dim(agent)}${import_picocolors2.default.dim(scope)}${changedMarker}`;
1881
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${line}`);
1882
+ }
1883
+ if (belowCount > 0) {
1884
+ lines.push(`${import_picocolors2.default.cyan(S_BAR2)} ${import_picocolors2.default.dim(`\u2193 ${belowCount} more below`)}`);
1885
+ }
1886
+ lines.push(`${import_picocolors2.default.cyan(S_BAR_END2)}`);
1887
+ return lines.join("\n");
1888
+ }
1889
+ async run() {
1890
+ const result = await this.prompt();
1891
+ if (BD(result)) {
1892
+ return result;
1893
+ }
1894
+ return {
1895
+ skills: this.state_data.skills,
1896
+ changes: this.state_data.changes
1897
+ };
1898
+ }
1899
+ };
1900
+ async function manageSkills() {
1901
+ let targetAgents = detectInstalledAgents();
1902
+ if (targetAgents.length === 0) {
1903
+ targetAgents = getAllAgents();
1904
+ }
1905
+ const allSkills = [];
1906
+ for (const agent of targetAgents) {
1907
+ const skills2 = listManagedSkills(agent);
1908
+ allSkills.push(...skills2);
1909
+ }
1910
+ allSkills.sort((a, b2) => {
1911
+ const agentCmp = a.agent.displayName.localeCompare(b2.agent.displayName);
1912
+ if (agentCmp !== 0) return agentCmp;
1913
+ const scopeCmp = a.scope.localeCompare(b2.scope);
1914
+ if (scopeCmp !== 0) return scopeCmp;
1915
+ return a.name.localeCompare(b2.name);
1916
+ });
1917
+ const prompt = new SkillManagerPrompt(allSkills);
1918
+ const result = await prompt.run();
1919
+ if (BD(result)) {
1920
+ return null;
1921
+ }
1922
+ const { skills, changes } = result;
1923
+ if (changes.size === 0) {
1924
+ return { enabled: 0, disabled: 0, failed: 0, errors: [] };
1925
+ }
1926
+ const results = { enabled: 0, disabled: 0, failed: 0, errors: [] };
1927
+ for (const skill of skills) {
1928
+ const key = getSkillKey(skill);
1929
+ if (!changes.has(key)) continue;
1930
+ const newState = changes.get(key);
1931
+ if (newState === skill.enabled) continue;
1932
+ const toggleResult = toggleSkill(skill);
1933
+ if (toggleResult.success) {
1934
+ if (toggleResult.enabled) {
1935
+ results.enabled++;
1936
+ } else {
1937
+ results.disabled++;
1938
+ }
1939
+ } else {
1940
+ results.failed++;
1941
+ results.errors.push({
1942
+ skill: skill.name,
1943
+ agent: skill.agent.displayName,
1944
+ error: toggleResult.error || "Unknown error"
1945
+ });
1946
+ }
1947
+ }
1948
+ return results;
1949
+ }
1950
+
1659
1951
  // src/dependencies.ts
1660
1952
  import * as fs6 from "fs";
1661
1953
  import * as path6 from "path";
@@ -1929,31 +2221,31 @@ function formatInstallStatus(statuses, isDryRun) {
1929
2221
  ${agent}:`));
1930
2222
  for (const skill of skills) {
1931
2223
  let icon;
1932
- let color2;
2224
+ let color3;
1933
2225
  let suffix = "";
1934
2226
  switch (skill.status) {
1935
2227
  case "installed":
1936
2228
  icon = "\u2713";
1937
- color2 = chalk.green;
2229
+ color3 = chalk.green;
1938
2230
  suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
1939
2231
  break;
1940
2232
  case "would-install":
1941
2233
  icon = "\u25CB";
1942
- color2 = chalk.cyan;
2234
+ color3 = chalk.cyan;
1943
2235
  suffix = skill.path ? chalk.dim(` \u2192 ${skill.path}`) : "";
1944
2236
  break;
1945
2237
  case "skipped":
1946
2238
  icon = "\u2013";
1947
- color2 = chalk.yellow;
2239
+ color3 = chalk.yellow;
1948
2240
  suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
1949
2241
  break;
1950
2242
  case "failed":
1951
2243
  icon = "\u2717";
1952
- color2 = chalk.red;
2244
+ color3 = chalk.red;
1953
2245
  suffix = skill.reason ? chalk.dim(` (${skill.reason})`) : "";
1954
2246
  break;
1955
2247
  }
1956
- console.log(` ${color2(icon)} ${skill.skillName}${suffix}`);
2248
+ console.log(` ${color3(icon)} ${skill.skillName}${suffix}`);
1957
2249
  }
1958
2250
  }
1959
2251
  if (isDryRun) {
@@ -1977,12 +2269,6 @@ async function runInstall(source, options) {
1977
2269
  clack.intro(chalk.cyan("skai - AI Agent Skills Package Manager"));
1978
2270
  if (!source) {
1979
2271
  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
2272
  clack.outro(chalk.red("No source provided"));
1987
2273
  process.exit(EXIT_ERROR);
1988
2274
  }
@@ -2602,10 +2888,42 @@ async function runUpdate(_skillNames, _options) {
2602
2888
  clack.log.info(" 2. Reinstall from the source: skai <source>");
2603
2889
  clack.outro(chalk.yellow("Update not yet implemented"));
2604
2890
  }
2891
+ async function runManage() {
2892
+ if (!process.stdin.isTTY) {
2893
+ clack.log.error("Interactive mode requires a TTY.");
2894
+ clack.log.info('Use "skai list" to view installed skills.');
2895
+ process.exit(EXIT_ERROR);
2896
+ }
2897
+ clack.intro(chalk.cyan("skai - Skill Manager"));
2898
+ const result = await manageSkills();
2899
+ if (result === null) {
2900
+ clack.outro(chalk.yellow("Cancelled"));
2901
+ return;
2902
+ }
2903
+ if (result.enabled === 0 && result.disabled === 0 && result.failed === 0) {
2904
+ clack.outro(chalk.dim("No changes made"));
2905
+ return;
2906
+ }
2907
+ const parts = [];
2908
+ if (result.enabled > 0) parts.push(chalk.green(`${result.enabled} enabled`));
2909
+ if (result.disabled > 0) parts.push(chalk.yellow(`${result.disabled} disabled`));
2910
+ if (result.failed > 0) parts.push(chalk.red(`${result.failed} failed`));
2911
+ for (const err of result.errors) {
2912
+ clack.log.warn(`Failed to update ${err.skill} (${err.agent}): ${err.error}`);
2913
+ }
2914
+ if (result.enabled > 0 || result.disabled > 0) {
2915
+ clack.note("Restart your AI agent to apply changes.", "Next steps");
2916
+ }
2917
+ clack.outro(parts.join(", "));
2918
+ }
2605
2919
  async function main() {
2606
2920
  const program = new Command();
2607
2921
  program.name("skai").description("The package manager for AI agent skills").version(getVersion(), "-V, --version", "Display version");
2608
2922
  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) => {
2923
+ if (!source) {
2924
+ await runManage();
2925
+ return;
2926
+ }
2609
2927
  await runInstall(source, options);
2610
2928
  });
2611
2929
  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 +2938,50 @@ async function main() {
2620
2938
  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
2939
  await runUpdate(skills, options);
2622
2940
  });
2941
+ program.command("manage").description("Interactively enable/disable installed skills").action(async () => {
2942
+ await runManage();
2943
+ });
2623
2944
  program.addHelpText(
2624
2945
  "after",
2625
2946
  `
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
2947
+ ${chalk.yellow("EXAMPLES")}
2948
+ ${chalk.dim("# Install skills from GitHub")}
2949
+ $ skai pproenca/dot-skills
2950
+
2951
+ ${chalk.dim("# Install from full URL")}
2952
+ $ skai https://github.com/org/repo
2637
2953
 
2638
- Supported Agents:
2954
+ ${chalk.dim("# Install from local directory")}
2955
+ $ skai ./local/skills
2956
+
2957
+ ${chalk.dim("# Install specific skill to specific agent")}
2958
+ $ skai pproenca/dot-skills -s typescript -a claude-code
2959
+
2960
+ ${chalk.dim("# Preview installation without changes")}
2961
+ $ skai pproenca/dot-skills --dry-run
2962
+
2963
+ ${chalk.dim("# List installed skills")}
2964
+ $ skai list
2965
+
2966
+ ${chalk.dim("# List skills for specific agent")}
2967
+ $ skai list -a cursor
2968
+
2969
+ ${chalk.dim("# Uninstall a skill")}
2970
+ $ skai uninstall typescript
2971
+
2972
+ ${chalk.dim("# Uninstall from specific agent")}
2973
+ $ skai uninstall typescript -a cursor
2974
+
2975
+ ${chalk.dim("# Manage skills (enable/disable)")}
2976
+ $ skai
2977
+ $ skai manage
2978
+
2979
+ ${chalk.yellow("SUPPORTED AGENTS")}
2639
2980
  claude-code, cursor, copilot, windsurf, codex, opencode, amp,
2640
2981
  kilo-code, roo-code, goose, gemini, antigravity, clawdbot, droid
2982
+
2983
+ ${chalk.yellow("LEARN MORE")}
2984
+ GitHub: ${chalk.cyan("https://github.com/pproenca/skai")}
2641
2985
  `
2642
2986
  );
2643
2987
  await program.parseAsync(process.argv);