// Main CLI entry point. Side-effect import — just running this module
// boots the CLI and parses argv. The published binary lives at
// `bin/cli.js` (a hand-written shim) and re-imports this file from
// `dist/`. See bin/cli.js and packages/cli/AGENTS.md for the
// surrounding architecture.
import { ConsoleLogger, getAppBaseURL } from '@agentuity/server';
import { isStructuredError } from '@agentuity/core';
import { createCLI, registerCommands } from "./cli.js";
import { validateRuntime } from "./runtime.js";
import { loadConfig } from "./config.js";
import { discoverCommands } from "./cmd/index.js";
import { detectColorScheme } from "./terminal.js";
import { setColorScheme } from "./tui.js";
import { getVersion, getPackageName } from "./version.js";
import { generateCLISchema } from "./schema-generator.js";
import { generateAIHelp } from "./ai-help.js";
import { setOutputOptions } from "./output.js";
import { ensureBunOnPath } from "./bun-path.js";
import { maybeDelegateToLocal } from "./local-delegate.js";
import { checkForUpdates } from "./version-check.js";
import { closeDatabase } from "./cache/index.js";
import { ErrorCode, createError, formatErrorJSON } from "./errors.js";
import { createInternalLogger } from "./internal-logger.js";
import { createCompositeLogger } from "./composite-logger.js";
import { getAuth } from "./config.js";
import { getExecutingAgent } from "./agent-detection.js";
/**
 * Extract --dir flag from process.argv before command parsing
 * Handles both --dir=<path> and --dir <path> formats
 * Returns undefined if not present
 */
function getProjectDirFromArgs() {
    const args = process.argv;
    for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        if (arg === undefined)
            continue;
        // Handle --dir=<path>
        if (arg.startsWith('--dir=')) {
            return arg.slice(6);
        }
        // Handle --dir <path>
        if (arg === '--dir' && i + 1 < args.length) {
            const nextArg = args[i + 1];
            // Make sure next arg isn't another flag
            if (nextArg !== undefined && !nextArg.startsWith('-')) {
                return nextArg;
            }
        }
    }
    return undefined;
}
// Cleanup TTY state before exit
function cleanupTTY() {
    // Skip in CI - terminals don't support cursor control sequences
    if (process.env.CI) {
        return;
    }
    if (process.stdin.isTTY) {
        try {
            process.stdin.setRawMode(false);
        }
        catch {
            // Ignore errors if stdin is already closed
        }
        process.stdout.write('\x1B[?25h'); // Restore cursor
    }
}
// Handle Ctrl+C gracefully - only cleanup TTY, let command handlers run
// Commands like 'dev' register their own SIGINT handlers for cleanup
process.on('SIGINT', () => {
    process.stdout.write('\b \b'); // erase the ctrl+c display
    cleanupTTY();
    closeDatabase();
});
process.on('SIGTERM', () => {
    cleanupTTY();
    closeDatabase();
});
validateRuntime();
await ensureBunOnPath();
// If we're a global install running inside a project that pins its own
// @agentuity/cli at a different version, hand off to the project-local CLI
// and exit. This keeps v2 projects on v2 behavior even when the global
// binary has been upgraded to v3. Runs before any command parsing so the
// local CLI sees the original argv verbatim.
await maybeDelegateToLocal(process.argv.slice(2));
// Preprocess arguments to convert --help=json to --help json
// Commander.js doesn't support --option=value syntax for optional values
const preprocessedArgs = process.argv.slice(2).flatMap((arg) => {
    if (arg === '--help=json') {
        return ['--help', 'json'];
    }
    return arg;
});
// Preserve the original process.argv[0] (runtime) and process.argv[1] (script path)
// This is important for Bun, Node, and bundled executables.
// argv[0] and argv[1] are guaranteed-present in any Node/Bun process,
// so the non-null assertions are safe.
process.argv = [process.argv[0], process.argv[1], ...preprocessedArgs];
const helpFlags = ['--help', '-h', 'help'];
const hasHelp = helpFlags.some((flag) => preprocessedArgs.includes(flag));
// Check for --help=json early (needs to exit before full initialization)
// After preprocessing, --help=json becomes ['--help', 'json'] (length 2)
if (preprocessedArgs.length === 2 &&
    preprocessedArgs[0] === '--help' &&
    preprocessedArgs[1] === 'json') {
    const version = getVersion();
    const program = await createCLI(version);
    const commands = await discoverCommands();
    const cliSchema = generateCLISchema(program, commands, version);
    console.log(JSON.stringify(cliSchema, null, 2));
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const exit = globalThis.AGENTUITY_PROCESS_EXIT || process.exit;
    closeDatabase();
    exit(0);
}
// Check for --ai-help early (dashdash format for AI agents)
// This runs before auth/validation per dashdash spec "eager" requirement
if (preprocessedArgs.includes('--ai-help')) {
    const version = getVersion();
    const program = await createCLI(version);
    const commands = await discoverCommands();
    const cliSchema = generateCLISchema(program, commands, version);
    const aiHelp = generateAIHelp(cliSchema);
    console.log(aiHelp);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const exit = globalThis.AGENTUITY_PROCESS_EXIT || process.exit;
    closeDatabase();
    exit(0);
}
const version = getVersion();
const program = await createCLI(version);
// Parse options early to check for color scheme override and extract operands
// Skip parseOptions if we have help flags to avoid "unknown option" error
// parseOptions returns { operands, unknown } where operands are positional args (command names)
// Pass only user args (slice(2) to skip node path and script path) per Commander v14 expectations
let parsedOperands = [];
if (!hasHelp) {
    const parsed = program.parseOptions(process.argv.slice(2));
    parsedOperands = parsed.operands;
}
const earlyOpts = program.opts();
// Normalize --org alias → orgId (agents prefer --org over --org-id)
if (earlyOpts.org !== undefined && earlyOpts.orgId === undefined) {
    earlyOpts.orgId = earlyOpts.org;
}
// Set JSON error format early, BEFORE any code that might throw,
// so that startup failures also use the JSON error formatter.
// Note: --error-format defaults to 'text', so we must check !== 'json'
// rather than !earlyOpts.errorFormat (which would always be false).
if (earlyOpts.json && earlyOpts.errorFormat !== 'json') {
    earlyOpts.errorFormat = 'json';
}
setOutputOptions(earlyOpts);
// Variables that may or may not be initialized by the time an error occurs.
// Hoisted here so the centralized error handler can reference them.
let config;
let logger;
/**
 * Main startup and command execution.
 *
 * Wrapped in a function so that the entire boot sequence (config loading,
 * command discovery, update checks, command registration, and execution)
 * is enclosed in a single try/catch whose error handler respects --json.
 */
