// Hatchway CLI - Built with Rollup
import chalk from 'chalk';
import { homedir, userInfo } from 'node:os';
import { join } from 'node:path';
import { r as reactExports, u as useInput, j as jsxRuntimeExports, B as Box, c as colors, T as Text, a as useStdout, b as render, R as React } from './theme-NAQBkisB.js';
import { l as logger } from './logger-6V5cBxba.js';
import { c as configManager } from './config-manager-DST6RbP8.js';
import { startRunner } from '../index.js';
import 'node:stream';
import 'node:process';
import 'node:events';
import { u as useApp } from './use-app-Ct3w2jLI.js';
import { u as useBuildState, a as useLogEntries, B as BuildPanel } from './useBuildState-Dy7pRR8Z.js';
import { g as getVersionInfo, B as Banner } from './Banner-DL1Fpz_g.js';
import { T as TextInput } from './index-DCC6HGdr.js';
import { g as getLogBuffer, i as initRunnerLogger, s as setFileLoggerTuiMode } from './runner-logger-instance-Dj_JMznn.js';
import { o as openBrowser, h as hasStoredToken, g as getStoredToken, p as performOAuthLogin, s as storeToken } from './cli-auth-B4Do-N8Y.js';
import 'node:fs';
import 'assert';
import 'events';
import 'module';
import 'node:buffer';
import 'conf';
import '@sentry/node';
import '@anthropic-ai/claude-agent-sdk';
import '@openai/codex-sdk';
import 'dotenv';
import 'node:url';
import 'fs/promises';
import 'path';
import 'ws';
import 'drizzle-orm/node-postgres';
import 'pg';
import 'drizzle-orm/pg-core';
import 'drizzle-orm';
import 'crypto';
import 'drizzle-orm/node-postgres/migrator';
import 'zod';
import 'node:child_process';
import 'node:crypto';
import 'express';
import 'node:net';
import 'node:fs/promises';
import 'simple-git';
import 'os';
import 'fs';
import './manager-DjVI7erc.js';
import 'http';
import 'http-proxy';
import 'zlib';
import 'node:http';

function LogPanel({ entries, isVerbose, width, height, isFocused }) {
    const [scrollOffset, setScrollOffset] = reactExports.useState(0);
    const [autoScroll, setAutoScroll] = reactExports.useState(true);
    // Filter entries based on verbose mode
    const visibleEntries = isVerbose
        ? entries
        : entries.filter(e => !e.verbose);
    // Available height for log lines (subtract 2 for border, 1 for header)
    const visibleLines = Math.max(1, height - 3);
    // Auto-scroll when new entries arrive
    reactExports.useEffect(() => {
        if (autoScroll && visibleEntries.length > 0) {
            const maxScroll = Math.max(0, visibleEntries.length - visibleLines);
            setScrollOffset(maxScroll);
        }
    }, [visibleEntries.length, autoScroll, visibleLines]);
    // Handle keyboard navigation
    useInput((input, key) => {
        if (!isFocused)
            return;
        if (key.upArrow) {
            setAutoScroll(false);
            setScrollOffset(prev => Math.max(0, prev - 1));
        }
        else if (key.downArrow) {
            const maxScroll = Math.max(0, visibleEntries.length - visibleLines);
            setScrollOffset(prev => Math.min(maxScroll, prev + 1));
            // Re-enable auto-scroll if we're at the bottom
            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, visibleEntries.length - visibleLines);
            setScrollOffset(prev => Math.min(maxScroll, prev + visibleLines));
        }
    });
    // Get visible slice of entries
    const displayedEntries = visibleEntries.slice(scrollOffset, scrollOffset + visibleLines);
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: isFocused ? colors.cyan : colors.darkGray, paddingX: 1, children: [jsxRuntimeExports.jsxs(Box, { justifyContent: "space-between", marginBottom: 0, children: [jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "LOGS" }), jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["[verbose: ", isVerbose ? 'on' : 'off', "]"] })] }), jsxRuntimeExports.jsx(Box, { flexDirection: "column", flexGrow: 1, children: displayedEntries.length === 0 ? (jsxRuntimeExports.jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "Waiting for logs..." }) })) : (displayedEntries.map((entry, index) => (jsxRuntimeExports.jsx(LogEntryRow, { entry: entry, maxWidth: width - 4 }, entry.id)))) }), visibleEntries.length > visibleLines && (jsxRuntimeExports.jsx(Box, { justifyContent: "flex-end", children: jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: [scrollOffset + 1, "-", Math.min(scrollOffset + visibleLines, visibleEntries.length), "/", visibleEntries.length, autoScroll ? ' (auto)' : ''] }) }))] }));
}
// Individual log entry row
function LogEntryRow({ entry, maxWidth }) {
    const time = new Date(entry.timestamp).toLocaleTimeString('en-US', {
        hour12: false,
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
    });
    const levelColors = {
        debug: colors.dimGray,
        info: colors.cyan,
        success: colors.success,
        warn: colors.warning,
        error: colors.error,
    };
    const levelIcons = {
        debug: '  ',
        info: '●',
        success: '✓',
        warn: '⚠',
        error: '✗',
    };
    // Tool calls get special formatting
    if (entry.toolName) {
        const argsText = entry.toolArgs ? ` ${entry.toolArgs}` : '';
        `${entry.toolName}${argsText}`;
        return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: "   \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: colors.white, children: entry.toolName }), entry.toolArgs && jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [" ", entry.toolArgs] })] }));
    }
    // Regular log entries
    const color = levelColors[entry.level];
    const icon = levelIcons[entry.level];
    // Truncate message if needed
    const availableWidth = maxWidth - 12; // time + space + icon + space
    const truncatedMessage = entry.message.length > availableWidth
        ? entry.message.substring(0, availableWidth - 3) + '...'
        : entry.message;
    return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsxs(Text, { color: color, children: [" ", icon, " "] }), jsxRuntimeExports.jsx(Text, { color: color, children: truncatedMessage })] }));
}

