skillbox 0.2.2 → 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 +71 -153
  2. package/dist/cli.js +13 -13
  3. package/dist/commands/add-repo.js +61 -42
  4. package/dist/commands/add.js +26 -23
  5. package/dist/commands/agent.js +5 -11
  6. package/dist/commands/config.js +8 -10
  7. package/dist/commands/convert.js +21 -6
  8. package/dist/commands/import.js +64 -67
  9. package/dist/commands/list.js +129 -91
  10. package/dist/commands/meta.js +5 -7
  11. package/dist/commands/project.js +24 -39
  12. package/dist/commands/remove.js +45 -20
  13. package/dist/commands/status.js +110 -84
  14. package/dist/commands/update.js +167 -75
  15. package/dist/lib/agent-detect.js +4 -13
  16. package/dist/lib/agents.js +65 -41
  17. package/dist/lib/command.js +4 -4
  18. package/dist/lib/config.js +18 -14
  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 +10 -10
  23. package/dist/lib/global-skills.js +24 -15
  24. package/dist/lib/grouping.js +6 -6
  25. package/dist/lib/index.js +13 -11
  26. package/dist/lib/installs.js +15 -14
  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 +29 -41
  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 +16 -17
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -1,200 +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
 
16
- ## Quick Start
17
-
18
- Skillbox will detect installed agents on your machine. Repo and URL installs track their origin so updates stay one command away.
19
-
20
- > Tip: run `skillbox list` right after install to see existing skills.
14
+ On first run, Skillbox auto-detects your installed agents and configures itself.
21
15
 
22
- Skillbox links agent folders to the canonical store using symlinks on macOS/Linux and file copies on Windows.
16
+ **Quick start:** Install the skillbox skill to teach your agent how to manage skills:
23
17
 
24
18
  ```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
32
- skillbox add https://example.com/skills/linting/SKILL.md
33
- # list installed skills
34
- skillbox list
35
- # check for updates
36
- skillbox status
37
- # update one skill
38
- skillbox update linting
19
+ skillbox add christiananagnostou/skillbox
39
20
  ```
40
21
 
41
- Notes:
42
- - GitHub unauthenticated API limit: 60 req/hr per IP
43
- - use `skillbox convert` for non-skill URLs
22
+ ## Golden Workflow
44
23
 
45
- ## Commands
46
-
47
- ### Core Commands
24
+ These three commands cover most use cases:
48
25
 
49
26
  ```bash
50
- # add skill from URL
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
55
- skillbox convert <url> [--name <name>] [--output <dir>] [--agent]
56
- # list skills
57
- skillbox list [--group=category|namespace|source|project] [--json]
58
- # check for updates
59
- skillbox status [--group=project|source] [--json]
60
- # update skills
61
- skillbox update [name] [--project <path>]
62
- # remove skills
63
- skillbox remove <name> [--project <path>]
64
- # import existing skills
65
- skillbox import <path>
66
- # update metadata
67
- skillbox meta set <name> --category foo --tag bar --namespace baz
68
- # open agent REPL
69
- skillbox agent
27
+ skillbox list # see installed skills
28
+ skillbox status # check for updates
29
+ skillbox update [name] # update skills
70
30
  ```
71
31
 
72
- ### Project Commands
32
+ ## Adding Skills
73
33
 
74
- ```bash
75
- # register project
76
- skillbox project add <path> [--agent-path agent=path]
77
- # list projects
78
- skillbox project list
79
- # show project details
80
- skillbox project inspect <path>
81
- # resync project skills
82
- skillbox project sync <path>
83
- ```
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 |
84
41
 
85
- ### Config
42
+ > **Note:** GitHub unauthenticated API limit is 60 requests/hour per IP.
86
43
 
87
- ```bash
88
- # show config
89
- skillbox config get
90
- # set default scope
91
- skillbox config set --default-scope user
92
- # replace default agents
93
- skillbox config set --default-agent claude --default-agent cursor
94
- # add default agent
95
- skillbox config set --add-agent codex
96
- # use symlink installs
97
- skillbox config set --install-mode symlink
98
- # use file copies
99
- skillbox config set --install-mode copy
100
- ```
44
+ ## All Commands
101
45
 
102
- Config defaults live in `~/.config/skillbox/config.json`:
46
+ ### Skills
103
47
 
104
- - `defaultScope`: `user` (default) or `project`
105
- - `defaultAgents`: empty array means all agents
106
- - `installMode`: `symlink` (macOS/Linux) or `copy` (Windows)
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 |
107
56
 