async function main() {
    // Detect or override terminal color scheme
    let colorScheme = await detectColorScheme();
    if (earlyOpts.colorScheme === 'light' || earlyOpts.colorScheme === 'dark') {
        colorScheme = earlyOpts.colorScheme;
        if (process.env.DEBUG_COLORS) {
            console.log(`[DEBUG] Using --color-scheme=${colorScheme} flag`);
        }
    }
    setColorScheme(colorScheme);
    // Debug: show detected color scheme
    if (process.env.DEBUG_COLORS) {
        console.log(`[DEBUG] Color scheme: ${colorScheme}`);
    }
    // Create logger instance with global options
    // In quiet or JSON mode, suppress most logging
    const effectiveLogLevel = earlyOpts.quiet || earlyOpts.json ? 'error' : earlyOpts.logLevel || 'info';
    const consoleLogger = new ConsoleLogger(effectiveLogLevel, earlyOpts.logTimestamp || false, colorScheme);
    consoleLogger.setShowPrefix(earlyOpts.logPrefix !== false);
    // Use the parsed operands from Commander.js parseOptions() which correctly
    // separates command names from option values. parsedOperands contains only
    // positional arguments (command/subcommand names), not flag values.
    // For help mode, parsedOperands is empty so we fall back to extracting from preprocessedArgs.
    const commandArgs = hasHelp
        ? preprocessedArgs.filter((arg) => !arg.startsWith('-'))
        : parsedOperands;
    // Check if we should skip internal logging based on command or help flags
    // We need to check the commands first to see if skipInternalLogging is set
    const earlyCommandName = commandArgs[0];
    const earlySubcommandName = commandArgs[1];
    const commands = await discoverCommands();
    const earlyCommandDef = commands.find((cmd) => cmd.name === earlyCommandName);
    const earlySubcommandDef = earlySubcommandName
        ? earlyCommandDef?.subcommands?.find((sub) => sub.name === earlySubcommandName)
        : undefined;
    // Skip internal logging if:
    // 1. Help flag is present
    // 2. No command provided (shows help)
    // 3. Command has skipInternalLogging set
    // 4. Subcommand has skipInternalLogging set
    const shouldSkipInternalLogging = hasHelp ||
        commandArgs.length === 0 ||
        earlyCommandDef?.skipInternalLogging ||
        earlySubcommandDef?.skipInternalLogging;
    // Create internal logger for trace/debug logging (always at trace level)
    const internalLogger = createInternalLogger(version, getPackageName());
    // Disable or initialize the internal logger based on command flags
    if (shouldSkipInternalLogging) {
        internalLogger.disable();
    }
    else {
        const command = commandArgs.length > 0 ? commandArgs.join(' ') : 'help';
        // Extract --dir from argv if present (for project context in logs)
        const projectDirArg = getProjectDirFromArgs();
        // Filter out command/subcommand names from args by position, not by value.
        // The first N entries of preprocessedArgs that are not flags are the command tokens.
        // Skip the leading command tokens to get only the actual arguments.
        let commandTokensToSkip = commandArgs.length;
        const filteredArgs = [];
        for (const arg of preprocessedArgs) {
            if (commandTokensToSkip > 0 && !arg.startsWith('-') && commandArgs.includes(arg)) {
                commandTokensToSkip--;
            }
            else {
                filteredArgs.push(arg);
            }
        }
        internalLogger.init(command, filteredArgs, undefined, projectDirArg);
        // Set session ID in environment so forked child processes can share the same log file
        process.env.AGENTUITY_INTERNAL_SESSION_ID = internalLogger.getSessionId();
        // Set detected agent in session logs
        const detectedAgent = getExecutingAgent();
        if (detectedAgent) {
            internalLogger.setDetectedAgent(detectedAgent);
        }
    }
    // Create composite logger that writes to both console and internal log
    logger = createCompositeLogger(consoleLogger, internalLogger);
    // Set version check skip flag from CLI option
    if (earlyOpts.skipVersionCheck) {
        process.env.AGENTUITY_SKIP_VERSION_CHECK = '1';
    }
    config = await loadConfig(earlyOpts.config, false, earlyOpts.profile);
    // Update internal logger with userId if available from auth (keychain or config)
    try {
        const auth = await getAuth();
        if (auth?.userId) {
            internalLogger.setUserId(auth.userId);
        }
    }
    catch {
        // Ignore auth errors - user might not be logged in
    }
    const ctx = {
        config,
        logger,
        options: earlyOpts,
        getExecutingAgent,
    };
    // Check for updates before running commands (may upgrade and re-exec)
    // Find the command being run to check if it opts out of upgrade check
    // Use parsedOperands from Commander's parseOptions which correctly separates
    // command names from option values (e.g., "--file myfile" won't treat "myfile" as a command)
    // Also find the subcommand if present (e.g., "auth whoami" -> command="auth", subcommand="whoami")
    const commandName = parsedOperands[0];
    const subcommandName = parsedOperands[1];
    const commandDef = commands.find((cmd) => cmd.name === commandName);
    const subcommandDef = subcommandName
        ? commandDef?.subcommands?.find((sub) => sub.name === subcommandName)
        : undefined;
    await checkForUpdates(config, logger, earlyOpts, commandDef, subcommandDef, preprocessedArgs);
    // Generate and store CLI schema globally for the schema command
    const cliSchema = generateCLISchema(program, commands, version);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    global.__CLI_SCHEMA__ = cliSchema;
    await registerCommands(program, commands, ctx);
    await program.parseAsync(process.argv);
    // Ensure clean exit after successful command execution.
    // In TTY environments, process.stdin may keep the event loop alive
    // (e.g., after confirm() calls resume()/setRawMode()), and Bun does not
    // support process.stdin.unref(), so we must explicitly exit.
    closeDatabase();
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const exit = globalThis.AGENTUITY_PROCESS_EXIT || process.exit;
    // Preserve process.exitCode set by command handlers (e.g., sandbox run/exec
    // propagate the sandbox process's exit code). Default to 0 for success.
    exit(process.exitCode ?? 0);
}
try {
    await main();
}
catch (error) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const exit = globalThis.AGENTUITY_PROCESS_EXIT || process.exit;
    // Don't log error if it's from Ctrl+C, user cancellation, or signal termination
    if (error instanceof Error) {
        const msg = error.message.toLowerCase();
        if (msg.includes('sigint') ||
            msg.includes('sigterm') ||
            msg.includes('user force closed') ||
            msg.includes('cancelled') || // UK
            msg.includes('canceled') || // US
            msg === '') {
            closeDatabase();
            exit(0);
        }
        if ('name' in error && error.name === 'AbortError') {
            closeDatabase();
            exit(0);
        }
    }
    // Also exit cleanly if error is empty/undefined (user cancellation)
    if (!error) {
        closeDatabase();
        exit(0);
    }
    const errorWithMessage = error;
    // Handle payment required (402) errors with a friendly TUI message
    if (isStructuredError(error) &&
        ((errorWithMessage._tag === 'ServiceException' && errorWithMessage.statusCode === 402) ||
            errorWithMessage._tag === 'PaymentRequiredError')) {
        if (earlyOpts.errorFormat === 'json') {
            console.error(formatErrorJSON(createError(ErrorCode.PAYMENT_REQUIRED, 'Your organization is out of credit. Please visit your billing page to add credit.')));
        }
        else {
            const { errorBox, link, newline } = await import("../src/tui.js");
            const overrides = config?.overrides;
            const appBaseUrl = getAppBaseURL(undefined, overrides);
            const billingUrl = `${appBaseUrl}/billing`;
            newline();
            errorBox('Out of Credit', `Your organization is out of credit.\n\nPlease add more here:\n${link(billingUrl)}`, false // standalone box, not connected to a guide
            );
        }
        closeDatabase();
        exit(1);
    }
    if (earlyOpts.errorFormat === 'json') {
        let message = errorWithMessage?.message ?? String(error);
        // Classify the error: treat as API_ERROR if it's a structured error
        // OR if the error carries a numeric status/statusCode (e.g. ServiceException)
        const hasStatus = typeof error === 'object' &&
            error !== null &&
            (('status' in error && typeof error.status === 'number') ||
                ('statusCode' in error && typeof error.statusCode === 'number'));
        const code = isStructuredError(error) || hasStatus ? ErrorCode.API_ERROR : ErrorCode.INTERNAL_ERROR;
        const details = {};
        if (isStructuredError(error) && errorWithMessage._tag) {
            details.tag = errorWithMessage._tag;
        }
        if (typeof error === 'object' && error !== null) {
            // Support both error.status (APIErrorResponse) and error.statusCode (ServiceException)
            if ('status' in error && typeof error.status === 'number') {
                details.status = error.status;
            }
            else if ('statusCode' in error && typeof error.statusCode === 'number') {
                details.status = error.statusCode;
            }
            if ('sessionId' in error && error.sessionId) {
                details.sessionId = error.sessionId;
            }
        }
        // Try to parse embedded JSON in API error messages
        // API errors often have messages like: '{"success":false,"message":"not found: ..."}'
        try {
            const parsed = JSON.parse(message);
            if (parsed && typeof parsed === 'object' && typeof parsed.message === 'string') {
                message = parsed.message;
            }
        }
        catch {
            // Not JSON, use message as-is
        }
        console.error(formatErrorJSON(createError(code, message, Object.keys(details).length > 0 ? details : undefined)));
    }
    else if (isStructuredError(error)) {
        // Use the composite logger if available; fall back to console.error
        // if the error occurred before logger initialization completed
        if (logger) {
            logger.error(error);
        }
        else {
            console.error(String(error));
        }
    }
    else {
        const msg = errorWithMessage?.message ? errorWithMessage.message : String(error);
        if (logger) {
            logger.error('CLI error: %s %s', msg, error);
        }
        else {
            console.error(`CLI error: ${msg}`);
        }
    }
    closeDatabase();
    exit(1);
}
//# sourceMappingURL=main.js.map