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:
79
src/commands/list.js
Normal file
79
src/commands/list.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user