skillbox 0.3.2 → 0.3.3
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 +12 -14
- package/dist/commands/add.js +5 -5
- package/dist/commands/import.js +14 -24
- package/dist/commands/list.js +27 -2
- package/dist/commands/project.js +49 -1
- package/dist/lib/agents.js +10 -4
- package/dist/lib/fetcher.js +40 -1
- package/dist/lib/index.js +22 -1
- package/dist/lib/skill-store.js +21 -0
- package/package.json +2 -1
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.3");
|
|
16
16
|
registerAdd(program);
|
|
17
17
|
registerAgent(program);
|
|
18
18
|
registerConfig(program);
|
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -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
|
}
|
package/dist/commands/add.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -71,16 +72,15 @@ export function registerAdd(program) {
|
|
|
71
72
|
if (!map) {
|
|
72
73
|
continue;
|
|
73
74
|
}
|
|
74
|
-
const targets = buildTargets(agent, map, scope).map((target) => target.path);
|
|
75
|
+
const targets = buildTargets(agent, map, scope).map((target) => path.join(target.path, skillName));
|
|
75
76
|
const results = await installSkillToTargets(skillName, targets, config);
|
|
76
|
-
const written = results
|
|
77
|
-
.filter((result) => result.mode !== "skipped")
|
|
78
|
-
.map((result) => result.path);
|
|
79
77
|
const warnings = buildSymlinkWarning(agent, results);
|
|
80
78
|
for (const warning of warnings) {
|
|
81
79
|
printInfo(warning);
|
|
82
80
|
}
|
|
83
|
-
|
|
81
|
+
// Record all targets, not just successfully written ones
|
|
82
|
+
// The warning tells users about symlink issues, but we still track the install intent
|
|
83
|
+
const deduped = recordInstallPaths(targets, recordedPaths);
|
|
84
84
|
if (deduped.length > 0) {
|
|
85
85
|
installed.push({ agent, scope, targets: deduped });
|
|
86
86
|
for (const target of deduped) {
|
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
|
}
|
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/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/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.3",
|
|
4
4
|
"description": "Local-first, agent-agnostic skills manager",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Christian Anagnostou",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"chalk": "^5.3.0",
|
|
40
40
|
"commander": "^12.0.0",
|
|
41
|
+
"terminal-link": "^3.0.0",
|
|
41
42
|
"zod": "^3.23.8"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|