function StatusBar({ isConnected, isVerbose, buildCount = 0, currentBuildIndex = 0, view, }) {
    // Get version info
    const versionInfo = getVersionInfo();
    // Connection indicator
    const connectionIndicator = (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: isConnected ? colors.success : colors.error, children: isConnected ? '●' : '○' }), jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [' ', isConnected ? 'Connected' : 'Disconnected'] })] }));
    // Build count indicator (only show if multiple builds)
    const buildIndicator = buildCount > 1 ? (jsxRuntimeExports.jsx(Box, { marginRight: 2, children: jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: ["Build ", currentBuildIndex + 1, "/", buildCount] }) })) : null;
    // Shortcuts based on current view
    const shortcuts = view === 'dashboard' ? (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut$1, { letter: "q", label: "quit" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "b", label: "browser" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "v", label: `verbose: ${isVerbose ? 'on' : 'off'}` }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "c", label: "copy" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "t", label: "text view" }), buildCount > 1 && jsxRuntimeExports.jsx(Shortcut$1, { letter: "n/p", label: "switch build" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "\u2191\u2193", label: "scroll" })] })) : view === 'fullLog' ? (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut$1, { letter: "t", label: "dashboard" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "c", label: "copy" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "/", label: "search" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "f", label: "filter" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "\u2191\u2193", label: "scroll" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "PgUp/Dn", label: "page" })] })) : (jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Shortcut$1, { letter: "Esc", label: "cancel" }) }));
    // Version display
    const versionDisplay = (jsxRuntimeExports.jsx(Box, { marginLeft: 2, children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: versionInfo.display }) }));
    return (jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Box, { children: [connectionIndicator, buildIndicator] }), jsxRuntimeExports.jsxs(Box, { children: [shortcuts, versionDisplay] })] }));
}
// Helper component for shortcuts
function Shortcut$1({ letter, label }) {
    return (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: letter }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "]" }), jsxRuntimeExports.jsx(Text, { color: colors.gray, children: label })] }));
}

