/**
 * Codewalkers CLI
 *
 * Commander-based CLI with help system and version display.
 * Supports server mode via --server flag.
 */

import { Command } from 'commander';
import { VERSION } from '../index.js';
import { CoordinationServer } from '../server/index.js';
import { GracefulShutdown } from '../server/shutdown.js';
import { createDefaultTrpcClient } from './trpc-client.js';
import { createContainer } from '../container.js';
import { findWorkspaceRoot, writeCwrc, defaultCwConfig } from '../config/index.js';
import { createModuleLogger } from '../logger/index.js';

/** Environment variable for custom port */
const CW_PORT_ENV = 'CW_PORT';

/**
 * Starts the coordination server in foreground mode.
 * Server runs until terminated via SIGTERM/SIGINT.
 */
async function startServer(port?: number, debug?: boolean): Promise<void> {
  // Get port from option, env var, or default
  const serverPort = port ??
    (process.env[CW_PORT_ENV] ? parseInt(process.env[CW_PORT_ENV], 10) : undefined);
  const log = createModuleLogger('server');

  // Create full dependency graph
  const container = await createContainer({ debug });

  // Create and start server
  const server = new CoordinationServer(
    { port: serverPort },
    container.processManager,
    container.logManager,
    container.eventBus,
    container.toContextDeps(),
  );

  try {
    await server.start();
  } catch (error) {
    log.fatal({ err: error }, 'failed to start server');
    console.error('Failed to start server:', (error as Error).message);
    process.exit(1);
  }

  // Install graceful shutdown handlers
  const shutdown = new GracefulShutdown(server, container.processManager, container.logManager, container.previewManager);
  shutdown.install();
}

/**
 * Creates and configures the CLI program.
 *
 * @param serverHandler - Optional handler to be called for server mode
 * @returns Configured Commander program ready for parsing
 */
