skillbox 0.3.1 → 0.3.3

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
@@ -67,8 +67,9 @@ skillbox update [name] # update skills
67
67
 
68
68
  | Command | Description |
69
69
  |---------|-------------|
70
- | `skillbox project add <path>` | Register a project |
70
+ | `skillbox project add <path>` | Register a project and auto-import skills from `skills/` and agent directories |
71
71
  | `skillbox project list` | List registered projects |
72
+ | `skillbox project inspect <path>` | Show project details and skills |
72
73
  | `skillbox project sync <path>` | Re-sync skills to a project |
73
74
 
74
75
  ## Supported Agents
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.3");
16
16
  registerAdd(program);
17
17
  registerAgent(program);
18
18
  registerConfig(program);
@@ -1,7 +1,9 @@
1
+ import path from "node:path";
1
2
  import { getErrorMessage } from "../lib/command.js";
2
3
  import { loadConfig } from "../lib/config.js";
3
4
  import { parseRepoRef } from "../lib/github.js";
4
5
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
6
+ import { recordInstallPaths } from "../lib/installs.js";
5
7
  import { printInfo, printJson } from "../lib/output.js";
6
8
  import { buildProjectAgentPaths } from "../lib/project-paths.js";
7
9
  import { fetchRepoFile, listRepoSkills, normalizeRepoRef, writeRepoSkillDirectory, } from "../lib/repo-skills.js";
