skillbox 0.2.0 → 0.2.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/README.md CHANGED
@@ -13,68 +13,108 @@
13
13
  npm install -g skillbox
14
14
  ```
15
15
 
16
-
17
-
18
16
  ## Quick Start
19
17
 
20
- Skillbox will detect installed agents on your machine. If detection succeeds, `enter` accepts the detected list; if nothing is found, `enter` defaults to all agents or you can type a comma-separated list.
18
+ Skillbox will detect installed agents on your machine. Repo and URL installs track their origin so updates stay one command away.
21
19
 
22
- Tip: run `skillbox list` right after install to see existing skills.
20
+ > Tip: run `skillbox list` right after install to see existing skills.
21
+
22
+ Skillbox links agent folders to the canonical store using symlinks on macOS/Linux and file copies on Windows.
23
23
 
24
24
  ```bash
25
+ # install all repo skills
26
+ skillbox add owner/repo
27
+ # list skills in repo
28
+ skillbox add owner/repo --list
29
+ # install single repo skill
30
+ skillbox add owner/repo --skill linting
31
+ # add skill from URL
25
32
  skillbox add https://example.com/skills/linting/SKILL.md
33
+ # list installed skills
26
34
  skillbox list
35
+ # check for updates
27
36
  skillbox status
37
+ # update one skill
28
38
  skillbox update linting
29
39
  ```
30
40
 
41
+ Notes:
42
+ - GitHub unauthenticated API limit: 60 req/hr per IP
43
+ - use `skillbox convert` for non-skill URLs
44
+
31
45
  ## Commands
32
46
 
33
47
  ### Core Commands
34
48
 
35
49
  ```bash
50
+ # add skill from URL
36
51
  skillbox add <url> [--name <name>] [--global] [--agents ...]
52
+ # add skill(s) from repo
53
+ skillbox add <repo> [--list] [--skill <name>] [--global] [--agents ...]
54
+ # convert content to skill
37
55
  skillbox convert <url> [--name <name>] [--output <dir>] [--agent]
56
+ # list skills
38
57
  skillbox list [--group=category|namespace|source|project] [--json]
58
+ # check for updates
39
59
  skillbox status [--group=project|source] [--json]
40
- skillbox update [name] [--system] [--project <path>]
60
+ # update skills
61
+ skillbox update [name] [--project <path>]
62
+ # remove skills
63
+ skillbox remove <name> [--project <path>]
64
+ # import existing skills
41
65
  skillbox import <path>
66
+ # update metadata
42
67
  skillbox meta set <name> --category foo --tag bar --namespace baz
68
+ # open agent REPL
43
69
  skillbox agent
44
70
  ```
45
71
 
46
72
  ### Project Commands
47
73
 
48
74
  ```bash
75
+ # register project
49
76
  skillbox project add <path> [--agent-path agent=path]
77
+ # list projects
50
78
  skillbox project list
79
+ # show project details
51
80
  skillbox project inspect <path>
81
+ # resync project skills
52
82
  skillbox project sync <path>
53
83
  ```
54
84
 
55
85
  ### Config
56
86
 
57
87
  ```bash
88
+ # show config
58
89
  skillbox config get
90
+ # set default scope
59
91
  skillbox config set --default-scope user
92
+ # replace default agents
60
93
  skillbox config set --default-agent claude --default-agent cursor
94
+ # add default agent
61
95
  skillbox config set --add-agent codex
62
- skillbox config set --manage-system
96
+ # use symlink installs
97
+ skillbox config set --install-mode symlink
98
+ # use file copies
99
+ skillbox config set --install-mode copy
63
100
  ```
64
101
 
65
102
  Config defaults live in `~/.config/skillbox/config.json`:
66
103
 
67
- - `defaultScope`: `project` (default) or `user`
104
+ - `defaultScope`: `user` (default) or `project`
68
105
  - `defaultAgents`: empty array means all agents
69
- - `manageSystem`: `false` by default
106
+ - `installMode`: `symlink` (macOS/Linux) or `copy` (Windows)
70
107
 
71
108
  ## Agent Mode
72
109
 
73
110
  Use `--json` for machine-readable output.
74
111
 
75
112
  ```bash
113
+ # list skills (json)
76
114
  skillbox list --json
115
+ # status check (json)
77
116
  skillbox status --json
117
+ # update one skill (json)
78
118
  skillbox update linting --json
79
119
  ```
80
120
 
@@ -89,9 +129,21 @@ Common workflow:
89
129
  3) skillbox update <name> --json
90
130
 
91
131
  If you need to install a new skill from a URL, run:
132
+ # add skill from URL
92
133
  skillbox add <url> [--name <name>]
93
134
 
135
+ If you need a skill from a repo, run:
136
+ # list skills in repo
137
+ skillbox add owner/repo --list
138
+ # install single repo skill
139
+ skillbox add owner/repo --skill <name>
140
+ # install all repo skills
141
+ skillbox add owner/repo
142
+
143
+ Note: GitHub unauthenticated API limits are 60 requests per hour per IP, so heavy repo usage may hit rate limits.
144
+
94
145
  If a URL is not a valid skill, run:
146
+ # convert content to skill
95
147
  skillbox convert <url> --agent
96
148
  ```
97
149
 
@@ -114,12 +166,10 @@ Agent paths (default):
114
166
  - OpenCode: `.opencode/skills/`, `~/.config/opencode/skills/` (Claude-compatible `.claude/skills/` also supported)
115
167
  - Claude: `.claude/skills/`, `~/.claude/skills/`
116
168
  - Cursor: `.cursor/skills/`, `.claude/skills/`, `~/.cursor/skills/`, `~/.claude/skills/`
117
- - Codex: `$REPO_ROOT/.codex/skills/`, `~/.codex/skills/`, `/etc/codex/skills` (system)
169
+ - Codex: `$REPO_ROOT/.codex/skills/`, `~/.codex/skills/`
118
170
  - Amp: `.agents/skills/`, `~/.config/agents/skills/` (Claude-compatible `.claude/skills/` also supported)
119
171
  - Antigravity: `.agent/skills/`, `~/.gemini/antigravity/skills/`
120
172
 
121
- Note: only Codex defines a system scope path.
122
-
123
173
  ## Usage with AI Agents
124
174
 
125
175
  ### Just ask the agent
