diff --git a/src/cli/index.ts b/src/cli/index.ts index 9aae69f..8d215be 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -320,6 +320,210 @@ export function createCli(serverHandler?: (port?: number) => Promise): Com } }); + // Message command group + const messageCommand = program + .command('message') + .description('View agent messages and questions'); + + // cw message list [--agent ] [--status ] + messageCommand + .command('list') + .description('List messages from agents') + .option('--agent ', 'Filter by agent ID') + .option('--status ', 'Filter by status (pending, read, responded)') + .action(async (options: { agent?: string; status?: string }) => { + // Validate status if provided + if (options.status && !['pending', 'read', 'responded'].includes(options.status)) { + console.error(`Invalid status: ${options.status}`); + console.error('Valid statuses: pending, read, responded'); + process.exit(1); + } + + try { + const client = createDefaultTrpcClient(); + const messages = await client.listMessages.query({ + agentId: options.agent, + status: options.status as 'pending' | 'read' | 'responded' | undefined, + }); + + if (messages.length === 0) { + console.log('No messages found'); + return; + } + + // Count pending + const pendingCount = messages.filter(m => m.status === 'pending').length; + if (pendingCount > 0) { + console.log(`(${pendingCount} pending)\n`); + } + + console.log('Messages:'); + console.log(''); + for (const msg of messages) { + const idShort = msg.id.slice(0, 8); + const agentId = msg.senderId?.slice(0, 8) ?? 'user'; + const content = msg.content.length > 50 ? msg.content.slice(0, 47) + '...' : msg.content; + const statusLabel = msg.status.toUpperCase(); + const createdAt = new Date(msg.createdAt).toLocaleString(); + console.log(` ${idShort} ${agentId} ${msg.type} [${statusLabel}] ${createdAt}`); + console.log(` ${content}`); + } + } catch (error) { + console.error('Failed to list messages:', (error as Error).message); + process.exit(1); + } + }); + + // cw message read + messageCommand + .command('read ') + .description('Read full message content') + .action(async (messageId: string) => { + try { + const client = createDefaultTrpcClient(); + const message = await client.getMessage.query({ id: messageId }); + + console.log(`Message: ${message.id}`); + console.log(` From: ${message.senderType} (${message.senderId ?? 'user'})`); + console.log(` To: ${message.recipientType} (${message.recipientId ?? 'user'})`); + console.log(` Type: ${message.type}`); + console.log(` Status: ${message.status}`); + console.log(` Created: ${new Date(message.createdAt).toLocaleString()}`); + console.log(''); + console.log('Content:'); + console.log(message.content); + + if (message.status === 'pending' && message.requiresResponse) { + console.log(''); + console.log('---'); + console.log('This message requires a response.'); + console.log(`Use: cw message respond ${message.id} ""`); + } + } catch (error) { + console.error('Failed to read message:', (error as Error).message); + process.exit(1); + } + }); + + // cw message respond + messageCommand + .command('respond ') + .description('Respond to a message') + .action(async (messageId: string, response: string) => { + try { + const client = createDefaultTrpcClient(); + const responseMessage = await client.respondToMessage.mutate({ + id: messageId, + response, + }); + + console.log(`Response recorded (${responseMessage.id.slice(0, 8)})`); + console.log(''); + console.log('If the agent is waiting, you may want to resume it:'); + console.log(' cw agent list (to see waiting agents)'); + console.log(' cw agent resume ""'); + } catch (error) { + console.error('Failed to respond to message:', (error as Error).message); + process.exit(1); + } + }); + + // Dispatch command group + const dispatchCommand = program + .command('dispatch') + .description('Control task dispatch queue'); + + // cw dispatch queue + dispatchCommand + .command('queue ') + .description('Queue a task for dispatch') + .action(async (taskId: string) => { + try { + const client = createDefaultTrpcClient(); + await client.queueTask.mutate({ taskId }); + console.log(`Task '${taskId}' queued for dispatch`); + } catch (error) { + console.error('Failed to queue task:', (error as Error).message); + process.exit(1); + } + }); + + // cw dispatch next + dispatchCommand + .command('next') + .description('Dispatch next available task to an agent') + .action(async () => { + try { + const client = createDefaultTrpcClient(); + const result = await client.dispatchNext.mutate(); + + if (result.success) { + console.log('Task dispatched successfully'); + console.log(` Task: ${result.taskId}`); + console.log(` Agent: ${result.agentId}`); + } else { + console.log('Dispatch failed'); + console.log(` Task: ${result.taskId || '(none)'}`); + console.log(` Reason: ${result.reason}`); + } + } catch (error) { + console.error('Failed to dispatch:', (error as Error).message); + process.exit(1); + } + }); + + // cw dispatch status + dispatchCommand + .command('status') + .description('Show dispatch queue status') + .action(async () => { + try { + const client = createDefaultTrpcClient(); + const state = await client.getQueueState.query(); + + console.log('Dispatch Queue Status'); + console.log('====================='); + console.log(`Queued: ${state.queued.length}`); + console.log(`Ready: ${state.ready.length}`); + console.log(`Blocked: ${state.blocked.length}`); + + if (state.ready.length > 0) { + console.log(''); + console.log('Ready tasks:'); + for (const task of state.ready) { + const priority = task.priority === 'high' ? '[HIGH]' : task.priority === 'low' ? '[low]' : ''; + console.log(` ${task.taskId} ${priority}`); + } + } + + if (state.blocked.length > 0) { + console.log(''); + console.log('Blocked tasks:'); + for (const bt of state.blocked) { + console.log(` ${bt.taskId}: ${bt.reason}`); + } + } + } catch (error) { + console.error('Failed to get queue status:', (error as Error).message); + process.exit(1); + } + }); + + // cw dispatch complete + dispatchCommand + .command('complete ') + .description('Mark a task as complete') + .action(async (taskId: string) => { + try { + const client = createDefaultTrpcClient(); + await client.completeTask.mutate({ taskId }); + console.log(`Task '${taskId}' marked as complete`); + } catch (error) { + console.error('Failed to complete task:', (error as Error).message); + process.exit(1); + } + }); + return program; }