108
- ## Agent Mode
109
-
110
- Use `--json` for machine-readable output.
57
+ ### Config
111
58
 
112
- ```bash
113
- # list skills (json)
114
- skillbox list --json
115
- # status check (json)
116
- skillbox status --json
117
- # update one skill (json)
118
- skillbox update linting --json
119
- ```
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`) |
120
65
 
121
- ### Agent Usage Snippet
66
+ ### Projects
122
67
 
123
- ```text
124
- Use skillbox for skill management.
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 |
125
73
 
126
- Common workflow:
127
- 1) skillbox list --json
128
- 2) skillbox status --json
129
- 3) skillbox update <name> --json
74
+ ## Supported Agents
130
75
 
131
- If you need to install a new skill from a URL, run:
132
- # add skill from URL
133
- skillbox add <url> [--name <name>]
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/` |
134
84
 
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
85
+ ## JSON Mode
142
86
 
143
- Note: GitHub unauthenticated API limits are 60 requests per hour per IP, so heavy repo usage may hit rate limits.
87
+ All commands support `--json` for machine-readable output:
144
88
 
145
- If a URL is not a valid skill, run:
146
- # convert content to skill
147
- skillbox convert <url> --agent
89
+ ```bash
90
+ skillbox list --json
91
+ skillbox status --json
92
+ skillbox update my-skill --json
148
93
  ```
149
94
 
150
- ## Skill Locations
151
-
152
- Skillbox maintains a canonical store and syncs into agent-native folders.
153
-
154
- Canonical store:
155
-
156
- - `~/.config/skillbox/skills/<name>/`
157
-
158
- Index + config:
159
-
160
- - `~/.config/skillbox/index.json`
161
- - `~/.config/skillbox/projects.json`
162
- - `~/.config/skillbox/config.json`
163
-
164
- Agent paths (default):
165
-
166
- - OpenCode: `.opencode/skills/`, `~/.config/opencode/skills/` (Claude-compatible `.claude/skills/` also supported)
167
- - Claude: `.claude/skills/`, `~/.claude/skills/`
168
- - Cursor: `.cursor/skills/`, `.claude/skills/`, `~/.cursor/skills/`, `~/.claude/skills/`
169
- - Codex: `$REPO_ROOT/.codex/skills/`, `~/.codex/skills/`
170
- - Amp: `.agents/skills/`, `~/.config/agents/skills/` (Claude-compatible `.claude/skills/` also supported)
171
- - Antigravity: `.agent/skills/`, `~/.gemini/antigravity/skills/`
95
+ ## For AI Agents
172
96
 
173
- ## Usage with AI Agents
97
+ Install the skillbox skill to teach your agent how to use skillbox:
174
98
 
175
- ### Just ask the agent
176
-
177
- The simplest approach is to instruct your agent to use Skillbox:
178
-
179
- ```
180
- Use skillbox to manage skills for this repo. Run skillbox --help for all commands.
99
+ ```bash
100
+ skillbox add christiananagnostou/skillbox
181
101
  ```
182
102
 
183
- ### AGENTS.md / CLAUDE.md
184
-
185
- Add this to your project instructions for more consistent results:
103
+ Or add this to your `CLAUDE.md` / `AGENTS.md`:
186
104
 
187
105
  ```markdown
188
- ## Skills
189
-
190
- Use `skillbox` to manage skills. Run `skillbox --help` for all commands.
106
+ Use `skillbox` to manage skills. Run `skillbox --help` for commands.
107
+ ```
191
108
 
192
- Core workflow:
109
+ ## File Locations
193
110
 
