skillbox 0.2.1 → 0.3.0

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.
Files changed (40) hide show
  1. package/README.md +73 -105
  2. package/dist/cli.js +15 -13
  3. package/dist/commands/add-repo.js +176 -0
  4. package/dist/commands/add.js +59 -29
  5. package/dist/commands/agent.js +5 -11
  6. package/dist/commands/config.js +11 -9
  7. package/dist/commands/convert.js +21 -6
  8. package/dist/commands/import.js +67 -77
  9. package/dist/commands/list.js +129 -92
  10. package/dist/commands/meta.js +5 -7
  11. package/dist/commands/project.js +24 -39
  12. package/dist/commands/remove.js +101 -0
  13. package/dist/commands/status.js +110 -101
  14. package/dist/commands/update.js +172 -43
  15. package/dist/lib/agent-detect.js +4 -13
  16. package/dist/lib/agents.js +64 -45
  17. package/dist/lib/command.js +6 -3
  18. package/dist/lib/config.js +19 -12
  19. package/dist/lib/discovery.js +3 -11
  20. package/dist/lib/fetcher.js +3 -3
  21. package/dist/lib/fs-utils.js +17 -0
  22. package/dist/lib/github.js +43 -0
  23. package/dist/lib/global-skills.js +23 -34
  24. package/dist/lib/grouping.js +6 -6
  25. package/dist/lib/index.js +13 -11
  26. package/dist/lib/installs.js +20 -13
  27. package/dist/lib/onboarding.js +31 -32
  28. package/dist/lib/options.js +4 -4
  29. package/dist/lib/output.js +13 -11
  30. package/dist/lib/paths.js +12 -4
  31. package/dist/lib/project-paths.js +2 -2
  32. package/dist/lib/project-root.js +3 -12
  33. package/dist/lib/projects.js +13 -11
  34. package/dist/lib/repo-skills.js +124 -0
  35. package/dist/lib/runtime.js +12 -12
  36. package/dist/lib/skill-parser.js +12 -12
  37. package/dist/lib/skill-store.js +13 -13
  38. package/dist/lib/source-grouping.js +49 -0
  39. package/dist/lib/sync.js +39 -16
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -1,150 +1,118 @@
1
- # skillbox
1
+ # Skillbox
2
2
 
3
- > Local-first, agent-agnostic skills manager. Track, update, and sync skills across popular AI coding agents with one CLI.
3
+ > Local-first, agent-agnostic skills manager for AI coding agents.
4
4
 
