// Hatchway CLI - Built with Rollup
import { existsSync, mkdirSync, createWriteStream, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { j as jsxRuntimeExports, B as Box, T as Text, a as useStdout, r as reactExports, u as useInput, b as render, R as React } from './theme-NAQBkisB.js';
import 'chalk';
import * as p from '@clack/prompts';
import pc from 'picocolors';
import { c as configManager } from './config-manager-DST6RbP8.js';
import { i as isInsideMonorepo } from './repo-detector-36VydrlB.js';
import { killProcessOnPort, killProcessTree } from './process-killer-ChXAqhfm.js';
import { e as errors, C as CLIError } from './cli-error-1drkrXNn.js';
import { spawn } from 'node:child_process';
import EventEmitter from 'node:events';
import 'node:stream';
import 'node:process';
import { u as useApp } from './use-app-Ct3w2jLI.js';
import { T as TextInput } from './index-DCC6HGdr.js';
import { exec } from 'child_process';
import { platform } from 'os';
import { u as useBuildState, B as BuildPanel } from './useBuildState-Dy7pRR8Z.js';
import { i as initRunnerLogger, s as setFileLoggerTuiMode } from './runner-logger-instance-Dj_JMznn.js';
import 'assert';
import 'events';
import 'node:os';
import 'module';
import 'node:buffer';
import 'conf';
import 'node:fs/promises';
import 'node:util';

/**
 * ServiceManager - Manages lifecycle and state of Web App, Broker, and Runner
 * Provides state updates for TUI dashboard
 */
class ServiceManager extends EventEmitter {
    constructor() {
        super();
        this.services = new Map();
    }
    /**
     * Register a service configuration
     */
    register(config) {
        this.services.set(config.name, {
            config,
            state: {
                name: config.name,
                displayName: config.displayName,
                status: 'stopped',
                port: config.port,
                uptime: 0,
            },
        });
    }
    /**
     * Start a specific service
     */
    async start(name) {
        const service = this.services.get(name);
        if (!service) {
            throw new Error(`Service ${name} not registered`);
        }
        if (service.process && !service.process.killed) {
            throw new Error(`Service ${name} is already running`);
        }
        // Update state to starting
        service.state.status = 'starting';
        this.emit('service:status-change', name, 'starting');
        try {
            // Spawn the process
            const proc = spawn(service.config.command, service.config.args, {
                cwd: service.config.cwd,
                stdio: ['ignore', 'pipe', 'pipe'],
                shell: true,
                env: {
                    ...process.env,
                    ...service.config.env,
                },
            });
            service.process = proc;
            service.state.pid = proc.pid;
            service.startTime = Date.now();
            // Handle stdout - ONLY emit events, NO console.log
            proc.stdout?.on('data', (data) => {
                const output = data.toString();
                service.state.lastOutput = output.trim();
                // Emit event - TUI will handle display
                this.emit('service:output', name, output, 'stdout');
                // Detect when service is ready
                if (this.isServiceReady(name, output)) {
                    service.state.status = 'running';
                    this.emit('service:status-change', name, 'running');
                }
            });
            // Handle stderr - ONLY emit events, NO console.log
            proc.stderr?.on('data', (data) => {
                const output = data.toString();
                // Emit event - TUI will handle display
                this.emit('service:output', name, output, 'stderr');
                // Check for errors in stderr
                if (output.toLowerCase().includes('error') && !output.includes('warn')) {
                    service.state.error = output.trim();
                }
            });
            // Handle process exit
            proc.on('exit', (code, signal) => {
                if (code !== 0 && code !== null && code !== 130 && code !== 143) {
                    // Abnormal exit
                    service.state.status = 'error';
                    service.state.error = `Exited with code ${code}`;
                    this.emit('service:status-change', name, 'error');
                    this.emit('service:error', name, new Error(`Process exited with code ${code}`));
                }
                else {
                    // Normal exit
                    service.state.status = 'stopped';
                    this.emit('service:status-change', name, 'stopped');
                }
                service.process = undefined;
                service.state.pid = undefined;
                service.startTime = undefined;
            });
            proc.on('error', (error) => {
                service.state.status = 'error';
                service.state.error = error.message;
                this.emit('service:status-change', name, 'error');
                this.emit('service:error', name, error);
            });
        }
        catch (error) {
            service.state.status = 'error';
            service.state.error = error instanceof Error ? error.message : 'Unknown error';
            this.emit('service:status-change', name, 'error');
            this.emit('service:error', name, error instanceof Error ? error : new Error(String(error)));
            throw error;
        }
    }
    /**
     * Start all registered services in sequence
     */
    async startAll(delayBetween = 2000) {
        const services = Array.from(this.services.keys());
        for (const name of services) {
            await this.start(name);
            if (delayBetween > 0) {
                await new Promise(resolve => setTimeout(resolve, delayBetween));
            }
        }
        // Start periodic updates for uptime/stats
        this.startUpdates();
        this.emit('all:started');
    }
    /**
     * Stop a specific service
     */
    async stop(name, signal = 'SIGTERM') {
        const service = this.services.get(name);
        if (!service) {
            return;
        }
        const proc = service.process;
        const port = service.config.port;
        const pid = proc?.pid;
        // If no process, just try to kill by port as cleanup
        if (!proc) {
            if (port) {
                await killProcessOnPort(port);
            }
            return;
        }
        return new Promise(async (resolve) => {
            // Set timeout for force kill
            const timeout = setTimeout(async () => {
                // Try killing the process tree first
                if (pid) {
                    await killProcessTree(pid, 'SIGKILL');
                }
                // Also kill by port as final fallback
                if (port) {
                    await killProcessOnPort(port);
                }
                resolve();
            }, 2000);
            proc.once('exit', () => {
                clearTimeout(timeout);
                resolve();
            });
            // First try graceful SIGTERM via process tree
            if (pid) {
                await killProcessTree(pid, signal);
            }
            else {
                proc.kill(signal);
            }
        });
    }
    /**
     * Stop all running services
     */
    async stopAll() {
        this.stopUpdates();
        const services = Array.from(this.services.keys()).reverse(); // Stop in reverse order
        // Collect all ports for final cleanup
        const ports = [];
        for (const name of services) {
            const service = this.services.get(name);
            if (service?.config.port) {
                ports.push(service.config.port);
            }
        }
        // Stop each service
        for (const name of services) {
            await this.stop(name);
        }
        // Final port cleanup - ensure no zombie processes remain
        // Small delay to let processes actually terminate
        await new Promise(resolve => setTimeout(resolve, 500));
        for (const port of ports) {
            await killProcessOnPort(port);
        }
        this.emit('all:stopped');
    }
    /**
     * Get current state of a service
     */
    getState(name) {
        return this.services.get(name)?.state;
    }
    /**
     * Get current state of all services
     */
    getAllStates() {
        return Array.from(this.services.values()).map(s => s.state);
    }
    /**
     * Check if a service is running
     */
    isRunning(name) {
        return this.services.get(name)?.state.status === 'running';
    }
    /**
     * Check if all services are running
     */
    areAllRunning() {
        return Array.from(this.services.values()).every(s => s.state.status === 'running');
    }
    /**
     * Restart a service
     */
    async restart(name) {
        await this.stop(name);
        await new Promise(resolve => setTimeout(resolve, 1000));
        await this.start(name);
    }
    /**
     * Restart all services
     */
    async restartAll() {
        await this.stopAll();
        await new Promise(resolve => setTimeout(resolve, 1000));
        await this.startAll();
    }
    /**
     * Start periodic state updates (uptime, memory, cpu)
     */
    startUpdates() {
        this.updateInterval = setInterval(() => {
            for (const [name, service] of this.services) {
                if (service.startTime) {
                    service.state.uptime = Date.now() - service.startTime;
                }
                // TODO: Add memory/CPU monitoring using ps or similar
                // For now, just update uptime
            }
        }, 1000); // Update every second
    }
    /**
     * Stop periodic updates
     */
    stopUpdates() {
        if (this.updateInterval) {
            clearInterval(this.updateInterval);
            this.updateInterval = undefined;
        }
    }
    /**
     * Detect if service is ready based on output
     */
    isServiceReady(name, output) {
        const lowerOutput = output.toLowerCase();
        switch (name) {
            case 'web':
                return lowerOutput.includes('ready') || lowerOutput.includes('started server');
            case 'broker':
                return lowerOutput.includes('listening') || lowerOutput.includes('ready');
            case 'runner':
                return lowerOutput.includes('connected') || lowerOutput.includes('ready');
            default:
                return false;
        }
    }
    /**
     * Create a Cloudflare tunnel for a service
     */
    async createTunnel(name) {
        const service = this.services.get(name);
        if (!service || !service.state.port) {
            throw new Error(`Service ${name} not found or has no port`);
        }
        // Update state to creating
        service.state.tunnelStatus = 'creating';
        this.emit('service:tunnel-change', name, null, 'creating');
        try {
            // Import tunnel manager
            const { tunnelManager } = await import('./manager-DjVI7erc.js');
            // Enable silent mode for TUI
            tunnelManager.setSilent(true);
            // Create tunnel
            const tunnelUrl = await tunnelManager.createTunnel(service.state.port);
            // Update state
            service.state.tunnelUrl = tunnelUrl;
            service.state.tunnelStatus = 'active';
            this.emit('service:tunnel-change', name, tunnelUrl, 'active');
            return tunnelUrl;
        }
        catch (error) {
            service.state.tunnelStatus = 'failed';
            service.state.error = error instanceof Error ? error.message : 'Tunnel creation failed';
            this.emit('service:tunnel-change', name, null, 'failed');
            return null;
        }
    }
    /**
     * Close tunnel for a service
     */
    async closeTunnel(name) {
        const service = this.services.get(name);
        if (!service || !service.state.port) {
            return;
        }
        // Update state immediately
        service.state.tunnelUrl = undefined;
        service.state.tunnelStatus = undefined;
        this.emit('service:tunnel-change', name, null, 'active');
        try {
            const { tunnelManager } = await import('./manager-DjVI7erc.js');
            await tunnelManager.closeTunnel(service.state.port);
        }
        catch (error) {
            // Best effort
        }
    }
    /**
     * Get tunnel URL for a service
     */
    getTunnelUrl(name) {
        return this.services.get(name)?.state.tunnelUrl || null;
    }
    /**
     * Cleanup and remove all listeners
     */
    destroy() {
        this.stopUpdates();
        this.removeAllListeners();
    }
}

// Theme colors matching init TUI
const colors = {
    cyan: '#06b6d4',
    brightPurple: '#c084fc',
};
/**
 * ASCII art banner component - centered with cyan/purple gradient
 * HATCH in cyan, WAY in purple
 */
function Banner() {
    const lines = [
        { hatch: '██╗  ██╗ █████╗ ████████╗ ██████╗██╗  ██╗', way: '██╗    ██╗ █████╗ ██╗   ██╗' },
        { hatch: '██║  ██║██╔══██╗╚══██╔══╝██╔════╝██║  ██║', way: '██║    ██║██╔══██╗╚██╗ ██╔╝' },
        { hatch: '███████║███████║   ██║   ██║     ███████║', way: '██║ █╗ ██║███████║ ╚████╔╝ ' },
        { hatch: '██╔══██║██╔══██║   ██║   ██║     ██╔══██║', way: '██║███╗██║██╔══██║  ╚██╔╝  ' },
        { hatch: '██║  ██║██║  ██║   ██║   ╚██████╗██║  ██║', way: '╚███╔███╔╝██║  ██║   ██║   ' },
        { hatch: '╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝    ╚═════╝╚═╝  ╚═╝', way: ' ╚══╝╚══╝ ╚═╝  ╚═╝   ╚═╝   ' },
    ];
    return (jsxRuntimeExports.jsx(Box, { flexDirection: "column", alignItems: "center", marginTop: 2, children: lines.map((line, index) => (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: line.hatch }), jsxRuntimeExports.jsx(Text, { color: colors.brightPurple, children: line.way })] }, index))) }));
}

