// Hatchway CLI - Built with Rollup
import { r as reactExports, u as useInput, j as jsxRuntimeExports, B as Box, T as Text, c as colors, s as symbols, a as useStdout, b as render } from './theme-NAQBkisB.js';
import 'chalk';
import { userInfo, homedir } from 'node:os';
import { join } from 'node:path';
import { B as Banner } from './Banner-DL1Fpz_g.js';
import 'node:fs';
import 'node:events';
import 'node:stream';
import 'node:process';
import { T as TextInput } from './index-DCC6HGdr.js';
import 'node:http';
import 'node:url';
import 'node:child_process';
import { c as configManager } from './config-manager-DST6RbP8.js';
import 'assert';
import 'events';
import 'module';
import 'node:buffer';
import 'conf';

/**
 * Arrow-key navigable menu component
 *
 * > Initialize Hatchway
 *   Start Runner
 *   Exit
 */
function Menu({ items, onSelect }) {
    const [selectedIndex, setSelectedIndex] = reactExports.useState(0);
    useInput((input, key) => {
        if (key.upArrow) {
            setSelectedIndex(prev => (prev > 0 ? prev - 1 : items.length - 1));
        }
        else if (key.downArrow) {
            setSelectedIndex(prev => (prev < items.length - 1 ? prev + 1 : 0));
        }
        else if (key.return) {
            onSelect(items[selectedIndex]);
        }
    });
    return (jsxRuntimeExports.jsx(Box, { flexDirection: "column", alignItems: "flex-start", children: items.map((item, index) => {
            const isSelected = index === selectedIndex;
            return (jsxRuntimeExports.jsxs(Box, { marginY: 0, children: [jsxRuntimeExports.jsx(Text, { color: isSelected ? colors.cyan : colors.gray, children: isSelected ? '› ' : '  ' }), jsxRuntimeExports.jsx(Text, { color: isSelected ? colors.white : colors.gray, bold: isSelected, children: item.label }), item.description && (jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ['  ', item.description] }))] }, item.id));
        }) }));
}

/**
 * Horizontal card selector component
 *
 * ┌─────────────────┐     ┌─────────────────┐
 * │   Local Mode    │     │   Runner Mode   │
 * │                 │     │                 │
 * │ Run Hatchway   │     │ Connect to a    │
 * │ locally         │     │ remote server   │
 * └─────────────────┘     └─────────────────┘
 *       [SELECTED]
 */
function HorizontalSelector({ options, onSelect, onEscape }) {
    const [selectedIndex, setSelectedIndex] = reactExports.useState(0);
    useInput((input, key) => {
        if (key.leftArrow) {
            setSelectedIndex(prev => (prev > 0 ? prev - 1 : options.length - 1));
        }
        else if (key.rightArrow) {
            setSelectedIndex(prev => (prev < options.length - 1 ? prev + 1 : 0));
        }
        else if (key.return) {
            onSelect(options[selectedIndex]);
        }
        else if (key.escape && onEscape) {
            onEscape();
        }
    });
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { flexDirection: "row", gap: 2, children: options.map((option, index) => {
                    const isSelected = index === selectedIndex;
                    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", borderStyle: "round", borderColor: isSelected ? colors.cyan : colors.darkGray, paddingX: 3, paddingY: 1, width: 25, children: [jsxRuntimeExports.jsx(Text, { color: isSelected ? colors.white : colors.gray, bold: isSelected, children: option.title }), jsxRuntimeExports.jsx(Box, { marginTop: 1, children: jsxRuntimeExports.jsx(Text, { color: isSelected ? colors.gray : colors.dimGray, wrap: "wrap", children: option.description }) })] }, option.id));
                }) }), jsxRuntimeExports.jsx(Box, { marginTop: 1, children: options.map((option, index) => (jsxRuntimeExports.jsx(Box, { width: 25, justifyContent: "center", marginX: 1, children: index === selectedIndex && (jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: '▲' })) }, option.id))) })] }));
}

