skillbox 0.1.1
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/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/cli.js +25 -0
- package/dist/commands/add.js +105 -0
- package/dist/commands/agent.js +33 -0
- package/dist/commands/config.js +55 -0
- package/dist/commands/convert.js +64 -0
- package/dist/commands/import.js +50 -0
- package/dist/commands/list.js +95 -0
- package/dist/commands/meta.js +60 -0
- package/dist/commands/project.js +174 -0
- package/dist/commands/status.js +129 -0
- package/dist/commands/update.js +84 -0
- package/dist/lib/agents.js +44 -0
- package/dist/lib/command.js +9 -0
- package/dist/lib/config.js +27 -0
- package/dist/lib/fetcher.js +8 -0
- package/dist/lib/grouping.js +34 -0
- package/dist/lib/index.js +42 -0
- package/dist/lib/installs.js +36 -0
- package/dist/lib/options.js +21 -0
- package/dist/lib/output.js +26 -0
- package/dist/lib/paths.js +6 -0
- package/dist/lib/project-paths.js +18 -0
- package/dist/lib/project-root.js +25 -0
- package/dist/lib/projects.js +31 -0
- package/dist/lib/runtime.js +24 -0
- package/dist/lib/skill-parser.js +61 -0
- package/dist/lib/skill-store.js +28 -0
- package/dist/lib/sync.js +43 -0
- package/dist/lib/types.js +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
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 findProjectRoot = async (startDir) => {
|
|
13
|
+
let current = path.resolve(startDir);
|
|
14
|
+
while (true) {
|
|
15
|
+
const gitDir = path.join(current, ".git");
|
|
16
|
+
if (await exists(gitDir)) {
|
|
17
|
+
return current;
|
|
18
|
+
}
|
|
19
|
+
const parent = path.dirname(current);
|
|
20
|
+
if (parent === current) {
|
|
21
|
+
return startDir;
|
|
22
|
+
}
|
|
23
|
+
current = parent;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { skillboxProjectsPath, skillboxRoot } from "./paths.js";
|
|
3
|
+
const emptyProjects = () => ({ version: 1, projects: [] });
|
|
4
|
+
export const loadProjects = async () => {
|
|
5
|
+
const filePath = skillboxProjectsPath();
|
|
6
|
+
try {
|
|
7
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
8
|
+
return JSON.parse(content);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code === "ENOENT") {
|
|
12
|
+
return emptyProjects();
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export const saveProjects = async (index) => {
|
|
18
|
+
await fs.mkdir(skillboxRoot(), { recursive: true });
|
|
19
|
+
const json = JSON.stringify(index, null, 2);
|
|
20
|
+
await fs.writeFile(skillboxProjectsPath(), `${json}\n`, "utf8");
|
|
21
|
+
};
|
|
22
|
+
export const upsertProject = (index, root) => {
|
|
23
|
+
const existing = index.projects.find((project) => project.root === root);
|
|
24
|
+
if (existing) {
|
|
25
|
+
return index;
|
|
26
|
+
}
|
|
27
|
+
return { ...index, projects: [...index.projects, { root }] };
|
|
28
|
+
};
|
|
29
|
+
export const findProject = (index, root) => {
|
|
30
|
+
return index.projects.find((project) => project.root === root);
|
|
31
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { loadConfig } from "./config.js";
|
|
2
|
+
import { findProjectRoot } from "./project-root.js";
|
|
3
|
+
import { loadProjects, findProject, saveProjects, upsertProject } from "./projects.js";
|
|
4
|
+
import { resolveAgentList } from "./options.js";
|
|
5
|
+
export const resolveRuntime = async (options) => {
|
|
6
|
+
const projectRoot = await findProjectRoot(process.cwd());
|
|
7
|
+
const config = await loadConfig();
|
|
8
|
+
const scope = options.global ? "user" : (config.defaultScope ?? "project");
|
|
9
|
+
const agentList = resolveAgentList(options.agents, config);
|
|
10
|
+
return { projectRoot, scope, agentList };
|
|
11
|
+
};
|
|
12
|
+
export const ensureProjectRegistered = async (projectRoot, scope) => {
|
|
13
|
+
if (scope !== "project") {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const projects = await loadProjects();
|
|
17
|
+
let projectEntry = findProject(projects, projectRoot);
|
|
18
|
+
if (!projectEntry) {
|
|
19
|
+
const merged = upsertProject(projects, projectRoot);
|
|
20
|
+
await saveProjects(merged);
|
|
21
|
+
projectEntry = findProject(merged, projectRoot);
|
|
22
|
+
}
|
|
23
|
+
return projectEntry;
|
|
24
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { hashContent } from "./skill-store.js";
|
|
2
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
|
|
3
|
+
export const parseSkillMarkdown = (markdown) => {
|
|
4
|
+
const match = markdown.match(frontmatterRegex);
|
|
5
|
+
let name;
|
|
6
|
+
let description;
|
|
7
|
+
if (match) {
|
|
8
|
+
const frontmatter = match[1];
|
|
9
|
+
for (const line of frontmatter.split("\n")) {
|
|
10
|
+
const [key, ...rest] = line.split(":");
|
|
11
|
+
if (!key || rest.length === 0) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const value = rest.join(":").trim();
|
|
15
|
+
if (key.trim() === "name") {
|
|
16
|
+
name = value;
|
|
17
|
+
}
|
|
18
|
+
if (key.trim() === "description") {
|
|
19
|
+
description = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const checksum = hashContent(markdown);
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
description,
|
|
27
|
+
markdown,
|
|
28
|
+
checksum,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export const inferNameFromUrl = (url) => {
|
|
32
|
+
const cleaned = url.split("?")[0].split("#")[0];
|
|
33
|
+
const parts = cleaned.split("/").filter(Boolean);
|
|
34
|
+
if (parts.length === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const last = parts[parts.length - 1];
|
|
38
|
+
const base = last.toLowerCase();
|
|
39
|
+
if (base === "skill.md" || base === "skill" || base === "skill.json") {
|
|
40
|
+
if (parts.length < 2) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return parts[parts.length - 2];
|
|
44
|
+
}
|
|
45
|
+
return last.replace(/\.md$/, "");
|
|
46
|
+
};
|
|
47
|
+
export const buildMetadata = (parsed, source, nameOverride) => {
|
|
48
|
+
const name = nameOverride ?? parsed.name;
|
|
49
|
+
if (!name) {
|
|
50
|
+
throw new Error("Skill metadata requires a name.");
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
version: "0.1.0",
|
|
55
|
+
description: parsed.description,
|
|
56
|
+
entry: "SKILL.md",
|
|
57
|
+
source,
|
|
58
|
+
checksum: parsed.checksum,
|
|
59
|
+
updatedAt: new Date().toISOString(),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { skillboxSkillsDir } from "./paths.js";
|
|
5
|
+
export const ensureSkillsDir = async () => {
|
|
6
|
+
await fs.mkdir(skillboxSkillsDir(), { recursive: true });
|
|
7
|
+
};
|
|
8
|
+
export const skillDir = (name) => {
|
|
9
|
+
return path.join(skillboxSkillsDir(), name);
|
|
10
|
+
};
|
|
11
|
+
export const writeSkillFiles = async (name, skillMarkdown, metadata) => {
|
|
12
|
+
const targetDir = skillDir(name);
|
|
13
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
14
|
+
await fs.writeFile(path.join(targetDir, "SKILL.md"), `${skillMarkdown}\n`, "utf8");
|
|
15
|
+
await fs.writeFile(path.join(targetDir, "skill.json"), `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
|
|
16
|
+
};
|
|
17
|
+
export const readSkillMetadata = async (name) => {
|
|
18
|
+
const targetDir = skillDir(name);
|
|
19
|
+
const content = await fs.readFile(path.join(targetDir, "skill.json"), "utf8");
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
};
|
|
22
|
+
export const writeSkillMetadata = async (name, metadata) => {
|
|
23
|
+
const targetDir = skillDir(name);
|
|
24
|
+
await fs.writeFile(path.join(targetDir, "skill.json"), `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
|
|
25
|
+
};
|
|
26
|
+
export const hashContent = (content) => {
|
|
27
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
28
|
+
};
|
package/dist/lib/sync.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { skillDir } from "./skill-store.js";
|
|
4
|
+
export const ensureDir = async (dir) => {
|
|
5
|
+
await fs.mkdir(dir, { recursive: true });
|
|
6
|
+
};
|
|
7
|
+
const copyFiles = async (sourceDir, targetDir) => {
|
|
8
|
+
const entries = await fs.readdir(sourceDir);
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const sourcePath = path.join(sourceDir, entry);
|
|
11
|
+
const destPath = path.join(targetDir, entry);
|
|
12
|
+
const stat = await fs.stat(sourcePath);
|
|
13
|
+
if (stat.isDirectory()) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
await fs.copyFile(sourcePath, destPath);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const copySkillToTargets = async (skillName, targets) => {
|
|
20
|
+
const sourceDir = skillDir(skillName);
|
|
21
|
+
const writtenPaths = [];
|
|
22
|
+
for (const targetRoot of targets) {
|
|
23
|
+
const targetDir = path.join(targetRoot, skillName);
|
|
24
|
+
await ensureDir(targetDir);
|
|
25
|
+
await copyFiles(sourceDir, targetDir);
|
|
26
|
+
writtenPaths.push(targetDir);
|
|
27
|
+
}
|
|
28
|
+
return writtenPaths;
|
|
29
|
+
};
|
|
30
|
+
export const copySkillToInstallPaths = async (skillName, installPaths) => {
|
|
31
|
+
const sourceDir = skillDir(skillName);
|
|
32
|
+
for (const installPath of installPaths) {
|
|
33
|
+
await ensureDir(installPath);
|
|
34
|
+
await copyFiles(sourceDir, installPath);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export const buildTargets = (agent, paths, scope) => {
|
|
38
|
+
if (scope === "system") {
|
|
39
|
+
return (paths.system ?? []).map((pathValue) => ({ agent, scope, path: pathValue }));
|
|
40
|
+
}
|
|
41
|
+
const list = scope === "user" ? paths.user : paths.project;
|
|
42
|
+
return list.map((pathValue) => ({ agent, scope, path: pathValue }));
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skillbox",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Local-first, agent-agnostic skills manager",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Christian Anagnostou",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/christiananagnostou/skillbox.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/christiananagnostou/skillbox",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/christiananagnostou/skillbox/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"skillbox": "dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"dev": "ts-node-esm src/cli.ts",
|
|
30
|
+
"prepare": "npm run build",
|
|
31
|
+
"lint": "oxlint",
|
|
32
|
+
"lint:fix": "oxlint --fix",
|
|
33
|
+
"lint:ci": "oxlint --deny-warnings",
|
|
34
|
+
"format": "oxfmt",
|
|
35
|
+
"format:check": "oxfmt --check",
|
|
36
|
+
"prepublishOnly": "npm run lint:ci && npm run format:check && npm run build"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "^5.3.0",
|
|
40
|
+
"commander": "^12.0.0",
|
|
41
|
+
"node-fetch": "^3.3.2",
|
|
42
|
+
"zod": "^3.23.8"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.11.19",
|
|
46
|
+
"oxfmt": "^0.16.0",
|
|
47
|
+
"oxlint": "^0.16.0",
|
|
48
|
+
"ts-node": "^10.9.2",
|
|
49
|
+
"typescript": "^5.4.2"
|
|
50
|
+
}
|
|
51
|
+
}
|