// Base colors that don't change with theme
const baseColors = {
    success: '#22c55e',
    error: '#ef4444',
    warning: '#f59e0b',
    cyan: '#06b6d4',
    white: '#ffffff',
    gray: '#6b7280',
    dimGray: '#4b5563'};
const symbols = {
    filledDot: '●',
    hollowDot: '○',
    check: '✓',
    cross: '✗'};
const THEMES = {
    sentry: {
        name: 'sentry',
        label: 'Sentry',
        description: 'Purple-pink gradient',
        colors: {
            primary: '#a855f7',
            secondary: '#ec4899',
            accent: '#c084fc',
            muted: '#7c3aed',
        },
    },
    ocean: {
        name: 'ocean',
        label: 'Ocean',
        description: 'Cool blue & teal',
        colors: {
            primary: '#3b82f6',
            secondary: '#22d3ee',
            accent: '#60a5fa',
            muted: '#2563eb',
        },
    },
    ember: {
        name: 'ember',
        label: 'Ember',
        description: 'Warm orange & red',
        colors: {
            primary: '#f97316',
            secondary: '#ef4444',
            accent: '#fb923c',
            muted: '#ea580c',
        },
    },
    forest: {
        name: 'forest',
        label: 'Forest',
        description: 'Green & earth tones',
        colors: {
            primary: '#10b981',
            secondary: '#84cc16',
            accent: '#34d399',
            muted: '#059669',
        },
    },
    noir: {
        name: 'noir',
        label: 'Noir',
        description: 'Monochrome dark',
        colors: {
            primary: '#ffffff',
            secondary: '#a1a1aa',
            accent: '#e4e4e7',
            muted: '#71717a',
        },
    },
};
const THEME_ORDER = ['sentry', 'ocean', 'ember', 'forest', 'noir'];
function formatTime(date) {
    return date.toLocaleTimeString('en-US', {
        hour12: false,
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
    });
}
function getLogLevel(message, stream) {
    if (stream === 'stderr')
        return 'warn';
    const lower = message.toLowerCase();
    if (lower.includes('error') || lower.includes('failed') || lower.includes('exception'))
        return 'error';
    if (lower.includes('warn') || lower.includes('warning'))
        return 'warn';
    if (lower.includes('success') || lower.includes('ready') || lower.includes('started') || lower.includes('connected') || lower.includes('✅') || lower.includes('✓'))
        return 'success';
    return 'info';
}
/**
 * Determines if a log message is internal/debug and should be hidden by default.
 * These are implementation details that aren't useful for end users.
 */
function isInternalLog(message) {
    const internalPatterns = [
        // Internal broadcasting/event system
        /Broadcasting/i,
        /Event emitted/i,
        /📡.*Broadcasting/,
        // Session/ID tracking details
        /sessionId=/,
        /todoIndex=/,
        /buildId=/,
        /commandId=/,
        // Internal processor notes
        /NOTE:.*DB writes/,
        /NOTE:.*HTTP/,
        /Registering build.*for WebSocket/,
        // Verbose implementation details
        /from database\)/,
        /immutable/,
        /\(waiting for runner/,
        // Duplicate/skip messages
        /Skipping duplicate/,
        // Internal state
        /SSE stream closed/,
        /persistent processor continues/,
        // Cost/optimization notes (internal)
        /Cost savings:/,
    ];
    return internalPatterns.some(pattern => pattern.test(message));
}
/**
 * Transforms a raw log message into a user-friendly format.
 * Extracts meaningful information and presents it clearly.
 */