/**
 * Masked text input for sensitive data like keys/passwords
 * Shows first N characters (default: 5) then mask characters for the rest
 * Supports paste (multi-character input) with buffering to prevent visual glitches
 */
function MaskedInput({ value, onChange, placeholder = '', maskChar = '*', focused = false, visiblePrefixLength = 5, }) {
    const [cursorVisible, setCursorVisible] = reactExports.useState(true);
    // Buffer to accumulate rapid input (for paste detection)
    const inputBufferRef = reactExports.useRef('');
    const flushTimeoutRef = reactExports.useRef(null);
    // Keep current value in ref for use in timeout callbacks
    const valueRef = reactExports.useRef(value);
    valueRef.current = value;
    // Blink cursor when focused
    reactExports.useEffect(() => {
        if (!focused) {
            setCursorVisible(false);
            return;
        }
        setCursorVisible(true);
        const interval = setInterval(() => {
            setCursorVisible(prev => !prev);
        }, 500);
        return () => clearInterval(interval);
    }, [focused]);
    // Flush buffered input - called after a brief delay to batch paste operations
    const flushBuffer = reactExports.useCallback(() => {
        if (inputBufferRef.current) {
            onChange(valueRef.current + inputBufferRef.current);
            inputBufferRef.current = '';
        }
    }, [onChange]);
    useInput((input, key) => {
        if (!focused)
            return;
        if (key.backspace || key.delete) {
            // Clear any pending buffer on backspace
            if (flushTimeoutRef.current) {
                clearTimeout(flushTimeoutRef.current);
                flushTimeoutRef.current = null;
            }
            if (inputBufferRef.current) {
                // Remove from buffer first
                inputBufferRef.current = inputBufferRef.current.slice(0, -1);
                if (!inputBufferRef.current) {
                    onChange(value.slice(0, -1));
                }
            }
            else {
                onChange(value.slice(0, -1));
            }
        }
        else if (!key.escape && !key.return && !key.tab &&
            !key.upArrow && !key.downArrow && !key.leftArrow && !key.rightArrow) {
            // Allow any printable input including pasted text (multi-character)
            // Filter out control characters but allow regular text
            const printable = input.replace(/[\x00-\x1F\x7F]/g, '');
            if (printable.length > 0) {
                // Buffer the input for batching (helps with paste)
                inputBufferRef.current += printable;
                // Clear any existing timeout
                if (flushTimeoutRef.current) {
                    clearTimeout(flushTimeoutRef.current);
                }
                // Set a short timeout to flush - if more input comes quickly (paste), 
                // it will be batched together
                flushTimeoutRef.current = setTimeout(() => {
                    flushBuffer();
                    flushTimeoutRef.current = null;
                }, 10);
            }
        }
    }, { isActive: focused });
    // Cleanup timeout on unmount
    reactExports.useEffect(() => {
        return () => {
            if (flushTimeoutRef.current) {
                clearTimeout(flushTimeoutRef.current);
            }
        };
    }, []);
    // Show first N characters unmasked, mask the rest
    const getDisplayValue = () => {
        if (!value)
            return '';
        if (value.length <= visiblePrefixLength) {
            return value;
        }
        const visiblePart = value.substring(0, visiblePrefixLength);
        const maskedPart = maskChar.repeat(value.length - visiblePrefixLength);
        return visiblePart + maskedPart;
    };
    const displayValue = getDisplayValue();
    const cursor = focused && cursorVisible ? '│' : ' ';
    if (!value && !focused) {
        return (jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: placeholder }));
    }
    return (jsxRuntimeExports.jsxs(Text, { color: colors.white, children: [displayValue, focused && jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: cursor })] }));
}

/**
 * Radio button group component
 *
 *   ● Option 1
 *   ○ Option 2
 */