5
5
  [![CI](https://github.com/christiananagnostou/skillbox/actions/workflows/ci.yml/badge.svg)](https://github.com/christiananagnostou/skillbox/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/skillbox.svg)](https://www.npmjs.com/package/skillbox)
7
7
 
8
- ## Installation
9
-
10
- ### npm (recommended)
8
+ ## Install
11
9
 
12
10
  ```bash
13
11
  npm install -g skillbox
14
12
  ```
15
13
 
14
+ On first run, Skillbox auto-detects your installed agents and configures itself.
16
15
 
17
-
18
- ## Quick Start
19
-
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.
21
-
22
- Tip: run `skillbox list` right after install to see existing skills.
23
-
24
- ```bash
25
- skillbox add https://example.com/skills/linting/SKILL.md
26
- skillbox list
27
- skillbox status
28
- skillbox update linting
29
- ```
30
-
31
- ## Commands
32
-
33
- ### Core Commands
34
-
35
- ```bash
36
- skillbox add <url> [--name <name>] [--global] [--agents ...]
37
- skillbox convert <url> [--name <name>] [--output <dir>] [--agent]
38
- skillbox list [--group=category|namespace|source|project] [--json]
39
- skillbox status [--group=project|source] [--json]
40
- skillbox update [name] [--system] [--project <path>]
41
- skillbox import <path>
42
- skillbox meta set <name> --category foo --tag bar --namespace baz
43
- skillbox agent
44
- ```
45
-
46
- ### Project Commands
47
-
48
- ```bash
49
- skillbox project add <path> [--agent-path agent=path]
50
- skillbox project list
51
- skillbox project inspect <path>
52
- skillbox project sync <path>
53
- ```
54
-
55
- ### Config
16
+ **Quick start:** Install the skillbox skill to teach your agent how to manage skills:
56
17
 
57
18
  ```bash
58
- skillbox config get
59
- skillbox config set --default-scope user
60
- skillbox config set --default-agent claude --default-agent cursor
61
- skillbox config set --add-agent codex
62
- skillbox config set --manage-system
19
+ skillbox add christiananagnostou/skillbox
63
20
  ```
64
21
 
65
- Config defaults live in `~/.config/skillbox/config.json`:
66
-
67
- - `defaultScope`: `project` (default) or `user`
68
- - `defaultAgents`: empty array means all agents
69
- - `manageSystem`: `false` by default
70
-
71
- ## Agent Mode
22
+ ## Golden Workflow
72
23
 
73
- Use `--json` for machine-readable output.
24
+ These three commands cover most use cases:
74
25
 
75
26
  ```bash
76
- skillbox list --json
77
- skillbox status --json
78
- skillbox update linting --json
27
+ skillbox list # see installed skills
28
+ skillbox status # check for updates
29
+ skillbox update [name] # update skills
79
30
  ```
80
31
 
81
- ### Agent Usage Snippet
32
+ ## Adding Skills
82
33
 
83
- ```text
84
- Use skillbox for skill management.
34
+ | Command | Description |
35
+ |---------|-------------|
36
+ | `skillbox add owner/repo` | Install all skills from a GitHub repo |
37
+ | `skillbox add owner/repo --list` | List available skills in a repo |
38
+ | `skillbox add owner/repo --skill name` | Install a specific skill from a repo |
39
+ | `skillbox add <url>` | Install skill from a direct URL |
40
+ | `skillbox add <url> --name my-skill` | Install with custom name |
85
41
 
86
- Common workflow:
87
- 1) skillbox list --json
88
- 2) skillbox status --json
89
- 3) skillbox update <name> --json
42
+ > **Note:** GitHub unauthenticated API limit is 60 requests/hour per IP.
90
43
 
91
- If you need to install a new skill from a URL, run:
92
- skillbox add <url> [--name <name>]
44
+ ## All Commands
93
45
 
94
- If a URL is not a valid skill, run:
95
- skillbox convert <url> --agent
96
- ```
46
+ ### Skills
97
47
 
98
- ## Skill Locations
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `skillbox list` | List installed skills |
51
+ | `skillbox status` | Check for outdated skills |
52
+ | `skillbox update [name]` | Update all or one skill |
53
+ | `skillbox remove <name>` | Remove a skill |
54
+ | `skillbox import <path>` | Import existing skill directory |
55
+ | `skillbox import --global` | Import all skills from agent folders |
99
56
 
100
- Skillbox maintains a canonical store and syncs into agent-native folders.
57
+ ### Config
101
58
 
102
- Canonical store:
59
+ | Command | Description |
60
+ |---------|-------------|
61
+ | `skillbox config get` | Show current config |
62
+ | `skillbox config set --add-agent <name>` | Add an agent |
63
+ | `skillbox config set --default-scope <scope>` | Set default scope (`user` or `project`) |
64
+ | `skillbox config set --install-mode <mode>` | Set install mode (`symlink` or `copy`) |
103
65
 
104
- - `~/.config/skillbox/skills/<name>/`
66
+ ### Projects
105
67
 
106
- Index + config:
68
+ | Command | Description |
69
+ |---------|-------------|
70
+ | `skillbox project add <path>` | Register a project |
71
+ | `skillbox project list` | List registered projects |
72
+ | `skillbox project sync <path>` | Re-sync skills to a project |
107
73
 
108
- - `~/.config/skillbox/index.json`
109
- - `~/.config/skillbox/projects.json`
110
- - `~/.config/skillbox/config.json`
74
+ ## Supported Agents
111
75
 
112
- Agent paths (default):
76
+ | Agent | User Path | Project Path |
77
+ |-------|-----------|--------------|
78
+ | Claude | `~/.claude/skills/` | `.claude/skills/` |
79
+ | Cursor | `~/.cursor/skills/` | `.cursor/skills/` |
80
+ | Codex | `~/.codex/skills/` | `.codex/skills/` |
81
+ | OpenCode | `~/.config/opencode/skills/` | `.opencode/skills/` |
82
+ | Amp | `~/.config/agents/skills/` | `.agents/skills/` |
83
+ | Antigravity | `~/.gemini/antigravity/skills/` | `.agent/skills/` |
113
84
 
114
- - OpenCode: `.opencode/skills/`, `~/.config/opencode/skills/` (Claude-compatible `.claude/skills/` also supported)
115
- - Claude: `.claude/skills/`, `~/.claude/skills/`
116
- - Cursor: `.cursor/skills/`, `.claude/skills/`, `~/.cursor/skills/`, `~/.claude/skills/`
117
- - Codex: `$REPO_ROOT/.codex/skills/`, `~/.codex/skills/`, `/etc/codex/skills` (system)
118
- - Amp: `.agents/skills/`, `~/.config/agents/skills/` (Claude-compatible `.claude/skills/` also supported)
119
- - Antigravity: `.agent/skills/`, `~/.gemini/antigravity/skills/`
85
+ ## JSON Mode
120
86
 
121
- Note: only Codex defines a system scope path.
87
+ All commands support `--json` for machine-readable output:
122
88
 
123
- ## Usage with AI Agents
89
+ ```bash
90
+ skillbox list --json
91
+ skillbox status --json
92
+ skillbox update my-skill --json
93
+ ```
124
94
 
125
- ### Just ask the agent
95
+ ## For AI Agents
126
96
 
127
- The simplest approach is to instruct your agent to use Skillbox:
97
+ Install the skillbox skill to teach your agent how to use skillbox:
128
98
 
129
- ```
130
- Use skillbox to manage skills for this repo. Run skillbox --help for all commands.
99
+ ```bash
100
+ skillbox add christiananagnostou/skillbox
131
101
  ```
132
102
 
133
- ### AGENTS.md / CLAUDE.md
134
-
135
- Add this to your project instructions for more consistent results:
103
+ Or add this to your `CLAUDE.md` / `AGENTS.md`:
136
104
 
137
105
  ```markdown
138
- ## Skills
139
-
140
- Use `skillbox` to manage skills. Run `skillbox --help` for all commands.
106
+ Use `skillbox` to manage skills. Run `skillbox --help` for commands.
107
+ ```
141
108
 
142
- Core workflow:
109
+ ## File Locations
143
110
 
144
- 1. `skillbox list --json`
145
- 2. `skillbox status --json`
146
- 3. `skillbox update <name> --json`
147
- ```
111
+ | Path | Purpose |
112
+ |------|---------|
113
+ | `~/.config/skillbox/skills/` | Canonical skill store |
114
+ | `~/.config/skillbox/config.json` | Configuration |
115
+ | `~/.config/skillbox/index.json` | Skill index |
148
116
 
149
117
  ## Development
150
118
 
@@ -153,7 +121,7 @@ git clone https://github.com/christiananagnostou/skillbox
153
121
  cd skillbox
154
122
  npm install
155
123
  npm run build
156
- npm link --global
124
+ npm link
157
125
  ```
158
126
 
159
127
  ## License
package/dist/cli.js CHANGED
@@ -1,30 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { registerAdd } from "./commands/add.js";
4
+ import { registerAgent } from "./commands/agent.js";
5
+ import { registerConfig } from "./commands/config.js";
4
6
  import { registerConvert } from "./commands/convert.js";
5
- import { registerList } from "./commands/list.js";
6
- import { registerStatus } from "./commands/status.js";
7
- import { registerUpdate } from "./commands/update.js";
8
7
  import { registerImport } from "./commands/import.js";
8
+ import { registerList } from "./commands/list.js";
9
9
  import { registerMeta } from "./commands/meta.js";
10
10
  import { registerProject } from "./commands/project.js";
11
- import { registerAgent } from "./commands/agent.js";
12
- import { registerConfig } from "./commands/config.js";
11
+ import { registerRemove } from "./commands/remove.js";
12
+ import { registerStatus } from "./commands/status.js";
13
+ import { registerUpdate } from "./commands/update.js";
13
14
  const program = new Command();
14
- program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.1.0");
15
+ program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.3.0");
15
16
  registerAdd(program);
17
+ registerAgent(program);
18
+ registerConfig(program);
16
19
  registerConvert(program);
17
- registerList(program);
18
- registerStatus(program);
19
- registerUpdate(program);
20
20
  registerImport(program);
21
+ registerList(program);
21
22
  registerMeta(program);
22
23
  registerProject(program);
23
- registerAgent(program);
24
- registerConfig(program);
25
- const run = async () => {
24
+ registerRemove(program);
25
+ registerStatus(program);
26
+ registerUpdate(program);
27
+ async function run() {
26
28
  const { runOnboarding } = await import("./lib/onboarding.js");
27
29
  await runOnboarding();
28
30
  program.parse();
29
- };
31
+ }
30
32
  void run();
@@ -0,0 +1,176 @@
1
+ import { getErrorMessage } from "../lib/command.js";
2
+ import { loadConfig } from "../lib/config.js";
3
+ import { parseRepoRef } from "../lib/github.js";
4
+ import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
5
+ import { printInfo, printJson } from "../lib/output.js";
6
+ import { buildProjectAgentPaths } from "../lib/project-paths.js";
7
+ import { fetchRepoFile, listRepoSkills, normalizeRepoRef, writeRepoSkillDirectory, } from "../lib/repo-skills.js";
8
+ import { ensureProjectRegistered, resolveRuntime } from "../lib/runtime.js";
9
+ import { buildMetadata, parseSkillMarkdown } from "../lib/skill-parser.js";
10
+ import { writeSkillMetadata } from "../lib/skill-store.js";
11
+ import { buildSymlinkWarning, buildTargets, installSkillToTargets } from "../lib/sync.js";
12
+ function 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
+ async function ensureRepoRef(input) {
20
+ const ref = parseRepoRef(input);
21
+ if (!ref) {
22
+ throw new Error("Unsupported repo URL or shorthand.");
23
+ }
24
+ return normalizeRepoRef(ref);
25
+ }
26
+ async function installSkillTargets(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
+ for (const target of written) {
49
+ installs.push({
50
+ scope,
51
+ agent,
52
+ path: target,
53
+ projectRoot: scope === "project" ? projectRoot : undefined,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ export function isRepoUrl(input) {
59
+ return Boolean(parseRepoRef(input));
60
+ }
61
+ export async function handleRepoInstall(input, options) {
62
+ const ref = await ensureRepoRef(input);
63
+ const { skills } = await listRepoSkills(ref);
64
+ const skillNames = skills.map((skill) => skill.name).sort();
65
+ if (options.list) {
66
+ if (options.json) {
67
+ printJson({ ok: true, command: "add", data: { repo: input, skills: skillNames } });
68
+ return;
69
+ }
70
+ printInfo(`Repo Skills: ${ref.owner}/${ref.repo}`);
71
+ printInfo("");
72
+ printInfo(`Found ${skillNames.length} skill(s):`);
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: { repo: `${ref.owner}/${ref.repo}`, ...summary } });
137
+ return;
138
+ }
139
+ printInfo(`Skills Added from: ${ref.owner}/${ref.repo}`);
140
+ printInfo("");
141
+ printInfo("Source: git");
142
+ printInfo(` ${ref.owner}/${ref.repo}${ref.path ? `/${ref.path}` : ""} (${ref.ref})`);
143
+ if (summary.installed.length > 0) {
144
+ printInfo("");
145
+ printInfo(`Installed (${summary.installed.length}):`);
146
+ for (const name of summary.installed) {
147
+ printInfo(` ✓ ${name}`);
148
+ }
149
+ }
150
+ if (summary.updated.length > 0) {
151
+ printInfo("");
152
+ printInfo(`Updated (${summary.updated.length}):`);
153
+ for (const name of summary.updated) {
154
+ printInfo(` ✓ ${name}`);
155
+ }
156
+ }
157
+ if (summary.skipped.length > 0) {
158
+ printInfo("");
159
+ printInfo(`Skipped (${summary.skipped.length}):`);
160
+ for (const name of summary.skipped) {
161
+ printInfo(` - ${name} (missing description)`);
162
+ }
163
+ }
164
+ if (summary.failed.length > 0) {
165
+ printInfo("");
166
+ printInfo(`Failed (${summary.failed.length}):`);
167
+ for (const failure of summary.failed) {
168
+ printInfo(` ✗ ${failure.name} (${failure.reason})`);
169
+ }
170
+ }
171
+ const total = summary.installed.length + summary.updated.length;
172
+ if (total === 0) {
173
+ printInfo("");
174
+ printInfo("No skills were added.");
175
+ }
176
+ }
@@ -1,22 +1,37 @@
1
- import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
2
- import { fetchText } from "../lib/fetcher.js";
3
- import { parseSkillMarkdown, inferNameFromUrl, buildMetadata } from "../lib/skill-parser.js";
4
1
  import { handleCommandError } from "../lib/command.js";
5
- import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
2
+ import { loadConfig } from "../lib/config.js";
3
+ import { fetchText } from "../lib/fetcher.js";
4
+ import { collect } from "../lib/fs-utils.js";
6
5
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
7
- import { buildTargets, copySkillToTargets } from "../lib/sync.js";
6
+ import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
8
7
  import { buildProjectAgentPaths } from "../lib/project-paths.js";
9
- import { resolveRuntime, ensureProjectRegistered } from "../lib/runtime.js";
10
- export const registerAdd = (program) => {
8
+ import { ensureProjectRegistered, resolveRuntime } from "../lib/runtime.js";
9
+ import { buildMetadata, inferNameFromUrl, parseSkillMarkdown } from "../lib/skill-parser.js";
10
+ import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
11
+ import { buildSymlinkWarning, buildTargets, installSkillToTargets } from "../lib/sync.js";
12
+ import { handleRepoInstall, isRepoUrl } from "./add-repo.js";
13
+ export function registerAdd(program) {
11
14
  program
12
15
  .command("add")
13
- .argument("<url>", "Skill URL")
16
+ .argument("<url>", "Skill URL or repo")
14
17
  .option("--name <name>", "Override skill name")
15
18
  .option("--global", "Install to user scope")
16
19
  .option("--agents <list>", "Comma-separated agent list")
20
+ .option("--skill <skill>", "Skill name to install", collect)
21
+ .option("--list", "List skills in repo without installing")
17
22
  .option("--json", "JSON output")
18
23
  .action(async (url, options) => {
19
24
  try {
25
+ if (options.list || options.skill || isRepoUrl(url)) {
26
+ await handleRepoInstall(url, {
27
+ global: options.global,
28
+ agents: options.agents,
29
+ json: options.json,
30
+ list: options.list,
31
+ skill: options.skill,
32
+ });
33
+ return;
34
+ }
20
35
  const skillMarkdown = await fetchText(url);
21
36
  const parsed = parseSkillMarkdown(skillMarkdown);
22
37
  const inferred = inferNameFromUrl(url);
@@ -27,10 +42,10 @@ export const registerAdd = (program) => {
27
42
  if (!parsed.name && !options.name) {
28
43
  throw new Error("Skill frontmatter missing name. Provide --name to continue.");
29
44
  }
30
- const metadata = buildMetadata(parsed, { type: "url", url }, skillName);
31
45
  if (!parsed.description) {
32
46
  throw new Error("Skill frontmatter missing description. Convert the source into a valid skill.");
33
47
  }
48
+ const metadata = buildMetadata(parsed, { type: "url", url }, skillName);
34
49
  await ensureSkillsDir();
35
50
  await writeSkillFiles(skillName, skillMarkdown, metadata);
36
51
  const index = await loadIndex();
@@ -46,6 +61,7 @@ export const registerAdd = (program) => {
46
61
  });
47
62
  const projectEntry = await ensureProjectRegistered(projectRoot, scope);
48
63
  const paths = buildProjectAgentPaths(projectRoot, projectEntry);
64
+ const config = await loadConfig();
49
65
  const installed = [];
50
66
  const installs = [];
51
67
  for (const agent of agentList) {
@@ -54,15 +70,24 @@ export const registerAdd = (program) => {
54
70
  continue;
55
71
  }
56
72
  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
- });
73
+ const results = await installSkillToTargets(skillName, targets, config);
74
+ const written = results
75
+ .filter((result) => result.mode !== "skipped")
76
+ .map((result) => result.path);
77
+ const warning = buildSymlinkWarning(agent, results);
78
+ if (warning) {
79
+ printInfo(warning);
80
+ }
81
+ if (written.length > 0) {
82
+ installed.push({ agent, scope, targets: written });
83
+ for (const target of written) {
84
+ installs.push({
85
+ scope,
86
+ agent,
87
+ path: target,
88
+ projectRoot: scope === "project" ? projectRoot : undefined,
89
+ });
90
+ }
66
91
  }
67
92
  }
68
93
  const nextIndex = upsertSkill(updated, {
@@ -79,27 +104,32 @@ export const registerAdd = (program) => {
79
104
  command: "add",
80
105
  data: {
81
106
  name: skillName,
82
- url,
107
+ source: { type: "url", url },
83
108
  scope,
84
- agents: installed,
109
+ installs,
85
110
  },
86
111
  });
87
112
  return;
88
113
  }
89
- printInfo(`Installed skill: ${skillName}`);
90
- printInfo(`Source: ${url}`);
91
- printInfo(`Scope: ${scope}`);
92
- if (installed.length === 0) {
93
- printInfo("No agent targets were updated.");
114
+ printInfo(`Skill Added: ${skillName}`);
115
+ printInfo("");
116
+ printInfo("Source: url");
117
+ printInfo(` ${url}`);
118
+ if (installs.length > 0) {
119
+ printInfo("");
120
+ printInfo("Installed to:");
121
+ for (const install of installs) {
122
+ const scopeLabel = install.scope === "project" ? `project:${install.projectRoot}` : "user";
123
+ printInfo(` ✓ ${scopeLabel}/${install.agent}`);
124
+ }
94
125
  }
95
126
  else {
96
- for (const entry of installed) {
97
- printInfo(`Updated ${entry.agent}: ${entry.targets.join(", ")}`);
98
- }
127
+ printInfo("");
128
+ printInfo("No agent targets were updated.");
99
129
  }
100
130
  }
101
131
  catch (error) {
102
132
  handleCommandError(options, "add", error);
103
133
  }
104
134
  });
105
- };
135
+ }
@@ -1,5 +1,5 @@
1
1
  import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
2
- const agentSnippet = `Use skillbox for skill management.
2
+ const AGENT_SNIPPET = `Use skillbox for skill management.
3
3
 
4
4
  Common workflow:
5
5
  1) skillbox list --json
@@ -12,22 +12,16 @@ skillbox add <url> [--name <name>]
12
12
  If a URL is not a valid skill, run:
13
13
  skillbox convert <url> --agent
14
14
  `;
15
- export const registerAgent = (program) => {
15
+ export function registerAgent(program) {
16
16
  program
17
17
  .command("agent")
18
18
  .description("Print agent-friendly usage")
19
19
  .option("--json", "JSON output")
20
20
  .action((options) => {
21
21
  if (isJsonEnabled(options)) {
22
- printJson({
23
- ok: true,
24
- command: "agent",
25
- data: {
26
- snippet: agentSnippet,
27
- },
28
- });
22
+ printJson({ ok: true, command: "agent", data: { snippet: AGENT_SNIPPET } });
29
23
  return;
30
24
  }
31
- printInfo(agentSnippet);
25
+ printInfo(AGENT_SNIPPET);
32
26
  });
33
- };
27
+ }