@@ -153,7 +203,7 @@ git clone https://github.com/christiananagnostou/skillbox
153
203
  cd skillbox
154
204
  npm install
155
205
  npm run build
156
- npm link --global
206
+ npm link
157
207
  ```
158
208
 
159
209
  ## License
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { registerMeta } from "./commands/meta.js";
10
10
  import { registerProject } from "./commands/project.js";
11
11
  import { registerAgent } from "./commands/agent.js";
12
12
  import { registerConfig } from "./commands/config.js";
13
+ import { registerRemove } from "./commands/remove.js";
13
14
  const program = new Command();
14
15
  program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.1.0");
15
16
  registerAdd(program);
@@ -22,6 +23,7 @@ registerMeta(program);
22
23
  registerProject(program);
23
24
  registerAgent(program);
24
25
  registerConfig(program);
26
+ registerRemove(program);
25
27
  const run = async () => {
26
28
  const { runOnboarding } = await import("./lib/onboarding.js");
27
29
  await runOnboarding();
@@ -0,0 +1,157 @@
1
+ import { listRepoSkills, normalizeRepoRef, fetchRepoFile, writeRepoSkillDirectory, } from "../lib/repo-skills.js";
2
+ import { parseSkillMarkdown, buildMetadata } from "../lib/skill-parser.js";
3
+ import { writeSkillMetadata } from "../lib/skill-store.js";
4
+ import { loadConfig } from "../lib/config.js";
5
+ import { buildProjectAgentPaths } from "../lib/project-paths.js";
6
+ import { resolveRuntime, ensureProjectRegistered } from "../lib/runtime.js";
7
+ import { buildSymlinkWarning, buildTargets, installSkillToTargets } from "../lib/sync.js";
8
+ import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
9
+ import { printInfo, printJson } from "../lib/output.js";
10
+ import { parseRepoRef } from "../lib/github.js";
11
+ import { getErrorMessage } from "../lib/command.js";
12
+ const normalizeSkillSelection = (skills, selections) => {
13
+ if (selections.length === 0) {
14
+ return skills;
15
+ }
16
+ const selectionSet = new Set(selections);
17
+ return skills.filter((name) => selectionSet.has(name));
18
+ };
19
+ const ensureRepoRef = async (input) => {
20
+ const ref = parseRepoRef(input);
21
+ if (!ref) {
22
+ throw new Error("Unsupported repo URL or shorthand.");
23
+ }
24
+ return await normalizeRepoRef(ref);
25
+ };
26
+ const installSkillTargets = async (skillName, options, installs) => {
27
+ const { projectRoot, scope, agentList } = await resolveRuntime({
28
+ global: options.global,
29
+ agents: options.agents,
30
+ });
31
+ const projectEntry = await ensureProjectRegistered(projectRoot, scope);
32
+ const paths = buildProjectAgentPaths(projectRoot, projectEntry);
33
+ const config = await loadConfig();
34
+ for (const agent of agentList) {
35
+ const map = paths[agent];
36
+ if (!map) {
37
+ continue;
38
+ }
39
+ const targets = buildTargets(agent, map, scope).map((target) => target.path);
40
+ const results = await installSkillToTargets(skillName, targets, config);
41
+ const written = results
42
+ .filter((result) => result.mode !== "skipped")
43
+ .map((result) => result.path);
44
+ const warning = buildSymlinkWarning(agent, results);
45
+ if (warning) {
46
+ printInfo(warning);
47
+ }
48
+ if (written.length > 0) {
49
+ for (const target of written) {
50
+ installs.push({
51
+ scope,
52
+ agent,
53
+ path: target,
54
+ projectRoot: scope === "project" ? projectRoot : undefined,
55
+ });
56
+ }
57
+ }
58
+ }
59
+ };
60
+ export const isRepoUrl = (input) => {
61
+ return Boolean(parseRepoRef(input));
62
+ };
63
+ export const handleRepoInstall = async (input, options) => {
64
+ const ref = await ensureRepoRef(input);
65
+ const { skills } = await listRepoSkills(ref);
66
+ const skillNames = skills.map((skill) => skill.name).sort();
67
+ if (options.list) {
68
+ if (options.json) {
69
+ printJson({ ok: true, command: "add", data: { repo: input, skills: skillNames } });
70
+ return;
71
+ }
72
+ printInfo(`Skills found: ${skillNames.length}`);
73
+ for (const name of skillNames) {
74
+ printInfo(`- ${name}`);
75
+ }
76
+ return;
77
+ }
78
+ const selected = normalizeSkillSelection(skillNames, options.skill ?? []);
79
+ if (selected.length === 0) {
80
+ throw new Error("No matching skills found. Use --list to see available skills.");
81
+ }
82
+ const summary = { installed: [], updated: [], skipped: [], failed: [] };
83
+ const index = await loadIndex();
84
+ for (const skill of skills) {
85
+ if (!selected.includes(skill.name)) {
86
+ continue;
87
+ }
88
+ const alreadyInstalled = index.skills.some((entry) => entry.name === skill.name);
89
+ try {
90
+ const skillMarkdown = await fetchRepoFile(ref, ref.path ? `${ref.path}/${skill.skillFile}` : skill.skillFile);
91
+ const parsed = parseSkillMarkdown(skillMarkdown);
92
+ if (!parsed.description) {
93
+ summary.skipped.push(skill.name);
94
+ continue;
95
+ }
96
+ await writeRepoSkillDirectory(ref, skill.path, skill.name);
97
+ const sourcePath = [ref.path, skill.path].filter(Boolean).join("/");
98
+ const source = {
99
+ type: "git",
100
+ repo: `${ref.owner}/${ref.repo}`,
101
+ path: sourcePath || undefined,
102
+ ref: ref.ref,
103
+ };
104
+ const metadata = buildMetadata(parsed, source, skill.name);
105
+ await writeSkillMetadata(skill.name, metadata);
106
+ const updated = upsertSkill(index, {
107
+ name: skill.name,
108
+ source,
109
+ checksum: parsed.checksum,
110
+ updatedAt: metadata.updatedAt,
111
+ });
112
+ const installs = [];
113
+ await installSkillTargets(skill.name, options, installs);
114
+ const nextIndex = upsertSkill(updated, {
115
+ name: skill.name,
116
+ source,
117
+ checksum: parsed.checksum,
118
+ updatedAt: metadata.updatedAt,
119
+ installs,
120
+ });
121
+ index.skills = nextIndex.skills;
122
+ if (alreadyInstalled) {
123
+ summary.updated.push(skill.name);
124
+ }
125
+ else {
126
+ summary.installed.push(skill.name);
127
+ }
128
+ }
129
+ catch (error) {
130
+ const message = getErrorMessage(error, "unknown");
131
+ summary.failed.push({ name: skill.name, reason: message });
132
+ }
133
+ }
134
+ await saveIndex(sortIndex(index));
135
+ if (options.json) {
136
+ printJson({ ok: true, command: "add", data: summary });
137
+ return;
138
+ }
139
+ if (summary.failed.length > 0) {
140
+ printInfo("Some skills failed to install:");
141
+ for (const failure of summary.failed) {
142
+ printInfo(`- ${failure.name}: ${failure.reason}`);
143
+ }
144
+ }
145
+ if (summary.installed.length > 0) {
146
+ printInfo(`Installed ${summary.installed.length} skill(s): ${summary.installed.join(", ")}`);
147
+ }
148
+ if (summary.updated.length > 0) {
149
+ printInfo(`Updated ${summary.updated.length} skill(s): ${summary.updated.join(", ")}`);
150
+ }
151
+ if (summary.skipped.length > 0) {
152
+ printInfo(`Skipped ${summary.skipped.length} skill(s): ${summary.skipped.join(", ")}`);
153
+ }
154
+ if (summary.installed.length === 0 && summary.updated.length === 0) {
155
+ printInfo("No agent targets were updated (canonical store only).");
156
+ }
157
+ };
@@ -4,19 +4,33 @@ import { parseSkillMarkdown, inferNameFromUrl, buildMetadata } from "../lib/skil
4
4
  import { handleCommandError } from "../lib/command.js";
5
5
  import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
6
6
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
7
- import { buildTargets, copySkillToTargets } from "../lib/sync.js";
7
+ import { buildSymlinkWarning, buildTargets, installSkillToTargets } from "../lib/sync.js";
8
8
  import { buildProjectAgentPaths } from "../lib/project-paths.js";
9
9
  import { resolveRuntime, ensureProjectRegistered } from "../lib/runtime.js";
10
+ import { loadConfig } from "../lib/config.js";
11
+ import { handleRepoInstall, isRepoUrl } from "./add-repo.js";
10
12
  export const registerAdd = (program) => {
11
13
  program
12
14
  .command("add")
13
- .argument("<url>", "Skill URL")
15
+ .argument("<url>", "Skill URL or repo")
14
16
  .option("--name <name>", "Override skill name")
15
17
  .option("--global", "Install to user scope")
16
18
  .option("--agents <list>", "Comma-separated agent list")
19
+ .option("--skill <skill>", "Skill name to install", collect)
20
+ .option("--list", "List skills in repo without installing")
17
21
  .option("--json", "JSON output")
18
22
  .action(async (url, options) => {
19
23
  try {
24
+ if (options.list || options.skill || isRepoUrl(url)) {
25
+ await handleRepoInstall(url, {
26
+ global: options.global,
27
+ agents: options.agents,
28
+ json: options.json,
29
+ list: options.list,
30
+ skill: options.skill,
31
+ });
32
+ return;
33
+ }
20
34
  const skillMarkdown = await fetchText(url);
21
35
  const parsed = parseSkillMarkdown(skillMarkdown);
22
36
  const inferred = inferNameFromUrl(url);
@@ -46,6 +60,7 @@ export const registerAdd = (program) => {
46
60
  });
47
61
  const projectEntry = await ensureProjectRegistered(projectRoot, scope);
48
62
  const paths = buildProjectAgentPaths(projectRoot, projectEntry);
63
+ const config = await loadConfig();
49
64
  const installed = [];
50
65
  const installs = [];
51
66
  for (const agent of agentList) {
@@ -54,15 +69,24 @@ export const registerAdd = (program) => {
54
69
  continue;
55
70
  }
56
71
  const targets = buildTargets(agent, map, scope).map((target) => target.path);
57
- const written = await copySkillToTargets(skillName, targets);
58
- installed.push({ agent, scope, targets: written });
59
- for (const target of written) {
60
- installs.push({
61
- scope,
62
- agent,
63
- path: target,
64
- projectRoot: scope === "project" ? projectRoot : undefined,
65
- });
72
+ const results = await installSkillToTargets(skillName, targets, config);
73
+ const written = results
74
+ .filter((result) => result.mode !== "skipped")
75
+ .map((result) => result.path);
76
+ const warning = buildSymlinkWarning(agent, results);
77
+ if (warning) {
78
+ printInfo(warning);
79
+ }
80
+ if (written.length > 0) {
81
+ installed.push({ agent, scope, targets: written });
82
+ for (const target of written) {
83
+ installs.push({
84
+ scope,
85
+ agent,
86
+ path: target,
87
+ projectRoot: scope === "project" ? projectRoot : undefined,
88
+ });
89
+ }
66
90
  }
67
91
  }
68
92
  const nextIndex = upsertSkill(updated, {
@@ -90,7 +114,7 @@ export const registerAdd = (program) => {
90
114
  printInfo(`Source: ${url}`);
91
115
  printInfo(`Scope: ${scope}`);
92
116
  if (installed.length === 0) {
93
- printInfo("No agent targets were updated.");
117
+ printInfo("No agent targets were updated (canonical store only).");
94
118
  }
95
119
  else {
96
120
  for (const entry of installed) {
@@ -103,3 +127,6 @@ export const registerAdd = (program) => {
103
127
  }
104
128
  });
105
129
  };
130
+ const collect = (value, previous = []) => {
131
+ return [...previous, value];
132
+ };
@@ -27,7 +27,7 @@ export const registerConfig = (program) => {
27
27
  .option("--default-agent <agent>", "Default agent", collect)
28
28
  .option("--add-agent <agent>", "Add to default agents", collect)
29
29
  .option("--default-scope <scope>", "Default scope: project or user")
30
- .option("--manage-system", "Enable system scope operations")
30
+ .option("--install-mode <mode>", "Install mode: symlink or copy")
31
31
  .option("--json", "JSON output")
32
32
  .action(async (options) => {
33
33
  try {
@@ -39,11 +39,15 @@ export const registerConfig = (program) => {
39
39
  const addedAgents = options.addAgent ?? [];
40
40
  const nextAgents = options.defaultAgent ?? current.defaultAgents;
41
41
  const mergedAgents = Array.from(new Set([...(nextAgents ?? []), ...addedAgents].filter((agent) => agent.length > 0)));
42
+ const nextMode = options.installMode ?? current.installMode;
43
+ if (nextMode !== "symlink" && nextMode !== "copy") {
44
+ throw new Error("installMode must be 'symlink' or 'copy'.");
45
+ }
42
46
  const next = {
43
47
  ...current,
44
48
  defaultAgents: mergedAgents,
45
49
  defaultScope: nextScope,
46
- manageSystem: options.manageSystem ?? current.manageSystem,
50
+ installMode: nextMode,
47
51
  };
48
52
  await saveConfig(next);
49
53
  if (isJsonEnabled(options)) {
@@ -6,24 +6,20 @@ import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
6
6
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
7
7
  import { handleCommandError } from "../lib/command.js";
8
8
  import { discoverSkills } from "../lib/discovery.js";
9
- import { getSystemAgentPaths, getUserAgentPaths } from "../lib/agents.js";
9
+ import { getUserAgentPaths } from "../lib/agents.js";
10
10
  export const registerImport = (program) => {
11
11
  program
12
12
  .command("import")
13
13
  .argument("[path]", "Path to skill directory")
14
14
  .option("--global", "Import skills from user agent folders")
15
- .option("--system", "Import skills from system agent folders")
16
15
  .option("--json", "JSON output")
17
16
  .action(async (inputPath, options) => {
18
17
  try {
19
- if (!inputPath && !options.global && !options.system) {
20
- throw new Error("Provide a path or use --global/--system.");
18
+ if (!inputPath && !options.global) {
19
+ throw new Error("Provide a path or use --global.");
21
20
  }
22
- if (options.global || options.system) {
23
- const summary = await importGlobalSkills({
24
- includeUser: Boolean(options.global),
25
- includeSystem: Boolean(options.system),
26
- });
21
+ if (options.global) {
22
+ const summary = await importGlobalSkills();
27
23
  if (isJsonEnabled(options)) {
28
24
  printJson({
29
25
  ok: true,
@@ -71,27 +67,24 @@ export const registerImport = (program) => {
71
67
  }
72
68
  });
73
69
  };
74
- const importGlobalSkills = async (options) => {
70
+ const importGlobalSkills = async () => {
75
71
  const projectRoot = process.cwd();
76
- const paths = [
77
- ...(options.includeUser ? getUserAgentPaths(projectRoot) : []),
78
- ...(options.includeSystem ? getSystemAgentPaths(projectRoot) : []),
79
- ];
72
+ const paths = getUserAgentPaths(projectRoot);
80
73
  const discovered = await discoverSkills(paths);
81
74
  const index = await loadIndex();
82
75
  const seen = new Set(index.skills.map((skill) => skill.name));
83
- const imported = [];
84
- const skipped = [];
76
+ const imported = new Set();
77
+ const skipped = new Set();
85
78
  for (const skill of discovered) {
86
79
  if (seen.has(skill.name)) {
87
- skipped.push(skill.name);
80
+ skipped.add(skill.name);
88
81
  continue;
89
82
  }
90
83
  const markdown = await fs.readFile(skill.skillFile, "utf8");
91
84
  const parsed = parseSkillMarkdown(markdown);
92
85
  const metadata = buildMetadata(parsed, { type: "local" });
93
86
  if (!parsed.description) {
94
- skipped.push(skill.name);
87
+ skipped.add(skill.name);
95
88
  continue;
96
89
  }
97
90
  await ensureSkillsDir();
@@ -103,18 +96,18 @@ const importGlobalSkills = async (options) => {
103
96
  updatedAt: metadata.updatedAt,
104
97
  installs: [
105
98
  {
106
- scope: options.includeSystem ? "system" : "user",
99
+ scope: "user",
107
100
  agent: "unknown",
108
101
  path: skill.skillDir,
109
102
  },
110
103
  ],
111
104
  });
112
105
  index.skills = next.skills;
113
- imported.push(metadata.name);
106
+ imported.add(metadata.name);
114
107
  }
115
108
  await saveIndex(sortIndex(index));
116
109
  return {
117
- imported: imported.sort(),
118
- skipped: skipped.sort(),
110
+ imported: Array.from(imported).sort(),
111
+ skipped: Array.from(skipped).sort(),
119
112
  };
120
113
  };
@@ -6,11 +6,10 @@ export const registerList = (program) => {
6
6
  program
7
7
  .command("list")
8
8
  .option("--group <group>", "Group by category, namespace, source, project")
9
- .option("--project-only", "Only show project-indexed skills")
10
9
  .option("--json", "JSON output")
11
10
  .action(async (options) => {
12
11
  const index = await loadIndex();
13
- const globalSkills = options.projectOnly ? [] : await listGlobalSkills(index.skills);
12
+ const globalSkills = await listGlobalSkills(index.skills);
14
13
  const skills = [...index.skills, ...globalSkills];
15
14
  const groupedProjects = groupByProject(skills);
16
15
  const groupedSources = groupBySource(skills);
@@ -0,0 +1,76 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
4
+ import { loadIndex, saveIndex, sortIndex } from "../lib/index.js";
5
+ import { skillDir } from "../lib/skill-store.js";
6
+ import { handleCommandError } from "../lib/command.js";
7
+ const removePaths = async (paths) => {
8
+ for (const target of paths) {
9
+ await fs.rm(target, { recursive: true, force: true });
10
+ }
11
+ };
12
+ export const registerRemove = (program) => {
13
+ program
14
+ .command("remove")
15
+ .argument("<name>", "Skill name")
16
+ .option("--project <path>", "Only remove installs for a project")
17
+ .option("--json", "JSON output")
18
+ .action(async (name, options) => {
19
+ try {
20
+ const index = await loadIndex();
21
+ const skill = index.skills.find((entry) => entry.name === name);
22
+ if (!skill) {
23
+ throw new Error(`Skill not found: ${name}`);
24
+ }
25
+ const projectRoot = options.project ? path.resolve(options.project) : null;
26
+ const installs = skill.installs ?? [];
27
+ const toRemove = projectRoot
28
+ ? installs.filter((install) => install.scope === "project" &&
29
+ install.projectRoot &&
30
+ install.projectRoot === projectRoot)
31
+ : installs;
32
+ if (projectRoot && toRemove.length === 0) {
33
+ throw new Error(`No installs found for ${name} in ${projectRoot}.`);
34
+ }
35
+ const removedPaths = toRemove.map((install) => install.path);
36
+ await removePaths(removedPaths);
37
+ let removedCanonical = false;
38
+ if (projectRoot) {
39
+ const remaining = installs.filter((install) => !(install.scope === "project" &&
40
+ install.projectRoot &&
41
+ install.projectRoot === projectRoot));
42
+ index.skills = index.skills.map((entry) => entry.name === name
43
+ ? { ...entry, installs: remaining.length > 0 ? remaining : undefined }
44
+ : entry);
45
+ }
46
+ else {
47
+ index.skills = index.skills.filter((entry) => entry.name !== name);
48
+ await fs.rm(skillDir(name), { recursive: true, force: true });
49
+ removedCanonical = true;
50
+ }
51
+ await saveIndex(sortIndex(index));
52
+ if (isJsonEnabled(options)) {
53
+ printJson({
54
+ ok: true,
55
+ command: "remove",
56
+ data: {
57
+ name,
58
+ project: projectRoot,
59
+ removed: removedPaths,
60
+ removedCanonical,
61
+ },
62
+ });
63
+ return;
64
+ }
65
+ if (projectRoot) {
66
+ printInfo(`Removed ${removedPaths.length} install(s) for ${name} in ${projectRoot}.`);
67
+ }
68
+ else {
69
+ printInfo(`Removed ${name} and ${removedPaths.length} install(s).`);
70
+ }
71
+ }
72
+ catch (error) {
73
+ handleCommandError(options, "remove", error);
74
+ }
75
+ });
76
+ };
@@ -3,7 +3,6 @@ import { loadIndex, saveIndex } from "../lib/index.js";
3
3
  import { fetchText } from "../lib/fetcher.js";
4
4
  import { hashContent } from "../lib/skill-store.js";
5
5
  import { groupStatusByKey } from "../lib/grouping.js";
6
- import { loadConfig } from "../lib/config.js";
7
6
  import { handleCommandError } from "../lib/command.js";
8
7
  export const registerStatus = (program) => {
9
8
  program
@@ -13,25 +12,11 @@ export const registerStatus = (program) => {
13
12
  .action(async (options) => {
14
13
  try {
15
14
  const index = await loadIndex();
16
- const config = await loadConfig();
17
15
  const results = [];
18
16
  for (const skill of index.skills) {
19
17
  const projects = Array.from(new Set((skill.installs ?? [])
20
18
  .filter((install) => install.scope === "project" && install.projectRoot)
21
19
  .map((install) => install.projectRoot)));
22
- const allowSystem = config.manageSystem;
23
- const isSystem = (skill.installs ?? []).some((install) => install.scope === "system");
24
- if (isSystem && !allowSystem) {
25
- results.push({
26
- name: skill.name,
27
- source: skill.source.type,
28
- outdated: false,
29
- localChecksum: skill.checksum,
30
- projects,
31
- system: true,
32
- });
33
- continue;
34
- }
35
20
  if (skill.source.type !== "url" || !skill.source.url) {
36
21
  results.push({
37
22
  name: skill.name,
@@ -39,7 +24,6 @@ export const registerStatus = (program) => {
39
24
  outdated: false,
40
25
  localChecksum: skill.checksum,
41
26
  projects,
42
- system: isSystem,
43
27
  });
44
28
  continue;
45
29
  }
@@ -54,7 +38,6 @@ export const registerStatus = (program) => {
54
38
  localChecksum: skill.checksum,
55
39
  remoteChecksum,
56
40
  projects,
57
- system: false,
58
41
  });
59
42
  }
60
43
  await saveIndex(index);
@@ -2,51 +2,89 @@ import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
2
2
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
3
3
  import { fetchText } from "../lib/fetcher.js";
4
4
  import { parseSkillMarkdown, buildMetadata } from "../lib/skill-parser.js";
5
- import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
6
- import { copySkillToInstallPaths } from "../lib/sync.js";
5
+ import { ensureSkillsDir, writeSkillFiles, writeSkillMetadata } from "../lib/skill-store.js";
7
6
  import path from "node:path";
8
- import { loadConfig } from "../lib/config.js";
7
+ import { installSkillToTargets } from "../lib/sync.js";
9
8
  import { handleCommandError } from "../lib/command.js";
9
+ import { loadConfig } from "../lib/config.js";
10
+ import { fetchRepoFile, normalizeRepoRef, writeRepoSkillDirectory } from "../lib/repo-skills.js";
11
+ import { getInstallPaths } from "../lib/installs.js";
10
12
  export const registerUpdate = (program) => {
11
13
  program
12
14
  .command("update")
13
15
  .argument("[name]", "Skill name")
14
- .option("--system", "Allow system-scope updates")
15
16
  .option("--project <path>", "Only update installs for a project")
16
17
  .option("--json", "JSON output")
17
18
  .action(async (name, options) => {
18
19
  try {
19
20
  const index = await loadIndex();
20
- const config = await loadConfig();
21
21
  const targets = name ? index.skills.filter((skill) => skill.name === name) : index.skills;
22
22
  if (name && targets.length === 0) {
23
23
  throw new Error(`Skill not found: ${name}`);
24
24
  }
25
25
  const updated = [];
26
26
  await ensureSkillsDir();
27
+ const config = await loadConfig();
27
28
  const projectRoot = options.project ? path.resolve(options.project) : null;
28
29
  for (const skill of targets) {
29
- if (skill.source.type !== "url" || !skill.source.url) {
30
+ if (skill.source.type === "url" && skill.source.url) {
31
+ const markdown = await fetchText(skill.source.url);
32
+ const parsed = parseSkillMarkdown(markdown);
33
+ if (!parsed.description) {
34
+ throw new Error(`Skill ${skill.name} is missing a description after update.`);
35
+ }
36
+ const metadata = buildMetadata(parsed, { type: "url", url: skill.source.url }, skill.name);
37
+ await writeSkillFiles(skill.name, markdown, metadata);
38
+ const installPaths = getInstallPaths(skill, projectRoot);
39
+ if (installPaths.length > 0) {
40
+ await installSkillToTargets(skill.name, installPaths, config);
41
+ }
42
+ const nextIndex = upsertSkill(index, {
43
+ name: skill.name,
44
+ source: { type: "url", url: skill.source.url },
45
+ checksum: parsed.checksum,
46
+ updatedAt: metadata.updatedAt,
47
+ lastSync: new Date().toISOString(),
48
+ });
49
+ index.skills = nextIndex.skills;
50
+ updated.push(skill.name);
30
51
  continue;
31
52
  }
32
- const markdown = await fetchText(skill.source.url);
53
+ if (skill.source.type !== "git" || !skill.source.repo) {
54
+ continue;
55
+ }
56
+ const [owner, repo] = skill.source.repo.split("/");
57
+ if (!owner || !repo) {
58
+ continue;
59
+ }
60
+ const skillPath = skill.source.path?.replace(/\/$/, "") ?? "";
61
+ const ref = await normalizeRepoRef({
62
+ owner,
63
+ repo,
64
+ ref: skill.source.ref ?? "main",
65
+ });
66
+ const skillFilePath = skillPath ? `${skillPath}/SKILL.md` : "SKILL.md";
67
+ const markdown = await fetchRepoFile(ref, skillFilePath);
33
68
  const parsed = parseSkillMarkdown(markdown);
34
69
  if (!parsed.description) {
35
70
  throw new Error(`Skill ${skill.name} is missing a description after update.`);
36
71
  }
37
- const metadata = buildMetadata(parsed, { type: "url", url: skill.source.url }, skill.name);
38
- await writeSkillFiles(skill.name, markdown, metadata);
39
- const allowSystem = options.system || config.manageSystem;
40
- const installPaths = (skill.installs ?? [])
41
- .filter((install) => allowSystem || install.scope !== "system")
42
- .filter((install) => !projectRoot || install.projectRoot === projectRoot)
43
- .map((install) => install.path);
72
+ await writeRepoSkillDirectory(ref, skillPath, skill.name);
73
+ const source = {
74
+ type: "git",
75
+ repo: skill.source.repo,
76
+ path: skillPath || undefined,
77
+ ref: ref.ref,
78
+ };
79
+ const metadata = buildMetadata(parsed, source, skill.name);
80
+ await writeSkillMetadata(skill.name, metadata);
81
+ const installPaths = getInstallPaths(skill, projectRoot);
44
82
  if (installPaths.length > 0) {
45
- await copySkillToInstallPaths(skill.name, installPaths);
83
+ await installSkillToTargets(skill.name, installPaths, config);
46
84
  }
47
85
  const nextIndex = upsertSkill(index, {
48
86
  name: skill.name,
49
- source: { type: "url", url: skill.source.url },
87
+ source,
50
88
  checksum: parsed.checksum,
51
89
  updatedAt: metadata.updatedAt,
52
90
  lastSync: new Date().toISOString(),
@@ -61,7 +99,6 @@ export const registerUpdate = (program) => {
61
99
  command: "update",
62
100
  data: {
63
101
  name: name ?? null,
64
- system: Boolean(options.system),
65
102
  project: projectRoot,
66
103
  updated,
67
104
  },
@@ -23,7 +23,6 @@ export const agentPaths = (projectRoot) => ({
23
23
  codex: {
24
24
  project: [path.join(projectRoot, ".codex", "skills")],
25
25
  user: [path.join(home, ".codex", "skills")],
26
- system: [path.join(path.sep, "etc", "codex", "skills")],
27
26
  },
28
27
  amp: {
29
28
  project: [
@@ -42,10 +41,6 @@ export const getUserAgentPaths = (projectRoot) => {
42
41
  const paths = agentPaths(projectRoot);
43
42
  return Object.values(paths).flatMap((entry) => entry.user);
44
43
  };
45
- export const getSystemAgentPaths = (projectRoot) => {
46
- const paths = agentPaths(projectRoot);
47
- return Object.values(paths).flatMap((entry) => entry.system ?? []);
48
- };
49
44
  const agentSet = new Set(allAgents);
50
45
  export const isAgentId = (value) => {
51
46
  return agentSet.has(value);
@@ -1,6 +1,9 @@
1
1
  import { isJsonEnabled, printError, printJson } from "./output.js";
2
+ export const getErrorMessage = (error, fallback = "Unexpected error") => {
3
+ return error instanceof Error ? error.message : fallback;
4
+ };
2
5
  export const handleCommandError = (options, command, error) => {
3
- const message = error instanceof Error ? error.message : "Unexpected error";
6
+ const message = getErrorMessage(error);
4
7
  if (isJsonEnabled(options)) {
5
8
  printJson({ ok: false, command, error: { message } });
6
9
  return;
@@ -1,11 +1,14 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { skillboxRoot } from "./paths.js";
3
3
  import path from "node:path";
4
+ const defaultInstallMode = () => {
5
+ return process.platform === "win32" ? "copy" : "symlink";
6
+ };
4
7
  const defaultConfig = () => ({
5
8
  version: 1,
6
9
  defaultAgents: [],
7
- defaultScope: "project",
8
- manageSystem: false,
10
+ defaultScope: "user",
11
+ installMode: defaultInstallMode(),
9
12
  });
10
13
  export const configPath = () => path.join(skillboxRoot(), "config.json");
11
14
  export const loadConfig = async () => {
@@ -0,0 +1,43 @@
1
+ import { fetchText } from "./fetcher.js";
2
+ const repoUrlRegex = /^https?:\/\/github\.com\/(.+?)\/(.+?)(?:\.git)?(?:\/)?$/i;
3
+ const treeUrlRegex = /^https?:\/\/github\.com\/(.+?)\/(.+?)\/tree\/([^/]+)\/(.+)$/i;
4
+ export const parseRepoRef = (input) => {
5
+ if (input.includes("github.com") && input.includes("/tree/")) {
6
+ const match = input.match(treeUrlRegex);
7
+ if (!match) {
8
+ return null;
9
+ }
10
+ return {
11
+ owner: match[1],
12
+ repo: match[2],
13
+ ref: match[3],
14
+ path: match[4],
15
+ };
16
+ }
17
+ if (!input.includes("github.com") && input.includes("/")) {
18
+ const [owner, repo] = input.split("/");
19
+ if (!owner || !repo) {
20
+ return null;
21
+ }
22
+ return { owner, repo, ref: "main" };
23
+ }
24
+ if (input.includes("github.com")) {
25
+ const match = input.match(repoUrlRegex);
26
+ if (!match) {
27
+ return null;
28
+ }
29
+ return {
30
+ owner: match[1],
31
+ repo: match[2].replace(/\.git$/, ""),
32
+ ref: "main",
33
+ };
34
+ }
35
+ return null;
36
+ };
37
+ export const buildRawUrl = (ref, filePath) => {
38
+ return `https://raw.githubusercontent.com/${ref.owner}/${ref.repo}/${ref.ref}/${filePath}`;
39
+ };
40
+ export const fetchJson = async (url) => {
41
+ const response = await fetchText(url);
42
+ return JSON.parse(response);
43
+ };
@@ -1,36 +1,16 @@
1
- import { getUserAgentPaths, getSystemAgentPaths } from "./agents.js";
1
+ import { getUserAgentPaths } from "./agents.js";
2
2
  import { discoverSkills } from "./discovery.js";
