diff --git a/apps/cli/src/commands/skills.ts b/apps/cli/src/commands/skills.ts new file mode 100644 index 0000000..7b26aeb --- /dev/null +++ b/apps/cli/src/commands/skills.ts @@ -0,0 +1,38 @@ +import { Cli, SyncSkills } from "incur"; +import { createLocalCli } from "../local-cli.js"; +import { createStartCli } from "../start-cli.js"; +import { CLI_METADATA } from "./registry.js"; + +type CommandEntry = NonNullable> extends Map< + string, + infer V +> + ? V + : never; + +/** Merges every registered command (start + local groups) into one incur command map. */ +export async function buildCommandMap(): Promise> { + const merged = new Map(); + for (const cli of [await createStartCli(), await createLocalCli()]) { + const commands = Cli.toCommands.get(cli); + if (!commands) continue; + for (const [name, entry] of commands) { + merged.set(name, entry); + } + } + return merged; +} + +/** Generates skill files from the full command surface and installs them natively. */ +export async function runSkillsAdd(): Promise { + const commands = await buildCommandMap(); + const result = await SyncSkills.sync(CLI_METADATA.name, commands, { + description: CLI_METADATA.description, + }); + + const count = result.skills.length; + process.stdout.write(`${count} skill${count === 1 ? "" : "s"} synced\n`); + for (const path of result.paths) { + process.stdout.write(` ${path}\n`); + } +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index b938132..d58fddc 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -22,6 +22,16 @@ async function run(argv = process.argv.slice(2)): Promise { return; } + if (command === "skills") { + if (argv[1] === "add") { + const { runSkillsAdd } = await import("./commands/skills.js"); + await runSkillsAdd(); + return; + } + process.stdout.write(`Usage:\n ${CLI_METADATA.name} skills add\n`); + return; + } + if (entry?.group === "local") { const { createLocalCli } = await import("./local-cli.js"); await (await createLocalCli()).serve(argv); @@ -49,10 +59,12 @@ Options: } function formatCommandHelp(): string { - const width = Math.max(...COMMAND_REGISTRY.map((command) => command.name.length)); - return COMMAND_REGISTRY.map( - (command) => ` ${command.name.padEnd(width)} ${command.summary}`, - ).join("\n"); + const entries = [ + ...COMMAND_REGISTRY.map((command) => ({ name: command.name, summary: command.summary })), + { name: "skills add", summary: "Sync skill files to your agent" }, + ]; + const width = Math.max(...entries.map((entry) => entry.name.length)); + return entries.map((entry) => ` ${entry.name.padEnd(width)} ${entry.summary}`).join("\n"); } run().catch((error) => { diff --git a/apps/cli/test/skills.test.ts b/apps/cli/test/skills.test.ts new file mode 100644 index 0000000..b5e5931 --- /dev/null +++ b/apps/cli/test/skills.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest"; +import { buildCommandMap } from "../src/commands/skills.js"; + +describe("buildCommandMap", () => { + it("includes every registered command across both groups", async () => { + const commands = await buildCommandMap(); + const names = [...commands.keys()].sort(); + + expect(names).toEqual( + ["clean", "init", "logs", "snapshot", "start", "status", "stop"].sort(), + ); + }); +});