Initial Commit
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bun
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, relative, resolve } from "node:path";
|
||||
|
||||
const PKG_ROOT = resolve(__dirname, "..");
|
||||
const ASSETS_OUT = resolve(PKG_ROOT, "assets/skills");
|
||||
const SOURCE_ROOT_DEFAULT = resolve(PKG_ROOT, "../../../../suaveplan-skills/plugins");
|
||||
|
||||
interface SkillEntry {
|
||||
readonly id: string;
|
||||
readonly version: string;
|
||||
readonly dir: string;
|
||||
readonly files: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
function walk(dir: string, out: string[] = []): string[] {
|
||||
let entries: string[];
|
||||
try {
|
||||
entries = readdirSync(dir);
|
||||
} catch {
|
||||
return out;
|
||||
}
|
||||
for (const name of entries) {
|
||||
if (name === "node_modules" || name === ".git") continue;
|
||||
const p = join(dir, name);
|
||||
const st = statSync(p);
|
||||
if (st.isDirectory()) {
|
||||
walk(p, out);
|
||||
} else {
|
||||
out.push(p);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function ensureDir(path: string): void {
|
||||
if (!existsSync(path)) mkdirSync(path, { recursive: true });
|
||||
}
|
||||
|
||||
function copyFile(from: string, to: string): void {
|
||||
ensureDir(dirname(to));
|
||||
const bytes = readFileSync(from);
|
||||
writeFileSync(to, bytes);
|
||||
}
|
||||
|
||||
function discoverSkills(sourceRoot: string): SkillEntry[] {
|
||||
if (!existsSync(sourceRoot)) {
|
||||
console.warn(`[build-skills-bundle] no source at ${sourceRoot} — skipping`);
|
||||
return [];
|
||||
}
|
||||
const skills: SkillEntry[] = [];
|
||||
const plugins = readdirSync(sourceRoot);
|
||||
for (const pluginName of plugins) {
|
||||
const pluginPath = join(sourceRoot, pluginName);
|
||||
if (!statSync(pluginPath).isDirectory()) continue;
|
||||
const skillsDir = join(pluginPath, "skills");
|
||||
if (!existsSync(skillsDir)) continue;
|
||||
for (const skillName of readdirSync(skillsDir)) {
|
||||
const skillSource = join(skillsDir, skillName);
|
||||
if (!statSync(skillSource).isDirectory()) continue;
|
||||
const skillManifest = join(skillSource, "SKILL.md");
|
||||
if (!existsSync(skillManifest)) continue;
|
||||
const targetDir = join(ASSETS_OUT, skillName);
|
||||
ensureDir(targetDir);
|
||||
const allFiles = walk(skillSource);
|
||||
const fileList: string[] = [];
|
||||
for (const file of allFiles) {
|
||||
const rel = relative(skillSource, file);
|
||||
copyFile(file, join(targetDir, rel));
|
||||
fileList.push(rel.split("\\").join("/"));
|
||||
}
|
||||
skills.push({
|
||||
id: skillName,
|
||||
version: "0.1.0",
|
||||
dir: skillName,
|
||||
files: fileList.sort(),
|
||||
});
|
||||
}
|
||||
}
|
||||
return skills.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
function discoverExistingSkills(): SkillEntry[] {
|
||||
if (!existsSync(ASSETS_OUT)) {
|
||||
return [];
|
||||
}
|
||||
const skills: SkillEntry[] = [];
|
||||
for (const skillName of readdirSync(ASSETS_OUT)) {
|
||||
const skillPath = join(ASSETS_OUT, skillName);
|
||||
if (!statSync(skillPath).isDirectory()) continue;
|
||||
const skillManifest = join(skillPath, "SKILL.md");
|
||||
if (!existsSync(skillManifest)) continue;
|
||||
const allFiles = walk(skillPath);
|
||||
const fileList: string[] = [];
|
||||
for (const file of allFiles) {
|
||||
const rel = relative(skillPath, file);
|
||||
fileList.push(rel.split("\\").join("/"));
|
||||
}
|
||||
skills.push({
|
||||
id: skillName,
|
||||
version: "0.1.0",
|
||||
dir: skillName,
|
||||
files: fileList.sort(),
|
||||
});
|
||||
}
|
||||
return skills.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
function writeManifest(skills: ReadonlyArray<SkillEntry>): void {
|
||||
ensureDir(ASSETS_OUT);
|
||||
const manifest = { schemaVersion: 1, skills };
|
||||
writeFileSync(
|
||||
join(ASSETS_OUT, "manifest.json"),
|
||||
`${JSON.stringify(manifest, null, 2)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const sourceRoot = process.env["SUAVEPLAN_SKILLS_ROOT"] ?? SOURCE_ROOT_DEFAULT;
|
||||
let skills = discoverSkills(sourceRoot);
|
||||
if (skills.length === 0) {
|
||||
skills = discoverExistingSkills();
|
||||
if (skills.length > 0) {
|
||||
console.log(`[build-skills-bundle] source missing; regenerated manifest for ${skills.length} existing skills`);
|
||||
}
|
||||
}
|
||||
writeManifest(skills);
|
||||
console.log(`[build-skills-bundle] bundled ${skills.length} skills into ${ASSETS_OUT}`);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user