function RadioGroup({ options, selected, onChange, focused = false }) {
    useInput((input, key) => {
        if (!focused)
            return;
        const currentIndex = options.findIndex(opt => opt.id === selected);
        if (key.upArrow) {
            const newIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1;
            onChange(options[newIndex].id);
        }
        else if (key.downArrow) {
            const newIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0;
            onChange(options[newIndex].id);
        }
        else ;
    }, { isActive: focused });
    return (jsxRuntimeExports.jsx(Box, { flexDirection: "column", children: options.map((option) => {
            const isSelected = option.id === selected;
            const isFocusedOption = focused && isSelected;
            return (jsxRuntimeExports.jsxs(Box, { marginY: 0, children: [jsxRuntimeExports.jsx(Text, { color: isSelected ? colors.cyan : colors.gray, children: isSelected ? symbols.filledDot : symbols.hollowDot }), jsxRuntimeExports.jsx(Text, { children: " " }), jsxRuntimeExports.jsx(Text, { color: isFocusedOption ? colors.white : (isSelected ? colors.gray : colors.dimGray), bold: isFocusedOption, children: option.label })] }, option.id));
        }) }));
}

const modeOptions = [
    {
        id: 'local',
        title: 'Local Mode',
        description: 'Run Hatchway on this machine',
    },
    {
        id: 'runner',
        title: 'Runner Mode',
        description: 'Connect to a remote server',
    },
];
/**
 * Initial mode selection screen
 * Shows two horizontal cards for Local Mode vs Runner Mode
 */
function ModeSelectScreen({ onSelect, onEscape }) {
    const { stdout } = useStdout();
    // Check for available update (set by auto-update check in index.ts)
    const updateAvailable = process.env.HATCHWAY_UPDATE_AVAILABLE;
    // Calculate vertical centering
    const terminalHeight = stdout?.rows || 24;
    const contentHeight = updateAvailable ? 20 : 18; // Extra space for update notice
    const topPadding = Math.max(0, Math.floor((terminalHeight - contentHeight) / 3));
    const handleSelect = (option) => {
        onSelect(option.id);
    };
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", paddingTop: topPadding, children: [jsxRuntimeExports.jsx(Banner, {}), updateAvailable && (jsxRuntimeExports.jsxs(Box, { marginTop: 1, 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.jsx(Box, { marginTop: 2 }), jsxRuntimeExports.jsx(HorizontalSelector, { options: modeOptions, onSelect: handleSelect, onEscape: onEscape }), jsxRuntimeExports.jsx(Box, { marginTop: 2 }), jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["Use ", '<-', " ", '->', " arrows to navigate, Enter to select, Esc to exit"] })] }));
}

/**
 * Local mode options screen
 * Shows Initialize/Reinitialize and Start options based on config state
 */
function LocalModeScreen({ isInitialized, onSelect, onEscape }) {
    const { stdout } = useStdout();
    // Calculate vertical centering
    const terminalHeight = stdout?.rows || 24;
    const contentHeight = 16;
    const topPadding = Math.max(0, Math.floor((terminalHeight - contentHeight) / 3));
    // Handle escape key
    useInput((input, key) => {
        if (key.escape) {
            onEscape();
        }
    });
    // Build menu items based on initialization state
    const menuItems = [];
    if (!isInitialized) {
        menuItems.push({
            id: 'init',
            label: 'Initialize Hatchway',
            description: 'Set up workspace and configuration',
        });
    }
    else {
        menuItems.push({
            id: 'init',
            label: 'Reinitialize Hatchway',
            description: 'Reset and reconfigure',
        });
        menuItems.push({
            id: 'start',
            label: 'Start Hatchway',
            description: 'Launch the full stack',
        });
    }
    const handleSelect = (item) => {
        onSelect(item.id);
    };
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", paddingTop: topPadding, children: [jsxRuntimeExports.jsx(Banner, {}), jsxRuntimeExports.jsx(Box, { marginTop: 1 }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "Local Mode" }), jsxRuntimeExports.jsx(Box, { marginTop: 1 }), isInitialized ? (jsxRuntimeExports.jsx(Text, { color: colors.success, children: "\u25CF Configured" })) : (jsxRuntimeExports.jsx(Text, { color: colors.warning, children: "\u25CB Not configured" })), jsxRuntimeExports.jsx(Box, { marginTop: 2 }), jsxRuntimeExports.jsx(Menu, { items: menuItems, onSelect: handleSelect }), jsxRuntimeExports.jsx(Box, { marginTop: 2 }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "Use up/down arrows to navigate, Enter to select, Esc to go back" })] }));
}

