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 +811 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1197
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
}
|
|
1864
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
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);
|