Upgrade CLI to v0.2.0: add validate, list commands, --version, --dry-run, --backup flags

- Fix npm test glob pattern for Node >= 21 compatibility
- Add --version/-v flag to display package version
- Add validate command: run 78 profile checks (files, skills, frontmatter, legacy patterns, AGENTS mapping)
- Add list command: display all 13 bundled skills with descriptions
- Add --dry-run/-n flag for init: preview files without copying
- Add --backup/-b flag for init --force: backup existing .agent before overwrite
- Add name field to workflow frontmatter (brainstorm, execute-plan, write-plan)
- Expand smoke check to cover all 13 skills and new command files
- Increase test coverage from 3 to 20 tests (cli, init, validate)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 01:25:28 +07:00
parent 5325629a81
commit 9f1d6eb880
12 changed files with 627 additions and 15 deletions

79
src/commands/list.js Normal file
View File

@@ -0,0 +1,79 @@
import { readdir, readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { join } from "node:path";
function getSkillsDir() {
return fileURLToPath(new URL("../../templates/.agent/skills", import.meta.url));
}
function parseFrontmatter(content) {
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) return {};
const fm = {};
const lines = match[1].split("\n");
let currentKey = null;
let currentValue = "";
for (const line of lines) {
const keyMatch = line.match(/^(\w+):\s*(.*)$/);
if (keyMatch) {
if (currentKey) {
fm[currentKey] = currentValue.trim();
}
currentKey = keyMatch[1];
currentValue = keyMatch[2].replace(/^["']|["']$/g, "");
} else if (currentKey) {
currentValue += " " + line.trim();
}
}
if (currentKey) {
fm[currentKey] = currentValue.trim();
}
return fm;
}
export async function listCommand({ stdout, stderr }) {
const skillsDir = getSkillsDir();
let entries;
try {
entries = await readdir(skillsDir, { withFileTypes: true });
} catch {
stderr.write("Could not read bundled skills directory.\n");
return 1;
}
const skills = [];
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const skillFile = join(skillsDir, entry.name, "SKILL.md");
try {
const content = await readFile(skillFile, "utf8");
const fm = parseFrontmatter(content);
skills.push({
name: fm.name || entry.name,
description: fm.description || "(no description)",
});
} catch {
skills.push({ name: entry.name, description: "(SKILL.md not found)" });
}
}
skills.sort((a, b) => a.name.localeCompare(b.name));
stdout.write(`Antigravity Superpowers — ${skills.length} skills available:\n\n`);
const maxName = Math.max(...skills.map((s) => s.name.length));
for (const skill of skills) {
const desc = skill.description.length > 80
? skill.description.slice(0, 77) + "..."
: skill.description;
stdout.write(` ${skill.name.padEnd(maxName + 2)} ${desc}\n`);
}
stdout.write("\n");
return 0;
}