#!/usr/bin/env node
// Hatchway CLI - Built with Rollup
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import { Command } from 'commander';
import chalk from 'chalk';
import { C as CLIError } from '../chunks/cli-error-1drkrXNn.js';

/**
 * ASCII art banner for Hatchway CLI
 */
// ANSI color codes
const colors = {
    reset: '\x1b[0m',
    brightPurple: '\x1b[95m',
    cyan: '\x1b[36m'};
/**
 * Displays the Hatchway banner
 */
function displayBanner() {
    const banner = `
${colors.cyan}██╗  ██╗ █████╗ ████████╗ ██████╗██╗  ██╗${colors.brightPurple}██╗    ██╗ █████╗ ██╗   ██╗${colors.reset}
${colors.cyan}██║  ██║██╔══██╗╚══██╔══╝██╔════╝██║  ██║${colors.brightPurple}██║    ██║██╔══██╗╚██╗ ██╔╝${colors.reset}
${colors.cyan}███████║███████║   ██║   ██║     ███████║${colors.brightPurple}██║ █╗ ██║███████║ ╚████╔╝ ${colors.reset}
${colors.cyan}██╔══██║██╔══██║   ██║   ██║     ██╔══██║${colors.brightPurple}██║███╗██║██╔══██║  ╚██╔╝  ${colors.reset}
${colors.cyan}██║  ██║██║  ██║   ██║   ╚██████╗██║  ██║${colors.brightPurple}╚███╔███╔╝██║  ██║   ██║   ${colors.reset}
${colors.cyan}╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝    ╚═════╝╚═╝  ╚═╝${colors.brightPurple} ╚══╝╚══╝ ╚═╝  ╚═╝   ╚═╝   ${colors.reset}
`;
    console.log(banner);
}

/**
 * Centralized error handling and formatting for CLI
 * Displays errors with context, suggestions, and proper formatting
 */
class CLIErrorHandler {
    constructor(options = {}) {
        this.debug = options.debug ?? process.env.DEBUG === '1';
        this.exitOnError = options.exitOnError ?? true;
    }
    /**
     * Main error handling method
     * Formats and displays error, then exits if fatal
     */
    handle(error) {
        // Convert to CLIError if needed
        const cliError = error instanceof CLIError
            ? error
            : this.convertToCLIError(error);
        // Display the error
        this.display(cliError);
        // Exit if fatal and exitOnError is enabled
        if (cliError.fatal && this.exitOnError) {
            process.exit(cliError.getExitCode());
        }
    }
    /**
     * Display formatted error to console
     */
    display(error) {
        console.error(); // Empty line for spacing
        // Error header
        console.error(chalk.red('✗'), chalk.bold(error.message));
        // Error code (in debug mode)
        if (this.debug) {
            console.error(chalk.dim(`  Code: ${error.code}`));
        }
        // Context information
        if (error.context && Object.keys(error.context).length > 0) {
            console.error();
            console.error(chalk.dim('  Details:'));
            Object.entries(error.context).forEach(([key, value]) => {
                console.error(chalk.dim(`    ${key}: ${this.formatValue(value)}`));
            });
        }
        // Suggestions
        if (error.suggestions.length > 0) {
            console.error();
            console.error(chalk.yellow('  Try this:'));
            error.suggestions.forEach((suggestion, index) => {
                console.error(chalk.yellow(`    ${index + 1}. ${suggestion}`));
            });
        }
        // Documentation link
        if (error.docs) {
            console.error();
            console.error(chalk.dim('  Documentation:'), chalk.cyan(error.docs));
        }
        // Stack trace (debug mode only)
        if (this.debug && error.stack) {
            console.error();
            console.error(chalk.dim('  Stack trace:'));
            console.error(chalk.dim(error.stack));
        }
        // Original error cause (if available)
        if (this.debug && error.cause) {
            console.error();
            console.error(chalk.dim('  Caused by:'));
            console.error(chalk.dim(error.cause.message));
            if (error.cause.stack) {
                console.error(chalk.dim(error.cause.stack));
            }
        }
        console.error(); // Empty line for spacing
    }
    /**
     * Format context values for display
     */
    formatValue(value) {
        if (typeof value === 'string')
            return value;
        if (typeof value === 'number')
            return String(value);
        if (typeof value === 'boolean')
            return String(value);
        if (value === null || value === undefined)
            return 'null';
        if (Array.isArray(value))
            return value.join(', ');
        return JSON.stringify(value);
    }
    /**
     * Convert generic errors to CLIError
     */
    convertToCLIError(error) {
        // Try to infer error code from message
        const code = this.inferErrorCode(error);
        return new CLIError({
            code,
            message: error.message || 'An unexpected error occurred',
            cause: error,
            suggestions: ['Run with --debug flag for more details'],
        });
    }
    /**
     * Infer error code from generic error
     */
    inferErrorCode(error) {
        const message = error.message.toLowerCase();
        if (message.includes('econnrefused') || message.includes('connection refused')) {
            return 'DB_CONNECTION_FAILED';
        }
        if (message.includes('eaddrinuse') || message.includes('address already in use')) {
            return 'PORT_IN_USE';
        }
        if (message.includes('eacces') || message.includes('permission denied')) {
            return 'PERMISSION_DENIED';
        }
        if (message.includes('enoent') || message.includes('no such file')) {
            return 'CONFIG_NOT_FOUND';
        }
        return 'UNKNOWN_ERROR';
    }
    /**
     * Wrap an async function with error handling
     * Usage: await errorHandler.wrap(() => someAsyncFunction(), 'Description')
     */
    async wrap(fn, context) {
        try {
            return await fn();
        }
        catch (error) {
            if (context && error instanceof CLIError) {
                // Create new CLIError with added context (can't modify readonly property)
                const enhancedError = new CLIError({
                    code: error.code,
                    message: error.message,
                    context: { ...error.context, operation: context },
                    suggestions: error.suggestions,
                    fatal: error.fatal,
                    cause: error.cause,
                    docs: error.docs,
                });
                this.handle(enhancedError);
                throw enhancedError;
            }
            this.handle(error);
            throw error; // Re-throw for callers that want to handle it
        }
    }
    /**
     * Enable or disable debug mode
     */
    setDebug(debug) {
        this.debug = debug;
    }
    /**
     * Enable or disable exit on error
     */
    setExitOnError(exitOnError) {
        this.exitOnError = exitOnError;
    }
}
/**
 * Global error handler instance
 * Can be configured once and used throughout the CLI
 */
