clawdie-ai/scripts/skill-list.ts
Clawdie AI 2983111a5d feat(phase2): add skill library catalog and tooling
- agent/library.yaml: catalog of all 48 skills (37 operational + 11 features),
  4 agents with skill assignments, 2 prompt refs
- src/skill-library.ts: loadLibrary, searchSkills, getSkillContent,
  getAgentSkills, validateLibrary, reloadLibrary
- scripts/skill-list.ts: grouped table output with color, optional search query
- scripts/skill-add.ts: add skill from local/codeberg/github/raw source
- scripts/skill-sync.ts: refresh all remote-sourced skills in cache
- scripts/validate-library.ts: validate all local: sources exist on disk
- .agent/identities/: COORDINATOR, SYSADMIN, DB_ADMIN, GIT_ADMIN stubs
- .agent/context/FREEBSD.md: FreeBSD gotchas context for agents

Typecheck passes. `just skill-list` and `just skill-search` ready to wire up
in the justfile (Phase 4).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---
Build: pass | Tests: pass — Tests  942 passed (942)
2026-04-13 23:06:50 +00:00

92 lines
3 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* scripts/skill-list.ts — List or search the skill library.
*
* Usage:
* just skill-list # list all skills
* just skill-search "database" # search by keyword
* just skill-search "infra" # matches tags too
*/
import { loadLibrary, searchSkills, searchFeatures } from '../src/skill-library.js';
const query = process.argv[2] ?? '';
const lib = loadLibrary();
const skills = query ? searchSkills(query) : lib.skills;
const features = query ? searchFeatures(query) : [];
// Format a tag list
function tags(t: string[]): string {
return t.map((x) => `\x1b[2m${x}\x1b[0m`).join(' ');
}
// Column widths
const ID_W = 32;
const DESC_W = 60;
function row(id: string, desc: string, tagList: string[]): void {
const idPad = id.padEnd(ID_W).slice(0, ID_W);
const descTrunc = desc.length > DESC_W ? desc.slice(0, DESC_W - 1) + '…' : desc.padEnd(DESC_W);
console.log(` \x1b[36m${idPad}\x1b[0m ${descTrunc} ${tags(tagList)}`);
}
// Header
if (!query) {
console.log(`\n\x1b[1mClawdie Skill Library\x1b[0m (${lib.skills.length} skills, ${lib.features.length} features)\n`);
} else {
const total = skills.length + features.length;
console.log(`\n\x1b[1mSearch: "${query}"\x1b[0m (${total} match${total === 1 ? '' : 'es'})\n`);
}
if (skills.length > 0) {
if (!query) {
// Group by first tag when listing all
const groups = new Map<string, typeof skills>();
for (const s of skills) {
const group = s.tags[0] ?? 'other';
if (!groups.has(group)) groups.set(group, []);
groups.get(group)!.push(s);
}
// Print by group
const order = ['infra', 'database', 'git', 'cms', 'agent', 'ml', 'channel', 'setup', 'other'];
const sorted = [
...order.filter((g) => groups.has(g)),
...[...groups.keys()].filter((g) => !order.includes(g)),
];
for (const group of sorted) {
const entries = groups.get(group);
if (!entries || entries.length === 0) continue;
console.log(` \x1b[33m── ${group.toUpperCase()} ──\x1b[0m`);
for (const s of entries) row(s.id, s.description, s.tags);
console.log('');
}
} else {
console.log(` \x1b[33m── skills ──\x1b[0m`);
for (const s of skills) row(s.id, s.description, s.tags);
console.log('');
}
}
if (features.length > 0) {
console.log(` \x1b[33m── features ──\x1b[0m`);
for (const f of features) row(f.id, f.description, f.tags);
console.log('');
}
if (skills.length === 0 && features.length === 0 && query) {
console.log(` No matches for "${query}"\n`);
}
// Agents summary (only when listing all, no query)
if (!query && lib.agents.length > 0) {
console.log(` \x1b[33m── agents ──\x1b[0m`);
for (const a of lib.agents) {
const skillCount = a.skills.length;
console.log(
` \x1b[36m${a.id.padEnd(ID_W)}\x1b[0m ${a.description.padEnd(DESC_W)} \x1b[2m${skillCount} skill${skillCount === 1 ? '' : 's'}\x1b[0m`,
);
}
console.log('');
}