194
- 1. `skillbox list --json`
195
- 2. `skillbox status --json`
196
- 3. `skillbox update <name> --json`
197
- ```
111
+ | Path | Purpose |
112
+ |------|---------|
113
+ | `~/.config/skillbox/skills/` | Canonical skill store |
114
+ | `~/.config/skillbox/config.json` | Configuration |
115
+ | `~/.config/skillbox/index.json` | Skill index |
198
116
 
199
117
  ## Development
200
118
 
package/dist/cli.js CHANGED
@@ -1,32 +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";
13
11
  import { registerRemove } from "./commands/remove.js";
12
+ import { registerStatus } from "./commands/status.js";
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.1.0");
15
+ program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.3.0");
16
16
  registerAdd(program);
17
+ registerAgent(program);
18
+ registerConfig(program);
17
19
  registerConvert(program);
18
- registerList(program);
19
- registerStatus(program);
20
- registerUpdate(program);
21
20
  registerImport(program);
21
+ registerList(program);
22
22
  registerMeta(program);
23
23
  registerProject(program);
24
- registerAgent(program);
25
- registerConfig(program);
26
24
  registerRemove(program);
27
- const run = async () => {
25
+ registerStatus(program);
26
+ registerUpdate(program);
27
+ async function run() {
28
28
  const { runOnboarding } = await import("./lib/onboarding.js");
29
29
  await runOnboarding();
30
30
  program.parse();
31
- };
31
+ }
32
32
  void run();
@@ -1,29 +1,29 @@
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";
1
+ import { getErrorMessage } from "../lib/command.js";
4
2
  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";
3
+ import { parseRepoRef } from "../lib/github.js";
8
4
  import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
9
5
  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) => {
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
13
  if (selections.length === 0) {
14
14
  return skills;
15
15
  }
16
16
  const selectionSet = new Set(selections);
17
17
  return skills.filter((name) => selectionSet.has(name));
18
- };
19
- const ensureRepoRef = async (input) => {
18
+ }
19
+ async function ensureRepoRef(input) {
20
20
  const ref = parseRepoRef(input);
21
21
  if (!ref) {
22
22
  throw new Error("Unsupported repo URL or shorthand.");
23
23
  }
24
- return await normalizeRepoRef(ref);
25
- };
26
- const installSkillTargets = async (skillName, options, installs) => {
24
+ return normalizeRepoRef(ref);
25
+ }
26
+ async function installSkillTargets(skillName, options, installs) {
27
27
  const { projectRoot, scope, agentList } = await resolveRuntime({
28
28
  global: options.global,
29
29
  agents: options.agents,
@@ -45,22 +45,20 @@ const installSkillTargets = async (skillName, options, installs) => {
45
45
  if (warning) {
46
46
  printInfo(warning);
47
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
- }
48
+ for (const target of written) {
49
+ installs.push({
50
+ scope,
51
+ agent,
52
+ path: target,
53
+ projectRoot: scope === "project" ? projectRoot : undefined,
54
+ });
57
55
  }
58
56
  }
59
- };
60
- export const isRepoUrl = (input) => {
57
+ }
58
+ export function isRepoUrl(input) {
61
59
  return Boolean(parseRepoRef(input));
62
- };
63
- export const handleRepoInstall = async (input, options) => {
60
+ }
61
+ export async function handleRepoInstall(input, options) {
64
62
  const ref = await ensureRepoRef(input);
65
63
  const { skills } = await listRepoSkills(ref);
66
64
  const skillNames = skills.map((skill) => skill.name).sort();
@@ -69,9 +67,11 @@ export const handleRepoInstall = async (input, options) => {
69
67
  printJson({ ok: true, command: "add", data: { repo: input, skills: skillNames } });
70
68
  return;
71
69
  }
72
- printInfo(`Skills found: ${skillNames.length}`);
70
+ printInfo(`Repo Skills: ${ref.owner}/${ref.repo}`);
71
+ printInfo("");
72
+ printInfo(`Found ${skillNames.length} skill(s):`);
73
73
  for (const name of skillNames) {
74
- printInfo(`- ${name}`);
74
+ printInfo(` - ${name}`);
75
75
  }
76
76
  return;
77
77
  }
@@ -133,25 +133,44 @@ export const handleRepoInstall = async (input, options) => {
133
133
  }
134
134
  await saveIndex(sortIndex(index));
135
135
  if (options.json) {
136
- printJson({ ok: true, command: "add", data: summary });
136
+ printJson({ ok: true, command: "add", data: { repo: `${ref.owner}/${ref.repo}`, ...summary } });
137
137
  return;
138
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
- }
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})`);
145
143
  if (summary.installed.length > 0) {
146
- printInfo(`Installed ${summary.installed.length} skill(s): ${summary.installed.join(", ")}`);
144
+ printInfo("");
145
+ printInfo(`Installed (${summary.installed.length}):`);
146
+ for (const name of summary.installed) {
147
+ printInfo(` ✓ ${name}`);
148
+ }
147
149
  }
148
150
  if (summary.updated.length > 0) {
149
- printInfo(`Updated ${summary.updated.length} skill(s): ${summary.updated.join(", ")}`);
151
+ printInfo("");
152
+ printInfo(`Updated (${summary.updated.length}):`);
153
+ for (const name of summary.updated) {
154
+ printInfo(` ✓ ${name}`);
155
+ }
150
156
  }
151
157
  if (summary.skipped.length > 0) {
152
- printInfo(`Skipped ${summary.skipped.length} skill(s): ${summary.skipped.join(", ")}`);
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
+ }
153
170
  }