const globalErrorHandler = new CLIErrorHandler();
/**
 * Setup global error handlers for uncaught errors
 */
function setupGlobalErrorHandlers() {
    process.on('uncaughtException', (error) => {
        console.error(chalk.red('\n✗ Uncaught exception:'));
        globalErrorHandler.handle(error);
    });
    process.on('unhandledRejection', (reason) => {
        console.error(chalk.red('\n✗ Unhandled promise rejection:'));
        const error = reason instanceof Error ? reason : new Error(String(reason));
        globalErrorHandler.handle(error);
    });
}

/**
 * Graceful shutdown handler for CLI
 * Ensures proper cleanup when user presses Ctrl+C or process is terminated
 */
class ShutdownHandler {
    constructor(options = {}) {
        this.cleanupFunctions = [];
        this.childProcesses = [];
        this.isShuttingDown = false;
        this.timeout = options.timeout ?? 5000;
        this.verbose = options.verbose !== false;
    }
    /**
     * Register a cleanup function to run on shutdown
     */
    onShutdown(fn) {
        this.cleanupFunctions.push(fn);
    }
    /**
     * Register a child process to kill on shutdown
     */
    registerProcess(process) {
        this.childProcesses.push(process);
    }
    /**
     * Setup signal handlers for graceful shutdown
     */
    setup() {
        // Handle Ctrl+C (SIGINT)
        process.on('SIGINT', () => {
            if (this.verbose) {
                console.log(); // New line after ^C
                console.log(chalk.yellow('⚠'), 'Received interrupt signal (Ctrl+C)');
            }
            this.shutdown('SIGINT');
        });
        // Handle termination (SIGTERM)
        process.on('SIGTERM', () => {
            if (this.verbose) {
                console.log(chalk.yellow('⚠'), 'Received termination signal (SIGTERM)');
            }
            this.shutdown('SIGTERM');
        });
        // Handle process exit
        process.on('exit', (code) => {
            if (this.verbose && code !== 0) {
                console.log(chalk.red('✗'), `Process exiting with code ${code}`);
            }
        });
    }
    /**
     * Execute graceful shutdown
     */
    async shutdown(signal) {
        // Prevent multiple shutdown attempts
        if (this.isShuttingDown) {
            if (this.verbose) {
                console.log(chalk.dim('  Already shutting down...'));
            }
            return;
        }
        this.isShuttingDown = true;
        if (this.verbose) {
            console.log(chalk.cyan('ℹ'), 'Shutting down gracefully...');
        }
        // Set a timeout to force exit if cleanup takes too long
        const forceExitTimeout = setTimeout(() => {
            console.error(chalk.red('✗'), 'Shutdown timeout exceeded, forcing exit');
            process.exit(1);
        }, this.timeout);
        try {
            // 1. Kill child processes first
            if (this.childProcesses.length > 0) {
                if (this.verbose) {
                    console.log(chalk.dim('  Stopping child processes...'));
                }
                await this.killChildProcesses();
            }
            // 2. Run cleanup functions
            if (this.cleanupFunctions.length > 0) {
                if (this.verbose) {
                    console.log(chalk.dim('  Running cleanup tasks...'));
                }
                await this.runCleanup();
            }
            // 3. Success
            if (this.verbose) {
                console.log(chalk.green('✓'), 'Shutdown complete');
            }
            clearTimeout(forceExitTimeout);
            process.exit(0);
        }
        catch (error) {
            console.error(chalk.red('✗'), 'Error during shutdown:', error);
            clearTimeout(forceExitTimeout);
            process.exit(1);
        }
    }
    /**
     * Kill all registered child processes
     */
    async killChildProcesses() {
        const killPromises = this.childProcesses.map(async (child) => {
            if (!child.killed && child.pid) {
                try {
                    // Try graceful SIGTERM first
                    child.kill('SIGTERM');
                    // Wait up to 2 seconds for graceful shutdown
                    await new Promise((resolve) => {
                        const timeout = setTimeout(() => {
                            // Force kill if still alive
                            if (!child.killed) {
                                child.kill('SIGKILL');
                            }
                            resolve();
                        }, 2000);
                        child.on('exit', () => {
                            clearTimeout(timeout);
                            resolve();
                        });
                    });
                }
                catch (error) {
                    // Process might already be dead
                    if (this.verbose) {
                        console.log(chalk.dim(`  Could not kill process ${child.pid}`));
                    }
                }
            }
        });
        await Promise.all(killPromises);
    }
    /**
     * Run all cleanup functions
     */
    async runCleanup() {
        const cleanupPromises = this.cleanupFunctions.map(async (fn) => {
            try {
                await fn();
            }
            catch (error) {
                if (this.verbose) {
                    console.error(chalk.yellow('⚠'), 'Cleanup function failed:', error);
                }
            }
        });
        await Promise.all(cleanupPromises);
    }
    /**
     * Manually trigger shutdown (for testing or programmatic use)
     */
    async triggerShutdown() {
        await this.shutdown('MANUAL');
    }
    /**
     * Remove a child process from tracking (e.g., after it exits naturally)
     */
    unregisterProcess(process) {
        const index = this.childProcesses.indexOf(process);
        if (index > -1) {
            this.childProcesses.splice(index, 1);
        }
    }
}
/**
 * Global shutdown handler instance
 */
