From 586f7caa7a0710d46d4aea609f3b4f804ac27bcd Mon Sep 17 00:00:00 2001 From: Lukas May Date: Fri, 30 Jan 2026 20:34:57 +0100 Subject: [PATCH] feat(05-02): add task CLI commands - Add `cw task list --plan ` command - Display tasks as table: name, status, type, priority - Show count of pending/in_progress/completed/blocked - Add `cw task get ` command - Display full task details: id, name, description, type, priority, status, order, timestamps - Add `cw task status ` command - Validate status is one of: pending, in_progress, completed, blocked - Confirm status update --- src/cli/index.ts | 90 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index ea7ab4b..9aae69f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -231,11 +231,93 @@ export function createCli(serverHandler?: (port?: number) => Promise): Com } }); - program + // Task command group + const taskCommand = program .command('task') - .description('Manage tasks') - .action(() => { - console.log('cw task: not implemented'); + .description('Manage tasks'); + + // cw task list --plan + taskCommand + .command('list') + .description('List tasks for a plan') + .requiredOption('--plan ', 'Plan ID to list tasks for') + .action(async (options: { plan: string }) => { + try { + const client = createDefaultTrpcClient(); + const tasks = await client.listTasks.query({ planId: options.plan }); + if (tasks.length === 0) { + console.log('No tasks found for this plan'); + return; + } + + // Count by status + const pending = tasks.filter(t => t.status === 'pending' || t.status === 'pending_approval').length; + const inProgress = tasks.filter(t => t.status === 'in_progress').length; + const completed = tasks.filter(t => t.status === 'completed').length; + const blocked = tasks.filter(t => t.status === 'blocked').length; + + console.log('Tasks:'); + console.log(''); + for (const task of tasks) { + const statusLabel = task.status === 'in_progress' ? 'IN_PROGRESS' : task.status.toUpperCase(); + const priorityLabel = task.priority === 'high' ? '[HIGH]' : task.priority === 'low' ? '[low]' : ''; + console.log(` ${task.order}. ${task.name} [${statusLabel}] ${task.type} ${priorityLabel}`); + } + console.log(''); + console.log(`Summary: ${pending} pending, ${inProgress} in progress, ${completed} completed, ${blocked} blocked`); + } catch (error) { + console.error('Failed to list tasks:', (error as Error).message); + process.exit(1); + } + }); + + // cw task get + taskCommand + .command('get ') + .description('Get task details by ID') + .action(async (taskId: string) => { + try { + const client = createDefaultTrpcClient(); + const task = await client.getTask.query({ id: taskId }); + console.log(`Task: ${task.name}`); + console.log(` ID: ${task.id}`); + console.log(` Plan: ${task.planId}`); + console.log(` Description: ${task.description ?? '(none)'}`); + console.log(` Type: ${task.type}`); + console.log(` Priority: ${task.priority}`); + console.log(` Status: ${task.status}`); + console.log(` Order: ${task.order}`); + console.log(` Created: ${task.createdAt}`); + console.log(` Updated: ${task.updatedAt}`); + } catch (error) { + console.error('Failed to get task:', (error as Error).message); + process.exit(1); + } + }); + + // cw task status + taskCommand + .command('status ') + .description('Update task status (pending, in_progress, completed, blocked)') + .action(async (taskId: string, status: string) => { + const validStatuses = ['pending', 'in_progress', 'completed', 'blocked']; + if (!validStatuses.includes(status)) { + console.error(`Invalid status: ${status}`); + console.error(`Valid statuses: ${validStatuses.join(', ')}`); + process.exit(1); + } + + try { + const client = createDefaultTrpcClient(); + const task = await client.updateTaskStatus.mutate({ + id: taskId, + status: status as 'pending' | 'in_progress' | 'completed' | 'blocked', + }); + console.log(`Task '${task.name}' status updated to: ${task.status}`); + } catch (error) { + console.error('Failed to update task status:', (error as Error).message); + process.exit(1); + } }); return program;