skillbox 0.3.1 → 0.3.2

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/cli.js CHANGED
@@ -12,7 +12,7 @@ import { registerRemove } from "./commands/remove.js";
12
12
  import { registerStatus } from "./commands/status.js";
13
13
  import { registerUpdate } from "./commands/update.js";
14
14
  const program = new Command();
15
- program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.3.1");
15
+ program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.3.2");
16
16
  registerAdd(program);
17
17
  registerAgent(program);
18
18
  registerConfig(program);
@@ -2,6 +2,7 @@ import { getErrorMessage } from "../lib/command.js";
2
2
  import { loadConfig } from "../lib/config.js";
3
3
  import { parseRepoRef } from "../lib/github.js";
4
4
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
5
+ import { recordInstallPaths } from "../lib/installs.js";
5
6
  import { printInfo, printJson } from "../lib/output.js";
6
7
  import { buildProjectAgentPaths } from "../lib/project-paths.js";
7
8
  import { fetchRepoFile, listRepoSkills, normalizeRepoRef, writeRepoSkillDirectory, } from "../lib/repo-skills.js";
@@ -31,6 +32,7 @@ async function installSkillTargets(skillName, options, installs) {
31
32
  const projectEntry = await ensureProjectRegistered(projectRoot, scope);
32
33
  const paths = buildProjectAgentPaths(projectRoot, projectEntry);
33
34
  const config = await loadConfig();
35
+ const recordedPaths = new Set();
34
36
  for (const agent of agentList) {
35
37
  const map = paths[agent];
36
38
  if (!map) {
@@ -45,13 +47,16 @@ async function installSkillTargets(skillName, options, installs) {
45
47
  for (const warning of warnings) {
46
48
  printInfo(warning);
47
49
  }
48
- for (const target of written) {
49
- installs.push({
50
- scope,
51
- agent,
52
- path: target,
53
- projectRoot: scope === "project" ? projectRoot : undefined,
54
- });
50
+ const deduped = recordInstallPaths(written, recordedPaths);
51
+ if (deduped.length > 0) {
52
+ for (const target of deduped) {
53
+ installs.push({
54
+ scope,
55
+ agent,
56
+ path: target,
57
+ projectRoot: scope === "project" ? projectRoot : undefined,
58
+ });
59
+ }
55
60
  }
56
61
  }
57
62
  }
@@ -3,6 +3,7 @@ import { loadConfig } from "../lib/config.js";
3
3
  import { fetchText } from "../lib/fetcher.js";
4
4
  import { collect } from "../lib/fs-utils.js";
5
5
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
6
+ import { recordInstallPaths } from "../lib/installs.js";
6
7
  import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
7
8
  import { buildProjectAgentPaths } from "../lib/project-paths.js";
8
9
  import { ensureProjectRegistered, resolveRuntime } from "../lib/runtime.js";
@@ -64,6 +65,7 @@ export function registerAdd(program) {
64
65
  const config = await loadConfig();
65
66
  const installed = [];
66
67
  const installs = [];
68
+ const recordedPaths = new Set();
67
69
  for (const agent of agentList) {
68
70
  const map = paths[agent];
69
71
  if (!map) {
@@ -78,9 +80,10 @@ export function registerAdd(program) {
78
80
  for (const warning of warnings) {
79
81
  printInfo(warning);
80
82
  }
81
- if (written.length > 0) {
82
- installed.push({ agent, scope, targets: written });
83
- for (const target of written) {
83
+ const deduped = recordInstallPaths(written, recordedPaths);
84
+ if (deduped.length > 0) {
85
+ installed.push({ agent, scope, targets: deduped });
86
+ for (const target of deduped) {
84
87
  installs.push({
85
88
  scope,
86
89
  agent,
@@ -58,9 +58,28 @@ function groupByScope(skills) {
58
58
  });
59
59
  }
60
60
  if (projectSkills.length > 0) {
61
+ const projectRoots = new Map();
62
+ for (const skill of projectSkills) {
63
+ const roots = getProjectRoots(skill);
64
+ if (roots.length === 0) {
65
+ continue;
66
+ }
67
+ for (const root of roots) {
68
+ const existing = projectRoots.get(root) ?? [];
69
+ existing.push(skill);
70
+ projectRoots.set(root, existing);
71
+ }
72
+ }
73
+ const projectGroups = Array.from(projectRoots.entries())
74
+ .sort(([a], [b]) => a.localeCompare(b))
75
+ .map(([projectRoot, skillsForProject]) => ({
76
+ projectRoot,
77
+ sourceGroups: groupBySourceType(skillsForProject),
78
+ }));
61
79
  result.push({
62
80
  scope: "project",
63
81
  sourceGroups: groupBySourceType(projectSkills),
82
+ projectGroups,
64
83
  });
65
84
  }
66
85
  return result;
@@ -71,10 +90,33 @@ function groupBySourceType(skills) {
71
90
  const grouped = groupAndSort(skills, (skill) => skill.source.type, LIST_SOURCE_ORDER, sortByName);
72
91
  return grouped.map(({ key, items }) => ({ source: key, skills: items }));
73
92
  }
93
+ function getProjectRoots(skill) {
94
+ const roots = (skill.installs ?? [])
95
+ .filter((install) => install.scope === "project")
96
+ .map((install) => install.projectRoot)
97
+ .filter((root) => Boolean(root));
98
+ return Array.from(new Set(roots));
99
+ }
74
100
  function printScopeGroup(group) {
75
101
  const label = group.scope === "global" ? "Global Skills" : "Project Skills";
76
102
  const totalCount = group.sourceGroups.reduce((sum, g) => sum + g.skills.length, 0);
77
103
  printInfo(`${label} (${totalCount})`);
104
+ if (group.scope === "project" && group.projectGroups) {
105
+ for (const projectGroup of group.projectGroups) {
106
+ printInfo("");
107
+ printInfo(projectGroup.projectRoot);
108
+ for (const sourceGroup of projectGroup.sourceGroups) {
109
+ printInfo(` ${sourceGroup.source}`);
110
+ for (const skill of sourceGroup.skills) {
111
+ printInfo(` ${skill.name}`);
112
+ if (skill.subcommands.length > 0) {
113
+ printInfo(` → ${skill.subcommands.join(", ")}`);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return;
119
+ }
78
120
  for (const sourceGroup of group.sourceGroups) {
79
121
  printInfo("");
80
122
  printInfo(`${sourceGroup.source}`);
@@ -31,6 +31,17 @@ export function getInstallPaths(skill, projectRoot) {
31
31
  .filter((install) => install.projectRoot === projectRoot)
32
32
  .map((install) => install.path);
33
33
  }
34
+ export function recordInstallPaths(paths, recorded) {
35
+ const deduped = [];
36
+ for (const path of paths) {
37
+ if (recorded.has(path)) {
38
+ continue;
39
+ }
40
+ recorded.add(path);
41
+ deduped.push(path);
42
+ }
43
+ return deduped;
44
+ }
34
45
  export function getProjectInstallPaths(skills, projectRoot) {
35
46
  const map = new Map();
36
47
  for (const skill of skills) {
package/dist/lib/sync.js CHANGED
@@ -58,9 +58,8 @@ export function buildSymlinkWarning(agent, results) {
58
58
  export async function installSkillToTargets(skillName, targets, config) {
59
59
  const sourceDir = skillDir(skillName);
60
60
  const results = [];
61
- for (const targetRoot of targets) {
62
- const targetDir = path.join(targetRoot, skillName);
63
- await ensureDir(targetRoot);
61
+ for (const targetDir of targets) {
62
+ await ensureDir(path.dirname(targetDir));
64
63
  if (config.installMode === "symlink") {
65
64
  try {
66
65
  const status = await createSymlink(sourceDir, targetDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillbox",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Local-first, agent-agnostic skills manager",
5
5
  "license": "MIT",
6
6
  "author": "Christian Anagnostou",