diff --git a/src/cli/index.ts b/src/cli/index.ts index 1d67a75..8e660cd 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -7,24 +7,46 @@ import { handoffCreate, handoffRead, handoffList } from './commands/handoff.js'; import { decisionCreate, decisionList } from './commands/decision.js'; import { delegate } from './commands/delegate.js'; import { serverStart } from './commands/server.js'; +import { remoteClient } from './remoteClient.js'; + +function getServerUrl(program: Command): string | undefined { + return (program.opts().server as string | undefined) || process.env.AGENTHUB_SERVER; +} + +function remoteOnly(): never { + console.error('Remote mode is not supported for this command. Run it locally or omit --server.'); + process.exit(1); +} export function createProgram(cwd: string): Command { const program = new Command('agenthub') .description('Local coordination layer for AI coding agents') - .version('0.1.0'); + .version('0.1.0') + .option('--server ', 'AgentHub server URL (env: AGENTHUB_SERVER)'); program .command('init') .description('Initialize AgentHub in the current directory') .option('-n, --project-name ', 'Project name') .option('-y, --yes', 'Use defaults without prompts') - .action((options) => init(cwd, options)); + .action((options) => { + if (getServerUrl(program)) remoteOnly(); + init(cwd, options); + }); program .command('status') .description('Show project status') .option('-u, --update', 'Regenerate status before showing') - .action((options) => status(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const body = options.update ? await remoteClient.updateStatus(serverUrl) : await remoteClient.getStatus(serverUrl); + console.log(body); + } else { + status(cwd, options); + } + }); const memoryCmd = new Command('memory').description('Manage memory entries'); memoryCmd @@ -33,15 +55,41 @@ export function createProgram(cwd: string): Command { .option('--title ', 'Title') .option('--category <category>', 'Category') .option('--content <content>', 'Content') - .action((options) => memoryAdd(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const memory = await remoteClient.addMemory(serverUrl, options); + console.log(`Memory saved as ${memory.id}.`); + } else { + await memoryAdd(cwd, options); + } + }); memoryCmd .command('search <query>') .description('Search memory and tasks') - .action((query) => memorySearch(cwd, query)); + .action(async (query) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const results = await remoteClient.searchMemory(serverUrl, query); + if (results.length === 0) { console.log('No results found.'); return; } + for (const r of results) console.log(`[${r.type}] ${r.id}: ${r.title}`); + } else { + memorySearch(cwd, query); + } + }); memoryCmd .command('list') .description('List memory entries') - .action(() => memoryList(cwd)); + .action(async () => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const memories = await remoteClient.listMemory(serverUrl); + if (memories.length === 0) { console.log('No memory entries found.'); return; } + for (const m of memories) console.log(`${m.id}: ${m.title}`); + } else { + memoryList(cwd); + } + }); program.addCommand(memoryCmd); const taskCmd = new Command('task').description('Manage tasks'); @@ -51,26 +99,69 @@ export function createProgram(cwd: string): Command { .option('--title <title>', 'Title') .option('--role <role>', 'Role') .option('--priority <priority>', 'Priority') - .action((options) => taskCreate(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const task = await remoteClient.createTask(serverUrl, options); + console.log(`Created ${task.id}: ${task.title}`); + } else { + await taskCreate(cwd, options); + } + }); taskCmd .command('list') .description('List tasks') .option('--status <status>', 'Filter by status') .option('--role <role>', 'Filter by role') - .action((options) => taskList(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const tasks = await remoteClient.listTasks(serverUrl, options); + if (tasks.length === 0) { console.log('No tasks found.'); return; } + for (const t of tasks) console.log(`${t.id} [${t.status}] (${t.role ?? 'unassigned'}) ${t.title}`); + } else { + taskList(cwd, options); + } + }); taskCmd .command('show <id>') .description('Show a task') - .action((id) => taskShow(cwd, id)); + .action(async (id) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const { task, body } = await remoteClient.getTask(serverUrl, id); + console.log(`# ${task.title}`); + console.log(`Status: ${task.status} | Role: ${task.role ?? '-'} | Priority: ${task.priority}`); + console.log('\n' + body); + } else { + taskShow(cwd, id); + } + }); taskCmd .command('claim <id>') .description('Claim a task') .requiredOption('--agent <agent>', 'Agent name') - .action((id, options) => taskClaim(cwd, id, options.agent)); + .action(async (id, options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + await remoteClient.claimTask(serverUrl, id, options.agent); + console.log(`${id} claimed by ${options.agent}.`); + } else { + taskClaim(cwd, id, options.agent); + } + }); taskCmd .command('done <id>') - .description('Mark task as done') - .action((id) => taskDone(cwd, id)); + .description('Mark a task as done') + .action(async (id) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + await remoteClient.doneTask(serverUrl, id); + console.log(`${id} marked as done.`); + } else { + taskDone(cwd, id); + } + }); program.addCommand(taskCmd); const handoffCmd = new Command('handoff').description('Manage handoffs'); @@ -82,15 +173,43 @@ export function createProgram(cwd: string): Command { .option('--taskId <id>', 'Related task id') .option('--summary <summary>', 'Summary') .option('--context <context>', 'Context') - .action((options) => handoffCreate(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const handoff = await remoteClient.createHandoff(serverUrl, options); + console.log(`Handoff created: ${handoff.id}`); + } else { + await handoffCreate(cwd, options); + } + }); handoffCmd .command('read <id>') .description('Read a handoff') - .action((id) => handoffRead(cwd, id)); + .action(async (id) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const { handoff, body } = await remoteClient.getHandoff(serverUrl, id); + console.log(`# ${handoff.summary}`); + console.log(`From: ${handoff.fromRole} → ${handoff.toRole}`); + if (handoff.taskId) console.log(`Task: ${handoff.taskId}`); + console.log('\n' + body); + } else { + handoffRead(cwd, id); + } + }); handoffCmd .command('list') .description('List handoffs') - .action(() => handoffList(cwd)); + .action(async () => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const handoffs = await remoteClient.listHandoffs(serverUrl); + if (handoffs.length === 0) { console.log('No handoffs found.'); return; } + for (const h of handoffs) console.log(`${h.id}: ${h.title}`); + } else { + handoffList(cwd); + } + }); program.addCommand(handoffCmd); const decisionCmd = new Command('decision').description('Manage decisions'); @@ -100,25 +219,58 @@ export function createProgram(cwd: string): Command { .option('--title <title>', 'Title') .option('--context <context>', 'Context') .option('--decision <decision>', 'Decision') - .action((options) => decisionCreate(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const decision = await remoteClient.createDecision(serverUrl, options); + console.log(`Decision recorded: ${decision.id}`); + } else { + await decisionCreate(cwd, options); + } + }); decisionCmd .command('list') .description('List decisions') - .action(() => decisionList(cwd)); + .action(async () => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const decisions = await remoteClient.listDecisions(serverUrl); + if (decisions.length === 0) { console.log('No decisions found.'); return; } + for (const d of decisions) console.log(`${d.id}: ${d.title}`); + } else { + decisionList(cwd); + } + }); program.addCommand(decisionCmd); program .command('delegate') .description('Suggest or auto-delegate open tasks') .option('--auto', 'Create handoff automatically') - .action((options) => delegate(cwd, options)); + .action(async (options) => { + const serverUrl = getServerUrl(program); + if (serverUrl) { + const result = await remoteClient.delegate(serverUrl, options.auto ?? false); + if (!result.suggestion) { console.log('No open tasks to delegate.'); return; } + const s = result.suggestion; + console.log('Suggested delegation:'); + console.log(` Task: ${s.task.id} — ${s.task.title}`); + console.log(` Role: ${s.role}`); + console.log(` Preferred agent: ${s.preferredAgent}`); + if (result.handoff) console.log('Handoff created automatically.'); + else console.log('Run with --auto to create the handoff.'); + } else { + await delegate(cwd, options); + } + }); const serverCmd = new Command('server').description('Optional local API server'); serverCmd .command('start') .description('Start the optional AgentHub API server') .option('-p, --port <port>', 'Port', '3377') - .action((options) => serverStart(cwd, { port: parseInt(options.port, 10) })); + .option('-h, --host <host>', 'Host to bind to', '127.0.0.1') + .action((options) => serverStart(cwd, { port: parseInt(options.port, 10), host: options.host })); program.addCommand(serverCmd); return program;