function FullLogView({ entries, onBack, onCopy }) {
    const { stdout } = useStdout();
    const terminalHeight = stdout?.rows || 24;
    stdout?.columns || 80;
    const [searchQuery, setSearchQuery] = reactExports.useState('');
    const [isSearching, setIsSearching] = reactExports.useState(false);
    const [filterMode, setFilterMode] = reactExports.useState('all');
    const [scrollOffset, setScrollOffset] = reactExports.useState(0);
    const [searchMode, setSearchMode] = reactExports.useState('highlight');
    // Available height for log lines
    const headerHeight = 3;
    const footerHeight = 2;
    const visibleLines = Math.max(1, terminalHeight - headerHeight - footerHeight);
    // Filter and search entries
    const processedEntries = entries.filter(entry => {
        // Apply filter mode
        if (filterMode === 'errors' && entry.level !== 'error' && entry.level !== 'warn') {
            return false;
        }
        if (filterMode === 'tools' && !entry.toolName) {
            return false;
        }
        if (filterMode !== 'verbose' && entry.verbose) {
            return false;
        }
        // Apply search filter (if in filter mode)
        if (searchQuery && searchMode === 'filter') {
            const query = searchQuery.toLowerCase();
            const messageMatch = entry.message.toLowerCase().includes(query);
            const toolMatch = entry.toolName?.toLowerCase().includes(query);
            const argsMatch = entry.toolArgs?.toLowerCase().includes(query);
            return messageMatch || toolMatch || argsMatch;
        }
        return true;
    });
    // Calculate max scroll
    const maxScroll = Math.max(0, processedEntries.length - visibleLines);
    // Get visible entries
    const visibleEntries = processedEntries.slice(scrollOffset, scrollOffset + visibleLines);
    // Handle keyboard input
    useInput((input, key) => {
        if (isSearching) {
            if (key.escape || key.return) {
                setIsSearching(false);
            }
            return;
        }
        if (input === 't') {
            onBack();
        }
        else if (input === 'c') {
            onCopy();
        }
        else if (input === '/') {
            setIsSearching(true);
        }
        else if (input === 'f') {
            // Cycle through filter modes
            const modes = ['all', 'errors', 'tools', 'verbose'];
            const currentIndex = modes.indexOf(filterMode);
            setFilterMode(modes[(currentIndex + 1) % modes.length]);
            setScrollOffset(0);
        }
        else if (input === 'm') {
            // Toggle search mode
            setSearchMode(prev => prev === 'filter' ? 'highlight' : 'filter');
        }
        else if (key.upArrow) {
            setScrollOffset(prev => Math.max(0, prev - 1));
        }
        else if (key.downArrow) {
            setScrollOffset(prev => Math.min(maxScroll, prev + 1));
        }
        else if (key.pageUp) {
            setScrollOffset(prev => Math.max(0, prev - visibleLines));
        }
        else if (key.pageDown) {
            setScrollOffset(prev => Math.min(maxScroll, prev + visibleLines));
        }
        else if (key.escape) {
            if (searchQuery) {
                setSearchQuery('');
            }
            else {
                onBack();
            }
        }
    });
    // Format time
    const formatTime = (timestamp) => {
        return new Date(timestamp).toLocaleTimeString('en-US', {
            hour12: false,
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
        });
    };
    // Check if text matches search query
    const highlightSearch = (text) => {
        if (!searchQuery || searchMode !== 'highlight') {
            return text;
        }
        const query = searchQuery.toLowerCase();
        const index = text.toLowerCase().indexOf(query);
        if (index === -1) {
            return text;
        }
        return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [text.slice(0, index), jsxRuntimeExports.jsx(Text, { backgroundColor: colors.warning, color: "black", children: text.slice(index, index + searchQuery.length) }), text.slice(index + searchQuery.length)] }));
    };
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "LOGS" }), jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "Search: " }), isSearching ? (jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: colors.cyan, paddingX: 1, children: jsxRuntimeExports.jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "type to search..." }) })) : (jsxRuntimeExports.jsxs(Text, { color: searchQuery ? colors.white : colors.dimGray, children: ["[", searchQuery || 'none', "] (", searchMode, ")"] })), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: " [/]" })] })] }), jsxRuntimeExports.jsx(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "single", borderColor: colors.darkGray, borderTop: false, borderBottom: false, paddingX: 1, children: visibleEntries.map((entry, index) => {
                    const time = formatTime(entry.timestamp);
                    const levelColors = {
                        debug: colors.dimGray,
                        info: colors.cyan,
                        success: colors.success,
                        warn: colors.warning,
                        error: colors.error,
                    };
                    const levelIcons = {
                        debug: '  ',
                        info: '●',
                        success: '✓',
                        warn: '⚠',
                        error: '✗',
                    };
                    if (entry.toolName) {
                        return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: "   \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: colors.white, children: highlightSearch(entry.toolName) }), entry.toolArgs && (jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [" ", highlightSearch(entry.toolArgs)] }))] }, entry.id));
                    }
                    return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsxs(Text, { color: levelColors[entry.level], children: [" ", levelIcons[entry.level], " "] }), jsxRuntimeExports.jsx(Text, { color: levelColors[entry.level], children: highlightSearch(entry.message) })] }, entry.id));
                }) }), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut, { letter: "t", label: "dashboard" }), jsxRuntimeExports.jsx(Shortcut, { letter: "c", label: "copy" }), jsxRuntimeExports.jsx(Shortcut, { letter: "/", label: "search" }), jsxRuntimeExports.jsx(Shortcut, { letter: "f", label: `filter: ${filterMode}` }), jsxRuntimeExports.jsx(Shortcut, { letter: "m", label: `mode: ${searchMode}` })] }), jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: [scrollOffset + 1, "-", Math.min(scrollOffset + visibleLines, processedEntries.length), "/", processedEntries.length] })] })] }));
}
function Shortcut({ letter, label }) {
    return (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: letter }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "]" }), jsxRuntimeExports.jsx(Text, { color: colors.gray, children: label })] }));
}

