skillbox 0.1.2 → 0.2.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 +18 -20
- package/dist/cli.js +6 -1
- package/dist/commands/config.js +5 -1
- package/dist/commands/import.js +71 -1
- package/dist/commands/list.js +19 -1
- package/dist/commands/status.js +2 -2
- package/dist/lib/agent-detect.js +31 -0
- package/dist/lib/agents.js +8 -0
- package/dist/lib/discovery.js +32 -0
- package/dist/lib/global-skills.js +36 -0
- package/dist/lib/onboarding.js +39 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,26 +13,13 @@
|
|
|
13
13
|
npm install -g skillbox
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
### From Source
|
|
17
16
|
|
|
18
|
-
```bash
|
|
19
|
-
git clone https://github.com/christiananagnostou/skillbox
|
|
20
|
-
cd skillbox
|
|
21
|
-
npm install
|
|
22
|
-
npm run build
|
|
23
|
-
npm link --global
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## CI
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
npm run lint:ci
|
|
30
|
-
npm run format:check
|
|
31
|
-
npm run build
|
|
32
|
-
```
|
|
18
|
+
## Quick Start
|
|
33
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.
|
|
34
21
|
|
|
35
|
-
|
|
22
|
+
Tip: run `skillbox list` right after install to see existing skills.
|
|
36
23
|
|
|
37
24
|
```bash
|
|
38
25
|
skillbox add https://example.com/skills/linting/SKILL.md
|
|
@@ -71,10 +58,15 @@ skillbox project sync <path>
|
|
|
71
58
|
skillbox config get
|
|
72
59
|
skillbox config set --default-scope user
|
|
73
60
|
skillbox config set --default-agent claude --default-agent cursor
|
|
61
|
+
skillbox config set --add-agent codex
|
|
74
62
|
skillbox config set --manage-system
|
|
75
63
|
```
|
|
76
64
|
|
|
77
|
-
|
|
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
|
|
78
70
|
|
|
79
71
|
## Agent Mode
|
|
80
72
|
|
|
@@ -126,7 +118,7 @@ Agent paths (default):
|
|
|
126
118
|
- Amp: `.agents/skills/`, `~/.config/agents/skills/` (Claude-compatible `.claude/skills/` also supported)
|
|
127
119
|
- Antigravity: `.agent/skills/`, `~/.gemini/antigravity/skills/`
|
|
128
120
|
|
|
129
|
-
|
|
121
|
+
Note: only Codex defines a system scope path.
|
|
130
122
|
|
|
131
123
|
## Usage with AI Agents
|
|
132
124
|
|
|
@@ -154,9 +146,15 @@ Core workflow:
|
|
|
154
146
|
3. `skillbox update <name> --json`
|
|
155
147
|
```
|
|
156
148
|
|
|
157
|
-
|
|
149
|
+
## Development
|
|
158
150
|
|
|
159
|
-
|
|
151
|
+
```bash
|
|
152
|
+
git clone https://github.com/christiananagnostou/skillbox
|
|
153
|
+
cd skillbox
|
|
154
|
+
npm install
|
|
155
|
+
npm run build
|
|
156
|
+
npm link --global
|
|
157
|
+
```
|
|
160
158
|
|
|
161
159
|
## License
|
|
162
160
|
|
package/dist/cli.js
CHANGED
|
@@ -22,4 +22,9 @@ registerMeta(program);
|
|
|
22
22
|
registerProject(program);
|
|
23
23
|
registerAgent(program);
|
|
24
24
|
registerConfig(program);
|
|
25
|
-
|
|
25
|
+
const run = async () => {
|
|
26
|
+
const { runOnboarding } = await import("./lib/onboarding.js");
|
|
27
|
+
await runOnboarding();
|
|
28
|
+
program.parse();
|
|
29
|
+
};
|
|
30
|
+
void run();
|
package/dist/commands/config.js
CHANGED
|
@@ -25,6 +25,7 @@ export const registerConfig = (program) => {
|
|
|
25
25
|
config
|
|
26
26
|
.command("set")
|
|
27
27
|
.option("--default-agent <agent>", "Default agent", collect)
|
|
28
|
+
.option("--add-agent <agent>", "Add to default agents", collect)
|
|
28
29
|
.option("--default-scope <scope>", "Default scope: project or user")
|
|
29
30
|
.option("--manage-system", "Enable system scope operations")
|
|
30
31
|
.option("--json", "JSON output")
|
|
@@ -35,9 +36,12 @@ export const registerConfig = (program) => {
|
|
|
35
36
|
if (nextScope !== "project" && nextScope !== "user") {
|
|
36
37
|
throw new Error("defaultScope must be 'project' or 'user'.");
|
|
37
38
|
}
|
|
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)));
|
|
38
42
|
const next = {
|
|
39
43
|
...current,
|
|
40
|
-
defaultAgents:
|
|
44
|
+
defaultAgents: mergedAgents,
|
|
41
45
|
defaultScope: nextScope,
|
|
42
46
|
manageSystem: options.manageSystem ?? current.manageSystem,
|
|
43
47
|
};
|
package/dist/commands/import.js
CHANGED
|
@@ -5,13 +5,36 @@ import { parseSkillMarkdown, buildMetadata } from "../lib/skill-parser.js";
|
|
|
5
5
|
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
|
+
import { discoverSkills } from "../lib/discovery.js";
|
|
9
|
+
import { getSystemAgentPaths, getUserAgentPaths } from "../lib/agents.js";
|
|
8
10
|
export const registerImport = (program) => {
|
|
9
11
|
program
|
|
10
12
|
.command("import")
|
|
11
|
-
.argument("
|
|
13
|
+
.argument("[path]", "Path to skill directory")
|
|
14
|
+
.option("--global", "Import skills from user agent folders")
|
|
15
|
+
.option("--system", "Import skills from system agent folders")
|
|
12
16
|
.option("--json", "JSON output")
|
|
13
17
|
.action(async (inputPath, options) => {
|
|
14
18
|
try {
|
|
19
|
+
if (!inputPath && !options.global && !options.system) {
|
|
20
|
+
throw new Error("Provide a path or use --global/--system.");
|
|
21
|
+
}
|
|
22
|
+
if (options.global || options.system) {
|
|
23
|
+
const summary = await importGlobalSkills({
|
|
24
|
+
includeUser: Boolean(options.global),
|
|
25
|
+
includeSystem: Boolean(options.system),
|
|
26
|
+
});
|
|
27
|
+
if (isJsonEnabled(options)) {
|
|
28
|
+
printJson({
|
|
29
|
+
ok: true,
|
|
30
|
+
command: "import",
|
|
31
|
+
data: summary,
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
printInfo(`Imported ${summary.imported.length} skill(s).`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
15
38
|
const resolved = path.resolve(inputPath);
|
|
16
39
|
const skillPath = path.join(resolved, "SKILL.md");
|
|
17
40
|
const markdown = await fs.readFile(skillPath, "utf8");
|
|
@@ -48,3 +71,50 @@ export const registerImport = (program) => {
|
|
|
48
71
|
}
|
|
49
72
|
});
|
|
50
73
|
};
|
|
74
|
+
const importGlobalSkills = async (options) => {
|
|
75
|
+
const projectRoot = process.cwd();
|
|
76
|
+
const paths = [
|
|
77
|
+
...(options.includeUser ? getUserAgentPaths(projectRoot) : []),
|
|
78
|
+
...(options.includeSystem ? getSystemAgentPaths(projectRoot) : []),
|
|
79
|
+
];
|
|
80
|
+
const discovered = await discoverSkills(paths);
|
|
81
|
+
const index = await loadIndex();
|
|
82
|
+
const seen = new Set(index.skills.map((skill) => skill.name));
|
|
83
|
+
const imported = [];
|
|
84
|
+
const skipped = [];
|
|
85
|
+
for (const skill of discovered) {
|
|
86
|
+
if (seen.has(skill.name)) {
|
|
87
|
+
skipped.push(skill.name);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const markdown = await fs.readFile(skill.skillFile, "utf8");
|
|
91
|
+
const parsed = parseSkillMarkdown(markdown);
|
|
92
|
+
const metadata = buildMetadata(parsed, { type: "local" });
|
|
93
|
+
if (!parsed.description) {
|
|
94
|
+
skipped.push(skill.name);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
await ensureSkillsDir();
|
|
98
|
+
await writeSkillFiles(metadata.name, markdown, metadata);
|
|
99
|
+
const next = upsertSkill(index, {
|
|
100
|
+
name: metadata.name,
|
|
101
|
+
source: { type: "local" },
|
|
102
|
+
checksum: parsed.checksum,
|
|
103
|
+
updatedAt: metadata.updatedAt,
|
|
104
|
+
installs: [
|
|
105
|
+
{
|
|
106
|
+
scope: options.includeSystem ? "system" : "user",
|
|
107
|
+
agent: "unknown",
|
|
108
|
+
path: skill.skillDir,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
index.skills = next.skills;
|
|
113
|
+
imported.push(metadata.name);
|
|
114
|
+
}
|
|
115
|
+
await saveIndex(sortIndex(index));
|
|
116
|
+
return {
|
|
117
|
+
imported: imported.sort(),
|
|
118
|
+
skipped: skipped.sort(),
|
|
119
|
+
};
|
|
120
|
+
};
|
package/dist/commands/list.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { isJsonEnabled, printInfo, printJson, printGroupList } from "../lib/output.js";
|
|
2
2
|
import { loadIndex } from "../lib/index.js";
|
|
3
3
|
import { groupNamesByKey } from "../lib/grouping.js";
|
|
4
|
+
import { discoverGlobalSkills } from "../lib/global-skills.js";
|
|
4
5
|
export const registerList = (program) => {
|
|
5
6
|
program
|
|
6
7
|
.command("list")
|
|
7
8
|
.option("--group <group>", "Group by category, namespace, source, project")
|
|
9
|
+
.option("--project-only", "Only show project-indexed skills")
|
|
8
10
|
.option("--json", "JSON output")
|
|
9
11
|
.action(async (options) => {
|
|
10
12
|
const index = await loadIndex();
|
|
11
|
-
const
|
|
13
|
+
const globalSkills = options.projectOnly ? [] : await listGlobalSkills(index.skills);
|
|
14
|
+
const skills = [...index.skills, ...globalSkills];
|
|
12
15
|
const groupedProjects = groupByProject(skills);
|
|
13
16
|
const groupedSources = groupBySource(skills);
|
|
14
17
|
const groupedNamespaces = groupByNamespace(skills);
|
|
@@ -61,6 +64,21 @@ const groupByProject = (skills) => {
|
|
|
61
64
|
.map((install) => install.projectRoot));
|
|
62
65
|
return grouped.map((group) => ({ root: group.key, skills: group.skills }));
|
|
63
66
|
};
|
|
67
|
+
const listGlobalSkills = async (existing) => {
|
|
68
|
+
const projectRoot = process.cwd();
|
|
69
|
+
const seen = new Set(existing.map((skill) => skill.name));
|
|
70
|
+
const discovered = await discoverGlobalSkills(projectRoot);
|
|
71
|
+
return discovered
|
|
72
|
+
.filter((skill) => !seen.has(skill.name))
|
|
73
|
+
.map((skill) => ({
|
|
74
|
+
name: skill.name,
|
|
75
|
+
source: { type: "local" },
|
|
76
|
+
installs: skill.installs,
|
|
77
|
+
namespace: undefined,
|
|
78
|
+
categories: undefined,
|
|
79
|
+
tags: undefined,
|
|
80
|
+
}));
|
|
81
|
+
};
|
|
64
82
|
const groupBySource = (skills) => {
|
|
65
83
|
const grouped = groupNamesByKey(skills, (skill) => skill.name, (skill) => [skill.source.type]);
|
|
66
84
|
return grouped.map((group) => ({ source: group.key, skills: group.skills }));
|
package/dist/commands/status.js
CHANGED
|
@@ -16,9 +16,9 @@ export const registerStatus = (program) => {
|
|
|
16
16
|
const config = await loadConfig();
|
|
17
17
|
const results = [];
|
|
18
18
|
for (const skill of index.skills) {
|
|
19
|
-
const projects = (skill.installs ?? [])
|
|
19
|
+
const projects = Array.from(new Set((skill.installs ?? [])
|
|
20
20
|
.filter((install) => install.scope === "project" && install.projectRoot)
|
|
21
|
-
.map((install) => install.projectRoot);
|
|
21
|
+
.map((install) => install.projectRoot)));
|
|
22
22
|
const allowSystem = config.manageSystem;
|
|
23
23
|
const isSystem = (skill.installs ?? []).some((install) => install.scope === "system");
|
|
24
24
|
if (isSystem && !allowSystem) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
const home = os.homedir();
|
|
5
|
+
const agentRoots = {
|
|
6
|
+
opencode: [path.join(home, ".config", "opencode")],
|
|
7
|
+
claude: [path.join(home, ".claude")],
|
|
8
|
+
cursor: [path.join(home, ".cursor")],
|
|
9
|
+
codex: [path.join(home, ".codex")],
|
|
10
|
+
amp: [path.join(home, ".config", "agents")],
|
|
11
|
+
antigravity: [path.join(home, ".gemini", "antigravity")],
|
|
12
|
+
};
|
|
13
|
+
const exists = async (target) => {
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(target);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const detectAgents = async () => {
|
|
23
|
+
const detected = [];
|
|
24
|
+
for (const [agent, roots] of Object.entries(agentRoots)) {
|
|
25
|
+
const matches = await Promise.all(roots.map((root) => exists(root)));
|
|
26
|
+
if (matches.some(Boolean)) {
|
|
27
|
+
detected.push(agent);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return detected;
|
|
31
|
+
};
|
package/dist/lib/agents.js
CHANGED
|
@@ -38,6 +38,14 @@ export const agentPaths = (projectRoot) => ({
|
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
40
|
export const allAgents = ["opencode", "claude", "cursor", "codex", "amp", "antigravity"];
|
|
41
|
+
export const getUserAgentPaths = (projectRoot) => {
|
|
42
|
+
const paths = agentPaths(projectRoot);
|
|
43
|
+
return Object.values(paths).flatMap((entry) => entry.user);
|
|
44
|
+
};
|
|
45
|
+
export const getSystemAgentPaths = (projectRoot) => {
|
|
46
|
+
const paths = agentPaths(projectRoot);
|
|
47
|
+
return Object.values(paths).flatMap((entry) => entry.system ?? []);
|
|
48
|
+
};
|
|
41
49
|
const agentSet = new Set(allAgents);
|
|
42
50
|
export const isAgentId = (value) => {
|
|
43
51
|
return agentSet.has(value);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const exists = async (target) => {
|
|
4
|
+
try {
|
|
5
|
+
await fs.access(target);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const discoverSkills = async (paths) => {
|
|
13
|
+
const results = [];
|
|
14
|
+
for (const root of paths) {
|
|
15
|
+
if (!(await exists(root))) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (!entry.isDirectory()) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const skillDir = path.join(root, entry.name);
|
|
24
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
25
|
+
if (!(await exists(skillFile))) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
results.push({ name: entry.name, skillDir, skillFile });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return results;
|
|
32
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getUserAgentPaths, getSystemAgentPaths } from "./agents.js";
|
|
2
|
+
import { discoverSkills } from "./discovery.js";
|
|
3
|
+
export const discoverGlobalSkills = async (projectRoot) => {
|
|
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;
|
|
36
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { allAgents, isAgentId } from "./agents.js";
|
|
4
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
5
|
+
import { detectAgents } from "./agent-detect.js";
|
|
6
|
+
const formatPrompt = (detected) => {
|
|
7
|
+
if (detected.length === 0) {
|
|
8
|
+
return `Which agents do you use? (comma-separated) [${allAgents.join(", ")}]: `;
|
|
9
|
+
}
|
|
10
|
+
return `Detected agents: ${detected.join(", ")}. Press enter to accept or edit: `;
|
|
11
|
+
};
|
|
12
|
+
const promptAgents = async () => {
|
|
13
|
+
const detected = await detectAgents();
|
|
14
|
+
const rl = readline.createInterface({ input, output });
|
|
15
|
+
const answer = await rl.question(formatPrompt(detected));
|
|
16
|
+
rl.close();
|
|
17
|
+
const raw = answer.trim().length > 0 ? answer : detected.join(",");
|
|
18
|
+
const selected = raw
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((agent) => agent.trim())
|
|
21
|
+
.filter((agent) => agent.length > 0)
|
|
22
|
+
.filter(isAgentId);
|
|
23
|
+
if (selected.length > 0) {
|
|
24
|
+
return selected;
|
|
25
|
+
}
|
|
26
|
+
return detected.length > 0 ? detected : allAgents;
|
|
27
|
+
};
|
|
28
|
+
export const runOnboarding = async () => {
|
|
29
|
+
const config = await loadConfig();
|
|
30
|
+
if (config.defaultAgents.length > 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const selected = await promptAgents();
|
|
34
|
+
const next = {
|
|
35
|
+
...config,
|
|
36
|
+
defaultAgents: selected,
|
|
37
|
+
};
|
|
38
|
+
await saveConfig(next);
|
|
39
|
+
};
|