// Fixed label width for alignment
const LABEL_WIDTH$1 = 14;
/**
 * Runner mode screen with key and runner ID inputs
 */
function RunnerModeScreen({ initialKey = '', initialRunnerId = '', onStart, onEscape, }) {
    const { stdout } = useStdout();
    const [runnerId, setRunnerId] = reactExports.useState(initialRunnerId);
    const [focusedField, setFocusedField] = reactExports.useState('key');
    // We need a separate state for the actual key value since MaskedInput doesn't use ink-text-input
    const [runnerKey, setRunnerKey] = reactExports.useState(initialKey);
    // Calculate vertical centering
    const terminalHeight = stdout?.rows || 24;
    const contentHeight = 18;
    const topPadding = Math.max(0, Math.floor((terminalHeight - contentHeight) / 3));
    const isLastField = focusedField === 'runnerId';
    const handleSubmit = () => {
        if (runnerKey.trim()) {
            onStart({ key: runnerKey, runnerId: runnerId.trim() || initialRunnerId });
        }
    };
    useInput((input, key) => {
        if (key.escape) {
            onEscape();
            return;
        }
        // Shift+Enter to submit immediately from any field
        if (key.return && key.shift) {
            handleSubmit();
            return;
        }
        // Regular Enter moves to next field, or submits if on last field
        if (key.return && !key.shift) {
            if (isLastField) {
                handleSubmit();
            }
            else {
                setFocusedField('runnerId');
            }
            return;
        }
        if (key.tab || key.downArrow) {
            setFocusedField(prev => prev === 'key' ? 'runnerId' : 'key');
            return;
        }
        if (key.upArrow) {
            setFocusedField(prev => prev === 'runnerId' ? 'key' : 'runnerId');
            return;
        }
    });
    const handleKeyChange = (value) => {
        setRunnerKey(value);
    };
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", paddingTop: topPadding, children: [jsxRuntimeExports.jsx(Banner, {}), jsxRuntimeExports.jsx(Box, { marginTop: 1 }), jsxRuntimeExports.jsx(Text, { color: colors.purple, bold: true, children: "Runner Mode" }), jsxRuntimeExports.jsx(Box, { marginTop: 2 }), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", gap: 1, children: [jsxRuntimeExports.jsxs(Box, { flexDirection: "row", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { width: LABEL_WIDTH$1, justifyContent: "flex-end", marginRight: 1, children: jsxRuntimeExports.jsx(Text, { color: focusedField === 'key' ? colors.cyan : colors.gray, children: "Runner Key" }) }), jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: focusedField === 'key' ? colors.cyan : colors.darkGray, paddingX: 1, width: 40, children: jsxRuntimeExports.jsx(MaskedInput, { value: runnerKey, onChange: handleKeyChange, placeholder: "Paste your runner key", focused: focusedField === 'key' }) })] }), initialKey && focusedField === 'key' && (jsxRuntimeExports.jsx(Box, { marginLeft: LABEL_WIDTH$1 + 2, children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, italic: true, children: "(auto-filled from previous config)" }) })), jsxRuntimeExports.jsxs(Box, { flexDirection: "row", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { width: LABEL_WIDTH$1, justifyContent: "flex-end", marginRight: 1, children: jsxRuntimeExports.jsx(Text, { color: focusedField === 'runnerId' ? colors.cyan : colors.gray, children: "Runner ID" }) }), jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: focusedField === 'runnerId' ? colors.cyan : colors.darkGray, paddingX: 1, width: 40, children: focusedField === 'runnerId' ? (jsxRuntimeExports.jsx(TextInput, { value: runnerId, onChange: setRunnerId, placeholder: initialRunnerId || 'Enter runner ID' })) : (jsxRuntimeExports.jsx(Text, { color: runnerId ? colors.white : colors.dimGray, children: runnerId || initialRunnerId || 'Enter runner ID' })) })] })] }), jsxRuntimeExports.jsx(Box, { marginTop: 3 }), jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["Enter: ", isLastField ? 'Start runner' : 'Next field', " | Shift+Enter: Start now | Esc: Back"] }), !runnerKey.trim() && (jsxRuntimeExports.jsx(Box, { marginTop: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.warning, children: "Runner key is required" }) }))] }));
}