function CopyMenu({ onSelect, onCancel, visibleCount, totalCount }) {
    useInput((input, key) => {
        if (key.escape) {
            onCancel();
        }
        else if (input === '1') {
            onSelect('visible');
        }
        else if (input === '2') {
            onSelect('last50');
        }
        else if (input === '3') {
            onSelect('last100');
        }
        else if (input === '4') {
            onSelect('all');
        }
        else if (input === '5') {
            onSelect('range');
        }
    });
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.cyan, paddingX: 2, paddingY: 1, children: [jsxRuntimeExports.jsx(Box, { marginBottom: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "Copy Logs" }) }), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [jsxRuntimeExports.jsx(CopyOption, { number: "1", label: `Copy visible (${visibleCount} lines)` }), jsxRuntimeExports.jsx(CopyOption, { number: "2", label: "Copy last 50 lines" }), jsxRuntimeExports.jsx(CopyOption, { number: "3", label: "Copy last 100 lines" }), jsxRuntimeExports.jsx(CopyOption, { number: "4", label: `Copy all from file (${totalCount} lines)` }), jsxRuntimeExports.jsx(CopyOption, { number: "5", label: "Copy range..." })] }), jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[Esc] Cancel" }) })] }));
}
function CopyOption({ number, label }) {
    return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: number }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "]" }), jsxRuntimeExports.jsxs(Text, { color: colors.white, children: [" ", label] })] }));
}