@@ -31,21 +33,22 @@ async function installSkillTargets(skillName, options, installs) {
31
33
  const projectEntry = await ensureProjectRegistered(projectRoot, scope);
32
34
  const paths = buildProjectAgentPaths(projectRoot, projectEntry);
33
35
  const config = await loadConfig();
36
+ const recordedPaths = new Set();
34
37
  for (const agent of agentList) {
35
38
  const map = paths[agent];
36
39
  if (!map) {
37
40
  continue;
38
41
  }
39
- const targets = buildTargets(agent, map, scope).map((target) => target.path);
42
+ const targets = buildTargets(agent, map, scope).map((target) => path.join(target.path, skillName));
40
43
  const results = await installSkillToTargets(skillName, targets, config);
41
- const written = results
42
- .filter((result) => result.mode !== "skipped")
43
- .map((result) => result.path);
44
44
  const warnings = buildSymlinkWarning(agent, results);
45
45
  for (const warning of warnings) {
46
46
  printInfo(warning);
47
47
  }
48
- for (const target of written) {
48
+ // Record all targets, not just successfully written ones
49
+ // The warning tells users about symlink issues, but we still track the install intent
50
+ const deduped = recordInstallPaths(targets, recordedPaths);
51
+ for (const target of deduped) {
49
52
  installs.push({
50
53
  scope,
51
54
  agent,
@@ -1,8 +1,10 @@
1
+ import path from "node:path";
1
2
  import { handleCommandError } from "../lib/command.js";
2
3
  import { loadConfig } from "../lib/config.js";
3
4
  import { fetchText } from "../lib/fetcher.js";
4
5
  import { collect } from "../lib/fs-utils.js";
5
6
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
7
+ import { recordInstallPaths } from "../lib/installs.js";
6
8
  import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
7
9
  import { buildProjectAgentPaths } from "../lib/project-paths.js";
8
10
  import { ensureProjectRegistered, resolveRuntime } from "../lib/runtime.js";
@@ -64,23 +66,24 @@ export function registerAdd(program) {
64
66
  const config = await loadConfig();
65
67
  const installed = [];
66
68
  const installs = [];
69
+ const recordedPaths = new Set();
67
70
  for (const agent of agentList) {
68
71
  const map = paths[agent];
69
72
  if (!map) {
70
73
  continue;
71
74
  }
72
- const targets = buildTargets(agent, map, scope).map((target) => target.path);
75
+ const targets = buildTargets(agent, map, scope).map((target) => path.join(target.path, skillName));
73
76
  const results = await installSkillToTargets(skillName, targets, config);
74
- const written = results
75
- .filter((result) => result.mode !== "skipped")
76
- .map((result) => result.path);
77
77
  const warnings = buildSymlinkWarning(agent, results);
78
78
  for (const warning of warnings) {
79
79
  printInfo(warning);
80
80
  }
81
- if (written.length > 0) {
82
- installed.push({ agent, scope, targets: written });
83
- for (const target of written) {
81
+ // Record all targets, not just successfully written ones
82
+ // The warning tells users about symlink issues, but we still track the install intent
83
+ const deduped = recordInstallPaths(targets, 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,
@@ -1,4 +1,3 @@
1
- import fs from "node:fs/promises";
2
1
  import path from "node:path";
3
2
  import { getUserPathsForAgents } from "../lib/agents.js";
4
3
  import { handleCommandError } from "../lib/command.js";
@@ -6,8 +5,7 @@ import { discoverSkills } from "../lib/discovery.js";
6
5
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
7
6
  import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
8
7
  import { resolveRuntime } from "../lib/runtime.js";
9
- import { buildMetadata, parseSkillMarkdown } from "../lib/skill-parser.js";
10
- import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
8
+ import { importSkillFromDir } from "../lib/skill-store.js";
11
9
  async function importGlobalSkills(agents) {
12
10
  const projectRoot = process.cwd();
13
11
  const agentPaths = getUserPathsForAgents(projectRoot, agents);
@@ -32,24 +30,20 @@ async function importGlobalSkills(agents) {
32
30
  skipped.add(skill.name);
33
31
  continue;
34
32
  }
35
- const markdown = await fs.readFile(skill.skillFile, "utf8");
36
- const parsed = parseSkillMarkdown(markdown);
37
- if (!parsed.description) {
33
+ const data = await importSkillFromDir(skill.skillFile);
34
+ if (!data) {
38
35
  skipped.add(skill.name);
39
36
  continue;
40
37
  }
41
- const metadata = buildMetadata(parsed, { type: "local" });
42
- await ensureSkillsDir();
43
- await writeSkillFiles(metadata.name, markdown, metadata);
44
38
  const next = upsertSkill(index, {
45
- name: metadata.name,
39
+ name: data.name,
46
40
  source: { type: "local" },
47
- checksum: parsed.checksum,
48
- updatedAt: metadata.updatedAt,
41
+ checksum: data.checksum,
42
+ updatedAt: data.updatedAt,
49
43
  installs: [{ scope: "user", agent: skill.agent, path: skill.skillDir }],
50
44
  });
51
45
  index.skills = next.skills;
52
- imported.add(metadata.name);
46
+ imported.add(data.name);
53
47
  }
54
48
  await saveIndex(sortIndex(index));
55
49
  return {
@@ -81,27 +75,23 @@ export function registerImport(program) {
81
75
  }
82
76
  const resolved = path.resolve(inputPath);
83
77
  const skillPath = path.join(resolved, "SKILL.md");
84
- const markdown = await fs.readFile(skillPath, "utf8");
85
- const parsed = parseSkillMarkdown(markdown);
86
- if (!parsed.description) {
78
+ const data = await importSkillFromDir(skillPath);
79
+ if (!data) {
87
80
  throw new Error("Skill frontmatter missing description.");
88
81
  }
89
- const metadata = buildMetadata(parsed, { type: "local" });
90
- await ensureSkillsDir();
91
- await writeSkillFiles(metadata.name, markdown, metadata);
92
82
  const index = await loadIndex();
93
83
  const updated = upsertSkill(index, {
94
- name: metadata.name,
84
+ name: data.name,
95
85
  source: { type: "local" },
96
- checksum: parsed.checksum,
97
- updatedAt: metadata.updatedAt,
86
+ checksum: data.checksum,
87
+ updatedAt: data.updatedAt,
98
88
  });
99
89
  await saveIndex(sortIndex(updated));
100
90
  if (isJsonEnabled(options)) {
101
- printJson({ ok: true, command: "import", data: { name: metadata.name, path: resolved } });
91
+ printJson({ ok: true, command: "import", data: { name: data.name, path: resolved } });
102
92
  return;
103
93
  }
104
- printInfo(`Imported skill: ${metadata.name}`);
94
+ printInfo(`Imported skill: ${data.name}`);
105
95
  }
106
96
  catch (error) {
107
97
  handleCommandError(options, "import", error);
@@ -1,9 +1,34 @@
1
+ import chalk from "chalk";
1
2
  import fs from "node:fs/promises";
3
+ import terminalLink from "terminal-link";
2
4
  import { discoverGlobalSkills } from "../lib/global-skills.js";
3
5
  import { loadIndex } from "../lib/index.js";
4
6
  import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
5
7
  import { resolveRuntime } from "../lib/runtime.js";
6
8
  import { groupAndSort, sortByName } from "../lib/source-grouping.js";
9
+ function getSkillUrl(skill) {
10
+ if (skill.source.type === "url" && skill.source.url) {
11
+ return skill.source.url;
12
+ }
13
+ if (skill.source.type === "git" && skill.source.repo) {
14
+ const repo = skill.source.repo;
15
+ // If already a full URL, use it directly
16
+ if (repo.startsWith("http://") || repo.startsWith("https://")) {
17
+ return repo;
18
+ }
19
+ // Convert shorthand (user/repo) to full GitHub URL
20
+ return `https://github.com/${repo}`;
21
+ }
22
+ return undefined;
23
+ }
24
+ function linkSkillName(skill) {
25
+ const url = getSkillUrl(skill);
26
+ if (url && terminalLink.isSupported) {
27
+ const linkIcon = terminalLink("‹↗›", url);
28
+ return `${skill.name} ${chalk.dim(linkIcon)}`;
29
+ }
30
+ return skill.name;
31
+ }
7
32
  async function detectSubcommands(skillPath) {
8
33
  try {
9
34
  const entries = await fs.readdir(skillPath);
@@ -58,9 +83,28 @@ function groupByScope(skills) {
58
83
  });
59
84
  }
60
85
  if (projectSkills.length > 0) {
86
+ const projectRoots = new Map();
87
+ for (const skill of projectSkills) {
88
+ const roots = getProjectRoots(skill);
89
+ if (roots.length === 0) {
90
+ continue;
91
+ }
92
+ for (const root of roots) {
93
+ const existing = projectRoots.get(root) ?? [];
94
+ existing.push(skill);
95
+ projectRoots.set(root, existing);
96
+ }
97
+ }
98
+ const projectGroups = Array.from(projectRoots.entries())
99
+ .sort(([a], [b]) => a.localeCompare(b))
100
+ .map(([projectRoot, skillsForProject]) => ({
101
+ projectRoot,
102
+ sourceGroups: groupBySourceType(skillsForProject),
103
+ }));
61
104
  result.push({
62
105
  scope: "project",
63
106
  sourceGroups: groupBySourceType(projectSkills),
107
+ projectGroups,
64
108
  });
65
109
  }
66
110
  return result;
@@ -71,15 +115,38 @@ function groupBySourceType(skills) {
71
115
  const grouped = groupAndSort(skills, (skill) => skill.source.type, LIST_SOURCE_ORDER, sortByName);
72
116
  return grouped.map(({ key, items }) => ({ source: key, skills: items }));
73
117
  }
118
+ function getProjectRoots(skill) {
119
+ const roots = (skill.installs ?? [])
120
+ .filter((install) => install.scope === "project")
121
+ .map((install) => install.projectRoot)
122
+ .filter((root) => Boolean(root));
123
+ return Array.from(new Set(roots));
124
+ }
74
125
  function printScopeGroup(group) {
75
126
  const label = group.scope === "global" ? "Global Skills" : "Project Skills";
76
127
  const totalCount = group.sourceGroups.reduce((sum, g) => sum + g.skills.length, 0);
77
128
  printInfo(`${label} (${totalCount})`);
129
+ if (group.scope === "project" && group.projectGroups) {
130
+ for (const projectGroup of group.projectGroups) {
131
+ printInfo("");
132
+ printInfo(projectGroup.projectRoot);
133
+ for (const sourceGroup of projectGroup.sourceGroups) {
134
+ printInfo(` ${sourceGroup.source}`);
135
+ for (const skill of sourceGroup.skills) {
136
+ printInfo(` ${linkSkillName(skill)}`);
137
+ if (skill.subcommands.length > 0) {
138
+ printInfo(` → ${skill.subcommands.join(", ")}`);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return;
144
+ }
78
145
  for (const sourceGroup of group.sourceGroups) {
79
146
  printInfo("");
80
147
  printInfo(`${sourceGroup.source}`);
81
148
  for (const skill of sourceGroup.skills) {
82
- printInfo(` ${skill.name}`);
149
+ printInfo(` ${linkSkillName(skill)}`);
83
150
  if (skill.subcommands.length > 0) {
84
151
  printInfo(` → ${skill.subcommands.join(", ")}`);
85
152
  }
@@ -1,11 +1,53 @@
1
1
  import path from "node:path";
2
+ import { allAgents, getProjectPathsForAgents } from "../lib/agents.js";
2
3
  import { handleCommandError } from "../lib/command.js";
4
+ import { discoverSkills } from "../lib/discovery.js";
3
5
  import { collect } from "../lib/fs-utils.js";
4
- import { loadIndex } from "../lib/index.js";
6
+ import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
5
7
  import { collectProjectSkills, getProjectInstallPaths, getProjectSkills } from "../lib/installs.js";
6
8
  import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
7
9
  import { loadProjects, saveProjects, upsertProject } from "../lib/projects.js";
10
+ import { importSkillFromDir } from "../lib/skill-store.js";
8
11
  import { copySkillToInstallPaths } from "../lib/sync.js";
12
+ function getProjectSkillPaths(projectRoot) {
13
+ const agentPaths = getProjectPathsForAgents(projectRoot, allAgents);
14
+ const seen = new Set();
15
+ // Add generic skills/ directory
16
+ seen.add(path.join(projectRoot, "skills"));
17
+ // Add all agent-specific project paths
18
+ for (const { path: agentPath } of agentPaths) {
19
+ seen.add(agentPath);
20
+ }
21
+ return Array.from(seen);
22
+ }
23
+ async function discoverProjectSkills(projectRoot) {
24
+ const skillPaths = getProjectSkillPaths(projectRoot);
25
+ const discovered = await discoverSkills(skillPaths);
26
+ if (discovered.length === 0) {
27
+ return [];
28
+ }
29
+ const index = await loadIndex();
30
+ const imported = [];
31
+ for (const skill of discovered) {
32
+ const data = await importSkillFromDir(skill.skillFile);
33
+ if (!data) {
34
+ continue;
35
+ }
36
+ const updated = upsertSkill(index, {
37
+ name: data.name,
38
+ source: { type: "local" },
39
+ checksum: data.checksum,
40
+ updatedAt: data.updatedAt,
41
+ installs: [{ scope: "project", agent: "claude", path: skill.skillDir, projectRoot }],
42
+ });
43
+ index.skills = updated.skills;
44
+ imported.push(data.name);
45
+ }
46
+ if (imported.length > 0) {
47
+ await saveIndex(sortIndex(index));
48
+ }
49
+ return imported;
50
+ }
9
51
  function parseAgentPaths(entries) {
10
52
  const overrides = {};
11
53
  for (const entry of entries) {
@@ -22,6 +64,7 @@ export function registerProject(program) {
22
64
  const project = program.command("project").description("Manage projects");
23
65
  project
24
66
  .command("add")
67
+ .description("Register a project and import skills from its skills/ directory")
25
68
  .argument("<path>", "Project path")
26
69
  .option("--agent-path <agentPath>", "Agent path override (agent=path)", collect)
27
70
  .option("--json", "JSON output")
@@ -45,6 +88,7 @@ export function registerProject(program) {
45
88
  }),
46
89
  };
47
90
  await saveProjects(merged);
91
+ const importedSkills = await discoverProjectSkills(resolved);
48
92
  if (isJsonEnabled(options)) {
49
93
  printJson({
50
94
  ok: true,
@@ -52,11 +96,15 @@ export function registerProject(program) {
52
96
  data: {
53
97
  path: resolved,
54
98
  agentPaths: merged.projects.find((p) => p.root === resolved)?.agentPaths ?? {},
99
+ skills: importedSkills,
55
100
  },
56
101
  });
57
102
  return;
58
103
  }
59
104
  printInfo(`Project registered: ${resolved}`);
105
+ if (importedSkills.length > 0) {
106
+ printInfo(`Discovered ${importedSkills.length} skill(s): ${importedSkills.join(", ")}`);
107
+ }
60
108
  }
61
109
  catch (error) {
62
110
  handleCommandError(options, "project add", error);
@@ -48,6 +48,12 @@ export function getUserAgentPaths(projectRoot) {
48
48
  return Object.values(paths).flatMap((entry) => entry.user);
49
49
  }
50
50
  export function getUserPathsForAgents(projectRoot, agents) {
51
+ return getPathsForAgents(projectRoot, agents, "user");
52
+ }
53
+ export function getProjectPathsForAgents(projectRoot, agents) {
54
+ return getPathsForAgents(projectRoot, agents, "project");
55
+ }
56
+ function getPathsForAgents(projectRoot, agents, scope) {
51
57
  const paths = agentPaths(projectRoot);
52
58
  const seen = new Set();
53
59
  const results = [];
@@ -56,12 +62,12 @@ export function getUserPathsForAgents(projectRoot, agents) {
56
62
  if (!agentEntry) {
57
63
  continue;
58
64
  }
59
- for (const userPath of agentEntry.user) {
60
- if (seen.has(userPath)) {
65
+ for (const pathValue of agentEntry[scope]) {
66
+ if (seen.has(pathValue)) {
61
67
  continue;
62
68
  }
63
- seen.add(userPath);
64
- results.push({ agent, path: userPath });
69
+ seen.add(pathValue);
70
+ results.push({ agent, path: pathValue });
65
71
  }
66
72
  }
67
73
  return results;
@@ -1,5 +1,44 @@
1
+ import { execSync } from "node:child_process";
2
+ const GITHUB_HOSTS = ["api.github.com", "raw.githubusercontent.com"];
3
+ let cachedToken;
4
+ function getGitHubToken() {
5
+ if (cachedToken !== undefined)
6
+ return cachedToken;
7
+ // Check environment variables first, then fall back to gh CLI
8
+ const envToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
9
+ if (envToken) {
10
+ cachedToken = envToken;
11
+ }
12
+ else {
13
+ try {
14
+ cachedToken =
15
+ execSync("gh auth token", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim() ||
16
+ null;
17
+ }
18
+ catch {
19
+ cachedToken = null;
20
+ }
21
+ }
22
+ return cachedToken;
23
+ }
24
+ function isGitHubUrl(url) {
25
+ try {
26
+ const { host } = new URL(url);
27
+ return GITHUB_HOSTS.includes(host);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
1
33
  export async function fetchText(url) {
2
- const response = await fetch(url);
34
+ const headers = {};
35
+ if (isGitHubUrl(url)) {
36
+ const token = getGitHubToken();
37
+ if (token) {
38
+ headers.Authorization = `Bearer ${token}`;
39
+ }
40
+ }
41
+ const response = await fetch(url, { headers });
3
42
  if (!response.ok) {
4
43
  throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
5
44
  }
package/dist/lib/index.js CHANGED
@@ -29,9 +29,30 @@ export function upsertSkill(index, skill) {
29
29
  next.skills.push(skill);
30
30
  return next;
31
31
  }
32
- next.skills[existingIndex] = { ...next.skills[existingIndex], ...skill };
32
+ const existing = next.skills[existingIndex];
33
+ const mergedInstalls = mergeInstalls(existing.installs, skill.installs);
34
+ next.skills[existingIndex] = { ...existing, ...skill, installs: mergedInstalls };
33
35
  return next;
34
36
  }
37
+ function mergeInstalls(existing, incoming) {
38
+ if (!existing && !incoming)
39
+ return undefined;
40
+ if (!existing)
41
+ return incoming;
42
+ if (!incoming)
43
+ return existing;
44
+ // Dedupe by scope + agent + projectRoot combination
45
+ const seen = new Set();
46
+ const merged = [];
47
+ for (const install of [...existing, ...incoming]) {
48
+ const key = `${install.scope}:${install.agent}:${install.projectRoot ?? ""}`;
49
+ if (seen.has(key))
50
+ continue;
51
+ seen.add(key);
52
+ merged.push(install);
53
+ }
54
+ return merged;
55
+ }
35
56
  export function sortIndex(index) {
36
57
  const skills = [...index.skills].sort((a, b) => a.name.localeCompare(b.name));
37
58
  return { ...index, skills };
@@ -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) {
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { skillboxSkillsDir } from "./paths.js";
5
+ import { buildMetadata, parseSkillMarkdown } from "./skill-parser.js";
5
6
  export async function ensureSkillsDir() {
6
7
  await fs.mkdir(skillboxSkillsDir(), { recursive: true });
7
8
  }
@@ -26,3 +27,23 @@ export async function writeSkillMetadata(name, metadata) {
26
27
  export function hashContent(content) {
27
28
  return crypto.createHash("sha256").update(content).digest("hex");
28
29
  }
30
+ /**
31
+ * Import a skill from a local directory. Reads the markdown, parses it,
32
+ * and writes to the skill store. Returns the data needed for index updates,
33
+ * or null if the skill is invalid (missing description).
34
+ */
35
+ export async function importSkillFromDir(skillFile) {
36
+ const markdown = await fs.readFile(skillFile, "utf8");
37
+ const parsed = parseSkillMarkdown(markdown);
38
+ if (!parsed.description) {
39
+ return null;
40
+ }
41
+ const metadata = buildMetadata(parsed, { type: "local" });
42
+ await ensureSkillsDir();
43
+ await writeSkillFiles(metadata.name, markdown, metadata);
44
+ return {
45
+ name: metadata.name,
46
+ checksum: parsed.checksum,
47
+ updatedAt: metadata.updatedAt,
48
+ };
49
+ }
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.3",
4
4
  "description": "Local-first, agent-agnostic skills manager",
5
5
  "license": "MIT",
6
6
  "author": "Christian Anagnostou",
@@ -38,6 +38,7 @@
38
38
  "dependencies": {
39
39
  "chalk": "^5.3.0",
40
40
  "commander": "^12.0.0",
41
+ "terminal-link": "^3.0.0",
41
42
  "zod": "^3.23.8"
42
43
  },
43
44
  "devDependencies": {