function transformLogMessage(message) {
    // NEW FORMAT: Server now logs "🔧 Edit: /path/to/file.ts" or "🔧 Run: npm install"
    // Match: 🔧 Action: details
    const newToolFormat = message.match(/🔧\s*(Read|Edit|Write|Run|Find|Search|Fetch|Update tasks):\s*(.+)/i);
    if (newToolFormat) {
        const action = newToolFormat[1];
        const detail = newToolFormat[2].trim();
        return { display: `${action}: ${detail}`, isUserFacing: true, toolAction: action };
    }
    // Also match bare tool names for backwards compat: "🔧 Read" (no colon/details)
    const bareToolMatch = message.match(/^🔧\s*(Read|Edit|Write|Bash|Glob|Grep|WebFetch|TodoWrite)$/i);
    if (bareToolMatch) {
        return { display: bareToolMatch[1], isUserFacing: true, toolAction: bareToolMatch[1] };
    }
    // OLD FORMAT: Tool calls with parentheses like "🔧 Read (todoIndex=..."
    // Try to extract any useful info
    const oldToolFormat = message.match(/🔧\s*(Read|Edit|Write|Bash|Glob|Grep)\s*\(/i);
    if (oldToolFormat) {
        const toolName = oldToolFormat[1];
        // Try to extract file path from the message
        const pathMatch = message.match(/(?:path|file)[:=]\s*["']?([^"'\s,)]+)/i);
        const cmdMatch = message.match(/(?:command|cmd)[:=]\s*["']?([^"'\n]+)/i);
        if (toolName.toLowerCase() === 'bash' && cmdMatch) {
            const cmd = cmdMatch[1].trim().substring(0, 50);
            return { display: `Run: ${cmd}${cmdMatch[1].length > 50 ? '...' : ''}`, isUserFacing: true, toolAction: 'Run' };
        }
        else if (pathMatch) {
            return { display: `${toolName}: ${pathMatch[1]}`, isUserFacing: true, toolAction: toolName };
        }
        // No details found, just show the tool name
        return { display: toolName, isUserFacing: true, toolAction: toolName };
    }
    // Template/framework selection - always show
    const templateMatch = message.match(/Template (?:selected|from tag):\s*(.+)/i);
    if (templateMatch) {
        return { display: `Using template: ${templateMatch[1]}`, isUserFacing: true };
    }
    const frameworkMatch = message.match(/Framework.*?:\s*(\w+)/i);
    if (frameworkMatch && !message.includes('emit')) {
        return { display: `Framework: ${frameworkMatch[1]}`, isUserFacing: true };
    }
    // Build start
    if (message.includes('start event received') || message.includes('build-started')) {
        return { display: 'Build started', isUserFacing: true };
    }
    // Build complete
    if (message.includes('marked complete') || message.includes('Build complete')) {
        return { display: 'Build complete', isUserFacing: true };
    }
    // Agent selection - clean it up
    const agentMatch = message.match(/(?:Using agent|Agent).*?:\s*(\S+)/i);
    if (agentMatch && !message.includes('NOTE:')) {
        return { display: `Agent: ${agentMatch[1].replace(/[()]/g, '')}`, isUserFacing: true };
    }
    // Generic success messages
    if (message.includes('✅') && !isInternalLog(message)) {
        // Clean up the message
        const cleaned = message.replace(/\[[\w-]+\]\s*/g, '').replace(/✅\s*/, '').trim();
        if (cleaned.length > 10) {
            return { display: cleaned, isUserFacing: true };
        }
    }
    // Default: not user-facing if it's internal
    return { display: message, isUserFacing: !isInternalLog(message) };
}
function parseLogMessage(message) {
    let tag;
    let emoji;
    let toolName;
    let content = message;
    const tagMatch = content.match(/^\[([^\]]+)\]\s*/);
    if (tagMatch) {
        tag = tagMatch[1];
        content = content.substring(tagMatch[0].length);
    }
    const emojiMatch = content.match(/^([\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|✅|✓|📜|📤|🔧|📡|⚠️|❌)\s*/u);
    if (emojiMatch) {
        emoji = emojiMatch[1];
        content = content.substring(emojiMatch[0].length);
    }
    const toolMatch = content.match(/^tool-input-available:\s*(\w+)/i);
    if (toolMatch) {
        toolName = toolMatch[1];
        emoji = '🔧';
    }
    // Also detect tool calls in format: "🔧 ToolName (..."
    const toolCallMatch = content.match(/^(Read|Edit|Write|Bash|Glob|Grep|TodoWrite)\s*\(/i);
    if (toolCallMatch) {
        toolName = toolCallMatch[1];
        emoji = '🔧';
    }
    return { tag, emoji, toolName, content: content.trim(), isInternal: isInternalLog(message) };
}
function openBrowser(url) {
    const os = platform();
    let command;
    if (os === 'darwin') {
        command = `open "${url}"`;
    }
    else if (os === 'win32') {
        command = `start "" "${url}"`;
    }
    else {
        command = `xdg-open "${url}"`;
    }
    exec(command, () => { });
}
function Dashboard({ serviceManager, apiUrl, webPort, logFilePath }) {
    const { exit } = useApp();
    const { stdout } = useStdout();
    const [view, setView] = reactExports.useState('dashboard');
    const [services, setServices] = reactExports.useState([]);
    const [logs, setLogs] = reactExports.useState([]);
    const [isShuttingDown, setIsShuttingDown] = reactExports.useState(false);
    const [serviceFilter, setServiceFilter] = reactExports.useState(null);
    const [scrollOffset, setScrollOffset] = reactExports.useState(0);
    const [autoScroll, setAutoScroll] = reactExports.useState(true);
    const [searchMode, setSearchMode] = reactExports.useState(false);
    const [searchQuery, setSearchQuery] = reactExports.useState('');
    const [isVerbose, setIsVerbose] = reactExports.useState(false);
    const [logIdCounter, setLogIdCounter] = reactExports.useState(0);
    const [filterMode, setFilterMode] = reactExports.useState('all');
    const [fullLogSearchMode, setFullLogSearchMode] = reactExports.useState(false);
    const [fullLogSearchQuery, setFullLogSearchQuery] = reactExports.useState('');
    const [fullLogScrollOffset, setFullLogScrollOffset] = reactExports.useState(0);
    // Theme state - load from config, default to 'sentry'
    const savedTheme = configManager.get('ui')?.theme;
    const [selectedTheme, setSelectedTheme] = reactExports.useState(savedTheme || 'sentry');
    const [previewTheme, setPreviewTheme] = reactExports.useState(null);
    const [themeBeforePreview, setThemeBeforePreview] = reactExports.useState(null);
    // Build state - tracks active builds and todos from the RunnerLogger
    const [buildState] = useBuildState();
    // Get current theme colors - use preview theme if active, otherwise selected theme
    const activeThemeName = previewTheme || selectedTheme;
    const theme = THEMES[activeThemeName];
    const themeColors = reactExports.useMemo(() => ({
        primary: theme.colors.primary,
        secondary: theme.colors.secondary,
        accent: theme.colors.accent,
        muted: theme.colors.muted,
        // Map to semantic colors
        highlight: theme.colors.primary,
        border: theme.colors.muted,
        text: baseColors.white,
        textDim: baseColors.gray,
        textMuted: baseColors.dimGray,
    }), [theme]);
    const terminalHeight = stdout?.rows || 40;
    const terminalWidth = stdout?.columns || 80;
    const bannerHeight = 7;
    const headerHeight = 3;
    const statusBarHeight = 3;
    const contentHeight = Math.max(1, terminalHeight - bannerHeight - headerHeight - statusBarHeight);
    // Show build panel only when there's an active build
    const showBuildPanel = buildState.currentBuild !== null;
    // 20/80 split when build panel is shown, otherwise full width
    const buildPanelWidth = Math.floor(terminalWidth * 0.2);
    const logPanelWidth = showBuildPanel ? terminalWidth - buildPanelWidth : terminalWidth;
    const allServicesRunning = reactExports.useMemo(() => {
        return services.length > 0 && services.every(s => s.status === 'running');
    }, [services]);
    reactExports.useEffect(() => {
        const handleStatusChange = () => {
            setServices(serviceManager.getAllStates());
        };
        setServices(serviceManager.getAllStates());
        serviceManager.on('service:status-change', handleStatusChange);
        return () => {
            serviceManager.off('service:status-change', handleStatusChange);
        };
    }, [serviceManager]);
    reactExports.useEffect(() => {
        const handleServiceOutput = (name, output, stream) => {
            const lines = output.split('\n').filter(line => line.trim());
            setLogIdCounter(prev => {
                const newLogs = lines.map((line, idx) => {
                    const trimmed = line.trim();
                    const parsed = parseLogMessage(trimmed);
                    const transformed = transformLogMessage(trimmed);
                    return {
                        id: `${Date.now()}-${prev + idx}`,
                        timestamp: new Date(),
                        service: name,
                        message: trimmed,
                        stream,
                        level: getLogLevel(trimmed, stream),
                        tag: parsed.tag,
                        emoji: parsed.emoji,
                        toolName: parsed.toolName,
                        content: parsed.content,
                        isInternal: parsed.isInternal,
                        displayMessage: transformed.isUserFacing ? transformed.display : undefined,
                    };
                });
                if (newLogs.length > 0) {
                    setLogs(prevLogs => {
                        const combined = [...prevLogs, ...newLogs];
                        return combined.slice(-1e4);
                    });
                }
                return prev + lines.length;
            });
        };
        serviceManager.on('service:output', handleServiceOutput);
        return () => {
            serviceManager.off('service:output', handleServiceOutput);
        };
    }, [serviceManager]);
    const filteredLogs = reactExports.useMemo(() => {
        let filtered = logs;
        // By default, hide internal/debug logs unless verbose mode is on
        if (!isVerbose) {
            filtered = filtered.filter(log => !log.isInternal &&
                !log.message.toLowerCase().includes('debug') &&
                !log.message.toLowerCase().includes('trace'));
        }
        if (serviceFilter) {
            filtered = filtered.filter(log => log.service === serviceFilter);
        }
        if (searchQuery.trim()) {
            const query = searchQuery.toLowerCase();
            filtered = filtered.filter(log => log.message.toLowerCase().includes(query) ||
                log.service.toLowerCase().includes(query));
        }
        return filtered;
    }, [logs, serviceFilter, searchQuery, isVerbose]);
    const fullLogFilteredLogs = reactExports.useMemo(() => {
        let filtered = logs;
        if (filterMode === 'errors') {
            filtered = filtered.filter(log => log.level === 'error' || log.level === 'warn');
        }
        else if (filterMode === 'tools') {
            filtered = filtered.filter(log => log.toolName || log.message.includes('tool-input'));
        }
        else if (filterMode === 'verbose') ;
        else {
            // Default 'all' mode - hide internal logs
            filtered = filtered.filter(log => !log.isInternal &&
                !log.message.toLowerCase().includes('debug') &&
                !log.message.toLowerCase().includes('trace'));
        }
        if (fullLogSearchQuery.trim()) {
            const query = fullLogSearchQuery.toLowerCase();
            filtered = filtered.filter(log => log.message.toLowerCase().includes(query) ||
                log.service.toLowerCase().includes(query) ||
                (log.tag && log.tag.toLowerCase().includes(query)) ||
                (log.toolName && log.toolName.toLowerCase().includes(query)));
        }
        return filtered;
    }, [logs, filterMode, fullLogSearchQuery]);
    const visibleLines = Math.max(1, contentHeight - 3);
    const fullLogVisibleLines = Math.max(1, terminalHeight - 6);
    reactExports.useEffect(() => {
        if (autoScroll && filteredLogs.length > 0) {
            const maxScroll = Math.max(0, filteredLogs.length - visibleLines);
            setScrollOffset(maxScroll);
        }
    }, [filteredLogs.length, autoScroll, visibleLines]);
    reactExports.useEffect(() => {
        if (view === 'fullLog') {
            const maxScroll = Math.max(0, fullLogFilteredLogs.length - fullLogVisibleLines);
            setFullLogScrollOffset(maxScroll);
        }
    }, [fullLogFilteredLogs.length, view, fullLogVisibleLines]);
    const displayedLogs = reactExports.useMemo(() => {
        return filteredLogs.slice(scrollOffset, scrollOffset + visibleLines);
    }, [filteredLogs, scrollOffset, visibleLines]);
    const fullLogDisplayedLogs = reactExports.useMemo(() => {
        return fullLogFilteredLogs.slice(fullLogScrollOffset, fullLogScrollOffset + fullLogVisibleLines);
    }, [fullLogFilteredLogs, fullLogScrollOffset, fullLogVisibleLines]);
    // Save theme to config and update state
    const saveTheme = (newTheme) => {
        setSelectedTheme(newTheme);
        setPreviewTheme(null);
        setThemeBeforePreview(null);
        // Persist to config
        configManager.set('ui', { theme: newTheme });
    };
    // Open theme selector
    const openThemeSelector = () => {
        setThemeBeforePreview(selectedTheme);
        setPreviewTheme(selectedTheme);
        setView('themeSelector');
    };
    // Cancel theme selection (revert to previous)
    const cancelThemeSelection = () => {
        setPreviewTheme(null);
        setThemeBeforePreview(null);
        setView('dashboard');
    };
    // Confirm theme selection
    const confirmThemeSelection = () => {
        if (previewTheme) {
            saveTheme(previewTheme);
        }
        setView('dashboard');
    };
    useInput((input, key) => {
        if (isShuttingDown)
            return;
        // Theme selector view
        if (view === 'themeSelector') {
            if (key.escape) {
                cancelThemeSelection();
                return;
            }
            if (key.return) {
                confirmThemeSelection();
                return;
            }
            if (key.upArrow && previewTheme) {
                const currentIndex = THEME_ORDER.indexOf(previewTheme);
                const prevIndex = (currentIndex - 1 + THEME_ORDER.length) % THEME_ORDER.length;
                setPreviewTheme(THEME_ORDER[prevIndex]);
                return;
            }
            if (key.downArrow && previewTheme) {
                const currentIndex = THEME_ORDER.indexOf(previewTheme);
                const nextIndex = (currentIndex + 1) % THEME_ORDER.length;
                setPreviewTheme(THEME_ORDER[nextIndex]);
                return;
            }
            return;
        }
        // Help/Menu view
        if (view === 'help') {
            if (key.escape) {
                setView('dashboard');
                return;
            }
            if (input === 'b') {
                openBrowser(`http://localhost:${webPort}`);
                return;
            }
            if (input === 'q') {
                setIsShuttingDown(true);
                serviceManager.stopAll().then(() => exit());
                return;
            }
            return;
        }
        // Full log view mode
        if (view === 'fullLog') {
            if (fullLogSearchMode) {
                if (key.escape || key.return) {
                    setFullLogSearchMode(false);
                }
                return;
            }
            if (key.escape) {
                setView('dashboard');
                return;
            }
            if (input === 'l' || input === 't') {
                setView('dashboard');
                return;
            }
            if (input === '/') {
                setFullLogSearchMode(true);
                return;
            }
            if (input === 'f') {
                const modes = ['all', 'errors', 'tools', 'verbose'];
                const currentIndex = modes.indexOf(filterMode);
                setFilterMode(modes[(currentIndex + 1) % modes.length]);
                setFullLogScrollOffset(0);
                return;
            }
            if (key.upArrow) {
                setFullLogScrollOffset(prev => Math.max(0, prev - 1));
                return;
            }
            if (key.downArrow) {
                const maxScroll = Math.max(0, fullLogFilteredLogs.length - fullLogVisibleLines);
                setFullLogScrollOffset(prev => Math.min(maxScroll, prev + 1));
                return;
            }
            if (key.pageUp) {
                setFullLogScrollOffset(prev => Math.max(0, prev - fullLogVisibleLines));
                return;
            }
            if (key.pageDown) {
                const maxScroll = Math.max(0, fullLogFilteredLogs.length - fullLogVisibleLines);
                setFullLogScrollOffset(prev => Math.min(maxScroll, prev + fullLogVisibleLines));
                return;
            }
            if (input === 'q') {
                setIsShuttingDown(true);
                serviceManager.stopAll().then(() => exit());
                return;
            }
            if (input === 'b') {
                openBrowser(`http://localhost:${webPort}`);
                return;
            }
            return;
        }
        // Dashboard mode
        if (key.escape) {
            if (searchMode) {
                setSearchMode(false);
                setSearchQuery('');
            }
            else if (view !== 'dashboard') {
                setView('dashboard');
            }
            return;
        }
        if (searchMode)
            return;
        if (input === '/' && view === 'dashboard') {
            setSearchMode(true);
            setSearchQuery('');
            return;
        }
        // Ctrl+T to open theme selector
        if (key.ctrl && input === 't') {
            openThemeSelector();
            return;
        }
        if (input === 'q' || (key.ctrl && input === 'c')) {
            setIsShuttingDown(true);
            serviceManager.stopAll().then(() => exit());
        }
        else if (input === 'b') {
            openBrowser(`http://localhost:${webPort}`);
        }
        else if (input === 'v') {
            setIsVerbose(!isVerbose);
        }
        else if (input === 'r' && view === 'dashboard') {
            serviceManager.restartAll();
        }
        else if (input === 'c' && view === 'dashboard') {
            setLogs([]);
            setScrollOffset(0);
            setAutoScroll(true);
        }
        else if (input === 'l' || input === 't') {
            setView('fullLog');
            setFullLogScrollOffset(Math.max(0, fullLogFilteredLogs.length - fullLogVisibleLines));
        }
        else if (input === 'f') {
            setServiceFilter(current => {
                if (!current)
                    return 'web';
                if (current === 'web')
                    return 'runner';
                return null;
            });
        }
        else if (key.upArrow) {
            setAutoScroll(false);
            setScrollOffset(prev => Math.max(0, prev - 1));
        }
        else if (key.downArrow) {
            const maxScroll = Math.max(0, filteredLogs.length - visibleLines);
            setScrollOffset(prev => Math.min(maxScroll, prev + 1));
            if (scrollOffset >= maxScroll - 1) {
                setAutoScroll(true);
            }
        }
        else if (key.pageUp) {
            setAutoScroll(false);
            setScrollOffset(prev => Math.max(0, prev - visibleLines));
        }
        else if (key.pageDown) {
            const maxScroll = Math.max(0, filteredLogs.length - visibleLines);
            setScrollOffset(prev => Math.min(maxScroll, prev + visibleLines));
        }
        else if (input === '?') {
            setView('help');
        }
    });
    const highlightSearch = (text, query) => {
        if (!query)
            return text;
        const lowerText = text.toLowerCase();
        const lowerQuery = query.toLowerCase();
        const index = lowerText.indexOf(lowerQuery);
        if (index === -1)
            return text;
        return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [text.slice(0, index), jsxRuntimeExports.jsx(Text, { backgroundColor: baseColors.warning, color: "black", children: text.slice(index, index + query.length) }), text.slice(index + query.length)] }));
    };
    // Full log view
    if (view === 'fullLog') {
        return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: themeColors.muted, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Text, { color: themeColors.primary, bold: true, children: "LOGS" }), jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: "Search: " }), fullLogSearchMode ? (jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: themeColors.primary, paddingX: 1, children: jsxRuntimeExports.jsx(TextInput, { value: fullLogSearchQuery, onChange: setFullLogSearchQuery, placeholder: "type to search..." }) })) : (jsxRuntimeExports.jsxs(Text, { color: fullLogSearchQuery ? themeColors.text : themeColors.textMuted, children: ["[", fullLogSearchQuery || 'none', "]"] })), jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: " [/]" })] })] }), jsxRuntimeExports.jsx(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "single", borderColor: themeColors.muted, borderTop: false, borderBottom: false, paddingX: 1, children: fullLogDisplayedLogs.map((log) => (jsxRuntimeExports.jsx(FullLogEntryRow, { log: log, maxWidth: terminalWidth - 4, searchQuery: fullLogSearchQuery, highlightSearch: highlightSearch, themeColors: themeColors }, log.id))) }), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: themeColors.muted, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut, { letter: "l", label: "dashboard", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "/", label: "search", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "f", label: `filter: ${filterMode}`, color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "b", label: "browser", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "q", label: "quit", color: themeColors.primary })] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: [fullLogScrollOffset + 1, "-", Math.min(fullLogScrollOffset + fullLogVisibleLines, fullLogFilteredLogs.length), "/", fullLogFilteredLogs.length] })] })] }));
    }
    // Theme Selector overlay
    if (view === 'themeSelector') {
        return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, width: terminalWidth, children: [jsxRuntimeExports.jsx(Banner, {}), jsxRuntimeExports.jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: jsxRuntimeExports.jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: themeColors.primary, paddingX: 3, paddingY: 1, width: 40, children: [jsxRuntimeExports.jsx(Box, { justifyContent: "center", marginBottom: 1, children: jsxRuntimeExports.jsx(Text, { color: themeColors.primary, bold: true, children: "Select Theme" }) }), THEME_ORDER.map((themeName) => {
                                const t = THEMES[themeName];
                                const isSelected = themeName === previewTheme;
                                return (jsxRuntimeExports.jsxs(Box, { paddingY: 0, children: [jsxRuntimeExports.jsx(Text, { color: isSelected ? t.colors.primary : themeColors.textMuted, children: isSelected ? '▸ ' : '  ' }), jsxRuntimeExports.jsx(Text, { color: isSelected ? t.colors.primary : themeColors.textDim, bold: isSelected, children: t.label }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: [" - ", t.description] })] }, themeName));
                            }), jsxRuntimeExports.jsx(Box, { marginTop: 1, justifyContent: "center", children: jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: [jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "\u2191\u2193" }), " navigate", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: " Enter" }), " select", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: " Esc" }), " cancel"] }) })] }) })] }));
    }
    // Help/Menu view
    if (view === 'help') {
        return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [jsxRuntimeExports.jsx(Banner, {}), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", padding: 2, children: [jsxRuntimeExports.jsx(Text, { color: themeColors.primary, bold: true, children: "Help & Keyboard Shortcuts" }), jsxRuntimeExports.jsx(Text, { children: " " }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "Ctrl+T" }), "    Change theme"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "b" }), "         Open in browser"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "l" }), "         Full log view"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "/" }), "         Search logs"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "f" }), "         Filter by service"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "v" }), "         Toggle verbose mode"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "r" }), "         Restart services"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "c" }), "         Clear logs"] }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: ["  ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "q" }), "         Quit"] }), jsxRuntimeExports.jsx(Text, { children: " " }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: ["Current theme: ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: theme.label })] }), jsxRuntimeExports.jsx(Text, { children: " " }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: ["Press ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "Esc" }), " to return to dashboard"] })] })] }));
    }
    // Check for available update (set by auto-update check in index.ts)
    const updateAvailable = process.env.HATCHWAY_UPDATE_AVAILABLE;
    // Main dashboard view
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, width: terminalWidth, children: [jsxRuntimeExports.jsx(Banner, {}), updateAvailable && (jsxRuntimeExports.jsxs(Box, { justifyContent: "center", paddingY: 0, children: [jsxRuntimeExports.jsx(Text, { color: baseColors.cyan, children: "\u2B06 Update available: " }), jsxRuntimeExports.jsx(Text, { color: baseColors.success, children: updateAvailable }), jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: " \u2014 Run " }), jsxRuntimeExports.jsx(Text, { color: baseColors.cyan, children: "hatchway upgrade" }), jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: " to update" })] })), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: themeColors.muted, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: ["Web: ", jsxRuntimeExports.jsxs(Text, { color: themeColors.primary, children: ["localhost:", webPort] }), ' • ', "Mode: ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "Local" }), ' • ', "Theme: ", jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: theme.label })] }), jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: allServicesRunning ? baseColors.success : baseColors.warning, children: allServicesRunning ? symbols.filledDot : symbols.hollowDot }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: [' ', allServicesRunning ? 'All Services Running' : 'Starting...'] })] })] }), jsxRuntimeExports.jsxs(Box, { flexGrow: 1, height: contentHeight, children: [showBuildPanel && (jsxRuntimeExports.jsx(BuildPanel, { build: buildState.currentBuild, width: buildPanelWidth, height: contentHeight })), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", width: logPanelWidth, height: contentHeight, borderStyle: "single", borderColor: themeColors.muted, paddingX: 1, children: [jsxRuntimeExports.jsxs(Box, { justifyContent: "space-between", marginBottom: 0, children: [jsxRuntimeExports.jsx(Text, { color: themeColors.primary, bold: true, children: "LOGS" }), jsxRuntimeExports.jsxs(Box, { children: [serviceFilter && (jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: ["filter: ", jsxRuntimeExports.jsx(Text, { color: baseColors.warning, children: serviceFilter }), "  "] })), jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: ["[verbose: ", isVerbose ? 'on' : 'off', "]"] })] })] }), jsxRuntimeExports.jsx(Box, { flexDirection: "column", flexGrow: 1, children: displayedLogs.length === 0 ? (jsxRuntimeExports.jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: "Waiting for logs..." }) })) : (displayedLogs.map((log) => (jsxRuntimeExports.jsx(LogEntryRow, { log: log, maxWidth: logPanelWidth - 4, themeColors: themeColors }, log.id)))) }), filteredLogs.length > visibleLines && (jsxRuntimeExports.jsx(Box, { justifyContent: "flex-end", children: jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: [scrollOffset + 1, "-", Math.min(scrollOffset + visibleLines, filteredLogs.length), "/", filteredLogs.length, autoScroll ? ' (auto)' : ''] }) }))] })] }), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: themeColors.muted, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: allServicesRunning ? baseColors.success : baseColors.warning, children: allServicesRunning ? symbols.filledDot : symbols.hollowDot }), jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: [' ', isShuttingDown ? 'Shutting down...' : allServicesRunning ? 'Ready' : 'Starting'] })] }), searchMode ? (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: "/" }), jsxRuntimeExports.jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, onSubmit: () => setSearchMode(false), placeholder: "Search... (Enter to apply, Esc to cancel)" })] })) : (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut, { letter: "b", label: "browser", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "l", label: "logs", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "/", label: "search", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "?", label: "menu", color: themeColors.primary }), jsxRuntimeExports.jsx(Shortcut, { letter: "q", label: "quit", color: themeColors.primary })] }))] })] }));
}
function LogEntryRow({ log, maxWidth, themeColors }) {
    const levelColors = {
        info: themeColors.primary,
        success: baseColors.success,
        warn: baseColors.warning,
        error: baseColors.error,
    };
    const levelIcons = {
        info: symbols.filledDot,
        success: symbols.check,
        warn: '⚠',
        error: symbols.cross,
    };
    const serviceColor = log.service === 'web' ? themeColors.primary : themeColors.secondary;
    const icon = log.emoji || levelIcons[log.level];
    const color = levelColors[log.level];
    // Use displayMessage if available (user-friendly transformed message)
    let displayContent = log.displayMessage || log.content || log.message;
    const availableWidth = maxWidth - 16;
    const truncatedMessage = displayContent.length > availableWidth
        ? displayContent.substring(0, availableWidth - 3) + '...'
        : displayContent;
    // Tool calls get special formatting
    if (log.toolName) {
        // If we have a displayMessage, use it (it's already user-friendly)
        if (log.displayMessage) {
            return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: formatTime(log.timestamp) }), jsxRuntimeExports.jsxs(Text, { color: serviceColor, children: [" [", log.service.substring(0, 3), "]"] }), jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: " \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: themeColors.text, children: log.displayMessage })] }));
        }
        // Fallback to showing tool name
        return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: formatTime(log.timestamp) }), jsxRuntimeExports.jsxs(Text, { color: serviceColor, children: [" [", log.service.substring(0, 3), "]"] }), jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: " \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: themeColors.text, children: log.toolName })] }));
    }
    return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: formatTime(log.timestamp) }), jsxRuntimeExports.jsxs(Text, { color: serviceColor, children: [" [", log.service.substring(0, 3), "]"] }), jsxRuntimeExports.jsxs(Text, { color: color, children: [" ", icon, " "] }), jsxRuntimeExports.jsx(Text, { color: log.level === 'error' || log.level === 'warn' ? color : themeColors.text, children: truncatedMessage })] }));
}
function FullLogEntryRow({ log, maxWidth, searchQuery, highlightSearch, themeColors }) {
    const levelColors = {
        info: themeColors.primary,
        success: baseColors.success,
        warn: baseColors.warning,
        error: baseColors.error,
    };
    const levelIcons = {
        info: symbols.filledDot,
        success: symbols.check,
        warn: '⚠',
        error: symbols.cross,
    };
    const serviceColor = log.service === 'web' ? themeColors.primary : themeColors.secondary;
    const icon = log.emoji || levelIcons[log.level];
    const color = levelColors[log.level];
    if (log.toolName) {
        return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: formatTime(log.timestamp) }), jsxRuntimeExports.jsxs(Text, { color: serviceColor, children: [" [", log.service, "]"] }), jsxRuntimeExports.jsx(Text, { color: themeColors.primary, children: " \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: themeColors.text, children: highlightSearch(log.toolName, searchQuery) }), log.content && (jsxRuntimeExports.jsxs(Text, { color: themeColors.textDim, children: [" ", highlightSearch(log.content.replace(`tool-input-available: ${log.toolName}`, '').trim(), searchQuery)] }))] }));
    }
    return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: themeColors.textMuted, children: formatTime(log.timestamp) }), jsxRuntimeExports.jsxs(Text, { color: serviceColor, children: [" [", log.service, "]"] }), jsxRuntimeExports.jsxs(Text, { color: color, children: [" ", icon, " "] }), log.tag && jsxRuntimeExports.jsxs(Text, { color: themeColors.textMuted, children: ["[", log.tag, "] "] }), jsxRuntimeExports.jsx(Text, { color: log.level === 'error' || log.level === 'warn' ? color : themeColors.text, children: highlightSearch(log.content || log.message, searchQuery) })] }));
}
function Shortcut({ letter, label, color }) {
    return (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: baseColors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: color, children: letter }), jsxRuntimeExports.jsx(Text, { color: baseColors.dimGray, children: "]" }), jsxRuntimeExports.jsx(Text, { color: baseColors.gray, children: label })] }));
}