function RunnerDashboard({ config, onQuit }) {
    const { exit } = useApp();
    const { stdout } = useStdout();
    const terminalHeight = stdout?.rows || 24;
    const terminalWidth = stdout?.columns || 80;
    // State management
    const [buildState, buildActions] = useBuildState();
    const logEntries = useLogEntries(100);
    const [view, setView] = reactExports.useState('dashboard');
    // Track if we've opened browser on first connection
    const hasOpenedBrowserRef = reactExports.useRef(false);
    // Get the dashboard URL (use apiUrl if provided, otherwise default to hatchway.sh)
    const dashboardUrl = config.apiUrl || 'https://hatchway.sh';
    // Open browser handler
    const handleOpenBrowser = reactExports.useCallback(() => {
        openBrowser(dashboardUrl).catch(() => {
            // Silently fail - user can manually open the URL
        });
    }, [dashboardUrl]);
    // Auto-open browser on first successful connection
    reactExports.useEffect(() => {
        if (buildState.isConnected && !hasOpenedBrowserRef.current) {
            hasOpenedBrowserRef.current = true;
            // Small delay to ensure the TUI is fully rendered before opening browser
            setTimeout(() => {
                handleOpenBrowser();
            }, 500);
        }
    }, [buildState.isConnected, handleOpenBrowser]);
    // Handle keyboard input
    useInput((input, key) => {
        // Global quit handler
        if (input === 'q' && view !== 'copyMenu') {
            if (onQuit) {
                onQuit();
            }
            exit();
            return;
        }
        // Copy menu is modal - handle separately
        if (view === 'copyMenu') {
            return; // CopyMenu handles its own input
        }
        // Dashboard shortcuts
        if (view === 'dashboard') {
            if (input === 'v') {
                buildActions.toggleVerbose();
            }
            else if (input === 'c') {
                setView('copyMenu');
            }
            else if (input === 't') {
                setView('fullLog');
            }
            else if (input === 'n') {
                buildActions.nextBuild();
            }
            else if (input === 'p') {
                buildActions.prevBuild();
            }
            else if (input === 'b') {
                handleOpenBrowser();
            }
        }
        // Full log view shortcuts
        if (view === 'fullLog') {
            if (input === 't') {
                setView('dashboard');
            }
            else if (input === 'c') {
                setView('copyMenu');
            }
        }
    });
    // Handle copy action
    const handleCopy = reactExports.useCallback(async (option) => {
        try {
            const buffer = getLogBuffer();
            let entriesToCopy = [];
            switch (option) {
                case 'visible':
                    entriesToCopy = logEntries.slice(-20); // Approximate visible count
                    break;
                case 'last50':
                    entriesToCopy = buffer.getRecent(50);
                    break;
                case 'last100':
                    entriesToCopy = buffer.getRecent(100);
                    break;
                case 'all':
                    entriesToCopy = buffer.readFromFile();
                    break;
                case 'range':
                    // TODO: Implement range selection
                    entriesToCopy = buffer.getRecent(100);
                    break;
            }
            const text = buffer.toText(entriesToCopy);
            // Copy to clipboard using pbcopy on macOS
            const { spawn } = await import('child_process');
            const pbcopy = spawn('pbcopy');
            pbcopy.stdin.write(text);
            pbcopy.stdin.end();
            // TODO: Show success message
        }
        catch (error) {
            console.error('Failed to copy to clipboard:', error);
        }
        setView('dashboard');
    }, [logEntries]);
    // Calculate panel dimensions
    const bannerHeight = 7; // ASCII art banner
    const headerHeight = 3; // Config/status line
    const statusBarHeight = 3;
    const contentHeight = Math.max(1, terminalHeight - bannerHeight - headerHeight - statusBarHeight);
    // 20/80 split
    const buildPanelWidth = Math.floor(terminalWidth * 0.2);
    const logPanelWidth = terminalWidth - buildPanelWidth;
    // Show build panel only when there's an active build
    const showBuildPanel = buildState.currentBuild !== null;
    // Check for available update (set by auto-update check in index.ts)
    const updateAvailable = process.env.HATCHWAY_UPDATE_AVAILABLE;
    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: colors.cyan, children: "\u2B06 Update available: " }), jsxRuntimeExports.jsx(Text, { color: colors.success, children: updateAvailable }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: " \u2014 Run " }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: "hatchway upgrade" }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: " to update" })] })), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["Runner: ", jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: config.runnerId }), " \u2022 Server: ", jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: config.serverUrl.replace(/^wss?:\/\//, '') })] }), jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: buildState.isConnected ? colors.success : colors.error, children: buildState.isConnected ? '●' : '○' }), jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [' ', buildState.isConnected ? 'Connected' : 'Disconnected'] })] })] }), view === 'dashboard' && (jsxRuntimeExports.jsxs(Box, { flexGrow: 1, height: contentHeight, children: [showBuildPanel && (jsxRuntimeExports.jsx(BuildPanel, { build: buildState.currentBuild, width: buildPanelWidth, height: contentHeight })), jsxRuntimeExports.jsx(LogPanel, { entries: logEntries, isVerbose: buildState.isVerbose, width: showBuildPanel ? logPanelWidth : terminalWidth, height: contentHeight, isFocused: true })] })), view === 'fullLog' && (jsxRuntimeExports.jsx(FullLogView, { entries: logEntries, onBack: () => setView('dashboard'), onCopy: () => setView('copyMenu') })), view === 'copyMenu' && (jsxRuntimeExports.jsx(Box, { position: "absolute", flexDirection: "column", justifyContent: "center", alignItems: "center", width: terminalWidth, height: terminalHeight, children: jsxRuntimeExports.jsx(CopyMenu, { onSelect: handleCopy, onCancel: () => setView('dashboard'), visibleCount: Math.min(20, logEntries.length), totalCount: getLogBuffer().readFromFile().length }) })), view === 'dashboard' && (jsxRuntimeExports.jsx(StatusBar, { isConnected: buildState.isConnected, isVerbose: buildState.isVerbose, buildCount: buildState.builds.length, currentBuildIndex: buildState.currentBuildIndex, view: view }))] }));
}