const databaseOptions = [
    { id: 'neon', label: 'Use Neon (automatic setup)' },
    { id: 'custom', label: 'Custom PostgreSQL' },
];
// Fixed label width for alignment
const LABEL_WIDTH = 14;
/**
 * Interactive configuration form for init/reinit
 */
function ConfigFormScreen({ initialConfig, onSubmit, onEscape, error, }) {
    const { stdout } = useStdout();
    // Form state
    const [branch, setBranch] = reactExports.useState(initialConfig?.branch || 'main');
    // Clear error when user starts typing in branch field
    const handleBranchChange = (value) => {
        setBranch(value);
    };
    const [workspace, setWorkspace] = reactExports.useState(initialConfig?.workspace || '~/hatchway-workspace');
    const [databaseType, setDatabaseType] = reactExports.useState(initialConfig?.useNeon === false ? 'custom' : 'neon');
    const [databaseUrl, setDatabaseUrl] = reactExports.useState(initialConfig?.databaseUrl || '');
    const [focusedField, setFocusedField] = reactExports.useState('branch');
    // Calculate vertical centering
    const terminalHeight = stdout?.rows || 24;
    const contentHeight = 22;
    const topPadding = Math.max(0, Math.floor((terminalHeight - contentHeight) / 3));
    // Field order for navigation
    const fieldOrder = databaseType === 'custom'
        ? ['branch', 'workspace', 'database', 'databaseUrl']
        : ['branch', 'workspace', 'database'];
    const currentFieldIndex = fieldOrder.indexOf(focusedField);
    const isLastField = currentFieldIndex === fieldOrder.length - 1;
    useInput((input, key) => {
        if (key.escape) {
            onEscape();
            return;
        }
        // Shift+Enter to submit immediately from any field
        if (key.return && key.shift) {
            handleSubmit();
            return;
        }
        // Regular Enter moves to next field, or submits if on last field
        if (key.return && !key.shift) {
            if (isLastField) {
                handleSubmit();
            }
            else {
                setFocusedField(fieldOrder[currentFieldIndex + 1]);
            }
            return;
        }
        // Tab or Down arrow moves to next field
        if (key.tab || key.downArrow) {
            if (focusedField !== 'database') {
                const nextIndex = (currentFieldIndex + 1) % fieldOrder.length;
                setFocusedField(fieldOrder[nextIndex]);
            }
            return;
        }
        // Up arrow moves to previous field
        if (key.upArrow) {
            if (focusedField !== 'database') {
                const prevIndex = currentFieldIndex > 0 ? currentFieldIndex - 1 : fieldOrder.length - 1;
                setFocusedField(fieldOrder[prevIndex]);
            }
            return;
        }
    });
    const handleSubmit = () => {
        const config = {
            branch: branch.trim() || 'main',
            workspace: workspace.trim() || '~/hatchway-workspace',
            useNeon: databaseType === 'neon',
            databaseUrl: databaseType === 'custom' ? databaseUrl.trim() : undefined,
        };
        onSubmit(config);
    };
    const handleDatabaseTypeChange = (id) => {
        setDatabaseType(id);
        // If switching to custom, focus the URL field
        if (id === 'custom') {
            setFocusedField('databaseUrl');
        }
    };
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", paddingTop: topPadding, children: [jsxRuntimeExports.jsx(Banner, {}), jsxRuntimeExports.jsx(Box, { marginTop: 1 }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "Configure Hatchway" }), jsxRuntimeExports.jsx(Box, { marginTop: 2 }), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", gap: 1, children: [jsxRuntimeExports.jsxs(Box, { flexDirection: "column", children: [jsxRuntimeExports.jsxs(Box, { flexDirection: "row", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { width: LABEL_WIDTH, justifyContent: "flex-end", marginRight: 1, children: jsxRuntimeExports.jsx(Text, { color: error ? colors.error : (focusedField === 'branch' ? colors.cyan : colors.gray), children: "Branch" }) }), jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: error ? colors.error : (focusedField === 'branch' ? colors.cyan : colors.darkGray), paddingX: 1, children: focusedField === 'branch' ? (jsxRuntimeExports.jsx(TextInput, { value: branch, onChange: handleBranchChange, placeholder: "main" })) : (jsxRuntimeExports.jsx(Text, { color: branch ? colors.white : colors.dimGray, children: branch || 'main' })) })] }), error && (jsxRuntimeExports.jsx(Box, { marginLeft: LABEL_WIDTH + 2, children: jsxRuntimeExports.jsxs(Text, { color: colors.error, children: [symbols.cross, " ", error] }) }))] }), jsxRuntimeExports.jsxs(Box, { flexDirection: "row", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { width: LABEL_WIDTH, justifyContent: "flex-end", marginRight: 1, children: jsxRuntimeExports.jsx(Text, { color: focusedField === 'workspace' ? colors.cyan : colors.gray, children: "Workspace" }) }), jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: focusedField === 'workspace' ? colors.cyan : colors.darkGray, paddingX: 1, width: 40, children: focusedField === 'workspace' ? (jsxRuntimeExports.jsx(TextInput, { value: workspace, onChange: setWorkspace, placeholder: "~/hatchway-workspace" })) : (jsxRuntimeExports.jsx(Text, { color: workspace ? colors.white : colors.dimGray, children: workspace || '~/hatchway-workspace' })) })] }), jsxRuntimeExports.jsxs(Box, { marginTop: 1, flexDirection: "row", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { width: LABEL_WIDTH, justifyContent: "flex-end", marginRight: 1, children: jsxRuntimeExports.jsx(Text, { color: focusedField === 'database' ? colors.cyan : colors.gray, children: "Database" }) }), jsxRuntimeExports.jsx(RadioGroup, { options: databaseOptions, selected: databaseType, onChange: handleDatabaseTypeChange, focused: focusedField === 'database' })] }), databaseType === 'custom' && (jsxRuntimeExports.jsxs(Box, { marginTop: 1, flexDirection: "row", alignItems: "center", children: [jsxRuntimeExports.jsx(Box, { width: LABEL_WIDTH, justifyContent: "flex-end", marginRight: 1, children: jsxRuntimeExports.jsx(Text, { color: focusedField === 'databaseUrl' ? colors.cyan : colors.gray, children: "URL" }) }), jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: focusedField === 'databaseUrl' ? colors.cyan : colors.darkGray, paddingX: 1, width: 50, children: focusedField === 'databaseUrl' ? (jsxRuntimeExports.jsx(TextInput, { value: databaseUrl, onChange: setDatabaseUrl, placeholder: "postgres://user:pass@host:5432/db" })) : (jsxRuntimeExports.jsx(Text, { color: databaseUrl ? colors.white : colors.dimGray, children: databaseUrl || 'postgres://user:pass@host:5432/db' })) })] }))] }), jsxRuntimeExports.jsx(Box, { marginTop: 3 }), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", alignItems: "center", children: [jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["Enter: ", isLastField ? 'Start' : 'Next field', " | Shift+Enter: Start now | Esc: Back"] }), databaseType === 'custom' && !databaseUrl.trim() && (jsxRuntimeExports.jsx(Box, { marginTop: 1, children: jsxRuntimeExports.jsxs(Text, { color: colors.warning, children: [symbols.hollowDot, " Database URL required for custom PostgreSQL"] }) }))] })] }));
}