new ShutdownHandler();
/**
 * Helper to setup shutdown handler with common options
 */
function setupShutdownHandler(options) {
    const handler = new ShutdownHandler(options);
    handler.setup();
    return handler;
}

// NOTE: SILENT_MODE is now set AFTER TUI rendering starts, not here at the top.
// This allows pre-TUI messages (errors, OAuth prompts, etc.) to be visible.
// See start.ts, run.ts, and main-tui.tsx for where SILENT_MODE is set.
// IMPORTANT: Ensure vendor packages are extracted before any imports
// pnpm postinstall doesn't always run reliably for global installs from URLs
//
// NOTE: @hatchway/agent-core is bundled directly into dist/ by tsup,
// so we don't need to check for it. But vendor packages (Sentry, etc.) still
// need to be installed from the vendor/ tarballs.
const __filename$1 = fileURLToPath(import.meta.url);
const __dirname$1 = dirname(__filename$1);
// Find the package root by looking for package.json
// This works regardless of where the bundled code ends up (dist/, dist/cli/, etc.)
function findPackageRoot(startDir) {
    let dir = startDir;
    for (let i = 0; i < 5; i++) { // Max 5 levels up
        if (existsSync(join(dir, 'package.json'))) {
            return dir;
        }
        const parent = dirname(dir);
        if (parent === dir)
            break; // Reached filesystem root
        dir = parent;
    }
    return startDir; // Fallback to start dir
}
const packageRoot = findPackageRoot(__dirname$1);
// Check if running in development mode (linked via pnpm/npm link)
// Skip vendor install if we're in the monorepo - dependencies are handled by pnpm
const isLinkedDevelopment = packageRoot.includes('/hatchway/apps/runner');
// Only run vendor install for production global installs
if (!isLinkedDevelopment) {
    // Check if Sentry packages are missing and extract from vendor if needed
    // (agent-core is bundled by tsup, but Sentry packages come from vendor tarballs)
    const nodeModulesDir = dirname(packageRoot); // Go up from package to node_modules/@hatchway
    const sentryNodePath = join(nodeModulesDir, "..", "@sentry", "node");
    if (!existsSync(sentryNodePath)) {
        // Silently initialize vendor packages in background
        try {
            const installScript = join(packageRoot, "scripts/install-vendor.js");
            execFileSync("node", [installScript], {
                cwd: packageRoot,
                stdio: "pipe" // Silent mode - output only shown if VERBOSE=1
            });
        }
        catch (error) {
            console.error("Failed to initialize vendor packages:", error);
            process.exit(1);
        }
    }
}
// Get package.json for version info
const packageJson = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf-8'));
// Setup global error handlers for uncaught errors
setupGlobalErrorHandlers();
// Setup graceful shutdown handlers for Ctrl+C
const shutdownHandler = setupShutdownHandler({
    timeout: 5000,
    verbose: true,
});
// Check if we're running in TUI mode or version mode - skip banner if so
const args = process.argv.slice(2);
const isInitWithYes = args[0] === 'init' && (args.includes('-y') || args.includes('--yes') || args.includes('--non-interactive'));
const isNoArgs = args.length === 0 || (args.length === 1 && args[0] === '--debug');
const isRunCommand = args[0] === 'run'; // `hatchway run` uses TUI Dashboard
const isRunnerCommand = args[0] === 'runner' && !args.includes('--no-tui'); // `hatchway runner` uses TUI Dashboard (unless --no-tui)
const isVersionCommand = args.includes('--version') || args.includes('-V'); // Skip banner for version output
const isSkipBanner = process.env.HATCHWAY_SKIP_BANNER === '1'; // Skip banner after auto-update restart
const isTUIMode = isInitWithYes || isNoArgs || isRunCommand || isRunnerCommand;
const isSilentMode = isTUIMode || isVersionCommand || isSkipBanner;
// NOTE: SILENT_MODE is NOT set here anymore.
// It's set right before TUI rendering starts in the respective command files.
// This allows pre-TUI messages (errors, OAuth prompts, etc.) to be visible.
// Auto-update check - do this BEFORE displaying banner to avoid double banners
// All modes (TUI and CLI): full auto-update with restart
// For version mode: skip entirely - just show version
let willAutoUpdate = false;
if (!process.env.HATCHWAY_SKIP_UPDATE_CHECK && !isVersionCommand) {
    const { checkAndAutoUpdate } = await import('../chunks/auto-update-Ddo5Ntt7.js');
    try {
        // Show banner first for non-TUI modes
        if (!isSilentMode) {
            displayBanner();
        }
        // Full auto-update for all modes
        const didUpdate = await checkAndAutoUpdate(packageJson.version);
        if (didUpdate) {
            // CLI will be relaunched by auto-update, exit this process
            willAutoUpdate = true;
            process.exit(0);
        }
    }
    catch {
        // Auto-update failed silently, continue with current version
    }
}
else if (!isSilentMode) {
    // Update check skipped, show banner for CLI mode (but not version mode)
    displayBanner();
}
const program = new Command();
program
    .name('hatchway')
    .description('Hatchway CLI - AI App Builder')
    .version(packageJson.version)
    .option('--runner', 'Start runner only (connect to remote server)')
    .option('--debug', 'Enable debug mode with verbose error output')
    .hook('preAction', (thisCommand) => {
    // Enable debug mode if --debug flag is present
    const opts = thisCommand.opts();
    if (opts.debug) {
        globalErrorHandler.setDebug(true);
        process.env.DEBUG = '1';
    }
})
    .action(async (options) => {
    try {
        // Default action when no subcommand is provided
        if (options.runner) {
            // Start runner only (legacy flag)
            const { runCommand } = await import('../chunks/run-Du6dvTJL.js');
            await runCommand({});
        }
        else {
            // Show TUI main menu
            const { mainTUICommand } = await import('../chunks/main-tui-DZ6e5DoU.js');
            await mainTUICommand();
        }
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
// Import commands
program
    .command('init')
    .description('Initialize workspace and configuration for local development')
    .option('--workspace <path>', 'Set workspace directory')
    .option('--url <url>', 'Set server URL (default: http://localhost:3000)')
    .option('--secret <secret>', 'Set shared secret')
    .option('--branch <branch>', 'Git branch to clone (default: main)')
    .option('--database [value]', 'Database setup: connection string, or omit to auto-setup Neon in -y mode')
    .option('-y, --yes', 'Accept all defaults (non-interactive mode)')
    .option('--non-interactive', 'Use defaults without prompts (alias for -y)')
    .action(async (options) => {
    try {
        const { initCommand } = await import('../chunks/init-Ckj9MlWI.js');
        await initCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('run')
    .description('Start the full stack locally (web app + runner)')
    .option('-p, --port <port>', 'Web app port (default: 3000)')
    .option('--dev', 'Use development mode (hot reload, slower startup)')
    .option('--rebuild', 'Rebuild services before starting')
    .option('--no-local', 'Disable local mode (require authentication)')
    .option('--no-tui', 'Disable TUI dashboard, use plain text logs')
    .option('-v, --verbose', 'Enable verbose logging (show debug info)')
    .action(async (options) => {
    try {
        const { startCommand } = await import('../chunks/start-Dkuro1jp.js');
        await startCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('build')
    .description('Build all services without starting (useful while app is running)')
    .option('--watch', 'Watch for changes and rebuild automatically')
    .action(async (options) => {
    try {
        const { buildCommand } = await import('../chunks/build-V8_D-JHF.js');
        await buildCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('runner')
    .description('Start runner only (connect to Hatchway server)')
    .option('-u, --url <url>', 'Hatchway server URL (default: https://hatchway.sh)')
    .option('-w, --workspace <path>', 'Workspace directory (default: ~/hatchway-workspace)')
    .option('-i, --runner-id <id>', 'Runner identifier (default: system username)')
    .option('-s, --secret <secret>', 'Shared secret for authentication (required)')
    .option('-b, --broker <url>', 'WebSocket URL override (advanced, inferred from --url)')
    .option('-v, --verbose', 'Enable verbose logging')
    .option('-l, --local', 'Enable local mode (bypasses authentication)')
    .option('--no-tui', 'Disable TUI dashboard, use plain text logs')
    .action(async (options) => {
    try {
        const { runCommand } = await import('../chunks/run-Du6dvTJL.js');
        await runCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('config <action> [key] [value]')
    .description('Manage configuration (actions: get, set, list, path, validate, reset)')
    .action(async (action, key, value) => {
    try {
        const { configCommand } = await import('../chunks/config-hFJA7z5y.js');
        await configCommand(action, key, value);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
// Alias for config validate
program
    .command('verify')
    .description('Verify configuration is valid (alias for config validate)')
    .action(async () => {
    try {
        const { configCommand } = await import('../chunks/config-hFJA7z5y.js');
        await configCommand('validate');
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('status')
    .description('Show runner status and configuration')
    .action(async () => {
    try {
        const { statusCommand } = await import('../chunks/status-BU3cFJm1.js');
        await statusCommand();
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('cleanup')
    .description('Clean up projects and resources')
    .option('--project <slug>', 'Delete specific project')
    .option('--all', 'Clean all projects in workspace')
    .option('--tunnels', 'Close all active tunnels')
    .option('--processes', 'Kill all dev servers')
    .action(async (options) => {
    try {
        const { cleanupCommand } = await import('../chunks/cleanup-BNuJNSve.js');
        await cleanupCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('database')
    .alias('db')
    .description('Set up a new database and initialize schema')
    .action(async () => {
    try {
        const { databaseCommand } = await import('../chunks/database-CsfD1Nxf.js');
        await databaseCommand();
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('upgrade')
    .description('Upgrade to latest version (preserves configuration)')
    .option('--branch <branch>', 'Upgrade to specific branch (default: main)')
    .option('--force', 'Skip safety checks (uncommitted changes)')
    .action(async (options) => {
    try {
        const { upgradeCommand } = await import('../chunks/upgrade-BBpJirEu.js');
        await upgradeCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('login')
    .description('Authenticate with Hatchway via OAuth (GitHub/Sentry)')
    .option('-u, --url <url>', 'Hatchway server URL (default: https://hatchway.sh)')
    .option('-f, --force', 'Force re-authentication even if already logged in')
    .action(async (options) => {
    try {
        const { loginCommand } = await import('../chunks/login-CA1XWUEM.js');
        await loginCommand(options);
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program
    .command('logout')
    .description('Clear stored authentication credentials')
    .action(async () => {
    try {
        const { logoutCommand } = await import('../chunks/logout-BC4VFt8f.js');
        await logoutCommand();
    }
    catch (error) {
        globalErrorHandler.handle(error);
    }
});
program.parse();

export { shutdownHandler };
//# sourceMappingURL=index.js.map
