library-skills 0.0.11 → 0.0.13

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 CHANGED
@@ -47,13 +47,19 @@ 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 ask you which of their skills you want to install in the project.
50
+ This will scan the dependencies for the current project, find the installed libraries, and show the current skill installation status.
51
51
 
52
- Then it will add them to the `.agents` directory as symbolic links, so when you update the libraries, the skills are updated too.
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.
57
+
58
+ By default it selects `.agents/skills`, the agent-neutral target. If the project already has a `.claude/` directory, it selects `.claude/skills` too.
53
59
 
54
60
  /// tip
55
61
 
56
- If you are using Claude Code, add the `--claude` CLI Option to install the skills in the `.claude/skills` directory too, as Claude Code doesn't support the standard `.agents` directory.
62
+ If you are using Claude Code, select `.claude/skills` when asked for installation targets, as Claude Code doesn't support the standard `.agents` directory. For non-interactive installs, add the `--claude` CLI option to install the skills in `.claude/skills` too.
57
63
 
58
64
  ///
59
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "library-skills",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Library Skills, AI Agents using libraries, as intended, always up to date.",
5
5
  "type": "module",
6
6
  "exports": {
package/ts/dist/cli.d.ts CHANGED
@@ -82,6 +82,23 @@ declare function installedStatuses({ targets, skills, }: {
82
82
  targets: InstallTarget[];
83
83
  skills: Skill[];
84
84
  }): InstalledStatus[];
85
+ declare function syncTargetDirs({ projectRoot, includeClaude, yes, check, }: {
86
+ projectRoot: string;
87
+ includeClaude?: boolean;
88
+ yes?: boolean;
89
+ check?: boolean;
90
+ }): InstallTarget[];
91
+ declare function repairableStatuses(statuses: InstalledStatus[]): InstalledStatus[];
92
+ declare function removableStatuses(statuses: InstalledStatus[]): InstalledStatus[];
93
+ declare function selectStatusesInteractive(statuses: InstalledStatus[], action: "repair" | "remove"): Promise<InstalledStatus[]>;
94
+ declare function repairSelected({ statuses, projectRoot, }: {
95
+ statuses: InstalledStatus[];
96
+ projectRoot: string;
97
+ }): number;
98
+ declare function removeSelected({ statuses, projectRoot, }: {
99
+ statuses: InstalledStatus[];
100
+ projectRoot: string;
101
+ }): number;
85
102
  declare function installSelected({ skills, targets, projectRoot, copy, }: {
86
103
  skills: Skill[];
87
104
  targets: InstallTarget[];
@@ -100,7 +117,13 @@ declare const testing: {
100
117
  installSelected: typeof installSelected;
101
118
  installedStatuses: typeof installedStatuses;
102
119
  listCommand: typeof listCommand;
120
+ removableStatuses: typeof removableStatuses;
121
+ removeSelected: typeof removeSelected;
122
+ repairSelected: typeof repairSelected;
123
+ repairableStatuses: typeof repairableStatuses;
103
124
  scanCommand: typeof scanCommand;
125
+ selectStatusesInteractive: typeof selectStatusesInteractive;
126
+ syncTargetDirs: typeof syncTargetDirs;
104
127
  sync: typeof sync;
105
128
  topLevelSkills: typeof topLevelSkills;
106
129
  displayPath: typeof displayPath;
package/ts/dist/cli.js CHANGED
@@ -660,6 +660,26 @@ function getTargetDirs(projectRoot, options = {}) {
660
660
  }
661
661
  return targets;
662
662
  }
663
+ function getAllTargetDirs(projectRoot) {
664
+ return getTargetDirs(projectRoot, { includeClaude: true });
665
+ }
666
+ function getDefaultInstallTargetDirs(projectRoot) {
667
+ const [universal, claude] = getAllTargetDirs(projectRoot);
668
+ const selected = [];
669
+ if (existsSync3(join3(projectRoot, ".agents"))) {
670
+ selected.push(universal);
671
+ }
672
+ if (existsSync3(join3(projectRoot, ".claude"))) {
673
+ selected.push(claude);
674
+ }
675
+ return selected.length > 0 ? selected : [universal];
676
+ }
677
+ function getExistingTargetDirs(projectRoot, options = {}) {
678
+ if (options.includeClaude) {
679
+ return getTargetDirs(projectRoot, { includeClaude: true });
680
+ }
681
+ return getAllTargetDirs(projectRoot).filter((target) => isDirectory2(target.path));
682
+ }
663
683
  function installSkill(skill, targetDir, options = {}) {
664
684
  const dest = join3(targetDir, skill.name);
665
685
  const source = resolve2(skill.skillDir);
@@ -1118,6 +1138,20 @@ function isDirectory4(path) {
1118
1138
  }
1119
1139
 
1120
1140
  // ts/src/cli.ts
1141
+ var ACTION_COLORS = {
1142
+ install: "\x1B[1;32m",
1143
+ repair: "\x1B[1;34m",
1144
+ remove: "\x1B[1;31m",
1145
+ reset: "\x1B[0m"
1146
+ };
1147
+ function printActionHeader(action) {
1148
+ const labels = {
1149
+ install: "Install new skills",
1150
+ repair: "Repair installed skills",
1151
+ remove: "Remove stale skills"
1152
+ };
1153
+ console.log(`${ACTION_COLORS[action]}${labels[action]}${ACTION_COLORS.reset}`);
1154
+ }
1121
1155
  function getProjectContext(cwd = process.cwd()) {
1122
1156
  const workspace = findUvWorkspace(cwd);
1123
1157
  const nodeWorkspace = findNodeWorkspace(cwd);
@@ -1304,6 +1338,19 @@ function findCollisions(skills) {
1304
1338
  [...counts.entries()].filter(([, count]) => count > 1).map(([name]) => name)
1305
1339
  );
1306
1340
  }
1341
+ function deduplicateSkills(skills) {
1342
+ const seen = /* @__PURE__ */ new Set();
1343
+ const unique = [];
1344
+ for (const skill of skills) {
1345
+ const key = resolve5(skill.skillDir);
1346
+ if (seen.has(key)) {
1347
+ continue;
1348
+ }
1349
+ seen.add(key);
1350
+ unique.push(skill);
1351
+ }
1352
+ return unique;
1353
+ }
1307
1354
  function filterInstallableSkills({
1308
1355
  skills,
1309
1356
  selectedNames,
@@ -1405,6 +1452,7 @@ function printInstalledSkillsTable(statuses, projectRoot) {
1405
1452
  printStatusTable(installed, projectRoot);
1406
1453
  }
1407
1454
  async function selectSkillsInteractive(skills) {
1455
+ printActionHeader("install");
1408
1456
  return checkbox({
1409
1457
  message: "Select skills to install (press Space to select, Enter to confirm):",
1410
1458
  choices: skills.map((skill) => ({
@@ -1413,6 +1461,34 @@ async function selectSkillsInteractive(skills) {
1413
1461
  }))
1414
1462
  });
1415
1463
  }
1464
+ async function selectTargetsInteractive({
1465
+ projectRoot,
1466
+ defaultTargets
1467
+ }) {
1468
+ const defaultNames = new Set(defaultTargets.map((target) => target.name));
1469
+ return checkbox({
1470
+ message: "Select installation targets (press Space to select, Enter to confirm):",
1471
+ choices: getAllTargetDirs(projectRoot).map((target) => ({
1472
+ name: displayPath(target.path, projectRoot),
1473
+ value: target,
1474
+ checked: defaultNames.has(target.name)
1475
+ })),
1476
+ validate: (selected) => selected.length > 0 || "Please select at least one installation target."
1477
+ });
1478
+ }
1479
+ async function selectInstallTargets({
1480
+ projectRoot,
1481
+ includeClaude,
1482
+ interactive
1483
+ }) {
1484
+ if (!interactive) {
1485
+ return getTargetDirs(projectRoot, { includeClaude });
1486
+ }
1487
+ return selectTargetsInteractive({
1488
+ projectRoot,
1489
+ defaultTargets: getDefaultInstallTargetDirs(projectRoot)
1490
+ });
1491
+ }
1416
1492
  async function selectInstalledSkillsInteractive(statuses) {
1417
1493
  const removable = statuses.filter((status) => status.type === "symlink");
1418
1494
  if (removable.length === 0) {
@@ -1426,6 +1502,82 @@ async function selectInstalledSkillsInteractive(statuses) {
1426
1502
  }))
1427
1503
  });
1428
1504
  }
1505
+ function syncTargetDirs({
1506
+ projectRoot,
1507
+ includeClaude,
1508
+ yes,
1509
+ check
1510
+ }) {
1511
+ const targetsByName = new Map(
1512
+ getExistingTargetDirs(projectRoot).map((target) => [target.name, target])
1513
+ );
1514
+ const defaultTargets = yes || check || includeClaude ? getTargetDirs(projectRoot, { includeClaude }) : getDefaultInstallTargetDirs(projectRoot);
1515
+ for (const target of defaultTargets) {
1516
+ targetsByName.set(target.name, target);
1517
+ }
1518
+ return [...targetsByName.values()];
1519
+ }
1520
+ function repairableStatuses(statuses) {
1521
+ return statuses.filter(
1522
+ (status) => status.type === "symlink" && (status.status === "broken" || status.status === "outdated") && status.skill !== null
1523
+ );
1524
+ }
1525
+ function removableStatuses(statuses) {
1526
+ return statuses.filter(
1527
+ (status) => status.type === "symlink" && (status.status === "orphaned" || status.status === "broken" && status.skill === null)
1528
+ );
1529
+ }
1530
+ async function selectStatusesInteractive(statuses, action) {
1531
+ if (statuses.length === 0) {
1532
+ return [];
1533
+ }
1534
+ printActionHeader(action);
1535
+ return checkbox({
1536
+ message: `Select skills to ${action} (press Space to select, Enter to confirm):`,
1537
+ choices: statuses.map((status) => ({
1538
+ name: `${status.name} [${status.target.name}]`,
1539
+ value: status,
1540
+ checked: true
1541
+ }))
1542
+ });
1543
+ }
1544
+ function repairSelected({
1545
+ statuses,
1546
+ projectRoot
1547
+ }) {
1548
+ let repairedCount = 0;
1549
+ for (const status of statuses) {
1550
+ installSkill(status.skill, status.target.path);
1551
+ console.log(
1552
+ `Repaired: ${status.name} (${status.target.name}) -> ${displayPath(
1553
+ status.path,
1554
+ projectRoot
1555
+ )}`
1556
+ );
1557
+ repairedCount++;
1558
+ }
1559
+ return repairedCount;
1560
+ }
1561
+ function removeSelected({
1562
+ statuses,
1563
+ projectRoot
1564
+ }) {
1565
+ let removedCount = 0;
1566
+ for (const status of statuses) {
1567
+ if (uninstallSkill(status.name, status.target.path)) {
1568
+ console.log(
1569
+ `Removed ${status.status} symlink: ${status.name} (${status.target.name}) -> ${displayPath(
1570
+ status.path,
1571
+ projectRoot
1572
+ )}`
1573
+ );
1574
+ removedCount++;
1575
+ } else {
1576
+ console.log(`Not found: ${status.name} (${status.target.name})`);
1577
+ }
1578
+ }
1579
+ return removedCount;
1580
+ }
1429
1581
  function installSelected({
1430
1582
  skills,
1431
1583
  targets,
@@ -1459,8 +1611,11 @@ function installSelected({
1459
1611
  async function sync(options) {
1460
1612
  const context = getProjectContext();
1461
1613
  const result = scanContext(context);
1462
- const targets = getTargetDirs(context.projectRoot, {
1463
- includeClaude: options.claude
1614
+ let targets = syncTargetDirs({
1615
+ projectRoot: context.projectRoot,
1616
+ includeClaude: options.claude,
1617
+ yes: options.yes,
1618
+ check: options.check
1464
1619
  });
1465
1620
  printContext(context);
1466
1621
  console.log();
@@ -1499,10 +1654,23 @@ async function sync(options) {
1499
1654
  }
1500
1655
  return;
1501
1656
  }
1502
- for (const status of drift) {
1503
- if (options.yes && (status.status === "broken" || status.status === "outdated") && status.skill) {
1504
- installSkill(status.skill, status.target.path);
1505
- }
1657
+ const repairable = repairableStatuses(drift);
1658
+ const removable = removableStatuses(drift);
1659
+ if (drift.length > 0) {
1660
+ console.log();
1661
+ console.log("Some installed skills need attention.");
1662
+ console.log("Select the skills to install, repair, or remove.");
1663
+ console.log("Only managed symlinks will be changed.");
1664
+ }
1665
+ const selectedRepairs = options.yes ? repairable : await selectStatusesInteractive(repairable, "repair");
1666
+ const selectedRemovals = options.yes ? removable : await selectStatusesInteractive(removable, "remove");
1667
+ if (selectedRepairs.length > 0) {
1668
+ console.log();
1669
+ repairSelected({ statuses: selectedRepairs, projectRoot: context.projectRoot });
1670
+ }
1671
+ if (selectedRemovals.length > 0) {
1672
+ console.log();
1673
+ removeSelected({ statuses: selectedRemovals, projectRoot: context.projectRoot });
1506
1674
  }
1507
1675
  let selected = filterInstallableSkills({
1508
1676
  skills: candidateSkills,
@@ -1512,10 +1680,19 @@ async function sync(options) {
1512
1680
  if (selected.length === 0 && !options.yes && selectedNames.length === 0 && !options.all) {
1513
1681
  const newSkills = visibleStatuses.filter((status) => status.status === "new" && status.skill !== null).map((status) => status.skill);
1514
1682
  if (newSkills.length > 0) {
1515
- selected = await selectSkillsInteractive(newSkills);
1683
+ selected = await selectSkillsInteractive(deduplicateSkills(newSkills));
1516
1684
  }
1517
1685
  }
1518
1686
  if (selected.length > 0) {
1687
+ targets = await selectInstallTargets({
1688
+ projectRoot: context.projectRoot,
1689
+ includeClaude: options.claude,
1690
+ interactive: !options.yes && !options.claude
1691
+ });
1692
+ if (targets.length === 0) {
1693
+ console.log("No installation targets selected.");
1694
+ return;
1695
+ }
1519
1696
  console.log();
1520
1697
  const installedCount = installSelected({
1521
1698
  skills: selected,
@@ -1525,6 +1702,9 @@ async function sync(options) {
1525
1702
  console.log();
1526
1703
  console.log(`Installed ${installedCount} skill target(s).`);
1527
1704
  }
1705
+ if (selectedRepairs.length === 0 && selectedRemovals.length === 0 && selected.length === 0) {
1706
+ console.log("No changes needed.");
1707
+ }
1528
1708
  }
1529
1709
  function scanCommand(options) {
1530
1710
  const context = getProjectContext();
@@ -1556,7 +1736,7 @@ function listCommand(options) {
1556
1736
  skills: result.skills,
1557
1737
  includeAll: Boolean(options.all)
1558
1738
  });
1559
- const targets = getTargetDirs(context.projectRoot, {
1739
+ const targets = getExistingTargetDirs(context.projectRoot, {
1560
1740
  includeClaude: options.claude
1561
1741
  });
1562
1742
  const statuses = installedStatuses({ targets, skills: result.skills });
@@ -1596,9 +1776,6 @@ async function installCommand(options) {
1596
1776
  skills: result.skills,
1597
1777
  includeAll: Boolean(options.all) || selectedNames.length > 0
1598
1778
  });
1599
- const targets = getTargetDirs(context.projectRoot, {
1600
- includeClaude: options.claude
1601
- });
1602
1779
  printWarnings(result.warnings);
1603
1780
  let selected = filterInstallableSkills({
1604
1781
  skills,
@@ -1612,6 +1789,15 @@ async function installCommand(options) {
1612
1789
  console.log("No skills selected.");
1613
1790
  return;
1614
1791
  }
1792
+ const targets = await selectInstallTargets({
1793
+ projectRoot: context.projectRoot,
1794
+ includeClaude: options.claude,
1795
+ interactive: !options.yes && !options.claude
1796
+ });
1797
+ if (targets.length === 0) {
1798
+ console.log("No installation targets selected.");
1799
+ return;
1800
+ }
1615
1801
  const installedCount = installSelected({
1616
1802
  skills: selected,
1617
1803
  targets,
@@ -1624,7 +1810,7 @@ async function installCommand(options) {
1624
1810
  async function removeCommand(skillNames, options) {
1625
1811
  const context = getProjectContext();
1626
1812
  const result = scanContext(context);
1627
- const targets = getTargetDirs(context.projectRoot, {
1813
+ const targets = getExistingTargetDirs(context.projectRoot, {
1628
1814
  includeClaude: options.claude
1629
1815
  });
1630
1816
  const statuses = installedStatuses({ targets, skills: result.skills });
@@ -1652,7 +1838,7 @@ async function removeCommand(skillNames, options) {
1652
1838
  function createProgram() {
1653
1839
  const program = new Command().enablePositionalOptions();
1654
1840
  program.name("library-skills").description(
1655
- "Discover and install agent skills from installed library packages."
1841
+ "Discover and reconcile agent skills from installed library packages."
1656
1842
  ).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(
1657
1843
  "-s, --skill <name>",
1658
1844
  "Install a specific discovered skill by name",
@@ -1716,7 +1902,13 @@ var testing = {
1716
1902
  installSelected,
1717
1903
  installedStatuses,
1718
1904
  listCommand,
1905
+ removableStatuses,
1906
+ removeSelected,
1907
+ repairSelected,
1908
+ repairableStatuses,
1719
1909
  scanCommand,
1910
+ selectStatusesInteractive,
1911
+ syncTargetDirs,
1720
1912
  sync,
1721
1913
  topLevelSkills,
1722
1914
  displayPath,
@@ -23,6 +23,9 @@ __export(installer_exports, {
23
23
  CLAUDE_SKILLS_DIR: () => CLAUDE_SKILLS_DIR,
24
24
  InstallError: () => InstallError,
25
25
  UNIVERSAL_SKILLS_DIR: () => UNIVERSAL_SKILLS_DIR,
26
+ getAllTargetDirs: () => getAllTargetDirs,
27
+ getDefaultInstallTargetDirs: () => getDefaultInstallTargetDirs,
28
+ getExistingTargetDirs: () => getExistingTargetDirs,
26
29
  getTargetDirs: () => getTargetDirs,
27
30
  installSkill: () => installSkill,
28
31
  listInstalledSkills: () => listInstalledSkills,
@@ -52,6 +55,26 @@ function getTargetDirs(projectRoot, options = {}) {
52
55
  }
53
56
  return targets;
54
57
  }
58
+ function getAllTargetDirs(projectRoot) {
59
+ return getTargetDirs(projectRoot, { includeClaude: true });
60
+ }
61
+ function getDefaultInstallTargetDirs(projectRoot) {
62
+ const [universal, claude] = getAllTargetDirs(projectRoot);
63
+ const selected = [];
64
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(projectRoot, ".agents"))) {
65
+ selected.push(universal);
66
+ }
67
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(projectRoot, ".claude"))) {
68
+ selected.push(claude);
69
+ }
70
+ return selected.length > 0 ? selected : [universal];
71
+ }
72
+ function getExistingTargetDirs(projectRoot, options = {}) {
73
+ if (options.includeClaude) {
74
+ return getTargetDirs(projectRoot, { includeClaude: true });
75
+ }
76
+ return getAllTargetDirs(projectRoot).filter((target) => isDirectory(target.path));
77
+ }
55
78
  function installSkill(skill, targetDir, options = {}) {
56
79
  const dest = (0, import_node_path.join)(targetDir, skill.name);
57
80
  const source = (0, import_node_path.resolve)(skill.skillDir);
@@ -137,6 +160,9 @@ var testing = {
137
160
  CLAUDE_SKILLS_DIR,
138
161
  InstallError,
139
162
  UNIVERSAL_SKILLS_DIR,
163
+ getAllTargetDirs,
164
+ getDefaultInstallTargetDirs,
165
+ getExistingTargetDirs,
140
166
  getTargetDirs,
141
167
  installSkill,
142
168
  listInstalledSkills,
@@ -19,6 +19,11 @@ declare class InstallError extends Error {
19
19
  declare function getTargetDirs(projectRoot: string, options?: {
20
20
  includeClaude?: boolean;
21
21
  }): InstallTarget[];
22
+ declare function getAllTargetDirs(projectRoot: string): InstallTarget[];
23
+ declare function getDefaultInstallTargetDirs(projectRoot: string): InstallTarget[];
24
+ declare function getExistingTargetDirs(projectRoot: string, options?: {
25
+ includeClaude?: boolean;
26
+ }): InstallTarget[];
22
27
  declare function installSkill(skill: Skill, targetDir: string, options?: {
23
28
  copy?: boolean;
24
29
  }): string;
@@ -38,4 +43,4 @@ declare const testing: {
38
43
  resolveSymlink: typeof resolveSymlink;
39
44
  };
40
45
 
41
- export { CLAUDE_SKILLS_DIR, InstallError, type InstallTarget, type InstalledSkill, UNIVERSAL_SKILLS_DIR, getTargetDirs, installSkill, listInstalledSkills, testing, uninstallSkill };
46
+ export { CLAUDE_SKILLS_DIR, InstallError, type InstallTarget, type InstalledSkill, UNIVERSAL_SKILLS_DIR, getAllTargetDirs, getDefaultInstallTargetDirs, getExistingTargetDirs, getTargetDirs, installSkill, listInstalledSkills, testing, uninstallSkill };
@@ -19,6 +19,11 @@ declare class InstallError extends Error {
19
19
  declare function getTargetDirs(projectRoot: string, options?: {
20
20
  includeClaude?: boolean;
21
21
  }): InstallTarget[];
22
+ declare function getAllTargetDirs(projectRoot: string): InstallTarget[];
23
+ declare function getDefaultInstallTargetDirs(projectRoot: string): InstallTarget[];
24
+ declare function getExistingTargetDirs(projectRoot: string, options?: {
25
+ includeClaude?: boolean;
26
+ }): InstallTarget[];
22
27
  declare function installSkill(skill: Skill, targetDir: string, options?: {
23
28
  copy?: boolean;
24
29
  }): string;
@@ -38,4 +43,4 @@ declare const testing: {
38
43
  resolveSymlink: typeof resolveSymlink;
39
44
  };
40
45
 
41
- export { CLAUDE_SKILLS_DIR, InstallError, type InstallTarget, type InstalledSkill, UNIVERSAL_SKILLS_DIR, getTargetDirs, installSkill, listInstalledSkills, testing, uninstallSkill };
46
+ export { CLAUDE_SKILLS_DIR, InstallError, type InstallTarget, type InstalledSkill, UNIVERSAL_SKILLS_DIR, getAllTargetDirs, getDefaultInstallTargetDirs, getExistingTargetDirs, getTargetDirs, installSkill, listInstalledSkills, testing, uninstallSkill };
@@ -31,6 +31,26 @@ function getTargetDirs(projectRoot, options = {}) {
31
31
  }
32
32
  return targets;
33
33
  }
34
+ function getAllTargetDirs(projectRoot) {
35
+ return getTargetDirs(projectRoot, { includeClaude: true });
36
+ }
37
+ function getDefaultInstallTargetDirs(projectRoot) {
38
+ const [universal, claude] = getAllTargetDirs(projectRoot);
39
+ const selected = [];
40
+ if (existsSync(join(projectRoot, ".agents"))) {
41
+ selected.push(universal);
42
+ }
43
+ if (existsSync(join(projectRoot, ".claude"))) {
44
+ selected.push(claude);
45
+ }
46
+ return selected.length > 0 ? selected : [universal];
47
+ }
48
+ function getExistingTargetDirs(projectRoot, options = {}) {
49
+ if (options.includeClaude) {
50
+ return getTargetDirs(projectRoot, { includeClaude: true });
51
+ }
52
+ return getAllTargetDirs(projectRoot).filter((target) => isDirectory(target.path));
53
+ }
34
54
  function installSkill(skill, targetDir, options = {}) {
35
55
  const dest = join(targetDir, skill.name);
36
56
  const source = resolve(skill.skillDir);
@@ -115,6 +135,9 @@ export {
115
135
  CLAUDE_SKILLS_DIR,
116
136
  InstallError,
117
137
  UNIVERSAL_SKILLS_DIR,
138
+ getAllTargetDirs,
139
+ getDefaultInstallTargetDirs,
140
+ getExistingTargetDirs,
118
141
  getTargetDirs,
119
142
  installSkill,
120
143
  listInstalledSkills,