/**
 * Main TUI App component with screen navigation
 */
function App({ initialState, onExit, onRunnerStart, onLocalStart, onInitStart, }) {
    const [state, setState] = reactExports.useState(initialState);
    // Screen navigation handlers
    const navigateTo = (screen) => {
        setState(prev => ({ ...prev, screen }));
    };
    // Navigate back to config form with error
    const navigateToConfigWithError = (error, lastBranch) => {
        setState(prev => ({
            ...prev,
            screen: { type: 'config-form', error, lastBranch }
        }));
    };
    // Mode Select handlers
    const handleModeSelect = (mode) => {
        if (mode === 'local') {
            navigateTo({ type: 'local-mode' });
        }
        else {
            navigateTo({ type: 'runner-mode' });
        }
    };
    // Local Mode handlers
    const handleLocalAction = (action) => {
        if (action === 'init') {
            navigateTo({ type: 'config-form' });
        }
        else if (action === 'start') {
            onLocalStart();
        }
    };
    // Runner Mode handler
    const handleRunnerStart = (config) => {
        onRunnerStart(config);
    };
    // Config Form handler - validates branch before proceeding
    const handleConfigSubmit = async (config) => {
        // Skip validation if using main branch (always exists)
        if (config.branch === 'main') {
            onInitStart(config);
            return;
        }
        // Validate branch exists before proceeding
        const { execSync } = await import('child_process');
        try {
            execSync(`git ls-remote --exit-code --heads https://github.com/codyde/hatchway.git ${config.branch}`, { stdio: 'pipe' });
            // Branch exists, proceed with init
            onInitStart(config);
        }
        catch {
            // Branch doesn't exist, show error
            navigateToConfigWithError(`Branch "${config.branch}" not found`, config.branch);
        }
    };
    // Render current screen
    switch (state.screen.type) {
        case 'mode-select':
            return (jsxRuntimeExports.jsx(ModeSelectScreen, { onSelect: handleModeSelect, onEscape: onExit }));
        case 'local-mode':
            return (jsxRuntimeExports.jsx(LocalModeScreen, { isInitialized: state.isInitialized, onSelect: handleLocalAction, onEscape: () => navigateTo({ type: 'mode-select' }) }));
        case 'runner-mode':
            return (jsxRuntimeExports.jsx(RunnerModeScreen, { initialKey: state.existingKey, initialRunnerId: state.existingRunnerId, onStart: handleRunnerStart, onEscape: () => navigateTo({ type: 'mode-select' }) }));
        case 'config-form':
            return (jsxRuntimeExports.jsx(ConfigFormScreen, { initialConfig: {
                    branch: state.screen.lastBranch || 'main',
                    workspace: state.existingWorkspace || join(homedir(), 'hatchway-workspace'),
                    useNeon: true,
                }, onSubmit: handleConfigSubmit, onEscape: () => navigateTo({ type: 'local-mode' }), error: state.screen.error }));
        default:
            return null;
    }
}
/**
 * Get system username for default runner ID
 */
