library-skills 0.0.10 → 0.0.12

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
@@ -49,11 +49,13 @@ $ npx library-skills
49
49
 
50
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.
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
+ Then it will ask where to install them and add them as symbolic links, so when you update the libraries, the skills are updated too.
53
+
54
+ By default it selects `.agents/skills`, the agent-neutral target. If the project already has a `.claude/` directory, it selects `.claude/skills` too.
53
55
 
54
56
  /// tip
55
57
 
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.
58
+ 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
59
 
58
60
  ///
59
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "library-skills",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Library Skills, AI Agents using libraries, as intended, always up to date.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+
4
+ interface Skill {
5
+ name: string;
6
+ description: string;
7
+ path: string;
8
+ packageName: string;
9
+ skillDir: string;
10
+ packageVersion: string;
11
+ }
12
+
13
+ interface InstallTarget {
14
+ name: string;
15
+ path: string;
16
+ }
17
+
18
+ interface UvWorkspace {
19
+ root: string;
20
+ members: string[];
21
+ currentMember: string | null;
22
+ }
23
+ interface NodeWorkspace {
24
+ root: string;
25
+ members: string[];
26
+ currentMember: string | null;
27
+ }
28
+
29
+ interface ProjectContext {
30
+ cwd: string;
31
+ projectRoot: string;
32
+ targetEnvironment: string | null;
33
+ sitePackagesDir: string | null;
34
+ nodeModulesDir: string | null;
35
+ workspace: UvWorkspace | null;
36
+ nodeWorkspace: NodeWorkspace | null;
37
+ workspaceDependencyFiles: string[];
38
+ nodeWorkspaceDependencyFiles: string[];
39
+ dependencyFiles: string[];
40
+ }
41
+ interface InstalledStatus {
42
+ target: InstallTarget;
43
+ name: string;
44
+ type: "symlink" | "directory" | "missing";
45
+ path: string;
46
+ targetPath: string | null;
47
+ status: "up to date" | "broken" | "outdated" | "orphaned" | "name mismatch" | "hand-authored" | "new";
48
+ skill: Skill | null;
49
+ }
50
+ interface GlobalOptions {
51
+ claude?: boolean;
52
+ yes?: boolean;
53
+ check?: boolean;
54
+ all?: boolean;
55
+ skill?: string[];
56
+ }
57
+ interface ListOptions {
58
+ installed?: boolean;
59
+ json?: boolean;
60
+ claude?: boolean;
61
+ all?: boolean;
62
+ }
63
+ interface ScanOptions {
64
+ all?: boolean;
65
+ json?: boolean;
66
+ }
67
+ declare function getProjectContext(cwd?: string): ProjectContext;
68
+ declare function topLevelSkills({ context, skills, includeAll, }: {
69
+ context: ProjectContext;
70
+ skills: Skill[];
71
+ includeAll: boolean;
72
+ }): Skill[];
73
+ declare function displayPath(path: string | null | undefined, projectRoot: string): string;
74
+ declare function printTable(columns: string[], rows: Array<Record<string, string>>): void;
75
+ declare function findCollisions(skills: Skill[]): Set<string>;
76
+ declare function filterInstallableSkills({ skills, selectedNames, includeAll, }: {
77
+ skills: Skill[];
78
+ selectedNames: string[];
79
+ includeAll: boolean;
80
+ }): Skill[];
81
+ declare function installedStatuses({ targets, skills, }: {
82
+ targets: InstallTarget[];
83
+ skills: Skill[];
84
+ }): InstalledStatus[];
85
+ declare function installSelected({ skills, targets, projectRoot, copy, }: {
86
+ skills: Skill[];
87
+ targets: InstallTarget[];
88
+ projectRoot: string;
89
+ copy?: boolean;
90
+ }): number;
91
+ declare function sync(options: GlobalOptions): Promise<void>;
92
+ declare function scanCommand(options: ScanOptions): void;
93
+ declare function listCommand(options: ListOptions): void;
94
+ declare function createProgram(): Command;
95
+ declare function main(argv?: string[]): Promise<void>;
96
+ declare const testing: {
97
+ filterInstallableSkills: typeof filterInstallableSkills;
98
+ findCollisions: typeof findCollisions;
99
+ getProjectContext: typeof getProjectContext;
100
+ installSelected: typeof installSelected;
101
+ installedStatuses: typeof installedStatuses;
102
+ listCommand: typeof listCommand;
103
+ scanCommand: typeof scanCommand;
104
+ sync: typeof sync;
105
+ topLevelSkills: typeof topLevelSkills;
106
+ displayPath: typeof displayPath;
107
+ printTable: typeof printTable;
108
+ };
109
+
110
+ export { createProgram, main, testing };
package/ts/dist/cli.js CHANGED
@@ -522,6 +522,28 @@ function getNodeTopLevelDeps(projectRoot) {
522
522
  if (!existsSync2(packageJson)) {
523
523
  return null;
524
524
  }
525
+ return getNodeTopLevelDepsFromFiles([packageJson]);
526
+ }
527
+ function getNodeTopLevelDepsFromFiles(packageJsonFiles) {
528
+ if (packageJsonFiles.length === 0) {
529
+ return null;
530
+ }
531
+ const deps = /* @__PURE__ */ new Set();
532
+ let found = false;
533
+ for (const packageJson of packageJsonFiles) {
534
+ if (!existsSync2(packageJson)) {
535
+ continue;
536
+ }
537
+ const data = readPackageJson(packageJson);
538
+ if (data === null) {
539
+ return null;
540
+ }
541
+ found = true;
542
+ extractNodeTopLevelDeps(data, deps);
543
+ }
544
+ return found ? deps : null;
545
+ }
546
+ function readPackageJson(packageJson) {
525
547
  let data;
526
548
  try {
527
549
  data = JSON.parse(readFileSync2(packageJson, "utf8"));
@@ -529,9 +551,11 @@ function getNodeTopLevelDeps(projectRoot) {
529
551
  return null;
530
552
  }
531
553
  if (!isRecord2(data)) {
532
- return /* @__PURE__ */ new Set();
554
+ return {};
533
555
  }
534
- const deps = /* @__PURE__ */ new Set();
556
+ return data;
557
+ }
558
+ function extractNodeTopLevelDeps(data, deps) {
535
559
  for (const field of [
536
560
  "dependencies",
537
561
  "devDependencies",
@@ -546,7 +570,6 @@ function getNodeTopLevelDeps(projectRoot) {
546
570
  deps.add(normalizePackageName(packageName));
547
571
  }
548
572
  }
549
- return deps;
550
573
  }
551
574
  function getTopLevelDeps(projectRoot) {
552
575
  const dependencySets = [
@@ -561,6 +584,9 @@ function getTopLevelDeps(projectRoot) {
561
584
  function getWorkspaceTopLevelDeps(pyprojects) {
562
585
  return getPythonTopLevelDepsFromFiles(pyprojects);
563
586
  }
587
+ function getNodeWorkspaceTopLevelDeps(packageJsonFiles) {
588
+ return getNodeTopLevelDepsFromFiles(packageJsonFiles);
589
+ }
564
590
  function extractDepsFromSpecs(depSpecs, deps) {
565
591
  for (const depSpec of depSpecs) {
566
592
  if (typeof depSpec !== "string") {
@@ -634,6 +660,26 @@ function getTargetDirs(projectRoot, options = {}) {
634
660
  }
635
661
  return targets;
636
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
+ }
637
683
  function installSkill(skill, targetDir, options = {}) {
638
684
  const dest = join3(targetDir, skill.name);
639
685
  const source = resolve2(skill.skillDir);
@@ -736,6 +782,26 @@ function findUvWorkspace(cwd) {
736
782
  }
737
783
  return null;
738
784
  }
785
+ function findNodeWorkspace(cwd) {
786
+ for (const directory of ancestors(cwd)) {
787
+ const packageJson = `${directory}/package.json`;
788
+ if (!existsSync4(packageJson)) {
789
+ continue;
790
+ }
791
+ const data = readPackageJson2(packageJson);
792
+ const memberGlobs = getNodeWorkspaceGlobs(data);
793
+ if (memberGlobs === null) {
794
+ continue;
795
+ }
796
+ const members = findNodeWorkspaceMembers(directory, memberGlobs);
797
+ return {
798
+ root: directory,
799
+ members,
800
+ currentMember: findCurrentMember(cwd, members)
801
+ };
802
+ }
803
+ return null;
804
+ }
739
805
  function workspaceDependencyFiles(workspace) {
740
806
  if (workspace.currentMember !== null) {
741
807
  return [`${workspace.currentMember}/pyproject.toml`];
@@ -745,6 +811,15 @@ function workspaceDependencyFiles(workspace) {
745
811
  ...workspace.members.map((member) => `${member}/pyproject.toml`)
746
812
  ].filter((path) => existsSync4(path));
747
813
  }
814
+ function nodeWorkspaceDependencyFiles(workspace) {
815
+ if (workspace.currentMember !== null) {
816
+ return [`${workspace.currentMember}/package.json`];
817
+ }
818
+ return [
819
+ `${workspace.root}/package.json`,
820
+ ...workspace.members.map((member) => `${member}/package.json`)
821
+ ].filter((path) => existsSync4(path));
822
+ }
748
823
  function readPyproject(path) {
749
824
  try {
750
825
  const data = parse3(readFileSync3(path, "utf8"));
@@ -753,6 +828,14 @@ function readPyproject(path) {
753
828
  return {};
754
829
  }
755
830
  }
831
+ function readPackageJson2(path) {
832
+ try {
833
+ const data = JSON.parse(readFileSync3(path, "utf8"));
834
+ return isRecord3(data) ? data : {};
835
+ } catch {
836
+ return {};
837
+ }
838
+ }
756
839
  function hasUvWorkspace(data) {
757
840
  const tool = data["tool"];
758
841
  if (!isRecord3(tool)) {
@@ -789,6 +872,36 @@ function findWorkspaceMembers(root, data) {
789
872
  }
790
873
  return [...members].sort();
791
874
  }
875
+ function getNodeWorkspaceGlobs(data) {
876
+ const workspaces = data["workspaces"];
877
+ if (Array.isArray(workspaces)) {
878
+ return workspaces.filter((value) => typeof value === "string");
879
+ }
880
+ if (isRecord3(workspaces)) {
881
+ const packages = workspaces["packages"];
882
+ if (Array.isArray(packages)) {
883
+ return packages.filter((value) => typeof value === "string");
884
+ }
885
+ }
886
+ return null;
887
+ }
888
+ function findNodeWorkspaceMembers(root, memberGlobs) {
889
+ const includes = memberGlobs.filter((pattern) => !pattern.startsWith("!"));
890
+ const excludes = memberGlobs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
891
+ const members = /* @__PURE__ */ new Set();
892
+ for (const memberGlob of includes) {
893
+ for (const member of expandMemberGlob(root, memberGlob)) {
894
+ const relativePath = relative3(root, member).split(sep).join("/");
895
+ if (isExcluded(relativePath, excludes)) {
896
+ continue;
897
+ }
898
+ if (isFile(`${member}/package.json`)) {
899
+ members.add(resolve3(member));
900
+ }
901
+ }
902
+ }
903
+ return [...members].sort();
904
+ }
792
905
  function getWorkspaceTable(data) {
793
906
  const tool = data["tool"];
794
907
  if (!isRecord3(tool)) {
@@ -896,6 +1009,10 @@ function findProjectRoot(cwd) {
896
1009
  if (workspace !== null) {
897
1010
  return workspace.root;
898
1011
  }
1012
+ const nodeWorkspace = findNodeWorkspace(cwd);
1013
+ if (nodeWorkspace !== null) {
1014
+ return nodeWorkspace.root;
1015
+ }
899
1016
  for (const directory of ancestors2(cwd)) {
900
1017
  if (isFile2(join4(directory, "pyproject.toml")) || isFile2(join4(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile2(join4(directory, "package.json")) || isDirectory4(join4(directory, "node_modules"))) {
901
1018
  return directory;
@@ -1023,7 +1140,10 @@ function isDirectory4(path) {
1023
1140
  // ts/src/cli.ts
1024
1141
  function getProjectContext(cwd = process.cwd()) {
1025
1142
  const workspace = findUvWorkspace(cwd);
1026
- const projectRoot = workspace?.root ?? findProjectRoot(cwd) ?? cwd;
1143
+ const nodeWorkspace = findNodeWorkspace(cwd);
1144
+ const projectRoot = workspace?.root ?? nodeWorkspace?.root ?? findProjectRoot(cwd) ?? cwd;
1145
+ const workspaceFiles = workspace === null ? [] : workspaceDependencyFiles(workspace);
1146
+ const nodeWorkspaceFiles = nodeWorkspace === null ? [] : nodeWorkspaceDependencyFiles(nodeWorkspace);
1027
1147
  const targetEnvironment = findVenv(cwd);
1028
1148
  const sitePackagesDir = targetEnvironment === null ? null : getSitePackagesDir(targetEnvironment);
1029
1149
  const nodeModulesDir = findNodeModules(cwd);
@@ -1034,7 +1154,10 @@ function getProjectContext(cwd = process.cwd()) {
1034
1154
  sitePackagesDir,
1035
1155
  nodeModulesDir,
1036
1156
  workspace,
1037
- dependencyFiles: workspace ? workspaceDependencyFiles(workspace) : []
1157
+ nodeWorkspace,
1158
+ workspaceDependencyFiles: workspaceFiles,
1159
+ nodeWorkspaceDependencyFiles: nodeWorkspaceFiles,
1160
+ dependencyFiles: [...workspaceFiles, ...nodeWorkspaceFiles]
1038
1161
  };
1039
1162
  }
1040
1163
  function scanContext(context) {
@@ -1077,8 +1200,8 @@ function topLevelSkills({
1077
1200
  return skills;
1078
1201
  }
1079
1202
  const topLevelDeps = getTopLevelDeps(context.projectRoot);
1080
- const workspaceTopLevelDeps = context.workspace === null ? null : getWorkspaceTopLevelDeps(context.dependencyFiles);
1081
- const selectedTopLevelDeps = context.workspace === null ? topLevelDeps : workspaceTopLevelDeps;
1203
+ const workspaceTopLevelDeps = getWorkspaceTopLevelDepsForContext(context);
1204
+ const selectedTopLevelDeps = context.workspace === null && context.nodeWorkspace === null ? topLevelDeps : workspaceTopLevelDeps;
1082
1205
  if (selectedTopLevelDeps === null) {
1083
1206
  return skills;
1084
1207
  }
@@ -1086,6 +1209,16 @@ function topLevelSkills({
1086
1209
  (skill) => selectedTopLevelDeps.has(normalizePackageName(skill.packageName))
1087
1210
  );
1088
1211
  }
1212
+ function getWorkspaceTopLevelDepsForContext(context) {
1213
+ const dependencySets = [
1214
+ context.workspace === null ? null : getWorkspaceTopLevelDeps(context.workspaceDependencyFiles),
1215
+ context.nodeWorkspace === null ? null : getNodeWorkspaceTopLevelDeps(context.nodeWorkspaceDependencyFiles)
1216
+ ].filter((deps) => deps !== null);
1217
+ if (dependencySets.length === 0) {
1218
+ return null;
1219
+ }
1220
+ return new Set(dependencySets.flatMap((deps) => [...deps]));
1221
+ }
1089
1222
  function printWarnings(warnings) {
1090
1223
  for (const warning of warnings) {
1091
1224
  console.log(`Warning: ${warning}`);
@@ -1111,6 +1244,11 @@ function printContext(context) {
1111
1244
  if (context.workspace.currentMember !== null) {
1112
1245
  console.log(`Workspace member: ${context.workspace.currentMember}`);
1113
1246
  }
1247
+ } else if (context.nodeWorkspace !== null) {
1248
+ console.log(`Workspace root: ${context.nodeWorkspace.root}`);
1249
+ if (context.nodeWorkspace.currentMember !== null) {
1250
+ console.log(`Workspace member: ${context.nodeWorkspace.currentMember}`);
1251
+ }
1114
1252
  }
1115
1253
  console.log(
1116
1254
  `Target Python environment: ${context.targetEnvironment ? displayPath(context.targetEnvironment, context.projectRoot) : "not found"}`
@@ -1151,8 +1289,8 @@ function scanJsonPayload({
1151
1289
  }) {
1152
1290
  return {
1153
1291
  project_root: context.projectRoot,
1154
- workspace_root: context.workspace?.root ?? "",
1155
- workspace_member: context.workspace?.currentMember ?? "",
1292
+ workspace_root: context.workspace?.root ?? context.nodeWorkspace?.root ?? "",
1293
+ workspace_member: context.workspace?.currentMember ?? context.nodeWorkspace?.currentMember ?? "",
1156
1294
  dependency_files: context.dependencyFiles,
1157
1295
  target_environment: context.targetEnvironment ?? "",
1158
1296
  node_modules: context.nodeModulesDir ?? "",
@@ -1186,6 +1324,19 @@ function findCollisions(skills) {
1186
1324
  [...counts.entries()].filter(([, count]) => count > 1).map(([name]) => name)
1187
1325
  );
1188
1326
  }
1327
+ function deduplicateSkills(skills) {
1328
+ const seen = /* @__PURE__ */ new Set();
1329
+ const unique = [];
1330
+ for (const skill of skills) {
1331
+ const key = resolve5(skill.skillDir);
1332
+ if (seen.has(key)) {
1333
+ continue;
1334
+ }
1335
+ seen.add(key);
1336
+ unique.push(skill);
1337
+ }
1338
+ return unique;
1339
+ }
1189
1340
  function filterInstallableSkills({
1190
1341
  skills,
1191
1342
  selectedNames,
@@ -1295,6 +1446,34 @@ async function selectSkillsInteractive(skills) {
1295
1446
  }))
1296
1447
  });
1297
1448
  }
1449
+ async function selectTargetsInteractive({
1450
+ projectRoot,
1451
+ defaultTargets
1452
+ }) {
1453
+ const defaultNames = new Set(defaultTargets.map((target) => target.name));
1454
+ return checkbox({
1455
+ message: "Select installation targets (press Space to select, Enter to confirm):",
1456
+ choices: getAllTargetDirs(projectRoot).map((target) => ({
1457
+ name: displayPath(target.path, projectRoot),
1458
+ value: target,
1459
+ checked: defaultNames.has(target.name)
1460
+ })),
1461
+ validate: (selected) => selected.length > 0 || "Please select at least one installation target."
1462
+ });
1463
+ }
1464
+ async function selectInstallTargets({
1465
+ projectRoot,
1466
+ includeClaude,
1467
+ interactive
1468
+ }) {
1469
+ if (!interactive) {
1470
+ return getTargetDirs(projectRoot, { includeClaude });
1471
+ }
1472
+ return selectTargetsInteractive({
1473
+ projectRoot,
1474
+ defaultTargets: getDefaultInstallTargetDirs(projectRoot)
1475
+ });
1476
+ }
1298
1477
  async function selectInstalledSkillsInteractive(statuses) {
1299
1478
  const removable = statuses.filter((status) => status.type === "symlink");
1300
1479
  if (removable.length === 0) {
@@ -1341,9 +1520,9 @@ function installSelected({
1341
1520
  async function sync(options) {
1342
1521
  const context = getProjectContext();
1343
1522
  const result = scanContext(context);
1344
- const targets = getTargetDirs(context.projectRoot, {
1523
+ let targets = options.yes || options.check || options.claude ? getTargetDirs(context.projectRoot, {
1345
1524
  includeClaude: options.claude
1346
- });
1525
+ }) : getDefaultInstallTargetDirs(context.projectRoot);
1347
1526
  printContext(context);
1348
1527
  console.log();
1349
1528
  printWarnings(result.warnings);
@@ -1394,10 +1573,19 @@ async function sync(options) {
1394
1573
  if (selected.length === 0 && !options.yes && selectedNames.length === 0 && !options.all) {
1395
1574
  const newSkills = visibleStatuses.filter((status) => status.status === "new" && status.skill !== null).map((status) => status.skill);
1396
1575
  if (newSkills.length > 0) {
1397
- selected = await selectSkillsInteractive(newSkills);
1576
+ selected = await selectSkillsInteractive(deduplicateSkills(newSkills));
1398
1577
  }
1399
1578
  }
1400
1579
  if (selected.length > 0) {
1580
+ targets = await selectInstallTargets({
1581
+ projectRoot: context.projectRoot,
1582
+ includeClaude: options.claude,
1583
+ interactive: !options.yes && !options.claude
1584
+ });
1585
+ if (targets.length === 0) {
1586
+ console.log("No installation targets selected.");
1587
+ return;
1588
+ }
1401
1589
  console.log();
1402
1590
  const installedCount = installSelected({
1403
1591
  skills: selected,
@@ -1438,7 +1626,7 @@ function listCommand(options) {
1438
1626
  skills: result.skills,
1439
1627
  includeAll: Boolean(options.all)
1440
1628
  });
1441
- const targets = getTargetDirs(context.projectRoot, {
1629
+ const targets = getExistingTargetDirs(context.projectRoot, {
1442
1630
  includeClaude: options.claude
1443
1631
  });
1444
1632
  const statuses = installedStatuses({ targets, skills: result.skills });
@@ -1478,9 +1666,6 @@ async function installCommand(options) {
1478
1666
  skills: result.skills,
1479
1667
  includeAll: Boolean(options.all) || selectedNames.length > 0
1480
1668
  });
1481
- const targets = getTargetDirs(context.projectRoot, {
1482
- includeClaude: options.claude
1483
- });
1484
1669
  printWarnings(result.warnings);
1485
1670
  let selected = filterInstallableSkills({
1486
1671
  skills,
@@ -1494,6 +1679,15 @@ async function installCommand(options) {
1494
1679
  console.log("No skills selected.");
1495
1680
  return;
1496
1681
  }
1682
+ const targets = await selectInstallTargets({
1683
+ projectRoot: context.projectRoot,
1684
+ includeClaude: options.claude,
1685
+ interactive: !options.yes && !options.claude
1686
+ });
1687
+ if (targets.length === 0) {
1688
+ console.log("No installation targets selected.");
1689
+ return;
1690
+ }
1497
1691
  const installedCount = installSelected({
1498
1692
  skills: selected,
1499
1693
  targets,
@@ -1506,7 +1700,7 @@ async function installCommand(options) {
1506
1700
  async function removeCommand(skillNames, options) {
1507
1701
  const context = getProjectContext();
1508
1702
  const result = scanContext(context);
1509
- const targets = getTargetDirs(context.projectRoot, {
1703
+ const targets = getExistingTargetDirs(context.projectRoot, {
1510
1704
  includeClaude: options.claude
1511
1705
  });
1512
1706
  const statuses = installedStatuses({ targets, skills: result.skills });
package/ts/dist/deps.cjs CHANGED
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var deps_exports = {};
22
22
  __export(deps_exports, {
23
23
  getNodeTopLevelDeps: () => getNodeTopLevelDeps,
24
+ getNodeTopLevelDepsFromFiles: () => getNodeTopLevelDepsFromFiles,
25
+ getNodeWorkspaceTopLevelDeps: () => getNodeWorkspaceTopLevelDeps,
24
26
  getPythonTopLevelDeps: () => getPythonTopLevelDeps,
25
27
  getPythonTopLevelDepsFromFiles: () => getPythonTopLevelDepsFromFiles,
26
28
  getTopLevelDeps: () => getTopLevelDeps,
@@ -98,6 +100,28 @@ function getNodeTopLevelDeps(projectRoot) {
98
100
  if (!(0, import_node_fs2.existsSync)(packageJson)) {
99
101
  return null;
100
102
  }
103
+ return getNodeTopLevelDepsFromFiles([packageJson]);
104
+ }
105
+ function getNodeTopLevelDepsFromFiles(packageJsonFiles) {
106
+ if (packageJsonFiles.length === 0) {
107
+ return null;
108
+ }
109
+ const deps = /* @__PURE__ */ new Set();
110
+ let found = false;
111
+ for (const packageJson of packageJsonFiles) {
112
+ if (!(0, import_node_fs2.existsSync)(packageJson)) {
113
+ continue;
114
+ }
115
+ const data = readPackageJson(packageJson);
116
+ if (data === null) {
117
+ return null;
118
+ }
119
+ found = true;
120
+ extractNodeTopLevelDeps(data, deps);
121
+ }
122
+ return found ? deps : null;
123
+ }
124
+ function readPackageJson(packageJson) {
101
125
  let data;
102
126
  try {
103
127
  data = JSON.parse((0, import_node_fs2.readFileSync)(packageJson, "utf8"));
@@ -105,9 +129,11 @@ function getNodeTopLevelDeps(projectRoot) {
105
129
  return null;
106
130
  }
107
131
  if (!isRecord(data)) {
108
- return /* @__PURE__ */ new Set();
132
+ return {};
109
133
  }
110
- const deps = /* @__PURE__ */ new Set();
134
+ return data;
135
+ }
136
+ function extractNodeTopLevelDeps(data, deps) {
111
137
  for (const field of [
112
138
  "dependencies",
113
139
  "devDependencies",
@@ -122,7 +148,6 @@ function getNodeTopLevelDeps(projectRoot) {
122
148
  deps.add(normalizePackageName(packageName));
123
149
  }
124
150
  }
125
- return deps;
126
151
  }
127
152
  function getTopLevelDeps(projectRoot) {
128
153
  const dependencySets = [
@@ -137,6 +162,9 @@ function getTopLevelDeps(projectRoot) {
137
162
  function getWorkspaceTopLevelDeps(pyprojects) {
138
163
  return getPythonTopLevelDepsFromFiles(pyprojects);
139
164
  }
165
+ function getNodeWorkspaceTopLevelDeps(packageJsonFiles) {
166
+ return getNodeTopLevelDepsFromFiles(packageJsonFiles);
167
+ }
140
168
  function extractDepsFromSpecs(depSpecs, deps) {
141
169
  for (const depSpec of depSpecs) {
142
170
  if (typeof depSpec !== "string") {
@@ -184,6 +212,8 @@ var testing = {
184
212
  // Annotate the CommonJS export names for ESM import in node:
185
213
  0 && (module.exports = {
186
214
  getNodeTopLevelDeps,
215
+ getNodeTopLevelDepsFromFiles,
216
+ getNodeWorkspaceTopLevelDeps,
187
217
  getPythonTopLevelDeps,
188
218
  getPythonTopLevelDepsFromFiles,
189
219
  getTopLevelDeps,
@@ -1,8 +1,10 @@
1
1
  declare function getPythonTopLevelDeps(projectRoot: string): Set<string> | null;
2
2
  declare function getPythonTopLevelDepsFromFiles(pyprojects: string[]): Set<string> | null;
3
3
  declare function getNodeTopLevelDeps(projectRoot: string): Set<string> | null;
4
+ declare function getNodeTopLevelDepsFromFiles(packageJsonFiles: string[]): Set<string> | null;
4
5
  declare function getTopLevelDeps(projectRoot: string): Set<string> | null;
5
6
  declare function getWorkspaceTopLevelDeps(pyprojects: string[]): Set<string> | null;
7
+ declare function getNodeWorkspaceTopLevelDeps(packageJsonFiles: string[]): Set<string> | null;
6
8
  declare function extractDepsFromSpecs(depSpecs: unknown[], deps: Set<string>): void;
7
9
  declare function extractDepsFromDependencyGroup(groupName: unknown, dependencyGroups: Record<string, unknown>, deps: Set<string>, visited: Set<unknown>): void;
8
10
  declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -12,4 +14,4 @@ declare const testing: {
12
14
  isRecord: typeof isRecord;
13
15
  };
14
16
 
15
- export { getNodeTopLevelDeps, getPythonTopLevelDeps, getPythonTopLevelDepsFromFiles, getTopLevelDeps, getWorkspaceTopLevelDeps, testing };
17
+ export { getNodeTopLevelDeps, getNodeTopLevelDepsFromFiles, getNodeWorkspaceTopLevelDeps, getPythonTopLevelDeps, getPythonTopLevelDepsFromFiles, getTopLevelDeps, getWorkspaceTopLevelDeps, testing };
package/ts/dist/deps.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  declare function getPythonTopLevelDeps(projectRoot: string): Set<string> | null;
2
2
  declare function getPythonTopLevelDepsFromFiles(pyprojects: string[]): Set<string> | null;
3
3
  declare function getNodeTopLevelDeps(projectRoot: string): Set<string> | null;
4
+ declare function getNodeTopLevelDepsFromFiles(packageJsonFiles: string[]): Set<string> | null;
4
5
  declare function getTopLevelDeps(projectRoot: string): Set<string> | null;
5
6
  declare function getWorkspaceTopLevelDeps(pyprojects: string[]): Set<string> | null;
7
+ declare function getNodeWorkspaceTopLevelDeps(packageJsonFiles: string[]): Set<string> | null;
6
8
  declare function extractDepsFromSpecs(depSpecs: unknown[], deps: Set<string>): void;
7
9
  declare function extractDepsFromDependencyGroup(groupName: unknown, dependencyGroups: Record<string, unknown>, deps: Set<string>, visited: Set<unknown>): void;
8
10
  declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -12,4 +14,4 @@ declare const testing: {
12
14
  isRecord: typeof isRecord;
13
15
  };
14
16
 
15
- export { getNodeTopLevelDeps, getPythonTopLevelDeps, getPythonTopLevelDepsFromFiles, getTopLevelDeps, getWorkspaceTopLevelDeps, testing };
17
+ export { getNodeTopLevelDeps, getNodeTopLevelDepsFromFiles, getNodeWorkspaceTopLevelDeps, getPythonTopLevelDeps, getPythonTopLevelDepsFromFiles, getTopLevelDeps, getWorkspaceTopLevelDeps, testing };
package/ts/dist/deps.js CHANGED
@@ -62,6 +62,28 @@ function getNodeTopLevelDeps(projectRoot) {
62
62
  if (!existsSync(packageJson)) {
63
63
  return null;
64
64
  }
65
+ return getNodeTopLevelDepsFromFiles([packageJson]);
66
+ }
67
+ function getNodeTopLevelDepsFromFiles(packageJsonFiles) {
68
+ if (packageJsonFiles.length === 0) {
69
+ return null;
70
+ }
71
+ const deps = /* @__PURE__ */ new Set();
72
+ let found = false;
73
+ for (const packageJson of packageJsonFiles) {
74
+ if (!existsSync(packageJson)) {
75
+ continue;
76
+ }
77
+ const data = readPackageJson(packageJson);
78
+ if (data === null) {
79
+ return null;
80
+ }
81
+ found = true;
82
+ extractNodeTopLevelDeps(data, deps);
83
+ }
84
+ return found ? deps : null;
85
+ }
86
+ function readPackageJson(packageJson) {
65
87
  let data;
66
88
  try {
67
89
  data = JSON.parse(readFileSync(packageJson, "utf8"));
@@ -69,9 +91,11 @@ function getNodeTopLevelDeps(projectRoot) {
69
91
  return null;
70
92
  }
71
93
  if (!isRecord(data)) {
72
- return /* @__PURE__ */ new Set();
94
+ return {};
73
95
  }
74
- const deps = /* @__PURE__ */ new Set();
96
+ return data;
97
+ }
98
+ function extractNodeTopLevelDeps(data, deps) {
75
99
  for (const field of [
76
100
  "dependencies",
77
101
  "devDependencies",
@@ -86,7 +110,6 @@ function getNodeTopLevelDeps(projectRoot) {
86
110
  deps.add(normalizePackageName(packageName));
87
111
  }
88
112
  }
89
- return deps;
90
113
  }
91
114
  function getTopLevelDeps(projectRoot) {
92
115
  const dependencySets = [
@@ -101,6 +124,9 @@ function getTopLevelDeps(projectRoot) {
101
124
  function getWorkspaceTopLevelDeps(pyprojects) {
102
125
  return getPythonTopLevelDepsFromFiles(pyprojects);
103
126
  }
127
+ function getNodeWorkspaceTopLevelDeps(packageJsonFiles) {
128
+ return getNodeTopLevelDepsFromFiles(packageJsonFiles);
129
+ }
104
130
  function extractDepsFromSpecs(depSpecs, deps) {
105
131
  for (const depSpec of depSpecs) {
106
132
  if (typeof depSpec !== "string") {
@@ -147,6 +173,8 @@ var testing = {
147
173
  };
148
174
  export {
149
175
  getNodeTopLevelDeps,
176
+ getNodeTopLevelDepsFromFiles,
177
+ getNodeWorkspaceTopLevelDeps,
150
178
  getPythonTopLevelDeps,
151
179
  getPythonTopLevelDepsFromFiles,
152
180
  getTopLevelDeps,
@@ -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,
@@ -53,6 +53,26 @@ function findUvWorkspace(cwd) {
53
53
  }
54
54
  return null;
55
55
  }
56
+ function findNodeWorkspace(cwd) {
57
+ for (const directory of ancestors(cwd)) {
58
+ const packageJson = `${directory}/package.json`;
59
+ if (!(0, import_node_fs.existsSync)(packageJson)) {
60
+ continue;
61
+ }
62
+ const data = readPackageJson(packageJson);
63
+ const memberGlobs = getNodeWorkspaceGlobs(data);
64
+ if (memberGlobs === null) {
65
+ continue;
66
+ }
67
+ const members = findNodeWorkspaceMembers(directory, memberGlobs);
68
+ return {
69
+ root: directory,
70
+ members,
71
+ currentMember: findCurrentMember(cwd, members)
72
+ };
73
+ }
74
+ return null;
75
+ }
56
76
  function readPyproject(path) {
57
77
  try {
58
78
  const data = (0, import_smol_toml.parse)((0, import_node_fs.readFileSync)(path, "utf8"));
@@ -61,6 +81,14 @@ function readPyproject(path) {
61
81
  return {};
62
82
  }
63
83
  }
84
+ function readPackageJson(path) {
85
+ try {
86
+ const data = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
87
+ return isRecord(data) ? data : {};
88
+ } catch {
89
+ return {};
90
+ }
91
+ }
64
92
  function hasUvWorkspace(data) {
65
93
  const tool = data["tool"];
66
94
  if (!isRecord(tool)) {
@@ -97,6 +125,36 @@ function findWorkspaceMembers(root, data) {
97
125
  }
98
126
  return [...members].sort();
99
127
  }
128
+ function getNodeWorkspaceGlobs(data) {
129
+ const workspaces = data["workspaces"];
130
+ if (Array.isArray(workspaces)) {
131
+ return workspaces.filter((value) => typeof value === "string");
132
+ }
133
+ if (isRecord(workspaces)) {
134
+ const packages = workspaces["packages"];
135
+ if (Array.isArray(packages)) {
136
+ return packages.filter((value) => typeof value === "string");
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ function findNodeWorkspaceMembers(root, memberGlobs) {
142
+ const includes = memberGlobs.filter((pattern) => !pattern.startsWith("!"));
143
+ const excludes = memberGlobs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
144
+ const members = /* @__PURE__ */ new Set();
145
+ for (const memberGlob of includes) {
146
+ for (const member of expandMemberGlob(root, memberGlob)) {
147
+ const relativePath = (0, import_node_path.relative)(root, member).split(import_node_path.sep).join("/");
148
+ if (isExcluded(relativePath, excludes)) {
149
+ continue;
150
+ }
151
+ if (isFile(`${member}/package.json`)) {
152
+ members.add((0, import_node_path.resolve)(member));
153
+ }
154
+ }
155
+ }
156
+ return [...members].sort();
157
+ }
100
158
  function getWorkspaceTable(data) {
101
159
  const tool = data["tool"];
102
160
  if (!isRecord(tool)) {
@@ -204,6 +262,10 @@ function findProjectRoot(cwd) {
204
262
  if (workspace !== null) {
205
263
  return workspace.root;
206
264
  }
265
+ const nodeWorkspace = findNodeWorkspace(cwd);
266
+ if (nodeWorkspace !== null) {
267
+ return nodeWorkspace.root;
268
+ }
207
269
  for (const directory of ancestors2(cwd)) {
208
270
  if (isFile2((0, import_node_path2.join)(directory, "pyproject.toml")) || isFile2((0, import_node_path2.join)(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile2((0, import_node_path2.join)(directory, "package.json")) || isDirectory2((0, import_node_path2.join)(directory, "node_modules"))) {
209
271
  return directory;
@@ -25,6 +25,26 @@ function findUvWorkspace(cwd) {
25
25
  }
26
26
  return null;
27
27
  }
28
+ function findNodeWorkspace(cwd) {
29
+ for (const directory of ancestors(cwd)) {
30
+ const packageJson = `${directory}/package.json`;
31
+ if (!existsSync(packageJson)) {
32
+ continue;
33
+ }
34
+ const data = readPackageJson(packageJson);
35
+ const memberGlobs = getNodeWorkspaceGlobs(data);
36
+ if (memberGlobs === null) {
37
+ continue;
38
+ }
39
+ const members = findNodeWorkspaceMembers(directory, memberGlobs);
40
+ return {
41
+ root: directory,
42
+ members,
43
+ currentMember: findCurrentMember(cwd, members)
44
+ };
45
+ }
46
+ return null;
47
+ }
28
48
  function readPyproject(path) {
29
49
  try {
30
50
  const data = parse(readFileSync(path, "utf8"));
@@ -33,6 +53,14 @@ function readPyproject(path) {
33
53
  return {};
34
54
  }
35
55
  }
56
+ function readPackageJson(path) {
57
+ try {
58
+ const data = JSON.parse(readFileSync(path, "utf8"));
59
+ return isRecord(data) ? data : {};
60
+ } catch {
61
+ return {};
62
+ }
63
+ }
36
64
  function hasUvWorkspace(data) {
37
65
  const tool = data["tool"];
38
66
  if (!isRecord(tool)) {
@@ -69,6 +97,36 @@ function findWorkspaceMembers(root, data) {
69
97
  }
70
98
  return [...members].sort();
71
99
  }
100
+ function getNodeWorkspaceGlobs(data) {
101
+ const workspaces = data["workspaces"];
102
+ if (Array.isArray(workspaces)) {
103
+ return workspaces.filter((value) => typeof value === "string");
104
+ }
105
+ if (isRecord(workspaces)) {
106
+ const packages = workspaces["packages"];
107
+ if (Array.isArray(packages)) {
108
+ return packages.filter((value) => typeof value === "string");
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ function findNodeWorkspaceMembers(root, memberGlobs) {
114
+ const includes = memberGlobs.filter((pattern) => !pattern.startsWith("!"));
115
+ const excludes = memberGlobs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
116
+ const members = /* @__PURE__ */ new Set();
117
+ for (const memberGlob of includes) {
118
+ for (const member of expandMemberGlob(root, memberGlob)) {
119
+ const relativePath = relative(root, member).split(sep).join("/");
120
+ if (isExcluded(relativePath, excludes)) {
121
+ continue;
122
+ }
123
+ if (isFile(`${member}/package.json`)) {
124
+ members.add(resolve(member));
125
+ }
126
+ }
127
+ }
128
+ return [...members].sort();
129
+ }
72
130
  function getWorkspaceTable(data) {
73
131
  const tool = data["tool"];
74
132
  if (!isRecord(tool)) {
@@ -176,6 +234,10 @@ function findProjectRoot(cwd) {
176
234
  if (workspace !== null) {
177
235
  return workspace.root;
178
236
  }
237
+ const nodeWorkspace = findNodeWorkspace(cwd);
238
+ if (nodeWorkspace !== null) {
239
+ return nodeWorkspace.root;
240
+ }
179
241
  for (const directory of ancestors2(cwd)) {
180
242
  if (isFile2(join(directory, "pyproject.toml")) || isFile2(join(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile2(join(directory, "package.json")) || isDirectory2(join(directory, "node_modules"))) {
181
243
  return directory;