3
3
  export const discoverGlobalSkills = async (projectRoot) => {
4
4
  const userPaths = getUserAgentPaths(projectRoot);
5
- const systemPaths = getSystemAgentPaths(projectRoot);
6
- const [userSkills, systemSkills] = await Promise.all([
7
- discoverSkills(userPaths),
8
- discoverSkills(systemPaths),
9
- ]);
10
- const entries = [];
11
- for (const skill of userSkills) {
12
- entries.push({
13
- name: skill.name,
14
- installs: [
15
- {
16
- scope: "user",
17
- agent: "unknown",
18
- path: skill.skillDir,
19
- },
20
- ],
21
- });
22
- }
23
- for (const skill of systemSkills) {
24
- entries.push({
25
- name: skill.name,
26
- installs: [
27
- {
28
- scope: "system",
29
- agent: "unknown",
30
- path: skill.skillDir,
31
- },
32
- ],
33
- });
34
- }
35
- return entries;
5
+ const userSkills = await discoverSkills(userPaths);
6
+ return userSkills.map((skill) => ({
7
+ name: skill.name,
8
+ installs: [
9
+ {
10
+ scope: "user",
11
+ agent: "unknown",
12
+ path: skill.skillDir,
13
+ },
14
+ ],
15
+ }));
36
16
  };