function getSystemUsername() {
    try {
        return userInfo().username;
    }
    catch {
        return process.env.USER || process.env.USERNAME || 'runner';
    }
}
/**
 * Run the main TUI menu
 */
async function mainTUICommand() {
    // Enable silent mode right before TUI renders
    // This prevents console output from bleeding into the TUI
    process.env.SILENT_MODE = '1';
    // Clear screen for fullscreen experience
    console.clear();
    const isInitialized = configManager.isInitialized();
    const existingKey = configManager.getSecret() || '';
    const config = configManager.get();
    // Use lastRunnerId if available, otherwise fall back to system username
    const existingRunnerId = config.runner?.lastRunnerId || getSystemUsername();
    const existingWorkspace = config.workspace || '';
    const initialState = {
        screen: { type: 'mode-select' },
        isInitialized,
        existingKey,
        existingRunnerId,
        existingWorkspace,
    };
    return new Promise((resolve, reject) => {
        let exitReason = null;
        let runnerConfig = null;
        let initConfig = null;
        const { unmount, waitUntilExit } = render(jsxRuntimeExports.jsx(App, { initialState: initialState, onExit: () => {
                exitReason = 'exit';
                unmount();
            }, onRunnerStart: (config) => {
                exitReason = 'runner-start';
                runnerConfig = config;
                unmount();
            }, onLocalStart: () => {
                exitReason = 'local-start';
                unmount();
            }, onInitStart: (config) => {
                exitReason = 'init-start';
                initConfig = config;
                unmount();
            } }), {
            exitOnCtrlC: true,
        });
        waitUntilExit().then(async () => {
            if (!exitReason) {
                // User pressed Ctrl+C
                console.clear();
                process.exit(0);
            }
            switch (exitReason) {
                case 'exit':
                    console.clear();
                    console.log('\n  Goodbye!\n');
                    process.exit(0);
                    break;
                case 'runner-start':
                    if (runnerConfig) {
                        console.clear();
                        await startRunner(runnerConfig);
                    }
                    break;
                case 'local-start':
                    console.clear();
                    await startLocalMode();
                    break;
                case 'init-start':
                    if (initConfig) {
                        // Clear screen with ANSI codes to ensure clean slate
                        process.stdout.write('\x1b[2J\x1b[H');
                        await runInitialization(initConfig);
                    }
                    break;
            }
            resolve();
        }).catch(reject);
    });
}
/**
 * Start runner mode (connects to remote server)
 */
