import fs from 'node:fs/promises'; import path from 'node:path'; import matter from 'gray-matter'; const SKILLS_DIR = path.resolve( process.env.SKILLS_DIR || "data/skills" ); const SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/; const MAX_SLUG_LENGTH = 64; function isValidSlug(slug) { return slug.length >= 2 && slug.length <= MAX_SLUG_LENGTH && SLUG_RE.test(slug); } function skillPath(slug) { return path.join(SKILLS_DIR, `${slug}.md`); } function parseTools(val) { if (Array.isArray(val)) return val.map(String); if (typeof val === "string") return val.split(",").map((t) => t.trim()).filter(Boolean); return []; } function parseSkill(slug, raw) { const { data, content } = matter(raw); return { slug, name: data.name || slug, description: data.description || "", "allowed-tools": parseTools(data["allowed-tools"] ?? data.allowedTools), "argument-hint": data["argument-hint"] || "", model: data.model || "", "user-invocable": data["user-invocable"] !== false, "disable-model-invocation": Boolean(data["disable-model-invocation"]), context: data.context || "", agent: data.agent || "", author: data.author || "", "author-email": data["author-email"] || "", "fork-of": data["fork-of"] || "", tags: parseTools(data.tags), hooks: typeof data.hooks === "object" && data.hooks !== null ? data.hooks : null, content: content.trim(), raw }; } async function listSkills() { await fs.mkdir(SKILLS_DIR, { recursive: true }); const files = await fs.readdir(SKILLS_DIR); const skills = []; for (const file of files) { if (!file.endsWith(".md")) continue; const slug = file.replace(/\.md$/, ""); const raw = await fs.readFile(path.join(SKILLS_DIR, file), "utf-8"); const { content: _, raw: __, ...meta } = parseSkill(slug, raw); skills.push(meta); } return skills.sort((a, b) => a.name.localeCompare(b.name)); } async function getAllTags() { const all = await listSkills(); return [...new Set(all.flatMap((s) => s.tags))].sort(); } async function getForksOf(slug) { const all = await listSkills(); return all.filter((s) => s["fork-of"] === slug); } async function getSkill(slug) { try { const raw = await fs.readFile(skillPath(slug), "utf-8"); return parseSkill(slug, raw); } catch { return null; } } async function createSkill(slug, content) { if (!isValidSlug(slug)) { throw new Error(`Invalid slug: ${slug}`); } await fs.mkdir(SKILLS_DIR, { recursive: true }); const dest = skillPath(slug); try { await fs.access(dest); throw new Error(`Skill already exists: ${slug}`); } catch (err) { if (err instanceof Error && err.message.startsWith("Skill already exists")) { throw err; } } await fs.writeFile(dest, content, "utf-8"); return parseSkill(slug, content); } async function updateSkill(slug, content) { const dest = skillPath(slug); try { await fs.access(dest); } catch { throw new Error(`Skill not found: ${slug}`); } await fs.writeFile(dest, content, "utf-8"); return parseSkill(slug, content); } async function deleteSkill(slug) { const dest = skillPath(slug); try { await fs.access(dest); } catch { throw new Error(`Skill not found: ${slug}`); } await fs.unlink(dest); } export { getAllTags as a, getForksOf as b, createSkill as c, deleteSkill as d, getSkill as g, isValidSlug as i, listSkills as l, updateSkill as u };