@@ -21,13 +21,19 @@ export const getProjectSkills = (skills, projectRoot) => {
21
21
  const map = collectProjectSkills(skills);
22
22
  return (map.get(projectRoot) ?? []).sort();
23
23
  };
24
+ export const getInstallPaths = (skill, projectRoot) => {
25
+ if (!projectRoot) {
26
+ return (skill.installs ?? []).map((install) => install.path);
27
+ }
28
+ return (skill.installs ?? [])
29
+ .filter(projectInstalls)
30
+ .filter((install) => install.projectRoot === projectRoot)
31
+ .map((install) => install.path);
32
+ };
24
33
  export const getProjectInstallPaths = (skills, projectRoot) => {
25
34
  const map = new Map();
26
35
  for (const skill of skills) {
27
- const paths = (skill.installs ?? [])
28
- .filter(projectInstalls)
29
- .filter((install) => install.projectRoot === projectRoot)
30
- .map((install) => install.path);
36
+ const paths = getInstallPaths(skill, projectRoot);
31
37
  if (paths.length > 0) {
32
38
  map.set(skill.name, paths);
33
39
  }
@@ -0,0 +1,136 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fetchJson, buildRawUrl, parseRepoRef } from "./github.js";
4
+ import { fetchText } from "./fetcher.js";
5
+ import { ensureSkillsDir, skillDir } from "./skill-store.js";
6
+ const skillRoots = [
7
+ "skills",
8
+ "skill",
9
+ ".skills",
10
+ ".skill",
11
+ "agents/skills",
12
+ ".claude/skills",
13
+ ".codex/skills",
14
+ ".cursor/skills",
15
+ ".opencode/skills",
16
+ ];
17
+ const buildTreeUrl = (ref) => {
18
+ return `https://api.github.com/repos/${ref.owner}/${ref.repo}/git/trees/${ref.ref}?recursive=1`;
19
+ };
20
+ const normalizeSkillPath = (filePath, basePath) => {
21
+ if (!filePath.endsWith("/SKILL.md") && filePath !== "SKILL.md") {
22
+ return null;
23
+ }
24
+ const normalized = basePath ? filePath.replace(`${basePath}/`, "") : filePath;
25
+ const segments = normalized.split("/");
26
+ if (segments.length === 1) {
27
+ const name = basePath ? (basePath.split("/").filter(Boolean).pop() ?? "root") : "root";
28
+ return {
29
+ name,
30
+ path: "",
31
+ skillFile: normalized,
32
+ };
33
+ }
34
+ return {
35
+ name: segments[segments.length - 2],
36
+ path: segments.slice(0, -1).join("/"),
37
+ skillFile: normalized,
38
+ };
39
+ };
40
+ const normalizeSkillFile = (skillPath) => {
41
+ return skillPath ? `${skillPath}/SKILL.md` : "SKILL.md";
42
+ };
43
+ const filterSkills = (entries, basePath, includeAll = false) => {
44
+ const skills = [];
45
+ for (const entry of entries) {
46
+ if (entry.type !== "blob") {
47
+ continue;
48
+ }
49
+ if (basePath &&
50
+ !entry.path.startsWith(`${basePath}/`) &&
51
+ entry.path !== `${basePath}/SKILL.md`) {
52
+ continue;
53
+ }
54
+ const skill = normalizeSkillPath(entry.path, basePath);
55
+ if (!skill) {
56
+ continue;
57
+ }
58
+ skills.push(skill);
59
+ }
60
+ if (basePath) {
61
+ return skills;
62
+ }
63
+ if (includeAll) {
64
+ return skills;
65
+ }
66
+ return skills.filter((skill) => {
67
+ const isRoot = skill.skillFile === "SKILL.md";
68
+ if (isRoot) {
69
+ return true;
70
+ }
71
+ return skillRoots.some((root) => skill.path.startsWith(root));
72
+ });
73
+ };
74
+ export const listRepoSkills = async (input) => {
75
+ const ref = typeof input === "string" ? parseRepoRef(input) : input;
76
+ if (!ref) {
77
+ throw new Error("Unsupported repo URL or shorthand.");
78
+ }
79
+ const normalized = await normalizeRepoRef(ref);
80
+ const tree = await fetchJson(buildTreeUrl(normalized));
81
+ const includeAll = normalized.repo.toLowerCase() === "skills" && !normalized.path;
82
+ const skills = filterSkills(tree.tree, normalized.path, includeAll);
83
+ if (skills.length === 0) {
84
+ throw new Error("No skills found in repository.");
85
+ }
86
+ return { ref: normalized, skills };
87
+ };
88
+ export const listRepoFiles = async (ref, skill, basePath) => {
89
+ const tree = await fetchJson(buildTreeUrl(ref));
90
+ const prefix = basePath ? [basePath, skill.path].filter(Boolean).join("/") : skill.path;
91
+ const files = tree.tree
92
+ .filter((entry) => entry.type === "blob")
93
+ .map((entry) => entry.path)
94
+ .filter((filePath) => (prefix ? filePath.startsWith(`${prefix}/`) : true))
95
+ .map((filePath) => (basePath ? filePath.replace(`${basePath}/`, "") : filePath));
96
+ if (files.length === 0) {
97
+ return [skill.skillFile];
98
+ }
99
+ return files;
100
+ };
101
+ export const fetchRepoFile = async (ref, filePath) => {
102
+ return await fetchText(buildRawUrl(ref, filePath));
103
+ };
104
+ export const writeRepoSkillDirectory = async (ref, skillPath, skillName) => {
105
+ const normalizedSkillPath = skillPath.replace(/\/$/, "");
106
+ const files = await listRepoFiles(ref, {
107
+ name: skillName,
108
+ path: normalizedSkillPath,
109
+ skillFile: normalizeSkillFile(normalizedSkillPath),
110
+ }, ref.path);
111
+ const targetDir = skillDir(skillName);
112
+ await ensureSkillsDir();
113
+ await fs.mkdir(targetDir, { recursive: true });
114
+ for (const file of files) {
115
+ const filePath = ref.path ? `${ref.path}/${file}` : file;
116
+ const content = await fetchRepoFile(ref, filePath);
117
+ const relative = normalizedSkillPath ? file.replace(`${normalizedSkillPath}/`, "") : file;
118
+ const targetPath = path.join(targetDir, relative);
119
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
120
+ await fs.writeFile(targetPath, content, "utf8");
121
+ }
122
+ };
123
+ export const normalizeRepoRef = async (ref) => {
124
+ try {
125
+ await fetchJson(buildTreeUrl(ref));
126
+ return ref;
127
+ }
128
+ catch {
129
+ if (ref.ref === "main") {
130
+ const fallback = { ...ref, ref: "master" };
131
+ await fetchJson(buildTreeUrl(fallback));
132
+ return fallback;
133
+ }
134
+ throw new Error("Unable to resolve repository ref.");
135
+ }
136
+ };
package/dist/lib/sync.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { skillDir } from "./skill-store.js";
4
+ import { getErrorMessage } from "./command.js";
4
5
  export const ensureDir = async (dir) => {
5
6
  await fs.mkdir(dir, { recursive: true });
6
7
  };
@@ -16,16 +17,42 @@ const copyFiles = async (sourceDir, targetDir) => {
16
17
  await fs.copyFile(sourcePath, destPath);
17
18
  }
18
19
  };
