- 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)
92 lines
3 KiB
TypeScript
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('');
|
|
}
|