154
- if (summary.installed.length === 0 && summary.updated.length === 0) {
155
- printInfo("No agent targets were updated (canonical store only).");
171
+ const total = summary.installed.length + summary.updated.length;
172
+ if (total === 0) {
173
+ printInfo("");
174
+ printInfo("No skills were added.");
156
175
  }
157
- };
176
+ }
@@ -1,15 +1,16 @@
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 { buildSymlinkWarning, buildTargets, installSkillToTargets } 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
- import { loadConfig } from "../lib/config.js";
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";
11
12
  import { handleRepoInstall, isRepoUrl } from "./add-repo.js";
12
- export const registerAdd = (program) => {
13
+ export function registerAdd(program) {
13
14
  program
14
15
  .command("add")
15
16
  .argument("<url>", "Skill URL or repo")
@@ -41,10 +42,10 @@ export const registerAdd = (program) => {
41
42
  if (!parsed.name && !options.name) {
42
43
  throw new Error("Skill frontmatter missing name. Provide --name to continue.");
43
44
  }
44
- const metadata = buildMetadata(parsed, { type: "url", url }, skillName);
45
45
  if (!parsed.description) {
46
46
  throw new Error("Skill frontmatter missing description. Convert the source into a valid skill.");
47
47
  }
48
+ const metadata = buildMetadata(parsed, { type: "url", url }, skillName);
48
49
  await ensureSkillsDir();
49
50
  await writeSkillFiles(skillName, skillMarkdown, metadata);
50
51
  const index = await loadIndex();
@@ -103,30 +104,32 @@ export const registerAdd = (program) => {
103
104
  command: "add",
104
105
  data: {
105
106
  name: skillName,
106
- url,
107
+ source: { type: "url", url },
107
108
  scope,
108
- agents: installed,
109
+ installs,
109
110
  },
110
111
  });
111
112
  return;
112
113
  }
113
- printInfo(`Installed skill: ${skillName}`);
114
- printInfo(`Source: ${url}`);
115
- printInfo(`Scope: ${scope}`);
116
- if (installed.length === 0) {
117
- printInfo("No agent targets were updated (canonical store only).");
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
+ }
118
125
  }
119
126
  else {
120
- for (const entry of installed) {
121
- printInfo(`Updated ${entry.agent}: ${entry.targets.join(", ")}`);
122
- }
127
+ printInfo("");
128
+ printInfo("No agent targets were updated.");
123
129
  }
124
130
  }
125
131
  catch (error) {
126
132
  handleCommandError(options, "add", error);
127
133
  }
128
134
  });
129
- };
130
- const collect = (value, previous = []) => {
131
- return [...previous, value];
132
- };
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
+ }
@@ -1,10 +1,8 @@
1
- import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
2
- import { loadConfig, saveConfig } from "../lib/config.js";
3
1
  import { handleCommandError } from "../lib/command.js";
4
- const collect = (value, previous = []) => {
5
- return [...previous, value];
6
- };
7
- export const registerConfig = (program) => {
2
+ import { loadConfig, saveConfig } from "../lib/config.js";
3
+ import { collect } from "../lib/fs-utils.js";
4
+ import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
5
+ export function registerConfig(program) {
8
6
  const config = program.command("config").description("View or edit skillbox config");
9
7
  config
10
8
  .command("get")
@@ -36,13 +34,13 @@ export const registerConfig = (program) => {
36
34
  if (nextScope !== "project" && nextScope !== "user") {
37
35
  throw new Error("defaultScope must be 'project' or 'user'.");
38
36
  }
39
- const addedAgents = options.addAgent ?? [];
40
- const nextAgents = options.defaultAgent ?? current.defaultAgents;
41
- const mergedAgents = Array.from(new Set([...(nextAgents ?? []), ...addedAgents].filter((agent) => agent.length > 0)));
42
37
  const nextMode = options.installMode ?? current.installMode;
43
38
  if (nextMode !== "symlink" && nextMode !== "copy") {
44
39
  throw new Error("installMode must be 'symlink' or 'copy'.");
45
40
  }
41
+ const addedAgents = options.addAgent ?? [];
42
+ const nextAgents = options.defaultAgent ?? current.defaultAgents;
43
+ const mergedAgents = Array.from(new Set([...(nextAgents ?? []), ...addedAgents].filter((agent) => agent.length > 0)));
46
44
  const next = {
47
45
  ...current,
48
46
  defaultAgents: mergedAgents,
@@ -60,4 +58,4 @@ export const registerConfig = (program) => {
60
58
  handleCommandError(options, "config set", error);
61
59
  }
62
60
  });
63
- };
61
+ }