/**
 * Console Interceptor for TUI Mode
 * Intercepts stdout/stderr writes to write to log file
 * Prevents logs from bleeding above the TUI
 */
class ConsoleInterceptor {
    constructor(serviceManager, logFileManager) {
        this.isActive = false;
        this.serviceManager = serviceManager;
        this.logFileManager = logFileManager;
        // Save original stdout/stderr write methods
        // Console methods (log, error, warn, info) all use these under the hood
        this.originalStdoutWrite = process.stdout.write.bind(process.stdout);
        this.originalStderrWrite = process.stderr.write.bind(process.stderr);
    }
    /**
     * Start intercepting console output and writing to log file
     * We only intercept stdout/stderr writes since console.* methods call these under the hood
     */
    start() {
        if (this.isActive)
            return;
        this.isActive = true;
        // Intercept stdout writes - this catches console.log, console.info, and direct writes
        process.stdout.write = (chunk, encoding, callback) => {
            const message = typeof chunk === 'string' ? chunk : chunk.toString();
            // ONLY pass through ANSI escape codes (for Ink rendering)
            // Block all other output from reaching the terminal
            if (message.match(/^\x1b\[/)) {
                // This is Ink's control sequence - let it through
                return this.originalStdoutWrite(chunk, encoding, callback);
            }
            // Everything else: write to log file only (don't show on terminal)
            // Don't trim or filter - write the raw message to preserve formatting
            if (message && message.length > 0) {
                const serviceName = this.detectService(message);
                this.logFileManager.write(serviceName, message.trim(), 'stdout');
            }
            // Report success but don't actually write to terminal
            if (callback)
                callback();
            return true;
        };
        // Intercept stderr writes
        process.stderr.write = (chunk, encoding, callback) => {
            const message = typeof chunk === 'string' ? chunk : chunk.toString();
            // ONLY pass through ANSI escape codes (for Ink rendering)
            // Block all other output from reaching the terminal
            if (message.match(/^\x1b\[/)) {
                // This is Ink's control sequence - let it through
                return this.originalStderrWrite(chunk, encoding, callback);
            }
            // Everything else: write to log file only (don't show on terminal)
            // Don't trim or filter - write the raw message to preserve formatting
            if (message && message.length > 0) {
                const serviceName = this.detectService(message);
                this.logFileManager.write(serviceName, message.trim(), 'stderr');
            }
            // Report success but don't actually write to terminal
            if (callback)
                callback();
            return true;
        };
    }
    /**
     * Get the log file path (null if logging is disabled)
     */
    getLogFilePath() {
        return this.logFileManager.getLogFilePath();
    }
    /**
     * Stop intercepting and restore original stdout/stderr
     */
    stop() {
        if (!this.isActive)
            return;
        this.isActive = false;
        // Restore stdout/stderr - this also restores console.* methods since they use these
        process.stdout.write = this.originalStdoutWrite;
        process.stderr.write = this.originalStderrWrite;
        // Stop log file writing
        this.logFileManager.stop();
    }
    /**
     * Detect which service the log is from based on content
     */
    detectService(message) {
        const lower = message.toLowerCase();
        // Check for explicit service tags
        if (message.startsWith('[web]'))
            return 'web';
        if (message.startsWith('[broker]'))
            return 'broker';
        if (message.startsWith('[runner]') || message.startsWith('[build]') || message.startsWith('[orchestrator]') || message.startsWith('[engine]')) {
            return 'runner';
        }
        // Infer from content
        if (lower.includes('broker') || lower.includes('websocket'))
            return 'broker';
        if (lower.includes('next.js') || lower.includes('compiled') || lower.includes('ready in'))
            return 'web';
        // Default to runner (most console.log calls come from runner)
        return 'runner';
    }
}

/**
 * Log File Manager
 * Writes logs to a file for TUI to read periodically
 * Creates a new log file each time the service starts
 */
class LogFileManager {
    constructor() {
        this.logFile = null;
        this.writeStream = null;
        // Create logs directory if it doesn't exist
        const logsDir = join(process.cwd(), 'logs');
        if (!existsSync(logsDir)) {
            mkdirSync(logsDir, { recursive: true });
        }
        // Create log file with timestamp
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        this.logFile = join(logsDir, `hatchway-${timestamp}.log`);
    }
    /**
     * Check if logging is enabled (always true now)
     */
    isEnabled() {
        return true;
    }
    /**
     * Start writing to log file
     */
    start() {
        if (!this.logFile)
            return;
        this.writeStream = createWriteStream(this.logFile, { flags: 'a' });
        // Write a startup marker
        this.writeStream.write(`[${new Date().toISOString()}] [system] [stdout] === Log file started ===\n`);
    }
    /**
     * Write a log entry to file
     * Format: [timestamp] [service] [stream] message
     */
    write(service, message, stream) {
        if (!this.writeStream)
            return;
        const timestamp = new Date().toISOString();
        const logLine = `[${timestamp}] [${service}] [${stream}] ${message}\n`;
        this.writeStream.write(logLine, (err) => {
            if (err) {
                // Fallback to console if write fails (but this shouldn't happen often)
                console.error('Failed to write to log file:', err);
            }
        });
    }
    /**
     * Get the log file path (null if logging is disabled)
     */
    getLogFilePath() {
        return this.logFile;
    }
    /**
     * Stop writing and close the file stream
     */
    stop() {
        if (this.writeStream) {
            this.writeStream.end();
            this.writeStream = null;
        }
    }
}

/**
 * Utility to extract meaningful error messages from build output
 * Works with TypeScript, Next.js, Turbo, and other common build tools
 */
// Patterns that indicate error lines
const ERROR_PATTERNS = [
    /error TS\d+:/i, // TypeScript errors
    /error:/i, // General errors
    /Error:/, // Error messages (case sensitive for JS errors)
    /ERR!/, // npm/pnpm errors
    /failed/i, // Failed messages
    /Cannot find/i, // Module not found
    /Module not found/i, // Webpack/Next.js errors
    /SyntaxError/i, // Syntax errors
    /TypeError/i, // Type errors
    /ReferenceError/i, // Reference errors
    /ENOENT/i, // File not found
    /EACCES/i, // Permission errors
    /✖|✗|×/, // Error symbols
    /Type '.+' is not assignable/i, // TypeScript type errors
    /Property '.+' does not exist/i, // TypeScript property errors
    /has no exported member/i, // Export errors
    /Unexpected token/i, // Parse errors
];
// Patterns that indicate we should stop collecting (success or unrelated output)
const STOP_PATTERNS = [
    /successfully/i,
    /completed/i,
    /✓|✔/, // Success symbols
    /Build succeeded/i,
];
/**
 * Extract the most relevant error lines from build output
 * @param output Combined stdout and stderr from build process
 * @param maxLines Maximum number of lines to return (default: 15)
 * @returns Array of relevant error lines
 */
function extractBuildErrors(output, maxLines = 15) {
    if (!output || !output.trim()) {
        return [];
    }
    const allLines = output.trim().split('\n');
    const relevantLines = [];
    let inErrorBlock = false;
    let emptyLineCount = 0;
    for (let i = 0; i < allLines.length; i++) {
        const line = allLines[i];
        const trimmedLine = line.trim();
        // Check if this line matches an error pattern
        const isErrorLine = ERROR_PATTERNS.some(pattern => pattern.test(line));
        // Check if we should stop collecting
        const shouldStop = STOP_PATTERNS.some(pattern => pattern.test(line));
        if (shouldStop && inErrorBlock) {
            // Don't stop immediately - there might be more errors after a success message
            inErrorBlock = false;
            emptyLineCount = 0;
            continue;
        }
        if (isErrorLine) {
            inErrorBlock = true;
            emptyLineCount = 0;
            // Include 1-2 lines before for context if we're starting a new error block
            if (relevantLines.length === 0 || !relevantLines[relevantLines.length - 1]) {
                // Add file path context if the previous line looks like a file reference
                if (i > 0) {
                    const prevLine = allLines[i - 1].trim();
                    // File paths often contain '/' or '\' and end with line numbers like :10:5
                    if (prevLine && (prevLine.includes('/') || prevLine.includes('\\') || /:\d+:\d+/.test(prevLine))) {
                        if (!relevantLines.includes(prevLine)) {
                            relevantLines.push(prevLine);
                        }
                    }
                }
            }
        }
        if (inErrorBlock) {
            // Track empty lines - stop after 2 consecutive empty lines
            if (trimmedLine === '') {
                emptyLineCount++;
                if (emptyLineCount >= 2) {
                    inErrorBlock = false;
                    emptyLineCount = 0;
                    continue;
                }
            }
            else {
                emptyLineCount = 0;
            }
            // Don't add duplicate lines
            if (!relevantLines.includes(trimmedLine) || trimmedLine === '') {
                relevantLines.push(trimmedLine);
            }
            // Stop if we've collected enough
            if (relevantLines.length >= maxLines) {
                break;
            }
        }
    }
    // If we didn't find specific error patterns, fall back to last N lines
    if (relevantLines.length === 0) {
        return allLines
            .slice(-10)
            .map(line => line.trim())
            .filter(line => line.length > 0);
    }
    // Clean up: remove trailing empty lines
    while (relevantLines.length > 0 && relevantLines[relevantLines.length - 1] === '') {
        relevantLines.pop();
    }
    // Truncate very long lines but keep enough context
    return relevantLines.map(line => {
        if (line.length > 150) {
            return line.substring(0, 147) + '...';
        }
        return line;
    });
}

/**
 * Start command with TUI dashboard support
 * Provides beautiful real-time monitoring of all services
 */
/**
 * Check if we should use TUI
 */
function shouldUseTUI(options) {
    // Explicit flag
    if (options.noTui)
        return false;
    // CI/CD environments
    if (process.env.CI === '1' || process.env.CI === 'true')
        return false;
    // Not a TTY
    if (!process.stdout.isTTY)
        return false;
    // Explicit env var to disable
    if (process.env.NO_TUI === '1')
        return false;
    return true;
}
async function startCommand(options) {
    const useTUI = shouldUseTUI(options);
    // If TUI is disabled, use the traditional start command
    if (!useTUI) {
        const { startCommand: traditionalStart } = await import('./start-traditional-7wlD2f2H.js');
        return traditionalStart(options);
    }
    // ========================================
    // TUI MODE
    // ========================================
    const s = p.spinner();
    // Step 1: Find monorepo
    s.start('Locating Hatchway repository');
    let monorepoRoot;
    const config = configManager.get();
    if (config.monorepoPath && existsSync(config.monorepoPath)) {
        monorepoRoot = config.monorepoPath;
    }
    if (!monorepoRoot) {
        const repoCheck = await isInsideMonorepo();
        if (repoCheck.inside && repoCheck.root) {
            monorepoRoot = repoCheck.root;
            configManager.set('monorepoPath', monorepoRoot);
        }
    }
    if (!monorepoRoot) {
        s.stop(pc.red('✗') + ' Repository not found');
        throw errors.monorepoNotFound([
            config.monorepoPath || 'none',
            process.cwd(),
        ]);
    }
    s.stop(pc.green('✓') + ' Repository found');
    // Step 2: Check dependencies
    const nodeModulesPath = join(monorepoRoot, 'node_modules');
    if (!existsSync(nodeModulesPath)) {
        s.start('Installing dependencies');
        const { installDependencies } = await import('./repo-cloner-UY3L2X7h.js');
        await installDependencies(monorepoRoot);
        s.stop(pc.green('✓') + ' Dependencies installed');
    }
    // Step 2.5: Check for production build (unless --dev mode)
    const nextBuildIdPath = join(monorepoRoot, 'apps', 'hatchway', '.next', 'BUILD_ID');
    const needsProductionBuild = !options.dev && !existsSync(nextBuildIdPath);
    // Rebuild services if requested OR if production build is missing
    if (options.rebuild || needsProductionBuild) {
        const buildReason = options.rebuild
            ? 'Rebuilding services'
            : 'Building for production (first run)';
        s.start(buildReason);
        const { spawn } = await import('child_process');
        // Capture build output for error reporting
        let buildOutput = '';
        let buildError = '';
        try {
            // Use turbo to build all services with caching
            await new Promise((resolve, reject) => {
                const buildProcess = spawn('pnpm', ['build:all'], {
                    cwd: monorepoRoot,
                    stdio: 'pipe', // Capture output
                });
                buildProcess.stdout?.on('data', (data) => {
                    buildOutput += data.toString();
                });
                buildProcess.stderr?.on('data', (data) => {
                    buildError += data.toString();
                });
                buildProcess.on('close', (code) => {
                    if (code === 0) {
                        resolve();
                    }
                    else {
                        reject(new Error(`Build failed with code ${code}`));
                    }
                });
                buildProcess.on('error', reject);
            });
            s.stop(pc.green('✓') + ' Build complete (using Turborepo cache)');
        }
        catch (error) {
            s.stop(pc.red('✗') + ' Build failed');
            // Extract meaningful error lines from build output
            const allOutput = (buildOutput + '\n' + buildError).trim();
            const errorLines = extractBuildErrors(allOutput);
            const suggestions = [
                'Check that all dependencies are installed',
                'Try running: pnpm build:all',
                'Run with --dev flag to skip build and use dev mode',
            ];
            // Add error context if available
            if (errorLines.length > 0) {
                console.log(pc.red('\nBuild errors:'));
                console.log(pc.gray('─'.repeat(60)));
                errorLines.forEach(line => console.log(pc.red(`  ${line}`)));
                console.log(pc.gray('─'.repeat(60)));
                console.log('');
            }
            throw new CLIError({
                code: 'BUILD_FAILED',
                message: 'Failed to build services',
                suggestions,
            });
        }
    }
    // Step 3: Check database configuration
    if (!config.databaseUrl) {
        throw new CLIError({
            code: 'MISSING_REQUIRED_CONFIG',
            message: 'Database URL not configured',
            suggestions: [
                'Run initialization: hatchway init',
                'Or set manually: hatchway config set databaseUrl <url>',
            ],
            docs: 'https://github.com/codyde/hatchway#database-setup',
        });
    }
    // Step 4: Clean up zombie processes
    const webPort = Number(options.port || '3000');
    s.start('Checking for port conflicts');
    await killProcessOnPort(webPort);
    s.stop(pc.green('✓') + ' Ports available');
    // Step 5: Create ServiceManager, LogFileManager, and Console Interceptor FIRST
    const serviceManager = new ServiceManager();
    const logFileManager = new LogFileManager();
    logFileManager.start(); // Start log file writing immediately
    const consoleInterceptor = new ConsoleInterceptor(serviceManager, logFileManager);
    // Start intercepting IMMEDIATELY before anything else can print
    consoleInterceptor.start();
    const sharedSecret = configManager.getSecret() || 'dev-secret';
    // Hook up service manager output to log file (for child process logs)
    serviceManager.on('service:output', (name, output, stream) => {
        logFileManager.write(name, output.trim(), stream);
    });
    // Keep silent mode for TUI, logs go to file
    process.env.SILENT_MODE = '1';
    // Clear screen for clean TUI start
    console.clear();
    // Register web app (now handles runner WebSocket connections directly)
    // Default to production mode unless --dev flag is present
    const webCommand = options.dev ? 'dev' : 'start';
    // Local mode is enabled by default, can be disabled with --no-local
    const isLocalMode = options.local !== false;
    // Write .env.local file to ensure env vars are available to Next.js
    // This is necessary because env vars passed to spawn() may not be visible
    // to Next.js server components in production mode
    const envLocalPath = join(monorepoRoot, 'apps', 'hatchway', '.env.local');
    const envContent = [
        '# Auto-generated by hatchway CLI - DO NOT EDIT',
        `# Generated at: ${new Date().toISOString()}`,
        '',
        `HATCHWAY_LOCAL_MODE=${isLocalMode ? 'true' : 'false'}`,
        `RUNNER_SHARED_SECRET=${sharedSecret}`,
        `WORKSPACE_ROOT=${config.workspace}`,
        `RUNNER_ID=${config.runner?.id || 'local'}`,
        `RUNNER_DEFAULT_ID=${config.runner?.id || 'local'}`,
        `DATABASE_URL=${config.databaseUrl || ''}`,
        '',
    ].join('\n');
    writeFileSync(envLocalPath, envContent);
    // Build environment variables for the web app
    const webEnv = {
        PORT: String(webPort),
        RUNNER_SHARED_SECRET: sharedSecret,
        WORKSPACE_ROOT: config.workspace,
        RUNNER_ID: config.runner?.id || 'local',
        RUNNER_DEFAULT_ID: config.runner?.id || 'local',
        DATABASE_URL: config.databaseUrl,
        // Enable local mode - bypasses authentication requirements (default: true)
        HATCHWAY_LOCAL_MODE: isLocalMode ? 'true' : 'false',
    };
    serviceManager.register({
        name: 'web',
        displayName: 'Web App',
        port: webPort,
        command: 'pnpm',
        args: ['--filter', 'hatchway', webCommand],
        cwd: monorepoRoot,
        env: webEnv,
    });
    // Register runner (special handling - not spawned, imported directly)
    serviceManager.register({
        name: 'runner',
        displayName: 'Runner',
        command: 'internal', // Not actually spawned
        args: [],
        cwd: monorepoRoot,
        env: {},
    });
    // Step 6: Clear screen and move cursor to home before TUI renders
    // Use ANSI codes that will pass through our interceptor
    process.stdout.write('\x1b[2J\x1b[H'); // Clear screen + move cursor to top-left
    // Ensure stdin is in raw mode for keyboard input
    if (process.stdin.setRawMode) {
        process.stdin.setRawMode(true);
    }
    process.stdin.resume();
    // Track runner cleanup function and shutting down state
    let isShuttingDown = false;
    let runnerCleanupFn;
    // Add backup SIGINT handler for Ctrl+C (in case Ink's doesn't fire)
    const handleSigInt = async () => {
        if (isShuttingDown) {
            // Force exit if already shutting down
            process.exit(1);
        }
        isShuttingDown = true;
        console.log('\n⚠ Received Ctrl+C, stopping services...');
        // Stop runner first if cleanup function exists
        if (runnerCleanupFn) {
            await runnerCleanupFn().catch(() => { });
        }
        // Then stop other services
        await serviceManager.stopAll().catch(() => { });
        consoleInterceptor.stop();
        process.exit(0);
    };
    process.on('SIGINT', handleSigInt);
    // One final clear right before rendering
    process.stdout.write('\x1b[2J\x1b[H');
    // Enable alternate screen buffer to prevent scrolling above TUI
    process.stdout.write('\x1b[?1049h'); // Enter alternate screen
    process.stdout.write('\x1b[2J\x1b[H'); // Clear and home
    // Initialize the RunnerLogger BEFORE rendering TUI so the TUI can subscribe to build events
    // This must happen before startRunner() which would create its own logger
    initRunnerLogger({
        verbose: options.verbose || false,
        tuiMode: true,
    });
    // Enable TUI mode in file-logger to suppress terminal output
    setFileLoggerTuiMode(true);
    // Render TUI immediately with log file path
    const { waitUntilExit, clear } = render(React.createElement(Dashboard, {
        serviceManager,
        apiUrl: `http://localhost:${webPort}`,
        webPort,
        logFilePath: consoleInterceptor.getLogFilePath()
    }), {
        stdin: process.stdin,
        stdout: process.stdout,
        stderr: process.stderr,
        exitOnCtrlC: true,
        patchConsole: false // Don't let Ink patch console since we already intercept
    });
    // Step 7: Start services
    try {
        // Start web app (runner will be started separately)
        await serviceManager.start('web');
        await new Promise(resolve => setTimeout(resolve, 3000)); // Wait for WebSocket server to initialize
        // Mark runner as starting
        const runnerService = serviceManager['services'].get('runner');
        if (runnerService) {
            runnerService.state.status = 'starting';
            runnerService.startTime = Date.now();
            serviceManager.emit('service:status-change', 'runner', 'starting');
        }
        // Start runner directly (this blocks until shutdown)
        const { startRunner } = await import('../index.js');
        // Mark as running
        if (runnerService) {
            runnerService.state.status = 'running';
            serviceManager.emit('service:status-change', 'runner', 'running');
        }
        // Start runner and get cleanup function - connects directly to Next.js WebSocket
        runnerCleanupFn = await startRunner({
            wsUrl: `ws://localhost:${webPort}/ws/runner`,
            sharedSecret: sharedSecret,
            runnerId: config.runner?.id || 'local',
            workspace: config.workspace,
            silent: false, // Changed to false - show all logs
            verbose: options.verbose,
            tuiMode: true, // TUI mode enabled
        });
        // Wait for TUI to exit
        await waitUntilExit();
        // Stop console interception and restore normal console
        consoleInterceptor.stop();
        // Clear TUI immediately
        clear();
        // Exit alternate screen buffer
        process.stdout.write('\x1b[?1049l');
        // Show shutdown message
        console.log();
        console.log(pc.yellow('⚠'), 'Stopping all services...');
        // Stop all services with timeout
        const shutdownPromise = Promise.race([
            (async () => {
                // Close any active tunnels first (give it 1s)
                await serviceManager.closeTunnel('web').catch(() => { });
                // Stop spawned services (web, broker)
                await serviceManager.stopAll();
                console.log(pc.green('✓'), 'Services stopped');
                // Stop runner explicitly using cleanup function
                if (runnerCleanupFn) {
                    console.log(pc.yellow('⚠'), 'Stopping runner...');
                    await runnerCleanupFn();
                    console.log(pc.green('✓'), 'Runner stopped');
                }
                console.log(pc.green('✓'), 'All services stopped');
            })(),
            new Promise((resolve) => setTimeout(() => {
                console.log(pc.yellow('⚠'), 'Shutdown timeout - forcing exit');
                resolve(undefined);
            }, 5000)) // Increased from 3s to 5s to allow tunnel cleanup
        ]);
        await shutdownPromise;
        // Force exit to ensure we return to prompt
        process.exit(0);
    }
    catch (error) {
        // Stop console interception on error
        consoleInterceptor.stop();
        clear();
        // Exit alternate screen buffer
        process.stdout.write('\x1b[?1049l');
        throw error;
    }
}

export { startCommand };
//# sourceMappingURL=start-Dkuro1jp.js.map
