@aubron/skill-tools 0.1.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/LICENSE +21 -0
- package/bin/aubron-skill.mjs +8 -0
- package/dist/chunk-EYUQPZTS.js +214 -0
- package/dist/chunk-EYUQPZTS.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +77 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aubron
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Committed launcher. pnpm creates a package's bin shims at INSTALL time and
|
|
3
|
+
// skips any whose target file is missing. The real CLI is built to dist/cli.js,
|
|
4
|
+
// which doesn't exist on a clean install — so pointing `bin` straight at it left
|
|
5
|
+
// `aubron-skill` unresolved on CI. This committed file always exists, so the
|
|
6
|
+
// shim is always created; it simply delegates to the built CLI (Turbo's
|
|
7
|
+
// `^build` guarantees dist/ is built before any consumer's script runs).
|
|
8
|
+
import "../dist/cli.js";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { existsSync as existsSync3 } from "fs";
|
|
5
|
+
import { dirname, join as join3, resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// src/frontmatter.ts
|
|
8
|
+
var stripQuotes = (s) => {
|
|
9
|
+
const t = s.trim();
|
|
10
|
+
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
|
|
11
|
+
return t.slice(1, -1);
|
|
12
|
+
}
|
|
13
|
+
return t;
|
|
14
|
+
};
|
|
15
|
+
function parseFrontmatter(content) {
|
|
16
|
+
const normalized = content.replace(/^\uFEFF/, "");
|
|
17
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(normalized);
|
|
18
|
+
if (!match) return { data: {}, body: normalized };
|
|
19
|
+
const data = {};
|
|
20
|
+
let currentKey = null;
|
|
21
|
+
for (const rawLine of match[1].split(/\r?\n/)) {
|
|
22
|
+
if (rawLine.trim() === "" || rawLine.trim().startsWith("#")) continue;
|
|
23
|
+
const listItem = /^\s*-\s+(.*)$/.exec(rawLine);
|
|
24
|
+
if (listItem && currentKey) {
|
|
25
|
+
const arr = Array.isArray(data[currentKey]) ? data[currentKey] : [];
|
|
26
|
+
arr.push(stripQuotes(listItem[1]));
|
|
27
|
+
data[currentKey] = arr;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(rawLine);
|
|
31
|
+
if (kv) {
|
|
32
|
+
currentKey = kv[1];
|
|
33
|
+
const value = kv[2];
|
|
34
|
+
data[currentKey] = value.trim() === "" ? [] : stripQuotes(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { data, body: match[2] ?? "" };
|
|
38
|
+
}
|
|
39
|
+
function asString(value) {
|
|
40
|
+
if (value === void 0) return void 0;
|
|
41
|
+
return Array.isArray(value) ? value[0] : value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/validate.ts
|
|
45
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
46
|
+
import { join } from "path";
|
|
47
|
+
var KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
48
|
+
var TRIGGER_SOFT_LIMIT = 1024;
|
|
49
|
+
var merge = (...results) => ({
|
|
50
|
+
ok: results.every((r) => r.ok),
|
|
51
|
+
errors: results.flatMap((r) => r.errors),
|
|
52
|
+
warnings: results.flatMap((r) => r.warnings)
|
|
53
|
+
});
|
|
54
|
+
function validateSkillMd(content, label = "SKILL.md") {
|
|
55
|
+
const errors = [];
|
|
56
|
+
const warnings = [];
|
|
57
|
+
const { data, body } = parseFrontmatter(content);
|
|
58
|
+
if (Object.keys(data).length === 0) {
|
|
59
|
+
errors.push(`${label}: missing YAML frontmatter (expected a leading \`---\` block)`);
|
|
60
|
+
return { ok: false, errors, warnings };
|
|
61
|
+
}
|
|
62
|
+
const name = asString(data.name);
|
|
63
|
+
if (!name) {
|
|
64
|
+
warnings.push(`${label}: no \`name\` \u2014 Claude will fall back to the directory name`);
|
|
65
|
+
} else if (!KEBAB.test(name)) {
|
|
66
|
+
errors.push(`${label}: \`name\` "${name}" must be kebab-case (a-z, 0-9, hyphens)`);
|
|
67
|
+
}
|
|
68
|
+
const description = asString(data.description);
|
|
69
|
+
if (!description || description.trim() === "") {
|
|
70
|
+
errors.push(
|
|
71
|
+
`${label}: \`description\` is required (it's how Claude decides to invoke the skill)`
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
const trigger = description.length + (asString(data.when_to_use)?.length ?? 0);
|
|
75
|
+
if (trigger > TRIGGER_SOFT_LIMIT) {
|
|
76
|
+
warnings.push(
|
|
77
|
+
`${label}: description + when_to_use is ${trigger} chars (keep under ~${TRIGGER_SOFT_LIMIT} for reliable triggering)`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (body.trim().length === 0) {
|
|
82
|
+
warnings.push(`${label}: body is empty \u2014 a skill should contain instructions`);
|
|
83
|
+
}
|
|
84
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
85
|
+
}
|
|
86
|
+
function validatePluginJson(obj, label = "plugin.json") {
|
|
87
|
+
const errors = [];
|
|
88
|
+
const warnings = [];
|
|
89
|
+
if (typeof obj !== "object" || obj === null) {
|
|
90
|
+
return { ok: false, errors: [`${label}: not a JSON object`], warnings };
|
|
91
|
+
}
|
|
92
|
+
const o = obj;
|
|
93
|
+
if (typeof o.name !== "string" || !KEBAB.test(o.name)) {
|
|
94
|
+
errors.push(`${label}: \`name\` is required and must be kebab-case`);
|
|
95
|
+
}
|
|
96
|
+
if (typeof o.description !== "string" || o.description.trim() === "") {
|
|
97
|
+
warnings.push(`${label}: \`description\` is recommended (shown in the plugin manager)`);
|
|
98
|
+
}
|
|
99
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
100
|
+
}
|
|
101
|
+
function validateSkillPackage(dir) {
|
|
102
|
+
const errors = [];
|
|
103
|
+
const results = [];
|
|
104
|
+
const manifestPath = join(dir, ".claude-plugin", "plugin.json");
|
|
105
|
+
if (!existsSync(manifestPath)) {
|
|
106
|
+
errors.push(`${dir}: missing .claude-plugin/plugin.json`);
|
|
107
|
+
} else {
|
|
108
|
+
try {
|
|
109
|
+
results.push(validatePluginJson(JSON.parse(readFileSync(manifestPath, "utf8"))));
|
|
110
|
+
} catch (err) {
|
|
111
|
+
errors.push(`plugin.json: invalid JSON \u2014 ${err.message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const skillsDir = join(dir, "skills");
|
|
115
|
+
if (!existsSync(skillsDir)) {
|
|
116
|
+
errors.push(`${dir}: missing skills/ directory`);
|
|
117
|
+
} else {
|
|
118
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
119
|
+
(e) => e.isDirectory()
|
|
120
|
+
);
|
|
121
|
+
if (skillDirs.length === 0) errors.push(`${skillsDir}: contains no skills`);
|
|
122
|
+
for (const sd of skillDirs) {
|
|
123
|
+
const skillMd = join(skillsDir, sd.name, "SKILL.md");
|
|
124
|
+
if (!existsSync(skillMd)) {
|
|
125
|
+
errors.push(`skills/${sd.name}: missing SKILL.md`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
results.push(validateSkillMd(readFileSync(skillMd, "utf8"), `skills/${sd.name}/SKILL.md`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return merge({ ok: errors.length === 0, errors, warnings: [] }, ...results);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/marketplace.ts
|
|
135
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
136
|
+
import { basename, join as join2 } from "path";
|
|
137
|
+
var DEFAULTS = {
|
|
138
|
+
name: "aubron",
|
|
139
|
+
owner: { name: "Aubron Wood" },
|
|
140
|
+
repoUrl: "https://github.com/GraffAI/aubron.git"
|
|
141
|
+
};
|
|
142
|
+
function discoverSkillPackages(packagesDir) {
|
|
143
|
+
if (!existsSync2(packagesDir)) return [];
|
|
144
|
+
const infos = [];
|
|
145
|
+
for (const entry of readdirSync2(packagesDir, { withFileTypes: true })) {
|
|
146
|
+
if (!entry.isDirectory()) continue;
|
|
147
|
+
const dir = join2(packagesDir, entry.name);
|
|
148
|
+
const manifestPath = join2(dir, ".claude-plugin", "plugin.json");
|
|
149
|
+
const pkgPath = join2(dir, "package.json");
|
|
150
|
+
if (!existsSync2(manifestPath) || !existsSync2(pkgPath)) continue;
|
|
151
|
+
const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
|
|
152
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
153
|
+
infos.push({
|
|
154
|
+
dirName: basename(dir),
|
|
155
|
+
pluginName: manifest.name ?? entry.name,
|
|
156
|
+
npmName: pkg.name ?? `@aubron/${entry.name}`,
|
|
157
|
+
version: pkg.version ?? "0.0.0",
|
|
158
|
+
description: pkg.description,
|
|
159
|
+
preferredSource: pkg.aubronSkill?.source ?? "git-subdir"
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return infos.sort((a, b) => a.pluginName.localeCompare(b.pluginName));
|
|
163
|
+
}
|
|
164
|
+
function sourceFor(info, repoUrl) {
|
|
165
|
+
if (info.preferredSource === "npm") {
|
|
166
|
+
return { source: "npm", package: info.npmName, version: `^${info.version}` };
|
|
167
|
+
}
|
|
168
|
+
return { source: "git-subdir", url: repoUrl, path: `packages/${info.dirName}` };
|
|
169
|
+
}
|
|
170
|
+
function buildMarketplace(infos, opts = {}) {
|
|
171
|
+
const name = opts.name ?? DEFAULTS.name;
|
|
172
|
+
const owner = opts.owner ?? DEFAULTS.owner;
|
|
173
|
+
const repoUrl = opts.repoUrl ?? DEFAULTS.repoUrl;
|
|
174
|
+
return {
|
|
175
|
+
$schema: "https://json.schemastore.org/claude-code-plugin-marketplace.json",
|
|
176
|
+
name,
|
|
177
|
+
owner,
|
|
178
|
+
description: "Claude Agent Skills published from the @aubron package factory.",
|
|
179
|
+
plugins: infos.map((info) => ({
|
|
180
|
+
name: info.pluginName,
|
|
181
|
+
source: sourceFor(info, repoUrl),
|
|
182
|
+
...info.description ? { description: info.description } : {},
|
|
183
|
+
version: info.version
|
|
184
|
+
}))
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function renderMarketplace(marketplace) {
|
|
188
|
+
return `${JSON.stringify(marketplace, null, 2)}
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/index.ts
|
|
193
|
+
function findRepoRoot(start) {
|
|
194
|
+
let dir = resolve(start);
|
|
195
|
+
for (; ; ) {
|
|
196
|
+
if (existsSync3(join3(dir, "pnpm-workspace.yaml"))) return dir;
|
|
197
|
+
const parent = dirname(dir);
|
|
198
|
+
if (parent === dir) throw new Error("could not find repo root (no pnpm-workspace.yaml)");
|
|
199
|
+
dir = parent;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export {
|
|
204
|
+
parseFrontmatter,
|
|
205
|
+
asString,
|
|
206
|
+
validateSkillMd,
|
|
207
|
+
validatePluginJson,
|
|
208
|
+
validateSkillPackage,
|
|
209
|
+
discoverSkillPackages,
|
|
210
|
+
buildMarketplace,
|
|
211
|
+
renderMarketplace,
|
|
212
|
+
findRepoRoot
|
|
213
|
+
};
|
|
214
|
+
//# sourceMappingURL=chunk-EYUQPZTS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/frontmatter.ts","../src/validate.ts","../src/marketplace.ts"],"sourcesContent":["/**\n * @aubron/skill-tools — validate and release Claude Agent Skills from the\n * @aubron package factory.\n *\n * This module is the library surface (validation + marketplace functions). The\n * `aubron-skill` CLI lives in `cli.ts` so it can run unconditionally as a bin\n * without a fragile main-module guard.\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\n\nexport * from \"./frontmatter.js\";\nexport * from \"./validate.js\";\nexport * from \"./marketplace.js\";\n\n/** Walk up from `start` until the pnpm workspace root is found. */\nexport function findRepoRoot(start: string): string {\n let dir = resolve(start);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) throw new Error(\"could not find repo root (no pnpm-workspace.yaml)\");\n dir = parent;\n }\n}\n","/**\n * Minimal YAML-frontmatter reader for SKILL.md files. SKILL.md frontmatter is\n * intentionally simple (scalars + the occasional list), so we parse just enough\n * to validate it without pulling in a YAML dependency.\n */\n\nexport interface Frontmatter {\n data: Record<string, string | string[]>;\n body: string;\n}\n\nconst stripQuotes = (s: string): string => {\n const t = s.trim();\n if ((t.startsWith('\"') && t.endsWith('\"')) || (t.startsWith(\"'\") && t.endsWith(\"'\"))) {\n return t.slice(1, -1);\n }\n return t;\n};\n\n/**\n * Split a `---`-delimited frontmatter block from a markdown document. Returns\n * `data: {}` when no frontmatter is present.\n */\nexport function parseFrontmatter(content: string): Frontmatter {\n const normalized = content.replace(/^\\uFEFF/, \"\");\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/.exec(normalized);\n if (!match) return { data: {}, body: normalized };\n\n const data: Record<string, string | string[]> = {};\n let currentKey: string | null = null;\n\n for (const rawLine of match[1]!.split(/\\r?\\n/)) {\n if (rawLine.trim() === \"\" || rawLine.trim().startsWith(\"#\")) continue;\n\n // List item belonging to the previous key (` - value`).\n const listItem = /^\\s*-\\s+(.*)$/.exec(rawLine);\n if (listItem && currentKey) {\n const arr = Array.isArray(data[currentKey]) ? (data[currentKey] as string[]) : [];\n arr.push(stripQuotes(listItem[1]!));\n data[currentKey] = arr;\n continue;\n }\n\n const kv = /^([A-Za-z0-9_-]+):\\s*(.*)$/.exec(rawLine);\n if (kv) {\n currentKey = kv[1]!;\n const value = kv[2]!;\n // `key:` with no inline value introduces a list (or block) for `key`.\n data[currentKey] = value.trim() === \"\" ? [] : stripQuotes(value);\n }\n }\n\n return { data, body: match[2] ?? \"\" };\n}\n\n/** Coerce a frontmatter value to a single string (first item of a list). */\nexport function asString(value: string | string[] | undefined): string | undefined {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? value[0] : value;\n}\n","/**\n * Validation for Claude Agent Skill packages (SKILL.md + a plugin manifest),\n * following the Agent Skills open standard (agentskills.io) and Claude Code's\n * plugin format. Pure checks over already-read content are unit-testable; the\n * filesystem walk is a thin wrapper.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { asString, parseFrontmatter } from \"./frontmatter.js\";\n\nexport interface ValidationResult {\n ok: boolean;\n errors: string[];\n warnings: string[];\n}\n\nconst KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n// Claude combines description + when_to_use into the trigger context; keep them\n// well within the documented ceiling so the skill stays discoverable.\nconst TRIGGER_SOFT_LIMIT = 1024;\n\nconst merge = (...results: ValidationResult[]): ValidationResult => ({\n ok: results.every((r) => r.ok),\n errors: results.flatMap((r) => r.errors),\n warnings: results.flatMap((r) => r.warnings),\n});\n\n/** Validate the frontmatter of a single SKILL.md document. */\nexport function validateSkillMd(content: string, label = \"SKILL.md\"): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const { data, body } = parseFrontmatter(content);\n\n if (Object.keys(data).length === 0) {\n errors.push(`${label}: missing YAML frontmatter (expected a leading \\`---\\` block)`);\n return { ok: false, errors, warnings };\n }\n\n const name = asString(data.name);\n if (!name) {\n warnings.push(`${label}: no \\`name\\` — Claude will fall back to the directory name`);\n } else if (!KEBAB.test(name)) {\n errors.push(`${label}: \\`name\\` \"${name}\" must be kebab-case (a-z, 0-9, hyphens)`);\n }\n\n const description = asString(data.description);\n if (!description || description.trim() === \"\") {\n errors.push(\n `${label}: \\`description\\` is required (it's how Claude decides to invoke the skill)`,\n );\n } else {\n const trigger = description.length + (asString(data.when_to_use)?.length ?? 0);\n if (trigger > TRIGGER_SOFT_LIMIT) {\n warnings.push(\n `${label}: description + when_to_use is ${trigger} chars (keep under ~${TRIGGER_SOFT_LIMIT} for reliable triggering)`,\n );\n }\n }\n\n if (body.trim().length === 0) {\n warnings.push(`${label}: body is empty — a skill should contain instructions`);\n }\n\n return { ok: errors.length === 0, errors, warnings };\n}\n\n/** Validate a parsed `.claude-plugin/plugin.json` object. */\nexport function validatePluginJson(obj: unknown, label = \"plugin.json\"): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n if (typeof obj !== \"object\" || obj === null) {\n return { ok: false, errors: [`${label}: not a JSON object`], warnings };\n }\n const o = obj as Record<string, unknown>;\n if (typeof o.name !== \"string\" || !KEBAB.test(o.name)) {\n errors.push(`${label}: \\`name\\` is required and must be kebab-case`);\n }\n if (typeof o.description !== \"string\" || o.description.trim() === \"\") {\n warnings.push(`${label}: \\`description\\` is recommended (shown in the plugin manager)`);\n }\n return { ok: errors.length === 0, errors, warnings };\n}\n\n/**\n * Validate a skill package directory: its `.claude-plugin/plugin.json` and every\n * `skills/<name>/SKILL.md` it bundles.\n */\nexport function validateSkillPackage(dir: string): ValidationResult {\n const errors: string[] = [];\n const results: ValidationResult[] = [];\n\n const manifestPath = join(dir, \".claude-plugin\", \"plugin.json\");\n if (!existsSync(manifestPath)) {\n errors.push(`${dir}: missing .claude-plugin/plugin.json`);\n } else {\n try {\n results.push(validatePluginJson(JSON.parse(readFileSync(manifestPath, \"utf8\"))));\n } catch (err) {\n errors.push(`plugin.json: invalid JSON — ${(err as Error).message}`);\n }\n }\n\n const skillsDir = join(dir, \"skills\");\n if (!existsSync(skillsDir)) {\n errors.push(`${dir}: missing skills/ directory`);\n } else {\n const skillDirs = readdirSync(skillsDir, { withFileTypes: true }).filter((e) =>\n e.isDirectory(),\n );\n if (skillDirs.length === 0) errors.push(`${skillsDir}: contains no skills`);\n for (const sd of skillDirs) {\n const skillMd = join(skillsDir, sd.name, \"SKILL.md\");\n if (!existsSync(skillMd)) {\n errors.push(`skills/${sd.name}: missing SKILL.md`);\n continue;\n }\n results.push(validateSkillMd(readFileSync(skillMd, \"utf8\"), `skills/${sd.name}/SKILL.md`));\n }\n }\n\n return merge({ ok: errors.length === 0, errors, warnings: [] }, ...results);\n}\n","/**\n * Build the root `.claude-plugin/marketplace.json` from the skill packages in\n * the monorepo. Sources are hybrid per the factory's needs (brief decision):\n * `git-subdir` by default (no publish step — points at the package folder), or\n * `npm` for released packages (version-pinned). A package opts into npm via\n * `\"aubronSkill\": { \"source\": \"npm\" }` in its package.json.\n */\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\n\nexport type SkillSource =\n | { source: \"git-subdir\"; url: string; path: string }\n | { source: \"npm\"; package: string; version: string };\n\nexport interface SkillPackageInfo {\n /** Folder name under packages/ (e.g. `skill-factory`). */\n dirName: string;\n /** Plugin name from .claude-plugin/plugin.json (the install id). */\n pluginName: string;\n /** npm package name from package.json. */\n npmName: string;\n version: string;\n description?: string;\n preferredSource: \"git-subdir\" | \"npm\";\n}\n\nexport interface MarketplaceOptions {\n name?: string;\n owner?: { name: string; email?: string };\n /** Git URL used for git-subdir sources. */\n repoUrl?: string;\n}\n\nconst DEFAULTS = {\n name: \"aubron\",\n owner: { name: \"Aubron Wood\" },\n repoUrl: \"https://github.com/GraffAI/aubron.git\",\n};\n\n/** Scan `packagesDir` for skill packages (those with a plugin manifest). */\nexport function discoverSkillPackages(packagesDir: string): SkillPackageInfo[] {\n if (!existsSync(packagesDir)) return [];\n const infos: SkillPackageInfo[] = [];\n for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const dir = join(packagesDir, entry.name);\n const manifestPath = join(dir, \".claude-plugin\", \"plugin.json\");\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(manifestPath) || !existsSync(pkgPath)) continue;\n\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as { name?: string };\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n name?: string;\n version?: string;\n description?: string;\n aubronSkill?: { source?: \"git-subdir\" | \"npm\" };\n };\n infos.push({\n dirName: basename(dir),\n pluginName: manifest.name ?? entry.name,\n npmName: pkg.name ?? `@aubron/${entry.name}`,\n version: pkg.version ?? \"0.0.0\",\n description: pkg.description,\n preferredSource: pkg.aubronSkill?.source ?? \"git-subdir\",\n });\n }\n return infos.sort((a, b) => a.pluginName.localeCompare(b.pluginName));\n}\n\nfunction sourceFor(info: SkillPackageInfo, repoUrl: string): SkillSource {\n if (info.preferredSource === \"npm\") {\n return { source: \"npm\", package: info.npmName, version: `^${info.version}` };\n }\n return { source: \"git-subdir\", url: repoUrl, path: `packages/${info.dirName}` };\n}\n\n/** Build the marketplace.json object (pure). */\nexport function buildMarketplace(\n infos: SkillPackageInfo[],\n opts: MarketplaceOptions = {},\n): unknown {\n const name = opts.name ?? DEFAULTS.name;\n const owner = opts.owner ?? DEFAULTS.owner;\n const repoUrl = opts.repoUrl ?? DEFAULTS.repoUrl;\n return {\n $schema: \"https://json.schemastore.org/claude-code-plugin-marketplace.json\",\n name,\n owner,\n description: \"Claude Agent Skills published from the @aubron package factory.\",\n plugins: infos.map((info) => ({\n name: info.pluginName,\n source: sourceFor(info, repoUrl),\n ...(info.description ? { description: info.description } : {}),\n version: info.version,\n })),\n };\n}\n\n/** Serialize the marketplace to the canonical on-disk form. */\nexport function renderMarketplace(marketplace: unknown): string {\n return `${JSON.stringify(marketplace, null, 2)}\\n`;\n}\n"],"mappings":"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","names":["existsSync","join","existsSync","readFileSync","readdirSync","join","existsSync","join"]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildMarketplace,
|
|
4
|
+
discoverSkillPackages,
|
|
5
|
+
findRepoRoot,
|
|
6
|
+
renderMarketplace,
|
|
7
|
+
validateSkillPackage
|
|
8
|
+
} from "./chunk-EYUQPZTS.js";
|
|
9
|
+
|
|
10
|
+
// src/cli.ts
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
12
|
+
import { join, resolve } from "path";
|
|
13
|
+
import { parseArgs } from "util";
|
|
14
|
+
function cmdValidate(argv) {
|
|
15
|
+
const { positionals } = parseArgs({ args: argv, allowPositionals: true });
|
|
16
|
+
const dir = resolve(positionals[0] ?? process.cwd());
|
|
17
|
+
const result = validateSkillPackage(dir);
|
|
18
|
+
for (const w of result.warnings) console.error(`\u26A0 ${w}`);
|
|
19
|
+
for (const e of result.errors) console.error(`\u2716 ${e}`);
|
|
20
|
+
if (result.ok) {
|
|
21
|
+
console.log(`\u2714 skill package valid: ${dir}`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
function readRepoUrl(root) {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
29
|
+
const repo = pkg.repository;
|
|
30
|
+
if (typeof repo === "string") {
|
|
31
|
+
return `${repo.replace(/^github:/, "https://github.com/")}${repo.endsWith(".git") ? "" : ".git"}`;
|
|
32
|
+
}
|
|
33
|
+
if (repo?.url) return repo.url;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
function cmdSyncMarketplace(argv) {
|
|
39
|
+
const { values } = parseArgs({ args: argv, options: { check: { type: "boolean" } } });
|
|
40
|
+
const root = findRepoRoot(process.cwd());
|
|
41
|
+
const infos = discoverSkillPackages(join(root, "packages"));
|
|
42
|
+
const repoUrl = readRepoUrl(root);
|
|
43
|
+
const content = renderMarketplace(buildMarketplace(infos, repoUrl ? { repoUrl } : {}));
|
|
44
|
+
const outPath = join(root, ".claude-plugin", "marketplace.json");
|
|
45
|
+
const existing = existsSync(outPath) ? readFileSync(outPath, "utf8") : "";
|
|
46
|
+
if (values.check) {
|
|
47
|
+
if (existing !== content) {
|
|
48
|
+
console.error("\u2716 marketplace.json is out of date \u2014 run `aubron-skill sync-marketplace`");
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
console.log(`\u2714 marketplace.json is up to date (${infos.length} skills)`);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
writeFileSync(outPath, content);
|
|
55
|
+
console.log(`\u2714 wrote ${outPath} (${infos.length} skills)`);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
function main(argv) {
|
|
59
|
+
const [sub, ...rest] = argv;
|
|
60
|
+
switch (sub) {
|
|
61
|
+
case "validate":
|
|
62
|
+
return cmdValidate(rest);
|
|
63
|
+
case "sync-marketplace":
|
|
64
|
+
return cmdSyncMarketplace(rest);
|
|
65
|
+
case void 0:
|
|
66
|
+
case "--help":
|
|
67
|
+
case "-h":
|
|
68
|
+
console.log("usage: aubron-skill <validate [dir] | sync-marketplace [--check]>");
|
|
69
|
+
return 0;
|
|
70
|
+
default:
|
|
71
|
+
console.error(`unknown subcommand "${sub}"`);
|
|
72
|
+
console.error("usage: aubron-skill <validate [dir] | sync-marketplace [--check]>");
|
|
73
|
+
return 2;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
process.exit(main(process.argv.slice(2)));
|
|
77
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/**\n * `aubron-skill` CLI — validate a skill package and keep the marketplace in sync.\n *\n * aubron-skill validate [dir] # validate a skill package (default cwd)\n * aubron-skill sync-marketplace [--check] # regenerate the root marketplace.json\n */\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport {\n buildMarketplace,\n discoverSkillPackages,\n findRepoRoot,\n renderMarketplace,\n validateSkillPackage,\n} from \"./index.js\";\n\nfunction cmdValidate(argv: string[]): number {\n const { positionals } = parseArgs({ args: argv, allowPositionals: true });\n const dir = resolve(positionals[0] ?? process.cwd());\n const result = validateSkillPackage(dir);\n for (const w of result.warnings) console.error(`⚠ ${w}`);\n for (const e of result.errors) console.error(`✖ ${e}`);\n if (result.ok) {\n console.log(`✔ skill package valid: ${dir}`);\n return 0;\n }\n return 1;\n}\n\nfunction readRepoUrl(root: string): string | undefined {\n try {\n const pkg = JSON.parse(readFileSync(join(root, \"package.json\"), \"utf8\")) as {\n repository?: string | { url?: string };\n };\n const repo = pkg.repository;\n if (typeof repo === \"string\") {\n return `${repo.replace(/^github:/, \"https://github.com/\")}${repo.endsWith(\".git\") ? \"\" : \".git\"}`;\n }\n if (repo?.url) return repo.url;\n } catch {\n /* fall back to default */\n }\n return undefined;\n}\n\nfunction cmdSyncMarketplace(argv: string[]): number {\n const { values } = parseArgs({ args: argv, options: { check: { type: \"boolean\" } } });\n const root = findRepoRoot(process.cwd());\n const infos = discoverSkillPackages(join(root, \"packages\"));\n const repoUrl = readRepoUrl(root);\n const content = renderMarketplace(buildMarketplace(infos, repoUrl ? { repoUrl } : {}));\n\n const outPath = join(root, \".claude-plugin\", \"marketplace.json\");\n const existing = existsSync(outPath) ? readFileSync(outPath, \"utf8\") : \"\";\n\n if (values.check) {\n if (existing !== content) {\n console.error(\"✖ marketplace.json is out of date — run `aubron-skill sync-marketplace`\");\n return 1;\n }\n console.log(`✔ marketplace.json is up to date (${infos.length} skills)`);\n return 0;\n }\n\n writeFileSync(outPath, content);\n console.log(`✔ wrote ${outPath} (${infos.length} skills)`);\n return 0;\n}\n\nfunction main(argv: string[]): number {\n const [sub, ...rest] = argv;\n switch (sub) {\n case \"validate\":\n return cmdValidate(rest);\n case \"sync-marketplace\":\n return cmdSyncMarketplace(rest);\n case undefined:\n case \"--help\":\n case \"-h\":\n console.log(\"usage: aubron-skill <validate [dir] | sync-marketplace [--check]>\");\n return 0;\n default:\n console.error(`unknown subcommand \"${sub}\"`);\n console.error(\"usage: aubron-skill <validate [dir] | sync-marketplace [--check]>\");\n return 2;\n }\n}\n\nprocess.exit(main(process.argv.slice(2)));\n"],"mappings":";;;;;;;;;;AAMA,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,MAAM,eAAe;AAC9B,SAAS,iBAAiB;AAU1B,SAAS,YAAY,MAAwB;AAC3C,QAAM,EAAE,YAAY,IAAI,UAAU,EAAE,MAAM,MAAM,kBAAkB,KAAK,CAAC;AACxE,QAAM,MAAM,QAAQ,YAAY,CAAC,KAAK,QAAQ,IAAI,CAAC;AACnD,QAAM,SAAS,qBAAqB,GAAG;AACvC,aAAW,KAAK,OAAO,SAAU,SAAQ,MAAM,UAAK,CAAC,EAAE;AACvD,aAAW,KAAK,OAAO,OAAQ,SAAQ,MAAM,UAAK,CAAC,EAAE;AACrD,MAAI,OAAO,IAAI;AACb,YAAQ,IAAI,+BAA0B,GAAG,EAAE;AAC3C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAAkC;AACrD,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,aAAa,KAAK,MAAM,cAAc,GAAG,MAAM,CAAC;AAGvE,UAAM,OAAO,IAAI;AACjB,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO,GAAG,KAAK,QAAQ,YAAY,qBAAqB,CAAC,GAAG,KAAK,SAAS,MAAM,IAAI,KAAK,MAAM;AAAA,IACjG;AACA,QAAI,MAAM,IAAK,QAAO,KAAK;AAAA,EAC7B,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAwB;AAClD,QAAM,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,MAAM,SAAS,EAAE,OAAO,EAAE,MAAM,UAAU,EAAE,EAAE,CAAC;AACpF,QAAM,OAAO,aAAa,QAAQ,IAAI,CAAC;AACvC,QAAM,QAAQ,sBAAsB,KAAK,MAAM,UAAU,CAAC;AAC1D,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,UAAU,kBAAkB,iBAAiB,OAAO,UAAU,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC;AAErF,QAAM,UAAU,KAAK,MAAM,kBAAkB,kBAAkB;AAC/D,QAAM,WAAW,WAAW,OAAO,IAAI,aAAa,SAAS,MAAM,IAAI;AAEvE,MAAI,OAAO,OAAO;AAChB,QAAI,aAAa,SAAS;AACxB,cAAQ,MAAM,mFAAyE;AACvF,aAAO;AAAA,IACT;AACA,YAAQ,IAAI,0CAAqC,MAAM,MAAM,UAAU;AACvE,WAAO;AAAA,EACT;AAEA,gBAAc,SAAS,OAAO;AAC9B,UAAQ,IAAI,gBAAW,OAAO,KAAK,MAAM,MAAM,UAAU;AACzD,SAAO;AACT;AAEA,SAAS,KAAK,MAAwB;AACpC,QAAM,CAAC,KAAK,GAAG,IAAI,IAAI;AACvB,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO,YAAY,IAAI;AAAA,IACzB,KAAK;AACH,aAAO,mBAAmB,IAAI;AAAA,IAChC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,IAAI,mEAAmE;AAC/E,aAAO;AAAA,IACT;AACE,cAAQ,MAAM,uBAAuB,GAAG,GAAG;AAC3C,cAAQ,MAAM,mEAAmE;AACjF,aAAO;AAAA,EACX;AACF;AAEA,QAAQ,KAAK,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML-frontmatter reader for SKILL.md files. SKILL.md frontmatter is
|
|
3
|
+
* intentionally simple (scalars + the occasional list), so we parse just enough
|
|
4
|
+
* to validate it without pulling in a YAML dependency.
|
|
5
|
+
*/
|
|
6
|
+
interface Frontmatter {
|
|
7
|
+
data: Record<string, string | string[]>;
|
|
8
|
+
body: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Split a `---`-delimited frontmatter block from a markdown document. Returns
|
|
12
|
+
* `data: {}` when no frontmatter is present.
|
|
13
|
+
*/
|
|
14
|
+
declare function parseFrontmatter(content: string): Frontmatter;
|
|
15
|
+
/** Coerce a frontmatter value to a single string (first item of a list). */
|
|
16
|
+
declare function asString(value: string | string[] | undefined): string | undefined;
|
|
17
|
+
|
|
18
|
+
interface ValidationResult {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
errors: string[];
|
|
21
|
+
warnings: string[];
|
|
22
|
+
}
|
|
23
|
+
/** Validate the frontmatter of a single SKILL.md document. */
|
|
24
|
+
declare function validateSkillMd(content: string, label?: string): ValidationResult;
|
|
25
|
+
/** Validate a parsed `.claude-plugin/plugin.json` object. */
|
|
26
|
+
declare function validatePluginJson(obj: unknown, label?: string): ValidationResult;
|
|
27
|
+
/**
|
|
28
|
+
* Validate a skill package directory: its `.claude-plugin/plugin.json` and every
|
|
29
|
+
* `skills/<name>/SKILL.md` it bundles.
|
|
30
|
+
*/
|
|
31
|
+
declare function validateSkillPackage(dir: string): ValidationResult;
|
|
32
|
+
|
|
33
|
+
type SkillSource = {
|
|
34
|
+
source: "git-subdir";
|
|
35
|
+
url: string;
|
|
36
|
+
path: string;
|
|
37
|
+
} | {
|
|
38
|
+
source: "npm";
|
|
39
|
+
package: string;
|
|
40
|
+
version: string;
|
|
41
|
+
};
|
|
42
|
+
interface SkillPackageInfo {
|
|
43
|
+
/** Folder name under packages/ (e.g. `skill-factory`). */
|
|
44
|
+
dirName: string;
|
|
45
|
+
/** Plugin name from .claude-plugin/plugin.json (the install id). */
|
|
46
|
+
pluginName: string;
|
|
47
|
+
/** npm package name from package.json. */
|
|
48
|
+
npmName: string;
|
|
49
|
+
version: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
preferredSource: "git-subdir" | "npm";
|
|
52
|
+
}
|
|
53
|
+
interface MarketplaceOptions {
|
|
54
|
+
name?: string;
|
|
55
|
+
owner?: {
|
|
56
|
+
name: string;
|
|
57
|
+
email?: string;
|
|
58
|
+
};
|
|
59
|
+
/** Git URL used for git-subdir sources. */
|
|
60
|
+
repoUrl?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Scan `packagesDir` for skill packages (those with a plugin manifest). */
|
|
63
|
+
declare function discoverSkillPackages(packagesDir: string): SkillPackageInfo[];
|
|
64
|
+
/** Build the marketplace.json object (pure). */
|
|
65
|
+
declare function buildMarketplace(infos: SkillPackageInfo[], opts?: MarketplaceOptions): unknown;
|
|
66
|
+
/** Serialize the marketplace to the canonical on-disk form. */
|
|
67
|
+
declare function renderMarketplace(marketplace: unknown): string;
|
|
68
|
+
|
|
69
|
+
/** Walk up from `start` until the pnpm workspace root is found. */
|
|
70
|
+
declare function findRepoRoot(start: string): string;
|
|
71
|
+
|
|
72
|
+
export { type Frontmatter, type MarketplaceOptions, type SkillPackageInfo, type SkillSource, type ValidationResult, asString, buildMarketplace, discoverSkillPackages, findRepoRoot, parseFrontmatter, renderMarketplace, validatePluginJson, validateSkillMd, validateSkillPackage };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
asString,
|
|
4
|
+
buildMarketplace,
|
|
5
|
+
discoverSkillPackages,
|
|
6
|
+
findRepoRoot,
|
|
7
|
+
parseFrontmatter,
|
|
8
|
+
renderMarketplace,
|
|
9
|
+
validatePluginJson,
|
|
10
|
+
validateSkillMd,
|
|
11
|
+
validateSkillPackage
|
|
12
|
+
} from "./chunk-EYUQPZTS.js";
|
|
13
|
+
export {
|
|
14
|
+
asString,
|
|
15
|
+
buildMarketplace,
|
|
16
|
+
discoverSkillPackages,
|
|
17
|
+
findRepoRoot,
|
|
18
|
+
parseFrontmatter,
|
|
19
|
+
renderMarketplace,
|
|
20
|
+
validatePluginJson,
|
|
21
|
+
validateSkillMd,
|
|
22
|
+
validateSkillPackage
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aubron/skill-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Validate and release Claude Agent Skills from the @aubron package factory.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"bin"
|
|
16
|
+
],
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.9.2",
|
|
19
|
+
"eslint": "^10.4.1",
|
|
20
|
+
"tsup": "^8.5.1",
|
|
21
|
+
"typescript": "^6.0.3",
|
|
22
|
+
"vitest": "^4.1.8",
|
|
23
|
+
"@aubron/eslint-config": "0.1.0",
|
|
24
|
+
"@aubron/tsup-config": "0.1.0",
|
|
25
|
+
"@aubron/tsconfig": "0.1.0",
|
|
26
|
+
"@aubron/prettier-config": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"provenance": true
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"aubron-skill": "./bin/aubron-skill.mjs"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"lint": "eslint .",
|
|
43
|
+
"typecheck": "tsc --noEmit"
|
|
44
|
+
}
|
|
45
|
+
}
|