async function startRunner(config) {
    // Save the key to config for future use
    if (config.key) {
        const serverConfig = configManager.get('server') || {};
        configManager.set('server', {
            ...serverConfig,
            secret: config.key,
        });
    }
    // Save the runner ID to config for future use
    if (config.runnerId) {
        const runnerConf = configManager.get('runner') || {};
        configManager.set('runner', {
            ...runnerConf,
            lastRunnerId: config.runnerId,
        });
    }
    console.log('\n  Starting Hatchway Runner...\n');
    console.log(`  Runner ID: ${config.runnerId}`);
    console.log('  Connecting to remote server...\n');
    const { runCommand } = await import('./run-Du6dvTJL.js');
    await runCommand({
        secret: config.key,
        runnerId: config.runnerId,
    });
}
/**
 * Start local mode (full stack)
 */
async function startLocalMode() {
    console.log('\n  Starting Hatchway in Local Mode...\n');
    const { startCommand } = await import('./start-Dkuro1jp.js');
    await startCommand({});
}
/**
 * Run initialization with form config
 */
async function runInitialization(config) {
    // Expand ~ in workspace path
    const workspace = config.workspace.startsWith('~')
        ? config.workspace.replace('~', homedir())
        : config.workspace;
    const { initTUICommand } = await import('./init-tui-m2supRiJ.js');
    // Build options based on form input
    const options = {
        workspace,
        branch: config.branch,
        yes: true,
    };
    // Handle database option
    if (config.useNeon) {
        options.database = true; // Use Neon auto-setup
    }
    else if (config.databaseUrl) {
        options.database = config.databaseUrl; // Custom connection string
    }
    await initTUICommand(options);
}

export { mainTUICommand };
//# sourceMappingURL=main-tui-DZ6e5DoU.js.map