export function createCli(serverHandler?: (port?: number) => Promise<void>): Command {
  const program = new Command();

  program
    .name('cw')
    .description('Multi-agent workspace for orchestrating multiple Claude Code agents')
    .version(VERSION, '-v, --version', 'Display version number');

  // Server mode options (global flags)
  program
    .option('-s, --server', 'Start the coordination server')
    .option('-p, --port <number>', 'Port for the server (default: 3847, env: CW_PORT)', parseInt)
    .option('-d, --debug', 'Enable debug mode (archive agent workdirs before cleanup)');

  // Handle the case where --server is provided without a command
  // This makes --server work as a standalone action
  program.hook('preAction', async (_thisCommand, _actionCommand) => {
    const opts = program.opts();
    if (opts.server && serverHandler) {
      await serverHandler(opts.port);
      process.exit(0);
    }
  });

  // Status command - shows workspace status via tRPC
  program
    .command('status')
    .description('Show workspace status')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const health = await client.health.query();

        console.log('Coordination Server Status');
        console.log('==========================');
        console.log(`Status: ${health.status}`);
        console.log(`Uptime: ${health.uptime}s`);
        console.log(`Processes: ${health.processCount}`);
      } catch {
        console.log('Server not running or unreachable. Start with: cw --server');
      }
    });

  // Init command - create .cwrc in current directory
  program
    .command('init')
    .description('Initialize a cw workspace in the current directory')
    .action(() => {
      const cwd = process.cwd();
      const existing = findWorkspaceRoot(cwd);
      if (existing && existing === cwd) {
        console.log(`Workspace already initialized at ${cwd}`);
        return;
      }
      if (existing) {
        console.log(`Parent workspace found at ${existing}`);
        console.log(`Creating nested workspace at ${cwd}`);
      }

      writeCwrc(cwd, defaultCwConfig());
      console.log(`Initialized cw workspace at ${cwd}`);
    });

  // ID generation command (stateless — no server, no tRPC)
  program
    .command('id')
    .description('Generate a unique nanoid (works offline, no server needed)')
    .option('-n, --count <count>', 'Number of IDs to generate', '1')
    .action(async (options: { count: string }) => {
      const { nanoid } = await import('nanoid');
      const count = parseInt(options.count, 10) || 1;
      for (let i = 0; i < count; i++) {
        console.log(nanoid());
      }
    });

  // Agent command group
  const agentCommand = program
    .command('agent')
    .description('Manage agents');

  // cw agent spawn --name <name> --task <taskId> <prompt>
  agentCommand
    .command('spawn <prompt>')
    .description('Spawn a new agent to work on a task')
    .option('--name <name>', 'Human-readable name for the agent (auto-generated if omitted)')
    .requiredOption('--task <taskId>', 'Task ID to assign to agent')
    .option('--cwd <path>', 'Working directory for agent')
    .option('--provider <provider>', 'Agent provider (claude, codex, gemini, cursor, auggie, amp, opencode)')
    .option('--initiative <id>', 'Initiative ID (creates worktrees for all linked projects)')
    .action(async (prompt: string, options: { name?: string; task: string; cwd?: string; provider?: string; initiative?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        const agent = await client.spawnAgent.mutate({
          name: options.name,
          taskId: options.task,
          prompt,
          cwd: options.cwd,
          provider: options.provider,
          initiativeId: options.initiative,
        });
        console.log(`Agent '${agent.name}' spawned`);
        console.log(`  ID: ${agent.id}`);
        console.log(`  Task: ${agent.taskId}`);
        console.log(`  Status: ${agent.status}`);
        console.log(`  Worktree: ${agent.worktreeId}`);
      } catch (error) {
        console.error('Failed to spawn agent:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw agent stop <name>
  agentCommand
    .command('stop <name>')
    .description('Stop a running agent by name')
    .action(async (name: string) => {
      try {
        const client = createDefaultTrpcClient();
        const result = await client.stopAgent.mutate({ name });
        console.log(`Agent '${result.name}' stopped`);
      } catch (error) {
        console.error('Failed to stop agent:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw agent delete <name>
  agentCommand
    .command('delete <name>')
    .description('Delete an agent and clean up its workdir, branches, and logs')
    .action(async (name: string) => {
      try {
        const client = createDefaultTrpcClient();
        const result = await client.deleteAgent.mutate({ name });
        console.log(`Agent '${result.name}' deleted`);
      } catch (error) {
        console.error('Failed to delete agent:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw agent list
  agentCommand
    .command('list')
    .description('List all agents')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const agents = await client.listAgents.query();
        if (agents.length === 0) {
          console.log('No agents found');
          return;
        }
        console.log('Agents:');
        for (const agent of agents) {
          const status = agent.status === 'waiting_for_input' ? 'WAITING' : agent.status.toUpperCase();
          console.log(`  ${agent.name} [${status}] - ${agent.taskId}`);
        }
      } catch (error) {
        console.error('Failed to list agents:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw agent get <name>
  agentCommand
    .command('get <name>')
    .description('Get agent details by name')
    .action(async (name: string) => {
      try {
        const client = createDefaultTrpcClient();
        const agent = await client.getAgent.query({ name });
        if (!agent) {
          console.log(`Agent '${name}' not found`);
          return;
        }
        console.log(`Agent: ${agent.name}`);
        console.log(`  ID: ${agent.id}`);
        console.log(`  Task: ${agent.taskId}`);
        console.log(`  Session: ${agent.sessionId ?? '(none)'}`);
        console.log(`  Worktree: ${agent.worktreeId}`);
        console.log(`  Status: ${agent.status}`);
        console.log(`  Created: ${agent.createdAt}`);
        console.log(`  Updated: ${agent.updatedAt}`);
      } catch (error) {
        console.error('Failed to get agent:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw agent resume <name> <answers>
  // Accepts either JSON object {"q1": "answer1", "q2": "answer2"} or single answer
  agentCommand
    .command('resume <name> <answers>')
    .description('Resume an agent with answers (JSON object or single answer string)')
    .action(async (name: string, answersInput: string) => {
      try {
        const client = createDefaultTrpcClient();
        // Try parsing as JSON first, fallback to single answer format
        let answers: Record<string, string>;
        try {
          const parsed = JSON.parse(answersInput);
          if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
            answers = parsed;
          } else {
            // Not a valid object, treat as single answer
            answers = { q1: answersInput };
          }
        } catch {
          // Not valid JSON, treat as single answer with default question ID
          answers = { q1: answersInput };
        }
        const result = await client.resumeAgent.mutate({ name, answers });
        console.log(`Agent '${result.name}' resumed`);
      } catch (error) {
        console.error('Failed to resume agent:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw agent result <name>
  agentCommand
    .command('result <name>')
    .description('Get agent execution result')
    .action(async (name: string) => {
      try {
        const client = createDefaultTrpcClient();
        const result = await client.getAgentResult.query({ name });
        if (!result) {
          console.log('No result available (agent may still be running)');
          return;
        }
        console.log(`Result: ${result.success ? 'SUCCESS' : 'FAILED'}`);
        console.log(`  Message: ${result.message}`);
        if (result.filesModified?.length) {
          console.log(`  Files modified: ${result.filesModified.join(', ')}`);
        }
      } catch (error) {
        console.error('Failed to get result:', (error as Error).message);
        process.exit(1);
      }
    });

  // Task command group
  const taskCommand = program
    .command('task')
    .description('Manage tasks');

  // cw task list --parent <parentTaskId> | --phase <phaseId> | --initiative <initiativeId>
  taskCommand
    .command('list')
    .description('List tasks (by parent task, phase, or initiative)')
    .option('--parent <parentTaskId>', 'Parent task ID (for child tasks)')
    .option('--phase <phaseId>', 'Phase ID')
    .option('--initiative <initiativeId>', 'Initiative ID')
    .action(async (options: { parent?: string; phase?: string; initiative?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        let tasks;

        if (options.parent) {
          tasks = await client.listTasks.query({ parentTaskId: options.parent });
        } else if (options.phase) {
          tasks = await client.listPhaseTasks.query({ phaseId: options.phase });
        } else if (options.initiative) {
          tasks = await client.listInitiativeTasks.query({ initiativeId: options.initiative });
        } else {
          console.error('One of --parent, --phase, or --initiative is required');
          process.exit(1);
        }

        if (tasks.length === 0) {
          console.log('No tasks found');
          return;
        }

        // Count by status
        const pending = tasks.filter(t => t.status === 'pending').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 <taskId>
  taskCommand
    .command('get <taskId>')
    .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(`  Phase: ${task.phaseId ?? '(none)'}`);
        console.log(`  Initiative: ${task.initiativeId ?? '(none)'}`);
        console.log(`  Parent Task: ${task.parentTaskId ?? '(none)'}`);
        console.log(`  Description: ${task.description ?? '(none)'}`);
        console.log(`  Category: ${task.category}`);
        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 <taskId> <status>
  taskCommand
    .command('status <taskId> <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);
      }
    });

  // Message command group
  const messageCommand = program
    .command('message')
    .description('View agent messages and questions');

  // cw message list [--agent <agentId>] [--status <status>]
  messageCommand
    .command('list')
    .description('List messages from agents')
    .option('--agent <agentId>', 'Filter by agent ID')
    .option('--status <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 <messageId>
  messageCommand
    .command('read <messageId>')
    .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} "<your response>"`);
        }
      } catch (error) {
        console.error('Failed to read message:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw message respond <messageId> <response>
  messageCommand
    .command('respond <messageId> <response>')
    .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 <name> "<response>"');
      } 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 <taskId>
  dispatchCommand
    .command('queue <taskId>')
    .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 <taskId>
  dispatchCommand
    .command('complete <taskId>')
    .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);
      }
    });

  // Merge command group
  const mergeCommand = program
    .command('merge')
    .description('Manage merge queue');

  // cw merge queue <taskId>
  mergeCommand
    .command('queue <taskId>')
    .description('Queue a completed task for merge')
    .action(async (taskId: string) => {
      try {
        const client = createDefaultTrpcClient();
        await client.queueMerge.mutate({ taskId });
        console.log(`Task '${taskId}' queued for merge`);
      } catch (error) {
        console.error('Failed to queue for merge:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw merge status
  mergeCommand
    .command('status')
    .description('Show merge queue status')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const state = await client.getMergeQueueStatus.query();

        console.log('Merge Queue Status');
        console.log('==================');
        console.log(`Queued: ${state.queued.length}`);
        console.log(`In Progress: ${state.inProgress.length}`);
        console.log(`Merged: ${state.merged.length}`);
        console.log(`Conflicted: ${state.conflicted.length}`);

        if (state.conflicted.length > 0) {
          console.log('');
          console.log('Conflicts:');
          for (const c of state.conflicted) {
            console.log(`  ${c.taskId}: ${c.conflicts.join(', ')}`);
          }
        }
      } catch (error) {
        console.error('Failed to get merge queue status:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw merge next
  mergeCommand
    .command('next')
    .description('Show next task ready to merge')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const next = await client.getNextMergeable.query();
        if (next) {
          console.log(`Next mergeable: ${next.taskId} (priority: ${next.priority})`);
        } else {
          console.log('No tasks ready to merge');
        }
      } catch (error) {
        console.error('Failed to get next mergeable:', (error as Error).message);
        process.exit(1);
      }
    });

  // Coordinate command - process all ready merges
  program
    .command('coordinate')
    .description('Process all ready merges in dependency order')
    .option('-t, --target <branch>', 'Target branch for merges', 'main')
    .action(async (options: { target: string }) => {
      try {
        const client = createDefaultTrpcClient();
        console.log(`Processing merges to ${options.target}...`);

        const { results } = await client.processMerges.mutate({
          targetBranch: options.target,
        });

        const succeeded = results.filter(r => r.success).length;
        const conflicted = results.filter(r => !r.success).length;

        console.log('');
        console.log('Results:');
        console.log(`  Merged: ${succeeded}`);
        console.log(`  Conflicts: ${conflicted}`);

        if (conflicted > 0) {
          console.log('');
          console.log('Conflicted tasks (bounce-back tasks created):');
          for (const r of results.filter(r => !r.success)) {
            console.log(`  ${r.taskId}: ${r.conflicts?.join(', ')}`);
          }
        }
      } catch (error) {
        console.error('Failed to process merges:', (error as Error).message);
        process.exit(1);
      }
    });

  // Initiative command group
  const initiativeCommand = program
    .command('initiative')
    .description('Manage initiatives');

  // cw initiative create <name>
  initiativeCommand
    .command('create <name>')
    .description('Create a new initiative')
    .option('--project <ids...>', 'Project IDs to associate')
    .action(async (name: string, options: { project?: string[] }) => {
      try {
        const client = createDefaultTrpcClient();
        const initiative = await client.createInitiative.mutate({
          name,
          projectIds: options.project,
        });
        console.log(`Created initiative: ${initiative.id}`);
        console.log(`  Name: ${initiative.name}`);
        if (options.project && options.project.length > 0) {
          console.log(`  Projects: ${options.project.length} associated`);
        }
      } catch (error) {
        console.error('Failed to create initiative:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw initiative list
  initiativeCommand
    .command('list')
    .description('List all initiatives')
    .option('-s, --status <status>', 'Filter by status (active, completed, archived)')
    .action(async (options: { status?: string }) => {
      // Validate status if provided
      const validStatuses = ['active', 'completed', 'archived'];
      if (options.status && !validStatuses.includes(options.status)) {
        console.error(`Invalid status: ${options.status}`);
        console.error(`Valid statuses: ${validStatuses.join(', ')}`);
        process.exit(1);
      }

      try {
        const client = createDefaultTrpcClient();
        const initiatives = await client.listInitiatives.query(
          options.status ? { status: options.status as 'active' | 'completed' | 'archived' } : undefined
        );
        if (initiatives.length === 0) {
          console.log('No initiatives found');
          return;
        }
        for (const init of initiatives) {
          console.log(`${init.id}  ${init.status.padEnd(10)}  ${init.name}`);
        }
      } catch (error) {
        console.error('Failed to list initiatives:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw initiative get <id>
  initiativeCommand
    .command('get <id>')
    .description('Get initiative details')
    .action(async (id: string) => {
      try {
        const client = createDefaultTrpcClient();
        const initiative = await client.getInitiative.query({ id });
        console.log(`ID: ${initiative.id}`);
        console.log(`Name: ${initiative.name}`);
        console.log(`Status: ${initiative.status}`);
        console.log(`Created: ${new Date(initiative.createdAt).toISOString()}`);
      } catch (error) {
        console.error('Failed to get initiative:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw initiative phases <initiative-id>
  initiativeCommand
    .command('phases <initiativeId>')
    .description('List phases for an initiative')
    .action(async (initiativeId: string) => {
      try {
        const client = createDefaultTrpcClient();
        const phases = await client.listPhases.query({ initiativeId });
        if (phases.length === 0) {
          console.log('No phases found');
          return;
        }
        for (const phase of phases) {
          const idx = phases.indexOf(phase);
          console.log(`${(idx + 1).toString().padStart(2)}. ${phase.name}  [${phase.status}]`);
          if (phase.content) {
            console.log(`    ${phase.content}`);
          }
        }
      } catch (error) {
        console.error('Failed to list phases:', (error as Error).message);
        process.exit(1);
      }
    });

  // Architect command group
  const architectCommand = program
    .command('architect')
    .description('Architect agent workflow');

  // cw architect discuss <initiative-id>
  architectCommand
    .command('discuss <initiativeId>')
    .description('Start discussion phase for an initiative')
    .option('--name <name>', 'Agent name (auto-generated if omitted)')
    .option('-c, --context <context>', 'Initial context')
    .action(async (initiativeId: string, options: { name?: string; context?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        const agent = await client.spawnArchitectDiscuss.mutate({
          name: options.name,
          initiativeId,
          context: options.context,
        });
        console.log(`Started architect agent in discuss mode`);
        console.log(`  Agent: ${agent.name} (${agent.id})`);
        console.log(`  Mode: ${agent.mode}`);
        console.log(`  Initiative: ${initiativeId}`);
      } catch (error) {
        console.error('Failed to start discuss:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw architect plan <initiative-id>
  architectCommand
    .command('plan <initiativeId>')
    .description('Plan phases for an initiative')
    .option('--name <name>', 'Agent name (auto-generated if omitted)')
    .option('-s, --summary <summary>', 'Context summary from discuss phase')
    .action(async (initiativeId: string, options: { name?: string; summary?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        const agent = await client.spawnArchitectPlan.mutate({
          name: options.name,
          initiativeId,
          contextSummary: options.summary,
        });
        console.log(`Started architect agent in plan mode`);
        console.log(`  Agent: ${agent.name} (${agent.id})`);
        console.log(`  Mode: ${agent.mode}`);
        console.log(`  Initiative: ${initiativeId}`);
      } catch (error) {
        console.error('Failed to start plan:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw architect detail <phase-id>
  architectCommand
    .command('detail <phaseId>')
    .description('Detail a phase into tasks')
    .option('--name <name>', 'Agent name (auto-generated if omitted)')
    .option('-t, --task-name <taskName>', 'Name for the detail task')
    .option('-c, --context <context>', 'Additional context')
    .action(async (phaseId: string, options: { name?: string; taskName?: string; context?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        const agent = await client.spawnArchitectDetail.mutate({
          name: options.name,
          phaseId,
          taskName: options.taskName,
          context: options.context,
        });
        console.log(`Started architect agent in detail mode`);
        console.log(`  Agent: ${agent.name} (${agent.id})`);
        console.log(`  Mode: ${agent.mode}`);
        console.log(`  Phase: ${phaseId}`);
      } catch (error) {
        console.error('Failed to start detail:', (error as Error).message);
        process.exit(1);
      }
    });

  // Phase command group
  const phaseCommand = program
    .command('phase')
    .description('Phase dependency and dispatch management');

  // cw phase add-dependency --phase <id> --depends-on <id>
  phaseCommand
    .command('add-dependency')
    .description('Add a dependency between phases')
    .requiredOption('--phase <id>', 'Phase that depends on another')
    .requiredOption('--depends-on <id>', 'Phase that must complete first')
    .action(async (options: { phase: string; dependsOn: string }) => {
      try {
        const client = createDefaultTrpcClient();
        await client.createPhaseDependency.mutate({
          phaseId: options.phase,
          dependsOnPhaseId: options.dependsOn,
        });
        console.log(`Added dependency: phase ${options.phase} depends on ${options.dependsOn}`);
      } catch (error) {
        console.error('Failed to add dependency:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw phase dependencies <phaseId>
  phaseCommand
    .command('dependencies <phaseId>')
    .description('List dependencies for a phase')
    .action(async (phaseId: string) => {
      try {
        const client = createDefaultTrpcClient();
        const result = await client.getPhaseDependencies.query({ phaseId });
        if (result.dependencies.length === 0) {
          console.log('No dependencies');
          return;
        }
        console.log('Dependencies:');
        for (const dep of result.dependencies) {
          console.log(`  ${dep}`);
        }
      } catch (error) {
        console.error('Failed to get dependencies:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw phase queue <phaseId>
  phaseCommand
    .command('queue <phaseId>')
    .description('Queue a phase for execution')
    .action(async (phaseId: string) => {
      try {
        const client = createDefaultTrpcClient();
        await client.queuePhase.mutate({ phaseId });
        console.log(`Phase ${phaseId} queued for execution`);
      } catch (error) {
        console.error('Failed to queue phase:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw phase dispatch
  phaseCommand
    .command('dispatch')
    .description('Dispatch next available phase')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const result = await client.dispatchNextPhase.mutate();
        if (result.success) {
          console.log('Phase dispatched successfully');
          console.log(`  Phase: ${result.phaseId}`);
        } else {
          console.log('Dispatch failed');
          console.log(`  Phase: ${result.phaseId || '(none)'}`);
          console.log(`  Reason: ${result.reason}`);
        }
      } catch (error) {
        console.error('Failed to dispatch phase:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw phase queue-status
  phaseCommand
    .command('queue-status')
    .description('Show phase queue status')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const state = await client.getPhaseQueueState.query();

        console.log('Phase 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 phases:');
          for (const phase of state.ready) {
            console.log(`  ${phase.phaseId}`);
          }
        }

        if (state.blocked.length > 0) {
          console.log('');
          console.log('Blocked phases:');
          for (const bp of state.blocked) {
            console.log(`  ${bp.phaseId}: ${bp.reason}`);
          }
        }
      } catch (error) {
        console.error('Failed to get queue status:', (error as Error).message);
        process.exit(1);
      }
    });

  // Project command group
  const projectCommand = program
    .command('project')
    .description('Manage registered projects');

  // cw project register --name <name> --url <url>
  projectCommand
    .command('register')
    .description('Register a git repository as a project')
    .requiredOption('--name <name>', 'Project name')
    .requiredOption('--url <url>', 'Git repository URL')
    .action(async (options: { name: string; url: string }) => {
      try {
        const client = createDefaultTrpcClient();
        const project = await client.registerProject.mutate({
          name: options.name,
          url: options.url,
        });
        console.log(`Registered project: ${project.id}`);
        console.log(`  Name: ${project.name}`);
        console.log(`  URL: ${project.url}`);
        if (project.hasPreviewConfig) {
          console.log('  Preview: .cw-preview.yml detected — preview deployments ready');
        } else {
          console.log('  Preview: No .cw-preview.yml found. Run `cw preview setup` for instructions.');
        }
      } catch (error) {
        console.error('Failed to register project:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw project list
  projectCommand
    .command('list')
    .description('List all registered projects')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const projects = await client.listProjects.query();
        if (projects.length === 0) {
          console.log('No projects registered');
          return;
        }
        for (const p of projects) {
          console.log(`${p.id}  ${p.name}  ${p.url}`);
        }
      } catch (error) {
        console.error('Failed to list projects:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw project delete <id>
  projectCommand
    .command('delete <id>')
    .description('Delete a registered project')
    .action(async (id: string) => {
      try {
        const client = createDefaultTrpcClient();
        await client.deleteProject.mutate({ id });
        console.log(`Deleted project: ${id}`);
      } catch (error) {
        console.error('Failed to delete project:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw project sync [name] --all
  projectCommand
    .command('sync [name]')
    .description('Sync project clone(s) from remote')
    .option('--all', 'Sync all projects')
    .action(async (name: string | undefined, options: { all?: boolean }) => {
      try {
        const client = createDefaultTrpcClient();
        if (options.all) {
          const results = await client.syncAllProjects.mutate();
          for (const r of results) {
            const status = r.success ? 'ok' : `FAILED: ${r.error}`;
            console.log(`${r.projectName}: ${status}`);
          }
        } else if (name) {
          const projects = await client.listProjects.query();
          const project = projects.find((p) => p.name === name || p.id === name);
          if (!project) {
            console.error(`Project not found: ${name}`);
            process.exit(1);
          }
          const result = await client.syncProject.mutate({ id: project.id });
          if (result.success) {
            console.log(`Synced ${result.projectName}: fetched=${result.fetched}, fast-forwarded=${result.fastForwarded}`);
          } else {
            console.error(`Sync failed: ${result.error}`);
            process.exit(1);
          }
        } else {
          console.error('Specify a project name or use --all');
          process.exit(1);
        }
      } catch (error) {
        console.error('Failed to sync:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw project status [name]
  projectCommand
    .command('status [name]')
    .description('Show sync status for a project')
    .action(async (name: string | undefined) => {
      try {
        const client = createDefaultTrpcClient();
        const projects = await client.listProjects.query();
        const targets = name
          ? projects.filter((p) => p.name === name || p.id === name)
          : projects;

        if (targets.length === 0) {
          console.log(name ? `Project not found: ${name}` : 'No projects registered');
          return;
        }

        for (const project of targets) {
          const status = await client.getProjectSyncStatus.query({ id: project.id });
          const fetchedStr = status.lastFetchedAt
            ? new Date(status.lastFetchedAt).toLocaleString()
            : 'never';
          console.log(`${project.name}:`);
          console.log(`  Last fetched: ${fetchedStr}`);
          console.log(`  Ahead: ${status.ahead}  Behind: ${status.behind}`);
        }
      } catch (error) {
        console.error('Failed to get status:', (error as Error).message);
        process.exit(1);
      }
    });

  // Account command group
  const accountCommand = program
    .command('account')
    .description('Manage provider accounts for agent spawning');

  // cw account add
  accountCommand
    .command('add')
    .description('Extract current Claude login and register as an account')
    .option('--provider <provider>', 'Provider name', 'claude')
    .option('--email <email>', 'Email (for manual registration without auto-extract)')
    .option('--token <token>', 'Setup token from `claude setup-token` (requires --email)')
    .action(async (options: { provider: string; email?: string; token?: string }) => {
      try {
        const client = createDefaultTrpcClient();

        if (options.token) {
          // Token-based registration
          if (!options.email) {
            console.error('Error: --email is required when using --token');
            process.exit(1);
          }

          const credentials = JSON.stringify({
            claudeAiOauth: { accessToken: options.token },
          });
          const configJson = JSON.stringify({ hasCompletedOnboarding: true });

          const existing = await client.listAccounts.query();
          const alreadyRegistered = existing.find((a: any) => a.email === options.email);
          if (alreadyRegistered) {
            await client.updateAccountAuth.mutate({
              id: alreadyRegistered.id,
              configJson,
              credentials,
            });
            console.log(`Updated credentials for account: ${alreadyRegistered.id}`);
            console.log(`  Email: ${options.email}`);
            console.log(`  Token updated (setup token)`);
            return;
          }

          const account = await client.addAccount.mutate({
            email: options.email,
            provider: options.provider,
            configJson,
            credentials,
          });
          console.log(`Registered account: ${account.id}`);
          console.log(`  Email: ${account.email}`);
          console.log(`  Provider: ${account.provider}`);
          console.log(`  Auth: setup token`);
        } else if (options.email) {
          // Manual registration — guard against duplicates
          const existing = await client.listAccounts.query();
          const alreadyRegistered = existing.find((a: any) => a.email === options.email);
          if (alreadyRegistered) {
            console.log(`Account '${options.email}' already registered (${alreadyRegistered.id})`);
            return;
          }

          const account = await client.addAccount.mutate({
            email: options.email,
            provider: options.provider,
          });
          console.log(`Registered account: ${account.id}`);
          console.log(`  Email: ${account.email}`);
          console.log(`  Provider: ${account.provider}`);
        } else {
          // Auto-extract from current Claude login
          const { extractCurrentClaudeAccount } = await import('../agent/accounts/index.js');
          const extracted = await extractCurrentClaudeAccount();

          // Check if already registered
          const existing = await client.listAccounts.query();
          const alreadyRegistered = existing.find((a: any) => a.email === extracted.email);
          if (alreadyRegistered) {
            // Compare refresh tokens to detect staleness
            let credentialsChanged = true;
            try {
              const dbCreds = alreadyRegistered.credentials ? JSON.parse(alreadyRegistered.credentials) : null;
              const sourceCreds = JSON.parse(extracted.credentials);
              const dbRefreshToken = dbCreds?.claudeAiOauth?.refreshToken;
              const sourceRefreshToken = sourceCreds?.claudeAiOauth?.refreshToken;
              credentialsChanged = dbRefreshToken !== sourceRefreshToken;
            } catch {
              // Parse error — assume changed, update to be safe
            }

            // Upsert: always update to be safe
            await client.updateAccountAuth.mutate({
              id: alreadyRegistered.id,
              configJson: JSON.stringify(extracted.configJson),
              credentials: extracted.credentials,
            });

            if (credentialsChanged) {
              console.log(`Updated credentials for account: ${alreadyRegistered.id}`);
              console.log(`  Email: ${extracted.email}`);
              console.log(`  Refresh token changed (source had fresher credentials)`);
            } else {
              console.log(`Credentials current for account: ${alreadyRegistered.id}`);
              console.log(`  Email: ${extracted.email}`);
              console.log(`  Refresh token unchanged`);
            }
            return;
          }

          // Create DB record with credentials stored in DB
          const account = await client.addAccount.mutate({
            email: extracted.email,
            provider: options.provider,
            configJson: JSON.stringify(extracted.configJson),
            credentials: extracted.credentials,
          });

          console.log(`Registered account: ${account.id}`);
          console.log(`  Email: ${account.email}`);
          console.log(`  Provider: ${account.provider}`);
        }
      } catch (error) {
        console.error('Failed to add account:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw account list
  accountCommand
    .command('list')
    .description('List all registered accounts')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const accounts = await client.listAccounts.query();
        if (accounts.length === 0) {
          console.log('No accounts registered');
          return;
        }
        for (const acct of accounts) {
          const status = acct.isExhausted ? 'EXHAUSTED' : 'AVAILABLE';
          const until = acct.exhaustedUntil ? ` (until ${new Date(acct.exhaustedUntil).toLocaleString()})` : '';
          console.log(`${acct.id}  ${acct.email}  ${acct.provider}  [${status}${until}]`);
        }
      } catch (error) {
        console.error('Failed to list accounts:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw account remove <id>
  accountCommand
    .command('remove <id>')
    .description('Remove an account')
    .action(async (id: string) => {
      try {
        const client = createDefaultTrpcClient();
        await client.removeAccount.mutate({ id });
        console.log(`Removed account: ${id}`);
      } catch (error) {
        console.error('Failed to remove account:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw account refresh
  accountCommand
    .command('refresh')
    .description('Clear expired exhaustion flags')
    .action(async () => {
      try {
        const client = createDefaultTrpcClient();
        const result = await client.refreshAccounts.mutate();
        console.log(`Cleared ${result.cleared} expired exhaustions`);
      } catch (error) {
        console.error('Failed to refresh accounts:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw account extract
  accountCommand
    .command('extract')
    .description('Extract current Claude credentials for use with the UI (does not require server)')
    .option('--email <email>', 'Verify extracted account matches this email')
    .action(async (options: { email?: string }) => {
      try {
        const { extractCurrentClaudeAccount } = await import('../agent/accounts/index.js');
        const extracted = await extractCurrentClaudeAccount();
        if (options.email && extracted.email !== options.email) {
          console.error(`Account '${options.email}' not found (active account is '${extracted.email}')`);
          process.exit(1);
          return;
        }
        const output = {
          email: extracted.email,
          configJson: JSON.stringify(extracted.configJson),
          credentials: extracted.credentials,
        };
        console.log(JSON.stringify(output, null, 2));
      } catch (error) {
        console.error('Failed to extract account:', (error as Error).message);
        process.exit(1);
      }
    });

  // Preview command group
  const previewCommand = program
    .command('preview')
    .description('Manage Docker-based preview deployments');

  // cw preview start --initiative <id> --project <id> --branch <branch> [--phase <id>]
  // cw preview start --agent <id>  (agent-simplified: server resolves everything)
  previewCommand
    .command('start')
    .description('Start a preview deployment')
    .option('--initiative <id>', 'Initiative ID')
    .option('--project <id>', 'Project ID')
    .option('--branch <branch>', 'Branch to deploy')
    .option('--phase <id>', 'Phase ID')
    .option('--agent <id>', 'Agent ID (server resolves initiative/project/branch)')
    .action(async (options: { initiative?: string; project?: string; branch?: string; phase?: string; agent?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        console.log('Starting preview deployment...');

        let preview;
        if (options.agent) {
          preview = await client.startPreviewForAgent.mutate({ agentId: options.agent });
        } else {
          if (!options.initiative || !options.project || !options.branch) {
            console.error('Either --agent or all of --initiative, --project, --branch are required');
            process.exit(1);
          }
          preview = await client.startPreview.mutate({
            initiativeId: options.initiative,
            projectId: options.project,
            branch: options.branch,
            phaseId: options.phase,
          });
        }

        console.log(`Preview started: ${preview.id}`);
        console.log(`  URL: ${preview.url}`);
        console.log(`  Branch: ${preview.branch}`);
        console.log(`  Mode: ${preview.mode}`);
        console.log(`  Status: ${preview.status}`);
        console.log(`  Services: ${preview.services.map(s => `${s.name} (${s.state})`).join(', ')}`);
      } catch (error) {
        console.error('Failed to start preview:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw preview stop <previewId>
  // cw preview stop --agent <id>
  previewCommand
    .command('stop [previewId]')
    .description('Stop a preview deployment')
    .option('--agent <id>', 'Stop preview by agent ID')
    .action(async (previewId: string | undefined, options: { agent?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        if (options.agent) {
          await client.stopPreviewByAgent.mutate({ agentId: options.agent });
          console.log(`Previews for agent '${options.agent}' stopped`);
        } else if (previewId) {
          await client.stopPreview.mutate({ previewId });
          console.log(`Preview '${previewId}' stopped`);
        } else {
          console.error('Either <previewId> or --agent <id> is required');
          process.exit(1);
        }
      } catch (error) {
        console.error('Failed to stop preview:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw preview list [--initiative <id>]
  previewCommand
    .command('list')
    .description('List active preview deployments')
    .option('--initiative <id>', 'Filter by initiative ID')
    .action(async (options: { initiative?: string }) => {
      try {
        const client = createDefaultTrpcClient();
        const previews = await client.listPreviews.query(
          options.initiative ? { initiativeId: options.initiative } : undefined,
        );
        if (previews.length === 0) {
          console.log('No active previews');
          return;
        }
        for (const p of previews) {
          console.log(`${p.id}  ${p.url}  ${p.branch}  ${p.mode}  [${p.status.toUpperCase()}]`);
        }
      } catch (error) {
        console.error('Failed to list previews:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw preview status <previewId>
  previewCommand
    .command('status <previewId>')
    .description('Get preview deployment status')
    .action(async (previewId: string) => {
      try {
        const client = createDefaultTrpcClient();
        const preview = await client.getPreviewStatus.query({ previewId });
        if (!preview) {
          console.log(`Preview '${previewId}' not found`);
          return;
        }
        console.log(`Preview: ${preview.id}`);
        console.log(`  URL: ${preview.url}`);
        console.log(`  Branch: ${preview.branch}`);
        console.log(`  Mode: ${preview.mode}`);
        console.log(`  Status: ${preview.status}`);
        console.log(`  Initiative: ${preview.initiativeId}`);
        console.log(`  Project: ${preview.projectId}`);
        if (preview.services.length > 0) {
          console.log('  Services:');
          for (const svc of preview.services) {
            console.log(`    ${svc.name}: ${svc.state} (health: ${svc.health})`);
          }
        }
      } catch (error) {
        console.error('Failed to get preview status:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw preview setup [--auto --project <id>]
  previewCommand
    .command('setup')
    .description('Show preview deployment setup instructions')
    .option('--auto', 'Auto-create initiative and spawn agent to set up preview config')
    .option('--project <id>', 'Project ID (required with --auto)')
    .option('--provider <name>', 'Agent provider (optional, with --auto)')
    .action(async (options: { auto?: boolean; project?: string; provider?: string }) => {
      if (!options.auto) {
        console.log(`Preview Deployment Setup
========================

Prerequisites:
  - Docker installed and running
  - Project registered in Codewalkers (cw project register)

Step 1: Add .cw-preview.yml to your project root

  Minimal (single service with Dockerfile):

    version: 1
    services:
      app:
        build: .
        port: 3000

  Multi-service (frontend + API + database):

    version: 1
    services:
      frontend:
        build:
          context: .
          dockerfile: apps/web/Dockerfile
        port: 3000
        route: /
        healthcheck:
          path: /
      backend:
        build:
          context: .
          dockerfile: packages/api/Dockerfile
        port: 8080
        route: /api
        healthcheck:
          path: /health
      db:
        image: postgres:16-alpine
        internal: true
        env:
          POSTGRES_PASSWORD: preview

Step 2: Commit .cw-preview.yml to your repo

Step 3: Start a preview
  cw preview start --initiative <id> --project <id> --branch <branch>

Without .cw-preview.yml, previews auto-discover:
  1. docker-compose.yml / compose.yml
  2. Dockerfile (single service, port 3000)

Full reference: docs/preview.md`);
        return;
      }

      // --auto mode
      if (!options.project) {
        console.error('--project <id> is required with --auto');
        process.exit(1);
      }

      try {
        const client = createDefaultTrpcClient();

        // Look up project
        const projects = await client.listProjects.query();
        const project = projects.find((p: { id: string }) => p.id === options.project);
        if (!project) {
          console.error(`Project '${options.project}' not found`);
          process.exit(1);
        }

        // Create initiative
        const initiative = await client.createInitiative.mutate({
          name: `Preview setup: ${project.name}`,
          projectIds: [project.id],
        });

        // Create root page with setup guide
        const rootPage = await client.getRootPage.query({ initiativeId: initiative.id });

        const { markdownToTiptapJson } = await import('../agent/markdown-to-tiptap.js');
        const setupMarkdown = `# Preview Setup for ${project.name}

Analyze the project structure and create a \`.cw-preview.yml\` configuration file for Docker-based preview deployments.

## What to do

1. Examine the project's build system, frameworks, and service architecture
2. Identify all services (frontend, backend, database, etc.)
3. Create a \`.cw-preview.yml\` at the project root with appropriate service definitions
4. Include dev mode configuration for hot-reload support
5. Add healthcheck endpoints where applicable
6. Commit the file to the repository

## Reference

See the Codewalkers documentation for .cw-preview.yml format and options.`;

        await client.updatePage.mutate({
          id: rootPage.id,
          content: JSON.stringify(markdownToTiptapJson(setupMarkdown)),
        });

        // Spawn refine agent
        const spawnInput: { initiativeId: string; instruction: string; provider?: string } = {
          initiativeId: initiative.id,
          instruction: 'Analyze the project and create a .cw-preview.yml for preview deployments. Follow the setup guide in the initiative pages.',
        };
        if (options.provider) {
          spawnInput.provider = options.provider;
        }
        const agent = await client.spawnArchitectRefine.mutate(spawnInput);

        console.log(`Created initiative: ${initiative.id}`);
        console.log(`Spawned agent: ${agent.id} (${agent.name})`);
      } catch (error) {
        console.error('Failed to set up preview:', (error as Error).message);
        process.exit(1);
      }
    });

  // =========================================================================
  // Inter-agent conversation commands
  // =========================================================================

  // cw listen --agent-id <id>
  program
    .command('listen')
    .description('Listen for pending conversations via SSE subscription')
    .requiredOption('--agent-id <id>', 'Agent ID to listen for')
    .action(async (options: { agentId: string }) => {
      try {
        const client = createDefaultTrpcClient();

        const subscription = client.onPendingConversation.subscribe(
          { agentId: options.agentId },
          {
            onData(envelope) {
              const conv = envelope.data;
              console.log(JSON.stringify({
                conversationId: conv.conversationId,
                fromAgentId: conv.fromAgentId,
                question: conv.question,
                phaseId: conv.phaseId,
                taskId: conv.taskId,
              }));
              subscription.unsubscribe();
              process.exit(0);
            },
            onError(err) {
              console.error('Failed to listen:', err.message);
              subscription.unsubscribe();
              process.exit(1);
            },
          },
        );
      } catch (error) {
        console.error('Failed to listen:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw ask <question> --from <agentId> --agent-id|--phase-id|--task-id <target>
  program
    .command('ask <question>')
    .description('Ask a question to another agent and wait for the answer via SSE')
    .requiredOption('--from <agentId>', 'Your agent ID (the asker)')
    .option('--agent-id <id>', 'Target agent ID')
    .option('--phase-id <id>', 'Target phase ID (find running agent in phase)')
    .option('--task-id <id>', 'Target task ID (find running agent for task)')
    .action(async (question: string, options: {
      from: string;
      agentId?: string;
      phaseId?: string;
      taskId?: string;
    }) => {
      try {
        const client = createDefaultTrpcClient();

        // Create the conversation
        const conversation = await client.createConversation.mutate({
          fromAgentId: options.from,
          toAgentId: options.agentId,
          phaseId: options.phaseId,
          taskId: options.taskId,
          question,
        });

        // Subscribe for answer via SSE
        const subscription = client.onConversationAnswer.subscribe(
          { conversationId: conversation.id },
          {
            onData(envelope) {
              console.log(envelope.data.answer);
              subscription.unsubscribe();
              process.exit(0);
            },
            onError(err) {
              console.error('Failed to ask:', err.message);
              subscription.unsubscribe();
              process.exit(1);
            },
          },
        );
      } catch (error) {
        console.error('Failed to ask:', (error as Error).message);
        process.exit(1);
      }
    });

  // cw answer <answer> --conversation-id <id>
  program
    .command('answer <answer>')
    .description('Answer a pending conversation')
    .requiredOption('--conversation-id <id>', 'Conversation ID to answer')
    .action(async (answer: string, options: { conversationId: string }) => {
      try {
        const client = createDefaultTrpcClient();
        await client.answerConversation.mutate({
          id: options.conversationId,
          answer,
        });
        console.log(JSON.stringify({ conversationId: options.conversationId, status: 'answered' }));
      } catch (error) {
        console.error('Failed to answer:', (error as Error).message);
        process.exit(1);
      }
    });

  return program;
}

/**
 * Runs the CLI, handling server mode and commands.
 */
export async function runCli(): Promise<void> {
  // Check for server flag early, before Commander processes
  const hasServerFlag = process.argv.includes('--server') || process.argv.includes('-s');

  if (hasServerFlag) {
    // Get port from args if present
    const portIndex = process.argv.findIndex(arg => arg === '-p' || arg === '--port');
    const port = portIndex !== -1 && process.argv[portIndex + 1]
      ? parseInt(process.argv[portIndex + 1], 10)
      : undefined;

    const debug = process.argv.includes('--debug') || process.argv.includes('-d');

    await startServer(port, debug);
    // Server runs indefinitely until signal
    return;
  }

  // Normal CLI processing
  const program = createCli();
  program.parse(process.argv);
}
