fix: include bundled skills/ dir in skills status and sync discovery (#461)

This commit is contained in:
Cameron
2026-03-02 17:06:08 -08:00
committed by GitHub
parent 0dd462b60d
commit 00b6f00446
3 changed files with 45 additions and 7 deletions

View File

@@ -335,11 +335,18 @@ async function main() {
break; break;
case 'skills': { case 'skills': {
const { showStatus, runSkillsSync } = await import('./skills/index.js'); const { showStatus, runSkillsSync, enableSkill } = await import('./skills/index.js');
switch (subCommand) { switch (subCommand) {
case 'status': case 'status':
await showStatus(); await showStatus();
break; break;
case 'enable':
if (!args[2]) {
console.error('Usage: lettabot skills enable <name>');
process.exit(1);
}
enableSkill(args[2]);
break;
default: default:
await runSkillsSync(); await runSkillsSync();
} }

View File

@@ -5,7 +5,7 @@
import { existsSync, readdirSync, cpSync, mkdirSync, rmSync } from 'node:fs'; import { existsSync, readdirSync, cpSync, mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import * as p from '@clack/prompts'; import * as p from '@clack/prompts';
import { PROJECT_SKILLS_DIR, GLOBAL_SKILLS_DIR, SKILLS_SH_DIR, parseSkillFile } from './loader.js'; import { PROJECT_SKILLS_DIR, BUNDLED_SKILLS_DIR, GLOBAL_SKILLS_DIR, SKILLS_SH_DIR, parseSkillFile } from './loader.js';
const HOME = process.env.HOME || process.env.USERPROFILE || ''; const HOME = process.env.HOME || process.env.USERPROFILE || '';
const WORKING_DIR = process.env.WORKING_DIR || '/tmp/lettabot'; const WORKING_DIR = process.env.WORKING_DIR || '/tmp/lettabot';
@@ -68,10 +68,12 @@ function discoverSkills(): SkillInfo[] {
} }
}; };
// Discover from all sources (order matters - first source wins for duplicates) // Discover from all sources (order matters - first source wins for duplicates).
// Priority matches the loader hierarchy: project (.skills/) > bundled (skills/) > external.
addFromDir(PROJECT_SKILLS_DIR, 'builtin'); // .skills/ project overrides
addFromDir(BUNDLED_SKILLS_DIR, 'builtin'); // skills/ bundled with repo
addFromDir(CLAWDHUB_DIR, 'clawdhub'); addFromDir(CLAWDHUB_DIR, 'clawdhub');
addFromDir(VERCEL_DIR, 'vercel'); addFromDir(VERCEL_DIR, 'vercel');
addFromDir(PROJECT_SKILLS_DIR, 'builtin');
return skills.sort((a, b) => a.name.localeCompare(b.name)); return skills.sort((a, b) => a.name.localeCompare(b.name));
} }
@@ -218,3 +220,32 @@ export async function runSkillsSync(): Promise<void> {
p.log.info(`Skills directory: ${TARGET_DIR}`); p.log.info(`Skills directory: ${TARGET_DIR}`);
p.outro(`✨ Added ${toAdd.length}, removed ${toRemove.length} skill(s)`); p.outro(`✨ Added ${toAdd.length}, removed ${toRemove.length} skill(s)`);
} }
/**
* Non-interactively enable a single skill by name.
* Searches BUNDLED_SKILLS_DIR, then GLOBAL_SKILLS_DIR, then SKILLS_SH_DIR.
*/
export function enableSkill(name: string): void {
// Search order: highest priority first (project local > global > bundled > skills.sh)
const sourceDirs = [PROJECT_SKILLS_DIR, GLOBAL_SKILLS_DIR, BUNDLED_SKILLS_DIR, SKILLS_SH_DIR];
mkdirSync(TARGET_DIR, { recursive: true });
const dest = join(TARGET_DIR, name);
if (existsSync(dest)) {
console.log(`Skill '${name}' is already enabled.`);
return;
}
for (const dir of sourceDirs) {
const src = join(dir, name);
if (existsSync(src) && existsSync(join(src, 'SKILL.md'))) {
cpSync(src, dest, { recursive: true });
console.log(`Enabled skill '${name}' from ${dir}`);
return;
}
}
console.error(`Skill '${name}' not found. Run 'lettabot skills status' to see available skills.`);
process.exit(1);
}

View File

@@ -6,7 +6,7 @@ import * as p from '@clack/prompts';
import { join } from 'node:path'; import { join } from 'node:path';
import { getSkillsSummary, type SkillsSummary } from './status.js'; import { getSkillsSummary, type SkillsSummary } from './status.js';
import { installSkillDeps } from './install.js'; import { installSkillDeps } from './install.js';
import { hasBinary, GLOBAL_SKILLS_DIR, SKILLS_SH_DIR } from './loader.js'; import { hasBinary, BUNDLED_SKILLS_DIR, GLOBAL_SKILLS_DIR, SKILLS_SH_DIR } from './loader.js';
import type { NodeManager, SkillStatus } from './types.js'; import type { NodeManager, SkillStatus } from './types.js';
import { createLogger } from '../logger.js'; import { createLogger } from '../logger.js';
@@ -261,7 +261,7 @@ export async function listSkills(): Promise<void> {
*/ */
export async function showStatus(): Promise<void> { export async function showStatus(): Promise<void> {
const enabledSummary = getSkillsSummary([WORKING_SKILLS_DIR]); const enabledSummary = getSkillsSummary([WORKING_SKILLS_DIR]);
const availableSummary = getSkillsSummary([GLOBAL_SKILLS_DIR, SKILLS_SH_DIR]); const availableSummary = getSkillsSummary([BUNDLED_SKILLS_DIR, GLOBAL_SKILLS_DIR, SKILLS_SH_DIR]);
// Get names of enabled skills to filter available // Get names of enabled skills to filter available
const enabledNames = new Set(enabledSummary.skills.map(s => s.skill.name)); const enabledNames = new Set(enabledSummary.skills.map(s => s.skill.name));
@@ -297,6 +297,6 @@ export async function showStatus(): Promise<void> {
} }
log.info(''); log.info('');
log.info(` To enable: lettabot skills enable <name>`); log.info(` To enable: lettabot skills enable <name> (or run: lettabot skills)`);
log.info(` Skills dir: ${WORKING_SKILLS_DIR}`); log.info(` Skills dir: ${WORKING_SKILLS_DIR}`);
} }