skillbox 0.3.2 → 0.3.4
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 +2 -1
- package/dist/cli.js +1 -1
- package/dist/commands/add-repo.js +55 -53
- package/dist/commands/add.js +21 -23
- package/dist/commands/import.js +14 -24
- package/dist/commands/list.js +38 -4
- package/dist/commands/project.js +49 -1
- package/dist/commands/update.js +40 -38
- package/dist/lib/agents.js +10 -4
- package/dist/lib/fetcher.js +40 -1
- package/dist/lib/index.js +22 -1
- package/dist/lib/output.js +42 -0
- package/dist/lib/skill-store.js +21 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -67,8 +67,9 @@ skillbox update [name] # update skills
|
|
|
67
67
|
|
|
68
68
|
| Command | Description |
|
|
69
69
|
|---------|-------------|
|
|
70
|
-
| `skillbox project add <path>` | Register a project |
|
|
70
|
+
| `skillbox project add <path>` | Register a project and auto-import skills from `skills/` and agent directories |
|
|
71
71
|
| `skillbox project list` | List registered projects |
|
|
72
|
+
| `skillbox project inspect <path>` | Show project details and skills |
|
|
72
73
|
| `skillbox project sync <path>` | Re-sync skills to a project |
|
|
73
74
|
|
|
74
75
|
## Supported Agents
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import { registerRemove } from "./commands/remove.js";
|
|
|
12
12
|
import { registerStatus } from "./commands/status.js";
|
|
13
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.3.
|
|
15
|
+
program.name("skillbox").description("Local-first, agent-agnostic skills manager").version("0.3.4");
|
|
16
16
|
registerAdd(program);
|
|
17
17
|
registerAgent(program);
|
|
18
18
|
registerConfig(program);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { getErrorMessage } from "../lib/command.js";
|
|
2
3
|
import { loadConfig } from "../lib/config.js";
|
|
3
4
|
import { parseRepoRef } from "../lib/github.js";
|
|
4
5
|
import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
|
|
5
6
|
import { recordInstallPaths } from "../lib/installs.js";
|
|
6
|
-
import { printInfo, printJson } from "../lib/output.js";
|
|
7
|
+
import { printFailure, printInfo, printJson, printSkipped, printSuccess, startSpinner, stopSpinner, } from "../lib/output.js";
|
|
7
8
|
import { buildProjectAgentPaths } from "../lib/project-paths.js";
|
|
8
9
|
import { fetchRepoFile, listRepoSkills, normalizeRepoRef, writeRepoSkillDirectory, } from "../lib/repo-skills.js";
|
|
9
10
|
import { ensureProjectRegistered, resolveRuntime } from "../lib/runtime.js";
|
|
@@ -38,25 +39,22 @@ async function installSkillTargets(skillName, options, installs) {
|
|
|
38
39
|
if (!map) {
|
|
39
40
|
continue;
|
|
40
41
|
}
|
|
41
|
-
const targets = buildTargets(agent, map, scope).map((target) => target.path);
|
|
42
|
+
const targets = buildTargets(agent, map, scope).map((target) => path.join(target.path, skillName));
|
|
42
43
|
const results = await installSkillToTargets(skillName, targets, config);
|
|
43
|
-
const written = results
|
|
44
|
-
.filter((result) => result.mode !== "skipped")
|
|
45
|
-
.map((result) => result.path);
|
|
46
44
|
const warnings = buildSymlinkWarning(agent, results);
|
|
47
45
|
for (const warning of warnings) {
|
|
48
46
|
printInfo(warning);
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
48
|
+
// Record all targets, not just successfully written ones
|
|
49
|
+
// The warning tells users about symlink issues, but we still track the install intent
|
|
50
|
+
const deduped = recordInstallPaths(targets, recordedPaths);
|
|
51
|
+
for (const target of deduped) {
|
|
52
|
+
installs.push({
|
|
53
|
+
scope,
|
|
54
|
+
agent,
|
|
55
|
+
path: target,
|
|
56
|
+
projectRoot: scope === "project" ? projectRoot : undefined,
|
|
57
|
+
});
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
}
|
|
@@ -86,16 +84,27 @@ export async function handleRepoInstall(input, options) {
|
|
|
86
84
|
}
|
|
87
85
|
const summary = { installed: [], updated: [], skipped: [], failed: [] };
|
|
88
86
|
const index = await loadIndex();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
const showProgress = !options.json;
|
|
88
|
+
const selectedSkills = skills.filter((s) => selected.includes(s.name));
|
|
89
|
+
const total = selectedSkills.length;
|
|
90
|
+
if (showProgress && total > 0) {
|
|
91
|
+
printInfo(`Adding ${total} skill${total === 1 ? "" : "s"} from ${ref.owner}/${ref.repo}...\n`);
|
|
92
|
+
}
|
|
93
|
+
for (let i = 0; i < selectedSkills.length; i++) {
|
|
94
|
+
const skill = selectedSkills[i];
|
|
95
|
+
const progress = `(${i + 1}/${total})`;
|
|
93
96
|
const alreadyInstalled = index.skills.some((entry) => entry.name === skill.name);
|
|
97
|
+
if (showProgress) {
|
|
98
|
+
startSpinner(`${skill.name} ${progress}`);
|
|
99
|
+
}
|
|
94
100
|
try {
|
|
95
101
|
const skillMarkdown = await fetchRepoFile(ref, ref.path ? `${ref.path}/${skill.skillFile}` : skill.skillFile);
|
|
96
102
|
const parsed = parseSkillMarkdown(skillMarkdown);
|
|
97
103
|
if (!parsed.description) {
|
|
98
104
|
summary.skipped.push(skill.name);
|
|
105
|
+
if (showProgress) {
|
|
106
|
+
printSkipped(skill.name, "missing description");
|
|
107
|
+
}
|
|
99
108
|
continue;
|
|
100
109
|
}
|
|
101
110
|
await writeRepoSkillDirectory(ref, skill.path, skill.name);
|
|
@@ -126,56 +135,49 @@ export async function handleRepoInstall(input, options) {
|
|
|
126
135
|
index.skills = nextIndex.skills;
|
|
127
136
|
if (alreadyInstalled) {
|
|
128
137
|
summary.updated.push(skill.name);
|
|
138
|
+
if (showProgress) {
|
|
139
|
+
printSuccess(skill.name, "updated");
|
|
140
|
+
}
|
|
129
141
|
}
|
|
130
142
|
else {
|
|
131
143
|
summary.installed.push(skill.name);
|
|
144
|
+
if (showProgress) {
|
|
145
|
+
printSuccess(skill.name);
|
|
146
|
+
}
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
catch (error) {
|
|
135
150
|
const message = getErrorMessage(error, "unknown");
|
|
136
151
|
summary.failed.push({ name: skill.name, reason: message });
|
|
152
|
+
if (showProgress) {
|
|
153
|
+
printFailure(skill.name, message);
|
|
154
|
+
}
|
|
137
155
|
}
|
|
138
156
|
}
|
|
157
|
+
if (showProgress) {
|
|
158
|
+
stopSpinner();
|
|
159
|
+
}
|
|
139
160
|
await saveIndex(sortIndex(index));
|
|
140
161
|
if (options.json) {
|
|
141
162
|
printJson({ ok: true, command: "add", data: { repo: `${ref.owner}/${ref.repo}`, ...summary } });
|
|
142
163
|
return;
|
|
143
164
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
printInfo("");
|
|
150
|
-
printInfo(`Installed (${summary.installed.length}):`);
|
|
151
|
-
for (const name of summary.installed) {
|
|
152
|
-
printInfo(` ✓ ${name}`);
|
|
153
|
-
}
|
|
165
|
+
// Summary line
|
|
166
|
+
const added = summary.installed.length + summary.updated.length;
|
|
167
|
+
const failed = summary.failed.length;
|
|
168
|
+
const skipped = summary.skipped.length;
|
|
169
|
+
if (added > 0 && failed === 0 && skipped === 0) {
|
|
170
|
+
printInfo(`\nAdded ${added} skill${added === 1 ? "" : "s"} from ${ref.owner}/${ref.repo}.`);
|
|
154
171
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (summary.skipped.length > 0) {
|
|
163
|
-
printInfo("");
|
|
164
|
-
printInfo(`Skipped (${summary.skipped.length}):`);
|
|
165
|
-
for (const name of summary.skipped) {
|
|
166
|
-
printInfo(` - ${name} (missing description)`);
|
|
167
|
-
}
|
|
172
|
+
else if (added > 0) {
|
|
173
|
+
const parts = [];
|
|
174
|
+
if (failed > 0)
|
|
175
|
+
parts.push(`${failed} failed`);
|
|
176
|
+
if (skipped > 0)
|
|
177
|
+
parts.push(`${skipped} skipped`);
|
|
178
|
+
printInfo(`\nAdded ${added} skill${added === 1 ? "" : "s"} (${parts.join(", ")}).`);
|
|
168
179
|
}
|
|
169
|
-
|
|
170
|
-
printInfo("");
|
|
171
|
-
printInfo(`Failed (${summary.failed.length}):`);
|
|
172
|
-
for (const failure of summary.failed) {
|
|
173
|
-
printInfo(` ✗ ${failure.name} (${failure.reason})`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const total = summary.installed.length + summary.updated.length;
|
|
177
|
-
if (total === 0) {
|
|
178
|
-
printInfo("");
|
|
179
|
-
printInfo("No skills were added.");
|
|
180
|
+
else {
|
|
181
|
+
printInfo("\nNo skills were added.");
|
|
180
182
|
}
|
|
181
183
|
}
|
package/dist/commands/add.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { handleCommandError } from "../lib/command.js";
|
|
2
3
|
import { loadConfig } from "../lib/config.js";
|
|
3
4
|
import { fetchText } from "../lib/fetcher.js";
|
|
4
5
|
import { collect } from "../lib/fs-utils.js";
|
|
5
6
|
import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
|
|
6
7
|
import { recordInstallPaths } from "../lib/installs.js";
|
|
7
|
-
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
8
|
+
import { isJsonEnabled, printInfo, printJson, printSuccess, startSpinner, stopSpinner, } from "../lib/output.js";
|
|
8
9
|
import { buildProjectAgentPaths } from "../lib/project-paths.js";
|
|
9
10
|
import { ensureProjectRegistered, resolveRuntime } from "../lib/runtime.js";
|
|
10
11
|
import { buildMetadata, inferNameFromUrl, parseSkillMarkdown } from "../lib/skill-parser.js";
|
|
@@ -33,17 +34,28 @@ export function registerAdd(program) {
|
|
|
33
34
|
});
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
37
|
+
const showProgress = !isJsonEnabled(options);
|
|
38
|
+
const inferred = inferNameFromUrl(url);
|
|
39
|
+
const displayName = options.name ?? inferred ?? "skill";
|
|
40
|
+
if (showProgress) {
|
|
41
|
+
startSpinner(`Adding ${displayName}`);
|
|
42
|
+
}
|
|
36
43
|
const skillMarkdown = await fetchText(url);
|
|
37
44
|
const parsed = parseSkillMarkdown(skillMarkdown);
|
|
38
|
-
const inferred = inferNameFromUrl(url);
|
|
39
45
|
const skillName = options.name ?? inferred ?? parsed.name;
|
|
40
46
|
if (!skillName) {
|
|
47
|
+
if (showProgress)
|
|
48
|
+
stopSpinner();
|
|
41
49
|
throw new Error("Unable to infer skill name. Use --name to specify it.");
|
|
42
50
|
}
|
|
43
51
|
if (!parsed.name && !options.name) {
|
|
52
|
+
if (showProgress)
|
|
53
|
+
stopSpinner();
|
|
44
54
|
throw new Error("Skill frontmatter missing name. Provide --name to continue.");
|
|
45
55
|
}
|
|
46
56
|
if (!parsed.description) {
|
|
57
|
+
if (showProgress)
|
|
58
|
+
stopSpinner();
|
|
47
59
|
throw new Error("Skill frontmatter missing description. Convert the source into a valid skill.");
|
|
48
60
|
}
|
|
49
61
|
const metadata = buildMetadata(parsed, { type: "url", url }, skillName);
|
|
@@ -71,16 +83,15 @@ export function registerAdd(program) {
|
|
|
71
83
|
if (!map) {
|
|
72
84
|
continue;
|
|
73
85
|
}
|
|
74
|
-
const targets = buildTargets(agent, map, scope).map((target) => target.path);
|
|
86
|
+
const targets = buildTargets(agent, map, scope).map((target) => path.join(target.path, skillName));
|
|
75
87
|
const results = await installSkillToTargets(skillName, targets, config);
|
|
76
|
-
const written = results
|
|
77
|
-
.filter((result) => result.mode !== "skipped")
|
|
78
|
-
.map((result) => result.path);
|
|
79
88
|
const warnings = buildSymlinkWarning(agent, results);
|
|
80
89
|
for (const warning of warnings) {
|
|
81
90
|
printInfo(warning);
|
|
82
91
|
}
|
|
83
|
-
|
|
92
|
+
// Record all targets, not just successfully written ones
|
|
93
|
+
// The warning tells users about symlink issues, but we still track the install intent
|
|
94
|
+
const deduped = recordInstallPaths(targets, recordedPaths);
|
|
84
95
|
if (deduped.length > 0) {
|
|
85
96
|
installed.push({ agent, scope, targets: deduped });
|
|
86
97
|
for (const target of deduped) {
|
|
@@ -102,6 +113,7 @@ export function registerAdd(program) {
|
|
|
102
113
|
});
|
|
103
114
|
await saveIndex(sortIndex(nextIndex));
|
|
104
115
|
if (isJsonEnabled(options)) {
|
|
116
|
+
stopSpinner();
|
|
105
117
|
printJson({
|
|
106
118
|
ok: true,
|
|
107
119
|
command: "add",
|
|
@@ -114,22 +126,8 @@ export function registerAdd(program) {
|
|
|
114
126
|
});
|
|
115
127
|
return;
|
|
116
128
|
}
|
|
117
|
-
|
|
118
|
-
printInfo(
|
|
119
|
-
printInfo("Source: url");
|
|
120
|
-
printInfo(` ${url}`);
|
|
121
|
-
if (installs.length > 0) {
|
|
122
|
-
printInfo("");
|
|
123
|
-
printInfo("Installed to:");
|
|
124
|
-
for (const install of installs) {
|
|
125
|
-
const scopeLabel = install.scope === "project" ? `project:${install.projectRoot}` : "user";
|
|
126
|
-
printInfo(` ✓ ${scopeLabel}/${install.agent}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
printInfo("");
|
|
131
|
-
printInfo("No agent targets were updated.");
|
|
132
|
-
}
|
|
129
|
+
printSuccess(skillName);
|
|
130
|
+
printInfo(`\nAdded skill from ${url}.`);
|
|
133
131
|
}
|
|
134
132
|
catch (error) {
|
|
135
133
|
handleCommandError(options, "add", error);
|
package/dist/commands/import.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { getUserPathsForAgents } from "../lib/agents.js";
|
|
4
3
|
import { handleCommandError } from "../lib/command.js";
|
|
@@ -6,8 +5,7 @@ import { discoverSkills } from "../lib/discovery.js";
|
|
|
6
5
|
import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
|
|
7
6
|
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
8
7
|
import { resolveRuntime } from "../lib/runtime.js";
|
|
9
|
-
import {
|
|
10
|
-
import { ensureSkillsDir, writeSkillFiles } from "../lib/skill-store.js";
|
|
8
|
+
import { importSkillFromDir } from "../lib/skill-store.js";
|
|
11
9
|
async function importGlobalSkills(agents) {
|
|
12
10
|
const projectRoot = process.cwd();
|
|
13
11
|
const agentPaths = getUserPathsForAgents(projectRoot, agents);
|
|
@@ -32,24 +30,20 @@ async function importGlobalSkills(agents) {
|
|
|
32
30
|
skipped.add(skill.name);
|
|
33
31
|
continue;
|
|
34
32
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
if (!parsed.description) {
|
|
33
|
+
const data = await importSkillFromDir(skill.skillFile);
|
|
34
|
+
if (!data) {
|
|
38
35
|
skipped.add(skill.name);
|
|
39
36
|
continue;
|
|
40
37
|
}
|
|
41
|
-
const metadata = buildMetadata(parsed, { type: "local" });
|
|
42
|
-
await ensureSkillsDir();
|
|
43
|
-
await writeSkillFiles(metadata.name, markdown, metadata);
|
|
44
38
|
const next = upsertSkill(index, {
|
|
45
|
-
name:
|
|
39
|
+
name: data.name,
|
|
46
40
|
source: { type: "local" },
|
|
47
|
-
checksum:
|
|
48
|
-
updatedAt:
|
|
41
|
+
checksum: data.checksum,
|
|
42
|
+
updatedAt: data.updatedAt,
|
|
49
43
|
installs: [{ scope: "user", agent: skill.agent, path: skill.skillDir }],
|
|
50
44
|
});
|
|
51
45
|
index.skills = next.skills;
|
|
52
|
-
imported.add(
|
|
46
|
+
imported.add(data.name);
|
|
53
47
|
}
|
|
54
48
|
await saveIndex(sortIndex(index));
|
|
55
49
|
return {
|
|
@@ -81,27 +75,23 @@ export function registerImport(program) {
|
|
|
81
75
|
}
|
|
82
76
|
const resolved = path.resolve(inputPath);
|
|
83
77
|
const skillPath = path.join(resolved, "SKILL.md");
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
if (!parsed.description) {
|
|
78
|
+
const data = await importSkillFromDir(skillPath);
|
|
79
|
+
if (!data) {
|
|
87
80
|
throw new Error("Skill frontmatter missing description.");
|
|
88
81
|
}
|
|
89
|
-
const metadata = buildMetadata(parsed, { type: "local" });
|
|
90
|
-
await ensureSkillsDir();
|
|
91
|
-
await writeSkillFiles(metadata.name, markdown, metadata);
|
|
92
82
|
const index = await loadIndex();
|
|
93
83
|
const updated = upsertSkill(index, {
|
|
94
|
-
name:
|
|
84
|
+
name: data.name,
|
|
95
85
|
source: { type: "local" },
|
|
96
|
-
checksum:
|
|
97
|
-
updatedAt:
|
|
86
|
+
checksum: data.checksum,
|
|
87
|
+
updatedAt: data.updatedAt,
|
|
98
88
|
});
|
|
99
89
|
await saveIndex(sortIndex(updated));
|
|
100
90
|
if (isJsonEnabled(options)) {
|
|
101
|
-
printJson({ ok: true, command: "import", data: { name:
|
|
91
|
+
printJson({ ok: true, command: "import", data: { name: data.name, path: resolved } });
|
|
102
92
|
return;
|
|
103
93
|
}
|
|
104
|
-
printInfo(`Imported skill: ${
|
|
94
|
+
printInfo(`Imported skill: ${data.name}`);
|
|
105
95
|
}
|
|
106
96
|
catch (error) {
|
|
107
97
|
handleCommandError(options, "import", error);
|
package/dist/commands/list.js
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import terminalLink from "terminal-link";
|
|
2
4
|
import { discoverGlobalSkills } from "../lib/global-skills.js";
|
|
3
5
|
import { loadIndex } from "../lib/index.js";
|
|
4
6
|
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
5
7
|
import { resolveRuntime } from "../lib/runtime.js";
|
|
6
8
|
import { groupAndSort, sortByName } from "../lib/source-grouping.js";
|
|
9
|
+
function getSkillUrl(skill) {
|
|
10
|
+
if (skill.source.type === "url" && skill.source.url) {
|
|
11
|
+
return skill.source.url;
|
|
12
|
+
}
|
|
13
|
+
if (skill.source.type === "git" && skill.source.repo) {
|
|
14
|
+
const repo = skill.source.repo;
|
|
15
|
+
// If already a full URL, use it directly
|
|
16
|
+
if (repo.startsWith("http://") || repo.startsWith("https://")) {
|
|
17
|
+
return repo;
|
|
18
|
+
}
|
|
19
|
+
// Convert shorthand (user/repo) to full GitHub URL
|
|
20
|
+
return `https://github.com/${repo}`;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function linkSkillName(skill) {
|
|
25
|
+
const url = getSkillUrl(skill);
|
|
26
|
+
if (url && terminalLink.isSupported) {
|
|
27
|
+
const linkIcon = terminalLink("‹↗›", url);
|
|
28
|
+
return `${skill.name} ${chalk.dim(linkIcon)}`;
|
|
29
|
+
}
|
|
30
|
+
return skill.name;
|
|
31
|
+
}
|
|
7
32
|
async function detectSubcommands(skillPath) {
|
|
8
33
|
try {
|
|
9
34
|
const entries = await fs.readdir(skillPath);
|
|
@@ -108,7 +133,7 @@ function printScopeGroup(group) {
|
|
|
108
133
|
for (const sourceGroup of projectGroup.sourceGroups) {
|
|
109
134
|
printInfo(` ${sourceGroup.source}`);
|
|
110
135
|
for (const skill of sourceGroup.skills) {
|
|
111
|
-
printInfo(` ${skill
|
|
136
|
+
printInfo(` ${linkSkillName(skill)}`);
|
|
112
137
|
if (skill.subcommands.length > 0) {
|
|
113
138
|
printInfo(` → ${skill.subcommands.join(", ")}`);
|
|
114
139
|
}
|
|
@@ -121,7 +146,7 @@ function printScopeGroup(group) {
|
|
|
121
146
|
printInfo("");
|
|
122
147
|
printInfo(`${sourceGroup.source}`);
|
|
123
148
|
for (const skill of sourceGroup.skills) {
|
|
124
|
-
printInfo(` ${skill
|
|
149
|
+
printInfo(` ${linkSkillName(skill)}`);
|
|
125
150
|
if (skill.subcommands.length > 0) {
|
|
126
151
|
printInfo(` → ${skill.subcommands.join(", ")}`);
|
|
127
152
|
}
|
|
@@ -147,6 +172,14 @@ function filterByAgents(skills, agents) {
|
|
|
147
172
|
const agentSet = new Set(agents);
|
|
148
173
|
return skills.filter((skill) => skill.installs?.some((install) => install.agent && agentSet.has(install.agent)));
|
|
149
174
|
}
|
|
175
|
+
function filterUserScope(skills) {
|
|
176
|
+
return skills
|
|
177
|
+
.filter((skill) => skill.installs?.some((install) => install.scope === "user") ?? !skill.installs?.length)
|
|
178
|
+
.map((skill) => ({
|
|
179
|
+
...skill,
|
|
180
|
+
installs: skill.installs?.filter((install) => install.scope === "user"),
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
150
183
|
export function registerList(program) {
|
|
151
184
|
program
|
|
152
185
|
.command("list")
|
|
@@ -161,8 +194,9 @@ export function registerList(program) {
|
|
|
161
194
|
const indexedSkills = options.agents
|
|
162
195
|
? filterByAgents(index.skills, runtime.agentList)
|
|
163
196
|
: index.skills;
|
|
164
|
-
const
|
|
165
|
-
const
|
|
197
|
+
const scopedSkills = options.global ? filterUserScope(indexedSkills) : indexedSkills;
|
|
198
|
+
const mergedSkills = [...scopedSkills, ...globalSkills];
|
|
199
|
+
const enrichedSkills = await enrichWithSubcommands(mergedSkills);
|
|
166
200
|
if (isJsonEnabled(options)) {
|
|
167
201
|
printJson({
|
|
168
202
|
ok: true,
|
package/dist/commands/project.js
CHANGED
|
@@ -1,11 +1,53 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { allAgents, getProjectPathsForAgents } from "../lib/agents.js";
|
|
2
3
|
import { handleCommandError } from "../lib/command.js";
|
|
4
|
+
import { discoverSkills } from "../lib/discovery.js";
|
|
3
5
|
import { collect } from "../lib/fs-utils.js";
|
|
4
|
-
import { loadIndex } from "../lib/index.js";
|
|
6
|
+
import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
|
|
5
7
|
import { collectProjectSkills, getProjectInstallPaths, getProjectSkills } from "../lib/installs.js";
|
|
6
8
|
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
7
9
|
import { loadProjects, saveProjects, upsertProject } from "../lib/projects.js";
|
|
10
|
+
import { importSkillFromDir } from "../lib/skill-store.js";
|
|
8
11
|
import { copySkillToInstallPaths } from "../lib/sync.js";
|
|
12
|
+
function getProjectSkillPaths(projectRoot) {
|
|
13
|
+
const agentPaths = getProjectPathsForAgents(projectRoot, allAgents);
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
// Add generic skills/ directory
|
|
16
|
+
seen.add(path.join(projectRoot, "skills"));
|
|
17
|
+
// Add all agent-specific project paths
|
|
18
|
+
for (const { path: agentPath } of agentPaths) {
|
|
19
|
+
seen.add(agentPath);
|
|
20
|
+
}
|
|
21
|
+
return Array.from(seen);
|
|
22
|
+
}
|
|
23
|
+
async function discoverProjectSkills(projectRoot) {
|
|
24
|
+
const skillPaths = getProjectSkillPaths(projectRoot);
|
|
25
|
+
const discovered = await discoverSkills(skillPaths);
|
|
26
|
+
if (discovered.length === 0) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const index = await loadIndex();
|
|
30
|
+
const imported = [];
|
|
31
|
+
for (const skill of discovered) {
|
|
32
|
+
const data = await importSkillFromDir(skill.skillFile);
|
|
33
|
+
if (!data) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const updated = upsertSkill(index, {
|
|
37
|
+
name: data.name,
|
|
38
|
+
source: { type: "local" },
|
|
39
|
+
checksum: data.checksum,
|
|
40
|
+
updatedAt: data.updatedAt,
|
|
41
|
+
installs: [{ scope: "project", agent: "claude", path: skill.skillDir, projectRoot }],
|
|
42
|
+
});
|
|
43
|
+
index.skills = updated.skills;
|
|
44
|
+
imported.push(data.name);
|
|
45
|
+
}
|
|
46
|
+
if (imported.length > 0) {
|
|
47
|
+
await saveIndex(sortIndex(index));
|
|
48
|
+
}
|
|
49
|
+
return imported;
|
|
50
|
+
}
|
|
9
51
|
function parseAgentPaths(entries) {
|
|
10
52
|
const overrides = {};
|
|
11
53
|
for (const entry of entries) {
|
|
@@ -22,6 +64,7 @@ export function registerProject(program) {
|
|
|
22
64
|
const project = program.command("project").description("Manage projects");
|
|
23
65
|
project
|
|
24
66
|
.command("add")
|
|
67
|
+
.description("Register a project and import skills from its skills/ directory")
|
|
25
68
|
.argument("<path>", "Project path")
|
|
26
69
|
.option("--agent-path <agentPath>", "Agent path override (agent=path)", collect)
|
|
27
70
|
.option("--json", "JSON output")
|
|
@@ -45,6 +88,7 @@ export function registerProject(program) {
|
|
|
45
88
|
}),
|
|
46
89
|
};
|
|
47
90
|
await saveProjects(merged);
|
|
91
|
+
const importedSkills = await discoverProjectSkills(resolved);
|
|
48
92
|
if (isJsonEnabled(options)) {
|
|
49
93
|
printJson({
|
|
50
94
|
ok: true,
|
|
@@ -52,11 +96,15 @@ export function registerProject(program) {
|
|
|
52
96
|
data: {
|
|
53
97
|
path: resolved,
|
|
54
98
|
agentPaths: merged.projects.find((p) => p.root === resolved)?.agentPaths ?? {},
|
|
99
|
+
skills: importedSkills,
|
|
55
100
|
},
|
|
56
101
|
});
|
|
57
102
|
return;
|
|
58
103
|
}
|
|
59
104
|
printInfo(`Project registered: ${resolved}`);
|
|
105
|
+
if (importedSkills.length > 0) {
|
|
106
|
+
printInfo(`Discovered ${importedSkills.length} skill(s): ${importedSkills.join(", ")}`);
|
|
107
|
+
}
|
|
60
108
|
}
|
|
61
109
|
catch (error) {
|
|
62
110
|
handleCommandError(options, "project add", error);
|
package/dist/commands/update.js
CHANGED
|
@@ -4,7 +4,7 @@ import { loadConfig } from "../lib/config.js";
|
|
|
4
4
|
import { fetchText } from "../lib/fetcher.js";
|
|
5
5
|
import { loadIndex, saveIndex, sortIndex, upsertSkill } from "../lib/index.js";
|
|
6
6
|
import { getInstallPaths } from "../lib/installs.js";
|
|
7
|
-
import { isJsonEnabled, printInfo, printJson } from "../lib/output.js";
|
|
7
|
+
import { isJsonEnabled, printFailure, printInfo, printJson, printSkipped, printSuccess, startSpinner, stopSpinner, } from "../lib/output.js";
|
|
8
8
|
import { fetchRepoFile, normalizeRepoRef, writeRepoSkillDirectory } from "../lib/repo-skills.js";
|
|
9
9
|
import { buildMetadata, parseSkillMarkdown } from "../lib/skill-parser.js";
|
|
10
10
|
import { ensureSkillsDir, writeSkillFiles, writeSkillMetadata } from "../lib/skill-store.js";
|
|
@@ -21,31 +21,6 @@ function groupBySource(results) {
|
|
|
21
21
|
failedCount: items.filter((r) => r.status === "failed").length,
|
|
22
22
|
}));
|
|
23
23
|
}
|
|
24
|
-
function formatSourceHeader(group) {
|
|
25
|
-
const count = group.results.length;
|
|
26
|
-
const skillWord = count === 1 ? "skill" : "skills";
|
|
27
|
-
if (group.source === "local") {
|
|
28
|
-
return `${group.source} (${count} ${skillWord} - skipped)`;
|
|
29
|
-
}
|
|
30
|
-
if (group.failedCount > 0) {
|
|
31
|
-
return `${group.source} (${count} ${skillWord}, ${group.failedCount} failed)`;
|
|
32
|
-
}
|
|
33
|
-
return `${group.source} (${count} ${skillWord})`;
|
|
34
|
-
}
|
|
35
|
-
function printSourceGroup(group) {
|
|
36
|
-
printInfo(formatSourceHeader(group));
|
|
37
|
-
for (const result of group.results) {
|
|
38
|
-
if (result.status === "skipped") {
|
|
39
|
-
printInfo(` - ${result.name}`);
|
|
40
|
-
}
|
|
41
|
-
else if (result.status === "failed") {
|
|
42
|
-
printInfo(` ✗ ${result.name} (${result.error ?? "failed"})`);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
printInfo(` ✓ ${result.name}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
24
|
async function updateUrlSkill(skill, index, projectRoot, config) {
|
|
50
25
|
if (!skill.source.url) {
|
|
51
26
|
return;
|
|
@@ -129,39 +104,72 @@ export function registerUpdate(program) {
|
|
|
129
104
|
const config = await loadConfig();
|
|
130
105
|
const projectRoot = options.project ? path.resolve(options.project) : null;
|
|
131
106
|
const results = [];
|
|
132
|
-
|
|
107
|
+
const showProgress = !isJsonEnabled(options);
|
|
108
|
+
if (showProgress && targets.length > 0) {
|
|
109
|
+
printInfo(`Updating ${targets.length} skill${targets.length === 1 ? "" : "s"}...\n`);
|
|
110
|
+
}
|
|
111
|
+
const total = targets.length;
|
|
112
|
+
for (let i = 0; i < targets.length; i++) {
|
|
113
|
+
const skill = targets[i];
|
|
114
|
+
const progress = `(${i + 1}/${total})`;
|
|
133
115
|
if (skill.source.type === "url") {
|
|
116
|
+
if (showProgress) {
|
|
117
|
+
startSpinner(`${skill.name} ${progress}`);
|
|
118
|
+
}
|
|
134
119
|
try {
|
|
135
120
|
await updateUrlSkill(skill, index, projectRoot, config);
|
|
136
121
|
results.push({ name: skill.name, source: "url", status: "updated" });
|
|
122
|
+
if (showProgress) {
|
|
123
|
+
printSuccess(skill.name);
|
|
124
|
+
}
|
|
137
125
|
}
|
|
138
126
|
catch (err) {
|
|
127
|
+
const errorMsg = err instanceof Error ? err.message : "unknown error";
|
|
139
128
|
results.push({
|
|
140
129
|
name: skill.name,
|
|
141
130
|
source: "url",
|
|
142
131
|
status: "failed",
|
|
143
|
-
error:
|
|
132
|
+
error: errorMsg,
|
|
144
133
|
});
|
|
134
|
+
if (showProgress) {
|
|
135
|
+
printFailure(skill.name, errorMsg);
|
|
136
|
+
}
|
|
145
137
|
}
|
|
146
138
|
}
|
|
147
139
|
else if (skill.source.type === "git") {
|
|
140
|
+
if (showProgress) {
|
|
141
|
+
startSpinner(`${skill.name} ${progress}`);
|
|
142
|
+
}
|
|
148
143
|
try {
|
|
149
144
|
await updateGitSkill(skill, index, projectRoot, config);
|
|
150
145
|
results.push({ name: skill.name, source: "git", status: "updated" });
|
|
146
|
+
if (showProgress) {
|
|
147
|
+
printSuccess(skill.name);
|
|
148
|
+
}
|
|
151
149
|
}
|
|
152
150
|
catch (err) {
|
|
151
|
+
const errorMsg = err instanceof Error ? err.message : "unknown error";
|
|
153
152
|
results.push({
|
|
154
153
|
name: skill.name,
|
|
155
154
|
source: "git",
|
|
156
155
|
status: "failed",
|
|
157
|
-
error:
|
|
156
|
+
error: errorMsg,
|
|
158
157
|
});
|
|
158
|
+
if (showProgress) {
|
|
159
|
+
printFailure(skill.name, errorMsg);
|
|
160
|
+
}
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
else {
|
|
162
164
|
results.push({ name: skill.name, source: skill.source.type, status: "skipped" });
|
|
165
|
+
if (showProgress) {
|
|
166
|
+
printSkipped(skill.name, "skipped");
|
|
167
|
+
}
|
|
163
168
|
}
|
|
164
169
|
}
|
|
170
|
+
if (showProgress) {
|
|
171
|
+
stopSpinner();
|
|
172
|
+
}
|
|
165
173
|
await saveIndex(sortIndex(index));
|
|
166
174
|
const sourceGroups = groupBySource(results);
|
|
167
175
|
const totalUpdated = results.filter((r) => r.status === "updated").length;
|
|
@@ -189,21 +197,15 @@ export function registerUpdate(program) {
|
|
|
189
197
|
printInfo("No skills to update.");
|
|
190
198
|
return;
|
|
191
199
|
}
|
|
192
|
-
printInfo("Skill Update");
|
|
193
|
-
for (const group of sourceGroups) {
|
|
194
|
-
printInfo("");
|
|
195
|
-
printSourceGroup(group);
|
|
196
|
-
}
|
|
197
200
|
// Summary line
|
|
198
|
-
printInfo("");
|
|
199
201
|
if (totalFailed > 0) {
|
|
200
|
-
printInfo(
|
|
202
|
+
printInfo(`\nUpdated ${totalUpdated} of ${totalTrackable} trackable skill${totalTrackable === 1 ? "" : "s"} (${totalFailed} failed).`);
|
|
201
203
|
}
|
|
202
204
|
else if (totalUpdated > 0) {
|
|
203
|
-
printInfo(
|
|
205
|
+
printInfo(`\nUpdated ${totalUpdated} of ${totalTrackable} trackable skill${totalTrackable === 1 ? "" : "s"}.`);
|
|
204
206
|
}
|
|
205
207
|
else if (totalSkipped > 0 && totalTrackable === 0) {
|
|
206
|
-
printInfo("
|
|
208
|
+
printInfo("\nNo trackable skills to update.");
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
catch (error) {
|
package/dist/lib/agents.js
CHANGED
|
@@ -48,6 +48,12 @@ export function getUserAgentPaths(projectRoot) {
|
|
|
48
48
|
return Object.values(paths).flatMap((entry) => entry.user);
|
|
49
49
|
}
|
|
50
50
|
export function getUserPathsForAgents(projectRoot, agents) {
|
|
51
|
+
return getPathsForAgents(projectRoot, agents, "user");
|
|
52
|
+
}
|
|
53
|
+
export function getProjectPathsForAgents(projectRoot, agents) {
|
|
54
|
+
return getPathsForAgents(projectRoot, agents, "project");
|
|
55
|
+
}
|
|
56
|
+
function getPathsForAgents(projectRoot, agents, scope) {
|
|
51
57
|
const paths = agentPaths(projectRoot);
|
|
52
58
|
const seen = new Set();
|
|
53
59
|
const results = [];
|
|
@@ -56,12 +62,12 @@ export function getUserPathsForAgents(projectRoot, agents) {
|
|
|
56
62
|
if (!agentEntry) {
|
|
57
63
|
continue;
|
|
58
64
|
}
|
|
59
|
-
for (const
|
|
60
|
-
if (seen.has(
|
|
65
|
+
for (const pathValue of agentEntry[scope]) {
|
|
66
|
+
if (seen.has(pathValue)) {
|
|
61
67
|
continue;
|
|
62
68
|
}
|
|
63
|
-
seen.add(
|
|
64
|
-
results.push({ agent, path:
|
|
69
|
+
seen.add(pathValue);
|
|
70
|
+
results.push({ agent, path: pathValue });
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
return results;
|
package/dist/lib/fetcher.js
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
const GITHUB_HOSTS = ["api.github.com", "raw.githubusercontent.com"];
|
|
3
|
+
let cachedToken;
|
|
4
|
+
function getGitHubToken() {
|
|
5
|
+
if (cachedToken !== undefined)
|
|
6
|
+
return cachedToken;
|
|
7
|
+
// Check environment variables first, then fall back to gh CLI
|
|
8
|
+
const envToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
9
|
+
if (envToken) {
|
|
10
|
+
cachedToken = envToken;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
try {
|
|
14
|
+
cachedToken =
|
|
15
|
+
execSync("gh auth token", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim() ||
|
|
16
|
+
null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
cachedToken = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return cachedToken;
|
|
23
|
+
}
|
|
24
|
+
function isGitHubUrl(url) {
|
|
25
|
+
try {
|
|
26
|
+
const { host } = new URL(url);
|
|
27
|
+
return GITHUB_HOSTS.includes(host);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
1
33
|
export async function fetchText(url) {
|
|
2
|
-
const
|
|
34
|
+
const headers = {};
|
|
35
|
+
if (isGitHubUrl(url)) {
|
|
36
|
+
const token = getGitHubToken();
|
|
37
|
+
if (token) {
|
|
38
|
+
headers.Authorization = `Bearer ${token}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const response = await fetch(url, { headers });
|
|
3
42
|
if (!response.ok) {
|
|
4
43
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
5
44
|
}
|
package/dist/lib/index.js
CHANGED
|
@@ -29,9 +29,30 @@ export function upsertSkill(index, skill) {
|
|
|
29
29
|
next.skills.push(skill);
|
|
30
30
|
return next;
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
const existing = next.skills[existingIndex];
|
|
33
|
+
const mergedInstalls = mergeInstalls(existing.installs, skill.installs);
|
|
34
|
+
next.skills[existingIndex] = { ...existing, ...skill, installs: mergedInstalls };
|
|
33
35
|
return next;
|
|
34
36
|
}
|
|
37
|
+
function mergeInstalls(existing, incoming) {
|
|
38
|
+
if (!existing && !incoming)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (!existing)
|
|
41
|
+
return incoming;
|
|
42
|
+
if (!incoming)
|
|
43
|
+
return existing;
|
|
44
|
+
// Dedupe by scope + agent + projectRoot combination
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const merged = [];
|
|
47
|
+
for (const install of [...existing, ...incoming]) {
|
|
48
|
+
const key = `${install.scope}:${install.agent}:${install.projectRoot ?? ""}`;
|
|
49
|
+
if (seen.has(key))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(key);
|
|
52
|
+
merged.push(install);
|
|
53
|
+
}
|
|
54
|
+
return merged;
|
|
55
|
+
}
|
|
35
56
|
export function sortIndex(index) {
|
|
36
57
|
const skills = [...index.skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
37
58
|
return { ...index, skills };
|
package/dist/lib/output.js
CHANGED
|
@@ -2,6 +2,48 @@ import chalk from "chalk";
|
|
|
2
2
|
export function isJsonEnabled(options) {
|
|
3
3
|
return Boolean(options.json);
|
|
4
4
|
}
|
|
5
|
+
// Progress indicator support
|
|
6
|
+
const isTTY = process.stdout.isTTY ?? false;
|
|
7
|
+
// Braille spinner frames (single character, smooth animation)
|
|
8
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
9
|
+
let spinnerInterval = null;
|
|
10
|
+
let spinnerFrame = 0;
|
|
11
|
+
export function startSpinner(message) {
|
|
12
|
+
if (!isTTY)
|
|
13
|
+
return;
|
|
14
|
+
spinnerFrame = 0;
|
|
15
|
+
const render = () => {
|
|
16
|
+
const frame = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length];
|
|
17
|
+
process.stdout.write(`\r\x1b[K ${frame} ${message}`);
|
|
18
|
+
spinnerFrame++;
|
|
19
|
+
};
|
|
20
|
+
render();
|
|
21
|
+
spinnerInterval = setInterval(render, 80);
|
|
22
|
+
}
|
|
23
|
+
export function stopSpinner() {
|
|
24
|
+
if (spinnerInterval) {
|
|
25
|
+
clearInterval(spinnerInterval);
|
|
26
|
+
spinnerInterval = null;
|
|
27
|
+
}
|
|
28
|
+
if (isTTY) {
|
|
29
|
+
process.stdout.write(`\r\x1b[K`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function printProgressResult(message) {
|
|
33
|
+
stopSpinner();
|
|
34
|
+
process.stdout.write(`${message}\n`);
|
|
35
|
+
}
|
|
36
|
+
// Common result formatters for progress output
|
|
37
|
+
export function printSuccess(name, suffix) {
|
|
38
|
+
const msg = suffix ? ` ✓ ${name} (${suffix})` : ` ✓ ${name}`;
|
|
39
|
+
printProgressResult(msg);
|
|
40
|
+
}
|
|
41
|
+
export function printFailure(name, error) {
|
|
42
|
+
printProgressResult(` ✗ ${name} (${error})`);
|
|
43
|
+
}
|
|
44
|
+
export function printSkipped(name, reason) {
|
|
45
|
+
printProgressResult(` - ${name} (${reason})`);
|
|
46
|
+
}
|
|
5
47
|
export function printJson(result) {
|
|
6
48
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
7
49
|
}
|
package/dist/lib/skill-store.js
CHANGED
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { skillboxSkillsDir } from "./paths.js";
|
|
5
|
+
import { buildMetadata, parseSkillMarkdown } from "./skill-parser.js";
|
|
5
6
|
export async function ensureSkillsDir() {
|
|
6
7
|
await fs.mkdir(skillboxSkillsDir(), { recursive: true });
|
|
7
8
|
}
|
|
@@ -26,3 +27,23 @@ export async function writeSkillMetadata(name, metadata) {
|
|
|
26
27
|
export function hashContent(content) {
|
|
27
28
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Import a skill from a local directory. Reads the markdown, parses it,
|
|
32
|
+
* and writes to the skill store. Returns the data needed for index updates,
|
|
33
|
+
* or null if the skill is invalid (missing description).
|
|
34
|
+
*/
|
|
35
|
+
export async function importSkillFromDir(skillFile) {
|
|
36
|
+
const markdown = await fs.readFile(skillFile, "utf8");
|
|
37
|
+
const parsed = parseSkillMarkdown(markdown);
|
|
38
|
+
if (!parsed.description) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const metadata = buildMetadata(parsed, { type: "local" });
|
|
42
|
+
await ensureSkillsDir();
|
|
43
|
+
await writeSkillFiles(metadata.name, markdown, metadata);
|
|
44
|
+
return {
|
|
45
|
+
name: metadata.name,
|
|
46
|
+
checksum: parsed.checksum,
|
|
47
|
+
updatedAt: metadata.updatedAt,
|
|
48
|
+
};
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillbox",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Local-first, agent-agnostic skills manager",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Christian Anagnostou",
|
|
@@ -33,18 +33,24 @@
|
|
|
33
33
|
"lint:ci": "oxlint --deny-warnings",
|
|
34
34
|
"format": "oxfmt",
|
|
35
35
|
"format:check": "oxfmt --check",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"test:ci": "vitest run --reporter=verbose --reporter=junit --outputFile=test-results.xml",
|
|
36
39
|
"prepublishOnly": "npm run lint:ci && npm run format:check && npm run build"
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"chalk": "^5.3.0",
|
|
40
43
|
"commander": "^12.0.0",
|
|
44
|
+
"terminal-link": "^3.0.0",
|
|
41
45
|
"zod": "^3.23.8"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
44
48
|
"@types/node": "^20.11.19",
|
|
49
|
+
"execa": "^9.6.1",
|
|
45
50
|
"oxfmt": "^0.16.0",
|
|
46
51
|
"oxlint": "^0.16.0",
|
|
47
52
|
"ts-node": "^10.9.2",
|
|
48
|
-
"typescript": "^5.4.2"
|
|
53
|
+
"typescript": "^5.4.2",
|
|
54
|
+
"vitest": "^4.0.18"
|
|
49
55
|
}
|
|
50
56
|
}
|