library-skills 0.0.12 → 0.0.14
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/README.md +6 -2
- package/package.json +1 -1
- package/ts/dist/cli.d.ts +24 -0
- package/ts/dist/cli.js +135 -11
- package/ts/dist/installer.cjs +8 -1
- package/ts/dist/installer.js +8 -1
package/README.md
CHANGED
|
@@ -47,9 +47,13 @@ In JavaScript/TypeScript, you can install them with:
|
|
|
47
47
|
$ npx library-skills
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
This will scan the dependencies for the current project, find the installed libraries, and
|
|
50
|
+
This will scan the dependencies for the current project, find the installed libraries, and show the current skill installation status.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
It can install new skills, repair managed symlinks that point to old skill locations, and remove managed symlinks for packages or skills that disappeared.
|
|
53
|
+
|
|
54
|
+
It will only remove managed symlinks, not hand-authored skill directories.
|
|
55
|
+
|
|
56
|
+
Then it will ask where to install new skills and add them as symbolic links, so when you update the libraries, the skills are updated too.
|
|
53
57
|
|
|
54
58
|
By default it selects `.agents/skills`, the agent-neutral target. If the project already has a `.claude/` directory, it selects `.claude/skills` too.
|
|
55
59
|
|
package/package.json
CHANGED
package/ts/dist/cli.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ interface GlobalOptions {
|
|
|
53
53
|
check?: boolean;
|
|
54
54
|
all?: boolean;
|
|
55
55
|
skill?: string[];
|
|
56
|
+
copy?: boolean;
|
|
56
57
|
}
|
|
57
58
|
interface ListOptions {
|
|
58
59
|
installed?: boolean;
|
|
@@ -82,6 +83,23 @@ declare function installedStatuses({ targets, skills, }: {
|
|
|
82
83
|
targets: InstallTarget[];
|
|
83
84
|
skills: Skill[];
|
|
84
85
|
}): InstalledStatus[];
|
|
86
|
+
declare function syncTargetDirs({ projectRoot, includeClaude, yes, check, }: {
|
|
87
|
+
projectRoot: string;
|
|
88
|
+
includeClaude?: boolean;
|
|
89
|
+
yes?: boolean;
|
|
90
|
+
check?: boolean;
|
|
91
|
+
}): InstallTarget[];
|
|
92
|
+
declare function repairableStatuses(statuses: InstalledStatus[]): InstalledStatus[];
|
|
93
|
+
declare function removableStatuses(statuses: InstalledStatus[]): InstalledStatus[];
|
|
94
|
+
declare function selectStatusesInteractive(statuses: InstalledStatus[], action: "repair" | "remove"): Promise<InstalledStatus[]>;
|
|
95
|
+
declare function repairSelected({ statuses, projectRoot, }: {
|
|
96
|
+
statuses: InstalledStatus[];
|
|
97
|
+
projectRoot: string;
|
|
98
|
+
}): number;
|
|
99
|
+
declare function removeSelected({ statuses, projectRoot, }: {
|
|
100
|
+
statuses: InstalledStatus[];
|
|
101
|
+
projectRoot: string;
|
|
102
|
+
}): number;
|
|
85
103
|
declare function installSelected({ skills, targets, projectRoot, copy, }: {
|
|
86
104
|
skills: Skill[];
|
|
87
105
|
targets: InstallTarget[];
|
|
@@ -100,7 +118,13 @@ declare const testing: {
|
|
|
100
118
|
installSelected: typeof installSelected;
|
|
101
119
|
installedStatuses: typeof installedStatuses;
|
|
102
120
|
listCommand: typeof listCommand;
|
|
121
|
+
removableStatuses: typeof removableStatuses;
|
|
122
|
+
removeSelected: typeof removeSelected;
|
|
123
|
+
repairSelected: typeof repairSelected;
|
|
124
|
+
repairableStatuses: typeof repairableStatuses;
|
|
103
125
|
scanCommand: typeof scanCommand;
|
|
126
|
+
selectStatusesInteractive: typeof selectStatusesInteractive;
|
|
127
|
+
syncTargetDirs: typeof syncTargetDirs;
|
|
104
128
|
sync: typeof sync;
|
|
105
129
|
topLevelSkills: typeof topLevelSkills;
|
|
106
130
|
displayPath: typeof displayPath;
|
package/ts/dist/cli.js
CHANGED
|
@@ -696,7 +696,14 @@ function installSkill(skill, targetDir, options = {}) {
|
|
|
696
696
|
if (options.copy) {
|
|
697
697
|
cpSync(source, dest, { recursive: true });
|
|
698
698
|
} else {
|
|
699
|
-
|
|
699
|
+
try {
|
|
700
|
+
symlinkSync(getSymlinkTarget({ source, dest }), dest, "dir");
|
|
701
|
+
} catch (error) {
|
|
702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
703
|
+
throw new InstallError(
|
|
704
|
+
`Could not create symlink: ${message}. Use --copy if your system does not support symlinks.`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
700
707
|
}
|
|
701
708
|
return dest;
|
|
702
709
|
}
|
|
@@ -1138,6 +1145,20 @@ function isDirectory4(path) {
|
|
|
1138
1145
|
}
|
|
1139
1146
|
|
|
1140
1147
|
// ts/src/cli.ts
|
|
1148
|
+
var ACTION_COLORS = {
|
|
1149
|
+
install: "\x1B[1;32m",
|
|
1150
|
+
repair: "\x1B[1;34m",
|
|
1151
|
+
remove: "\x1B[1;31m",
|
|
1152
|
+
reset: "\x1B[0m"
|
|
1153
|
+
};
|
|
1154
|
+
function printActionHeader(action) {
|
|
1155
|
+
const labels = {
|
|
1156
|
+
install: "Install new skills",
|
|
1157
|
+
repair: "Repair installed skills",
|
|
1158
|
+
remove: "Remove stale skills"
|
|
1159
|
+
};
|
|
1160
|
+
console.log(`${ACTION_COLORS[action]}${labels[action]}${ACTION_COLORS.reset}`);
|
|
1161
|
+
}
|
|
1141
1162
|
function getProjectContext(cwd = process.cwd()) {
|
|
1142
1163
|
const workspace = findUvWorkspace(cwd);
|
|
1143
1164
|
const nodeWorkspace = findNodeWorkspace(cwd);
|
|
@@ -1438,6 +1459,7 @@ function printInstalledSkillsTable(statuses, projectRoot) {
|
|
|
1438
1459
|
printStatusTable(installed, projectRoot);
|
|
1439
1460
|
}
|
|
1440
1461
|
async function selectSkillsInteractive(skills) {
|
|
1462
|
+
printActionHeader("install");
|
|
1441
1463
|
return checkbox({
|
|
1442
1464
|
message: "Select skills to install (press Space to select, Enter to confirm):",
|
|
1443
1465
|
choices: skills.map((skill) => ({
|
|
@@ -1487,6 +1509,82 @@ async function selectInstalledSkillsInteractive(statuses) {
|
|
|
1487
1509
|
}))
|
|
1488
1510
|
});
|
|
1489
1511
|
}
|
|
1512
|
+
function syncTargetDirs({
|
|
1513
|
+
projectRoot,
|
|
1514
|
+
includeClaude,
|
|
1515
|
+
yes,
|
|
1516
|
+
check
|
|
1517
|
+
}) {
|
|
1518
|
+
const targetsByName = new Map(
|
|
1519
|
+
getExistingTargetDirs(projectRoot).map((target) => [target.name, target])
|
|
1520
|
+
);
|
|
1521
|
+
const defaultTargets = yes || check || includeClaude ? getTargetDirs(projectRoot, { includeClaude }) : getDefaultInstallTargetDirs(projectRoot);
|
|
1522
|
+
for (const target of defaultTargets) {
|
|
1523
|
+
targetsByName.set(target.name, target);
|
|
1524
|
+
}
|
|
1525
|
+
return [...targetsByName.values()];
|
|
1526
|
+
}
|
|
1527
|
+
function repairableStatuses(statuses) {
|
|
1528
|
+
return statuses.filter(
|
|
1529
|
+
(status) => status.type === "symlink" && (status.status === "broken" || status.status === "outdated") && status.skill !== null
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
function removableStatuses(statuses) {
|
|
1533
|
+
return statuses.filter(
|
|
1534
|
+
(status) => status.type === "symlink" && (status.status === "orphaned" || status.status === "broken" && status.skill === null)
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
async function selectStatusesInteractive(statuses, action) {
|
|
1538
|
+
if (statuses.length === 0) {
|
|
1539
|
+
return [];
|
|
1540
|
+
}
|
|
1541
|
+
printActionHeader(action);
|
|
1542
|
+
return checkbox({
|
|
1543
|
+
message: `Select skills to ${action} (press Space to select, Enter to confirm):`,
|
|
1544
|
+
choices: statuses.map((status) => ({
|
|
1545
|
+
name: `${status.name} [${status.target.name}]`,
|
|
1546
|
+
value: status,
|
|
1547
|
+
checked: true
|
|
1548
|
+
}))
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
function repairSelected({
|
|
1552
|
+
statuses,
|
|
1553
|
+
projectRoot
|
|
1554
|
+
}) {
|
|
1555
|
+
let repairedCount = 0;
|
|
1556
|
+
for (const status of statuses) {
|
|
1557
|
+
installSkill(status.skill, status.target.path);
|
|
1558
|
+
console.log(
|
|
1559
|
+
`Repaired: ${status.name} (${status.target.name}) -> ${displayPath(
|
|
1560
|
+
status.path,
|
|
1561
|
+
projectRoot
|
|
1562
|
+
)}`
|
|
1563
|
+
);
|
|
1564
|
+
repairedCount++;
|
|
1565
|
+
}
|
|
1566
|
+
return repairedCount;
|
|
1567
|
+
}
|
|
1568
|
+
function removeSelected({
|
|
1569
|
+
statuses,
|
|
1570
|
+
projectRoot
|
|
1571
|
+
}) {
|
|
1572
|
+
let removedCount = 0;
|
|
1573
|
+
for (const status of statuses) {
|
|
1574
|
+
if (uninstallSkill(status.name, status.target.path)) {
|
|
1575
|
+
console.log(
|
|
1576
|
+
`Removed ${status.status} symlink: ${status.name} (${status.target.name}) -> ${displayPath(
|
|
1577
|
+
status.path,
|
|
1578
|
+
projectRoot
|
|
1579
|
+
)}`
|
|
1580
|
+
);
|
|
1581
|
+
removedCount++;
|
|
1582
|
+
} else {
|
|
1583
|
+
console.log(`Not found: ${status.name} (${status.target.name})`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return removedCount;
|
|
1587
|
+
}
|
|
1490
1588
|
function installSelected({
|
|
1491
1589
|
skills,
|
|
1492
1590
|
targets,
|
|
@@ -1520,9 +1618,12 @@ function installSelected({
|
|
|
1520
1618
|
async function sync(options) {
|
|
1521
1619
|
const context = getProjectContext();
|
|
1522
1620
|
const result = scanContext(context);
|
|
1523
|
-
let targets =
|
|
1524
|
-
|
|
1525
|
-
|
|
1621
|
+
let targets = syncTargetDirs({
|
|
1622
|
+
projectRoot: context.projectRoot,
|
|
1623
|
+
includeClaude: options.claude,
|
|
1624
|
+
yes: options.yes,
|
|
1625
|
+
check: options.check
|
|
1626
|
+
});
|
|
1526
1627
|
printContext(context);
|
|
1527
1628
|
console.log();
|
|
1528
1629
|
printWarnings(result.warnings);
|
|
@@ -1560,10 +1661,23 @@ async function sync(options) {
|
|
|
1560
1661
|
}
|
|
1561
1662
|
return;
|
|
1562
1663
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1664
|
+
const repairable = repairableStatuses(drift);
|
|
1665
|
+
const removable = removableStatuses(drift);
|
|
1666
|
+
if (drift.length > 0) {
|
|
1667
|
+
console.log();
|
|
1668
|
+
console.log("Some installed skills need attention.");
|
|
1669
|
+
console.log("Select the skills to install, repair, or remove.");
|
|
1670
|
+
console.log("Only managed symlinks will be changed.");
|
|
1671
|
+
}
|
|
1672
|
+
const selectedRepairs = options.yes ? repairable : await selectStatusesInteractive(repairable, "repair");
|
|
1673
|
+
const selectedRemovals = options.yes ? removable : await selectStatusesInteractive(removable, "remove");
|
|
1674
|
+
if (selectedRepairs.length > 0) {
|
|
1675
|
+
console.log();
|
|
1676
|
+
repairSelected({ statuses: selectedRepairs, projectRoot: context.projectRoot });
|
|
1677
|
+
}
|
|
1678
|
+
if (selectedRemovals.length > 0) {
|
|
1679
|
+
console.log();
|
|
1680
|
+
removeSelected({ statuses: selectedRemovals, projectRoot: context.projectRoot });
|
|
1567
1681
|
}
|
|
1568
1682
|
let selected = filterInstallableSkills({
|
|
1569
1683
|
skills: candidateSkills,
|
|
@@ -1590,11 +1704,15 @@ async function sync(options) {
|
|
|
1590
1704
|
const installedCount = installSelected({
|
|
1591
1705
|
skills: selected,
|
|
1592
1706
|
targets,
|
|
1593
|
-
projectRoot: context.projectRoot
|
|
1707
|
+
projectRoot: context.projectRoot,
|
|
1708
|
+
copy: options.copy
|
|
1594
1709
|
});
|
|
1595
1710
|
console.log();
|
|
1596
1711
|
console.log(`Installed ${installedCount} skill target(s).`);
|
|
1597
1712
|
}
|
|
1713
|
+
if (selectedRepairs.length === 0 && selectedRemovals.length === 0 && selected.length === 0) {
|
|
1714
|
+
console.log("No changes needed.");
|
|
1715
|
+
}
|
|
1598
1716
|
}
|
|
1599
1717
|
function scanCommand(options) {
|
|
1600
1718
|
const context = getProjectContext();
|
|
@@ -1728,8 +1846,8 @@ async function removeCommand(skillNames, options) {
|
|
|
1728
1846
|
function createProgram() {
|
|
1729
1847
|
const program = new Command().enablePositionalOptions();
|
|
1730
1848
|
program.name("library-skills").description(
|
|
1731
|
-
"Discover and
|
|
1732
|
-
).option("--claude", "Also manage .claude/skills alongside .agents/skills").option("-y, --yes", "Skip confirmation prompts").option("--check", "Validate only; exit 1 if installs drift").option("--all", "Install all newly discovered unmanaged skills").option(
|
|
1849
|
+
"Discover and reconcile agent skills from installed library packages."
|
|
1850
|
+
).option("--claude", "Also manage .claude/skills alongside .agents/skills").option("-y, --yes", "Skip confirmation prompts").option("--check", "Validate only; exit 1 if installs drift").option("--all", "Install all newly discovered unmanaged skills").option("--copy", "Copy files instead of creating symlinks").option(
|
|
1733
1851
|
"-s, --skill <name>",
|
|
1734
1852
|
"Install a specific discovered skill by name",
|
|
1735
1853
|
collect,
|
|
@@ -1792,7 +1910,13 @@ var testing = {
|
|
|
1792
1910
|
installSelected,
|
|
1793
1911
|
installedStatuses,
|
|
1794
1912
|
listCommand,
|
|
1913
|
+
removableStatuses,
|
|
1914
|
+
removeSelected,
|
|
1915
|
+
repairSelected,
|
|
1916
|
+
repairableStatuses,
|
|
1795
1917
|
scanCommand,
|
|
1918
|
+
selectStatusesInteractive,
|
|
1919
|
+
syncTargetDirs,
|
|
1796
1920
|
sync,
|
|
1797
1921
|
topLevelSkills,
|
|
1798
1922
|
displayPath,
|
package/ts/dist/installer.cjs
CHANGED
|
@@ -91,7 +91,14 @@ function installSkill(skill, targetDir, options = {}) {
|
|
|
91
91
|
if (options.copy) {
|
|
92
92
|
(0, import_node_fs.cpSync)(source, dest, { recursive: true });
|
|
93
93
|
} else {
|
|
94
|
-
|
|
94
|
+
try {
|
|
95
|
+
(0, import_node_fs.symlinkSync)(getSymlinkTarget({ source, dest }), dest, "dir");
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
throw new InstallError(
|
|
99
|
+
`Could not create symlink: ${message}. Use --copy if your system does not support symlinks.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
95
102
|
}
|
|
96
103
|
return dest;
|
|
97
104
|
}
|
package/ts/dist/installer.js
CHANGED
|
@@ -67,7 +67,14 @@ function installSkill(skill, targetDir, options = {}) {
|
|
|
67
67
|
if (options.copy) {
|
|
68
68
|
cpSync(source, dest, { recursive: true });
|
|
69
69
|
} else {
|
|
70
|
-
|
|
70
|
+
try {
|
|
71
|
+
symlinkSync(getSymlinkTarget({ source, dest }), dest, "dir");
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
+
throw new InstallError(
|
|
75
|
+
`Could not create symlink: ${message}. Use --copy if your system does not support symlinks.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
71
78
|
}
|
|
72
79
|
return dest;
|
|
73
80
|
}
|