diff --git a/src/agent/skills-cli.ts b/src/agent/skills-cli.ts index dd1a910b..b8b1ab8c 100644 --- a/src/agent/skills-cli.ts +++ b/src/agent/skills-cli.ts @@ -8,24 +8,30 @@ * pnpm skills:cli list List all skills * pnpm skills:cli status [id] Show skill status * pnpm skills:cli install Install skill dependencies + * pnpm skills:cli add Add skill from GitHub + * pnpm skills:cli remove Remove an installed skill */ import { SkillManager, installSkill, getInstallOptions, + addSkill, + removeSkill, + listInstalledSkills, } from "./skills/index.js"; // ============================================================================ // Types // ============================================================================ -type Command = "list" | "status" | "install" | "help"; +type Command = "list" | "status" | "install" | "add" | "remove" | "help"; interface ParsedArgs { command: Command; args: string[]; verbose: boolean; + force: boolean; } // ============================================================================ @@ -35,6 +41,7 @@ interface ParsedArgs { function parseArgs(argv: string[]): ParsedArgs { const args = [...argv]; let verbose = false; + let force = false; const positional: string[] = []; while (args.length > 0) { @@ -46,8 +53,13 @@ function parseArgs(argv: string[]): ParsedArgs { continue; } + if (arg === "--force" || arg === "-f") { + force = true; + continue; + } + if (arg === "--help" || arg === "-h") { - return { command: "help", args: [], verbose }; + return { command: "help", args: [], verbose, force }; } positional.push(arg); @@ -56,7 +68,7 @@ function parseArgs(argv: string[]): ParsedArgs { const command = (positional[0] ?? "help") as Command; const commandArgs = positional.slice(1); - return { command, args: commandArgs, verbose }; + return { command, args: commandArgs, verbose, force }; } // ============================================================================ @@ -74,15 +86,21 @@ Commands: list List all available skills status [id] Show detailed status of a skill (or all skills) install Install dependencies for a skill + add Add skill from GitHub (owner/repo or owner/repo/skill) + remove Remove an installed skill Options: -v, --verbose Show more details + -f, --force Force overwrite existing skill -h, --help Show this help Examples: pnpm skills:cli list pnpm skills:cli status commit pnpm skills:cli install nano-pdf + pnpm skills:cli add vercel-labs/agent-skills + pnpm skills:cli add vercel-labs/agent-skills/perplexity + pnpm skills:cli remove agent-skills `); } @@ -246,18 +264,100 @@ async function cmdInstall(manager: SkillManager, skillId: string, installId?: st } } +// ============================================================================ +// Add/Remove Commands +// ============================================================================ + +async function cmdAdd(source: string, force: boolean): Promise { + console.log(`\nAdding skill from '${source}'...`); + + const result = await addSkill({ + source, + force, + }); + + if (result.ok) { + console.log(`\n\x1b[32m✓ ${result.message}\x1b[0m`); + if (result.skills && result.skills.length > 1) { + console.log("\nSkills found:"); + for (const name of result.skills) { + console.log(` - ${name}`); + } + } + if (result.path) { + console.log(`\nInstalled to: ${result.path}`); + } + } else { + console.error(`\n\x1b[31m✗ ${result.message}\x1b[0m`); + process.exit(1); + } +} + +async function cmdRemove(name: string): Promise { + console.log(`\nRemoving skill '${name}'...`); + + const result = await removeSkill(name); + + if (result.ok) { + console.log(`\n\x1b[32m✓ ${result.message}\x1b[0m`); + } else { + console.error(`\n\x1b[31m✗ ${result.message}\x1b[0m`); + process.exit(1); + } +} + +async function cmdListInstalled(): Promise { + const skills = await listInstalledSkills(); + + if (skills.length === 0) { + console.log("\nNo skills installed in ~/.super-multica/skills/"); + console.log("Use 'pnpm skills:cli add ' to add skills."); + return; + } + + console.log("\nInstalled skills (~/.super-multica/skills/):\n"); + for (const name of skills) { + console.log(` - ${name}`); + } + console.log(`\nTotal: ${skills.length} installed`); +} + // ============================================================================ // Main // ============================================================================ async function main(): Promise { - const { command, args, verbose } = parseArgs(process.argv.slice(2)); + const { command, args, verbose, force } = parseArgs(process.argv.slice(2)); if (command === "help") { printHelp(); return; } + switch (command) { + case "add": + if (!args[0]) { + console.error("Usage: pnpm skills:cli add [--force]"); + console.error("\nSource formats:"); + console.error(" owner/repo Clone entire repository"); + console.error(" owner/repo/skill-name Clone single skill directory"); + console.error(" owner/repo@branch Clone specific branch/tag"); + process.exit(1); + } + await cmdAdd(args[0], force); + return; + + case "remove": + if (!args[0]) { + console.error("Usage: pnpm skills:cli remove "); + await cmdListInstalled(); + process.exit(1); + } + await cmdRemove(args[0]); + return; + } + + // Commands that need SkillManager const manager = new SkillManager(); switch (command) { diff --git a/src/agent/skills/index.ts b/src/agent/skills/index.ts index fe94bb1e..e3f96085 100644 --- a/src/agent/skills/index.ts +++ b/src/agent/skills/index.ts @@ -81,6 +81,16 @@ export { type SkillsChangeListener, } from "./watcher.js"; +// Export add module +export { + addSkill, + removeSkill, + listInstalledSkills, + parseSource, + type SkillAddRequest, + type SkillAddResult, +} from "./add.js"; + /** * SkillManager - Loads and manages skills *