#!/usr/bin/env node // Generate the OpenClaw / ClawHub skill package (.openclaw/skills/) from the // canonical skills/. OpenClaw skills are SKILL.md (frontmatter + body), the same // format ponytail already uses, with one difference: `description` must be a // single line under 160 chars. The canonical descriptions are long (tuned for // Claude's skill picker), so each ships a short one here. The body is copied // verbatim from skills//SKILL.md so the ruleset never drifts; only the // frontmatter is rewritten. // // Run: node scripts/build-openclaw-skills.js // tests/openclaw-skills.test.js fails if the committed copies are stale. const fs = require('fs'); const path = require('path'); const ROOT = path.join(__dirname, '..'); const HOMEPAGE = 'https://github.com/DietrichGebert/ponytail'; const DESCRIPTIONS = { 'ponytail': 'Lazy senior dev mode. Forces the simplest, shortest solution that works: YAGNI, stdlib first, no unrequested abstractions.', 'ponytail-review': 'Review a diff for over-engineering. Finds what to delete: reinvented stdlib, needless deps, speculative abstractions. One line per finding.', 'ponytail-audit': 'Audit the whole repo for over-engineering. A ranked list of what to delete, simplify, or replace with stdlib or native features.', 'ponytail-debt': 'Harvest every ponytail: shortcut comment into one debt ledger, so deferrals get tracked instead of forgotten. One-shot report.', 'ponytail-gain': 'Show ponytail measured impact as a scoreboard: less code, less cost, more speed, from the benchmark medians. One-shot display.', 'ponytail-help': "Quick reference for ponytail's modes, skills, and commands. One-shot display.", }; const NAMES = Object.keys(DESCRIPTIONS); function sourceBody(name) { const src = fs.readFileSync(path.join(ROOT, 'skills', name, 'SKILL.md'), 'utf8').replace(/\r\n/g, '\n'); const fm = src.match(/^---\n[\s\S]*?\n---\n?/); if (!fm) throw new Error(`skills/${name}/SKILL.md has no frontmatter`); return src.slice(fm[0].length); } function render(name) { const desc = DESCRIPTIONS[name]; if (desc.length > 160 || desc.includes('\n') || desc.includes('"')) { throw new Error(`description for ${name} must be one line, no quotes, under 160 chars`); } const frontmatter = `---\nname: ${name}\ndescription: "${desc}"\nhomepage: ${HOMEPAGE}\nlicense: MIT\n---\n`; return frontmatter + sourceBody(name); } function outPath(name) { return path.join(ROOT, '.openclaw', 'skills', name, 'SKILL.md'); } module.exports = { DESCRIPTIONS, NAMES, render, outPath, sourceBody }; if (require.main === module) { for (const name of NAMES) { const p = outPath(name); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, render(name)); console.log('wrote', path.relative(ROOT, p).replace(/\\/g, '/')); } }