// Default public Hatchway instance
const DEFAULT_URL = 'https://hatchway.sh';
const DEFAULT_WORKSPACE = join(homedir(), 'hatchway-workspace');
/**
 * Normalize URL by adding protocol if missing
 * Uses http:// for localhost, https:// for everything else
 */
function normalizeUrl(url) {
    if (!url)
        return url;
    // If protocol already present, return as-is
    if (url.match(/^https?:\/\//i)) {
        return url;
    }
    // For localhost or 127.0.0.1, use http://
    if (url.match(/^(localhost|127\.0\.0\.1)(:|\/|$)/i)) {
        return `http://${url}`;
    }
    // For everything else, use https://
    return `https://${url}`;
}
/**
 * Derive WebSocket URL from a base HTTP/HTTPS URL
 * Converts https://example.com to wss://example.com/ws/runner
 */
function deriveWsUrl(baseUrl) {
    const normalized = normalizeUrl(baseUrl);
    const wsProtocol = normalized.startsWith('https://') ? 'wss://' : 'ws://';
    const hostPath = normalized.replace(/^https?:\/\//, '');
    // Remove trailing slash if present
    const cleanHostPath = hostPath.replace(/\/$/, '');
    return `${wsProtocol}${cleanHostPath}/ws/runner`;
}
/**
 * Get the current system username
 */
function getSystemUsername() {
    try {
        return userInfo().username;
    }
    catch {
        // Fallback if userInfo() fails
        return process.env.USER || process.env.USERNAME || 'runner';
    }
}
/**
 * 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 runCommand(options) {
    // Set local mode environment variable if requested
    if (options.local) {
        process.env.HATCHWAY_LOCAL_MODE = 'true';
        logger.info(chalk.yellow('Local mode enabled - authentication bypassed'));
    }
    const useTUI = shouldUseTUI(options);
    // Build runner options from CLI flags or smart defaults
    // NOTE: For the `runner` command, we intentionally ignore local config values
    // and default to the public Hatchway instance. This command is specifically
    // for connecting to remote servers, not local development.
    // Users can still override with CLI flags if needed.
    // Resolve API URL: CLI flag > default public instance (ignore config)
    const apiUrl = normalizeUrl(options.url || DEFAULT_URL);
    // Resolve WebSocket URL: CLI broker flag > derive from API URL (ignore config)
    const wsUrl = options.broker || deriveWsUrl(apiUrl);
    // Resolve workspace: CLI flag > config > default ~/hatchway-workspace
    // (workspace from config is fine since it's user's preference for where projects go)
    const config = configManager.get();
    const workspace = options.workspace || config.workspace || DEFAULT_WORKSPACE;
    // Resolve runner ID: CLI flag > system username (ignore config 'local' default)
    const runnerId = options.runnerId || getSystemUsername();
    // Resolve secret: CLI flag > config (required)
    // Only use config secret if it looks like a valid token (starts with sv_)
    // This prevents the default 'dev-secret' from being used in runner mode
    const configSecret = configManager.getSecret();
    const sharedSecret = options.secret || (configSecret?.startsWith('sv_') ? configSecret : undefined);
    const runnerOptions = {
        wsUrl,
        apiUrl,
        sharedSecret,
        runnerId,
        workspace,
        verbose: options.verbose,
        tuiMode: useTUI,
    };
    // Validate required options - secret is required
    // If not provided, try to use stored OAuth token or trigger OAuth flow
    if (!runnerOptions.sharedSecret) {
        // Check if we have a stored OAuth token
        if (hasStoredToken()) {
            const storedToken = getStoredToken();
            if (storedToken) {
                runnerOptions.sharedSecret = storedToken;
                logger.info(`Using stored runner token: ${chalk.cyan(storedToken.substring(0, 12) + '...')}`);
            }
        }
        // If still no secret and not in local mode, trigger OAuth flow
        if (!runnerOptions.sharedSecret && !options.local) {
            logger.info('No runner token found. Starting OAuth authentication...');
            logger.info('');
            const result = await performOAuthLogin({
                apiUrl: runnerOptions.apiUrl,
                silent: false,
            });
            if (result.success && result.token) {
                storeToken(result.token, runnerOptions.apiUrl);
                runnerOptions.sharedSecret = result.token;
                logger.log('');
                logger.success('Authentication successful!');
                logger.info(`Token: ${chalk.cyan(result.token.substring(0, 12) + '...')}`);
                logger.log('');
            }
            else {
                logger.error(result.error || 'Authentication failed');
                logger.info('');
                logger.info('You can also provide a token manually:');
                logger.info(`  ${chalk.cyan('hatchway runner --secret <your-secret>')}`);
                logger.info('');
                logger.info('Or login first:');
                logger.info(`  ${chalk.cyan('hatchway login')}`);
                process.exit(1);
            }
        }
        // Final check - if still no secret (and not local mode)
        if (!runnerOptions.sharedSecret && !options.local) {
            logger.error('Shared secret is required');
            logger.info('');
            logger.info('Get a runner key from your Hatchway dashboard, or provide via:');
            logger.info(`  ${chalk.cyan('hatchway runner --secret <your-secret>')}`);
            logger.info('');
            logger.info('Or login with OAuth:');
            logger.info(`  ${chalk.cyan('hatchway login')}`);
            process.exit(1);
        }
    }
    // ========================================
    // PLAIN TEXT MODE (--no-tui)
    // ========================================
    if (!useTUI) {
        // Display startup info
        logger.section('Starting Hatchway Runner');
        logger.info(`Server: ${chalk.cyan(runnerOptions.wsUrl)}`);
        logger.info(`API URL: ${chalk.cyan(runnerOptions.apiUrl)}`);
        logger.info(`Runner ID: ${chalk.cyan(runnerOptions.runnerId)}`);
        logger.info(`Workspace: ${chalk.cyan(runnerOptions.workspace)}`);
        logger.log('');
        if (options.verbose) {
            logger.debug('Verbose logging enabled');
            logger.debug(`Full options: ${JSON.stringify(runnerOptions, null, 2)}`);
        }
        try {
            // Start the runner (runs indefinitely)
            await startRunner(runnerOptions);
        }
        catch (error) {
            logger.error('Failed to start runner:');
            logger.error(error instanceof Error ? error.message : 'Unknown error');
            if (error instanceof Error && error.stack) {
                logger.debug(error.stack);
            }
            process.exit(1);
        }
        return;
    }
    // ========================================
    // TUI MODE (default)
    // ========================================
    // Initialize the logger BEFORE rendering TUI so the TUI can subscribe to 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);
    // Track runner cleanup function
    let runnerCleanupFn;
    // Clear screen and enter alternate buffer for clean TUI
    process.stdout.write('\x1b[?1049h'); // Enter alternate screen
    process.stdout.write('\x1b[2J\x1b[H'); // Clear and home
    // Ensure stdin is in raw mode for keyboard input
    if (process.stdin.setRawMode) {
        process.stdin.setRawMode(true);
    }
    process.stdin.resume();
    // Handle quit from TUI
    const handleQuit = async () => {
        // Exit alternate screen buffer
        process.stdout.write('\x1b[?1049l');
        console.log('\n' + chalk.yellow('Shutting down runner...'));
        if (runnerCleanupFn) {
            try {
                await runnerCleanupFn();
                console.log(chalk.green('✓') + ' Runner stopped');
            }
            catch (e) {
                console.error(chalk.red('✗') + ' Error stopping runner:', e);
            }
        }
        process.exit(0);
    };
    // Handle SIGINT (Ctrl+C)
    process.on('SIGINT', handleQuit);
    // Render the TUI dashboard
    const { waitUntilExit, clear } = render(React.createElement(RunnerDashboard, {
        config: {
            runnerId,
            serverUrl: wsUrl,
            workspace,
            apiUrl,
        },
        onQuit: handleQuit,
    }), {
        stdin: process.stdin,
        stdout: process.stdout,
        stderr: process.stderr,
        exitOnCtrlC: false, // We handle this ourselves
        patchConsole: false, // We use our own logging
    });
    try {
        // Start the runner and get cleanup function
        runnerCleanupFn = await startRunner(runnerOptions);
        // Wait for TUI to exit (user pressed 'q')
        await waitUntilExit();
        // Clean up
        clear();
        await handleQuit();
    }
    catch (error) {
        clear();
        process.stdout.write('\x1b[?1049l'); // Exit alternate screen
        logger.error('Failed to start runner:');
        logger.error(error instanceof Error ? error.message : 'Unknown error');
        if (error instanceof Error && error.stack) {
            logger.debug(error.stack);
        }
        process.exit(1);
    }
}

export { runCommand };
//# sourceMappingURL=run-Du6dvTJL.js.map
