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.
- package/README.md +73 -105
- package/dist/cli.js +15 -13
- package/dist/commands/add-repo.js +176 -0
- package/dist/commands/add.js +59 -29
- package/dist/commands/agent.js +5 -11
- package/dist/commands/config.js +11 -9
- package/dist/commands/convert.js +21 -6
- package/dist/commands/import.js +67 -77
- package/dist/commands/list.js +129 -92
- package/dist/commands/meta.js +5 -7
- package/dist/commands/project.js +24 -39
- package/dist/commands/remove.js +101 -0
- package/dist/commands/status.js +110 -101
- package/dist/commands/update.js +172 -43
- package/dist/lib/agent-detect.js +4 -13
- package/dist/lib/agents.js +64 -45
- package/dist/lib/command.js +6 -3
- package/dist/lib/config.js +19 -12
- package/dist/lib/discovery.js +3 -11
- package/dist/lib/fetcher.js +3 -3
- package/dist/lib/fs-utils.js +17 -0
- package/dist/lib/github.js +43 -0
- package/dist/lib/global-skills.js +23 -34
- package/dist/lib/grouping.js +6 -6
- package/dist/lib/index.js +13 -11
- package/dist/lib/installs.js +20 -13
- package/dist/lib/onboarding.js +31 -32
- package/dist/lib/options.js +4 -4
- package/dist/lib/output.js +13 -11
- package/dist/lib/paths.js +12 -4
- package/dist/lib/project-paths.js +2 -2
- package/dist/lib/project-root.js +3 -12
- package/dist/lib/projects.js +13 -11
- package/dist/lib/repo-skills.js +124 -0
- package/dist/lib/runtime.js +12 -12
- package/dist/lib/skill-parser.js +12 -12
- package/dist/lib/skill-store.js +13 -13
- package/dist/lib/source-grouping.js +49 -0
- package/dist/lib/sync.js +39 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,150 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Skillbox
|
|
2
2
|
|
|
3
|
-
> Local-first, agent-agnostic skills manager
|
|
3
|
+
> Local-first, agent-agnostic skills manager for AI coding agents.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/christiananagnostou/skillbox/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/skillbox)
|
|
7
7
|
|
|
8
|
-
##
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
These three commands cover most use cases:
|
|
74
25
|
|
|
75
26
|
```bash
|
|
76
|
-
skillbox list
|
|
77
|
-
skillbox status
|
|
78
|
-
skillbox update
|
|
27
|
+
skillbox list # see installed skills
|
|
28
|
+
skillbox status # check for updates
|
|
29
|
+
skillbox update [name] # update skills
|
|
79
30
|
```
|
|
80
31
|
|
|
81
|
-
|
|
32
|
+
## Adding Skills
|
|
82
33
|
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
skillbox add <url> [--name <name>]
|
|
44
|
+
## All Commands
|
|
93
45
|
|
|
94
|
-
|
|
95
|
-
skillbox convert <url> --agent
|
|
96
|
-
```
|
|
46
|
+
### Skills
|
|
97
47
|
|
|
98
|
-
|
|
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
|
-
|
|
57
|
+
### Config
|
|
101
58
|
|
|
102
|
-
|
|
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
|
-
|
|
66
|
+
### Projects
|
|
105
67
|
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
- `~/.config/skillbox/projects.json`
|
|
110
|
-
- `~/.config/skillbox/config.json`
|
|
74
|
+
## Supported Agents
|
|
111
75
|
|
|
112
|
-
Agent
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
All commands support `--json` for machine-readable output:
|
|
122
88
|
|
|
123
|
-
|
|
89
|
+
```bash
|
|
90
|
+
skillbox list --json
|
|
91
|
+
skillbox status --json
|
|
92
|
+
skillbox update my-skill --json
|
|
93
|
+
```
|
|
124
94
|
|
|
125
|
-
|
|
95
|
+
## For AI Agents
|
|
126
96
|
|
|
127
|
-
|
|
97
|
+
Install the skillbox skill to teach your agent how to use skillbox:
|
|
128
98
|
|
|
129
|
-
```
|
|
130
|
-
|
|
99
|
+
```bash
|
|
100
|
+
skillbox add christiananagnostou/skillbox
|
|
131
101
|
```
|
|
132
102
|
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
+
## File Locations
|
|
143
110
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
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 {
|
|
12
|
-
import {
|
|
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.
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
}
|
package/dist/commands/add.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
6
|
+
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
8
7
|
import { buildProjectAgentPaths } from "../lib/project-paths.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
109
|
+
installs,
|
|
85
110
|
},
|
|
86
111
|
});
|
|
87
112
|
return;
|
|
88
113
|
}
|
|
89
|
-
printInfo(`
|
|
90
|
-
printInfo(
|
|
91
|
-
printInfo(
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
+
}
|
package/dist/commands/agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
2
|
-
const
|
|
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
|
|
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(
|
|
25
|
+
printInfo(AGENT_SNIPPET);
|
|
32
26
|
});
|
|
33
|
-
}
|
|
27
|
+
}
|