19
- export const copySkillToTargets = async (skillName, targets) => {
20
+ const createSymlink = async (sourceDir, targetDir) => {
21
+ await fs.symlink(sourceDir, targetDir, "dir");
22
+ };
23
+ export const buildSymlinkWarning = (agent, results) => {
24
+ const skipped = results.filter((result) => result.mode === "skipped");
25
+ if (skipped.length === 0) {
26
+ return null;
27
+ }
28
+ const details = skipped
29
+ .map((result) => `${result.path}: ${result.error ?? "unknown error"}`)
30
+ .join("; ");
31
+ return `Warning: symlink failed for ${agent}. ${details}. Remove the existing target or run "skillbox config set --install-mode copy" to use file copies.`;
32
+ };
33
+ export const installSkillToTargets = async (skillName, targets, config) => {
20
34
  const sourceDir = skillDir(skillName);
21
- const writtenPaths = [];
35
+ const results = [];
22
36
  for (const targetRoot of targets) {
23
37
  const targetDir = path.join(targetRoot, skillName);
38
+ await ensureDir(targetRoot);
39
+ if (config.installMode === "symlink") {
40
+ try {
41
+ await createSymlink(sourceDir, targetDir);
42
+ results.push({ path: targetDir, mode: "symlink" });
43
+ continue;
44
+ }
45
+ catch (error) {
46
+ const message = getErrorMessage(error, "unknown error");
47
+ results.push({ path: targetDir, mode: "skipped", error: message });
48
+ continue;
49
+ }
50
+ }
24
51
  await ensureDir(targetDir);
25
52
  await copyFiles(sourceDir, targetDir);
26
- writtenPaths.push(targetDir);
53
+ results.push({ path: targetDir, mode: "copy" });
27
54
  }
28
- return writtenPaths;
55
+ return results;
29
56
  };
30
57
  export const copySkillToInstallPaths = async (skillName, installPaths) => {
31
58
  const sourceDir = skillDir(skillName);
@@ -35,9 +62,6 @@ export const copySkillToInstallPaths = async (skillName, installPaths) => {
35
62
  }
36
63
  };
37
64
  export const buildTargets = (agent, paths, scope) => {
38
- if (scope === "system") {
39
- return (paths.system ?? []).map((pathValue) => ({ agent, scope, path: pathValue }));
40
- }
41
65
  const list = scope === "user" ? paths.user : paths.project;
42
66
  return list.map((pathValue) => ({ agent, scope, path: pathValue }));
43
67
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillbox",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Local-first, agent-agnostic skills manager",
5
5
  "license": "MIT",
6
6
  "author": "Christian Anagnostou",