/** Runtime CLI entrypoint that wires commands to the app, shell, and embedded-agent surfaces. */
import chalk from 'chalk';
import { existsSync } from 'fs';
import { supportsXhigh } from './ai/models.js';
import { SessionAutoApproveState } from './app/approvals/session-auto-approve.js';
import { AuthStorage, FileAuthStorageBackend } from './app/auth/auth-storage.js';
import { startProactiveRefresh } from './app/auth/refresh-timer.js';
import { buildConnectionEnv } from './app/connectors/secret-store.js';
import { default as memoryExtension } from './app/extensions/durable-memory/index.js';
import { LoopManager } from './app/loop/loop-manager.js';
import { isHostedMcpOAuthProviderId } from './app/mcp/hosted-oauth-provider.js';
import { createMcpManager } from './app/mcp/index.js';
import { isManagedMcpOAuthProvider } from './app/mcp/managed-oauth-providers.js';
import { McpOAuthStore } from './app/mcp/oauth-state-store.js';
import { actionAgent, askAgent, getMode, installationAgent, planAgent } from './app/modes/index.js';
import { isMogSdkAvailable } from './app/new-sheet/sdk-types.js';
import { attachShortcutSessionEventLogging, getTelemetryToggleLog, getXllStartupLog, HttpShortcutClientLogTransport, ShortcutClientLogger, shouldEnableShortcutTelemetry } from './app/observability/index.js';
import { registerRuntimeTool } from './app/permissions/tool-gates.js';
import { setDefaultEnginePrompts } from './app/prompts/shared-guidelines.js';
import { ModelRegistry } from './app/providers/model-registry.js';
import { resolveCliModel } from './app/providers/model-resolver.js';
import { registerShortcutProvider } from './app/providers/register-shortcut-provider.js';
import { DefaultResourceLoader } from './app/resources/resource-loader.js';
import { syncInstalledShortcutXLPlugins } from './app/resources/shortcutxl-plugin-sync.js';
import { listAllSessions, listSessions } from './app/session/session-catalog.js';
import { openSessionManager } from './app/session/session-factory.js';
import { SettingsManager } from './app/settings-manager.js';
import { default as skillProposalsExtension } from './app/skill-proposals/extension.js';
import { createBashTool } from './app/tools/bash.js';
import { createEditTool } from './app/tools/edit.js';
import { createExecuteCodeTool } from './app/tools/execute-code/index.js';
import { createExecuteToolTool } from './app/tools/execute-tool.js';
import { createFindTool } from './app/tools/find.js';
import { createGetToolInfoTool } from './app/tools/get-tool-info.js';
import { createGrepTool } from './app/tools/grep.js';
import { createLlmAnalysisTool } from './app/tools/llm-analysis.js';
import { createLoopTool, LOOP_TOOL_SUMMARY } from './app/tools/loop.js';
import { createLsTool } from './app/tools/ls.js';
import { createReadTool } from './app/tools/read.js';
import { createRefreshContextTool } from './app/tools/refresh-context.js';
import { createGetSessionSettingsTool, createUpdateSessionSettingsTool } from './app/tools/session-settings/index.js';
import { createActionSwitchModeTool, createInstallationSwitchModeTool, createPlanSwitchModeTool } from './app/tools/switch-mode.js';
import { createTakeScreenshotTool, defaultOperations as defaultScreenshotOperations } from './app/tools/take-screenshot.js';
import { createSendMessageTool, createTaskTool, TASK_NOTIFICATION_CUSTOM_TYPE } from './app/tools/task/index.js';
import { createWebCrawlTool } from './app/tools/web-crawl.js';
import { createWebSearchTool } from './app/tools/web-search.js';
import { createWriteTool } from './app/tools/write.js';
import { fetchWorkbookConnectionState } from './app/workbook-context/workbook-summary.js';
import { SCOPE_UNSET } from './app/workbook-scope.js';
import { parseArgs, printHelp } from './cli/args.js';
import { listModels } from './cli/list-models.js';
import { handleMcpCliCommand } from './cli/mcp-commands.js';
import { handlePackageCommand } from './cli/package-commands.js';
import { reportSettingsErrors } from './cli/report-settings-errors.js';
import { selectSession } from './cli/session-picker.js';
import { getAgentDir, getAlpineRootfsPath, getModelsPath, getWslDir, isInstalled, isNewSheetEnabled, isNewSheetForcedEnabled, isSourceCheckout, VERSION } from './config.js';
import { SHORTCUT_LLM_PROXY_URL } from './constants.js';
import { fetchCreditBalance } from './credits/shortcut-credits.js';
import { SHORTCUT_API_HEADERS } from './endpoints.js';
import { createLocalAgentSession } from './local/index.js';
import { handleConfigCommand, handleShortcutAuthCommand, handleXllStartupStatus, isTruthyEnvFlag, prepareInitialMessage, readPipedStdin, runInteractiveShell, validateAgentMode } from './main-helpers.js';
import { getCurrentMode, MODE, setCurrentMode } from './mode-names.js';
import { SHORTCUT_PROVIDER_ID } from './model-ids.js';
import { attachSessionUpload, BashApprovalComponent, createApprovalGate, createBatchingApprovalGate, createInteractiveSessionClient, createNetworkPrompt, createPrintModeSessionClient, createSandboxBashTool, createShortySlashCommand, ensureWslSandbox, ExcelApprovalComponent, exportFromFile, FileAccessApprovalComponent, getTraceHooks, hasCustomExtensionUI, initTheme, InteractiveMode, isDistroInstalled, isWsl2Available, KeybindingsManager, runPrintMode, SANDBOX_DISTRO, startCronStatusPolling, startDurableMemoryStatusUpdater, startLoopStatusPolling, startSkillProposalStatusUpdater, stopThemeWatcher, SwitchModeApprovalComponent, TaskSpawnApprovalComponent } from './shell/index.js';
import { resolveStartupXll } from './startup/startup-xll.js';
import { printTimings, time } from './startup/timings.js';
import { EDIT, FIND, GREP, LS, READ } from './tool-names.js';
// NOTE: The pure decision logic from lines 562-690 is also encoded in
// startup/runtime-routing.ts for contract-level testing. If you change the
// routing conditions below, update runtime-routing.ts to match.
import { createExecuteCodeApprovalObservability } from './app/local/tools/execute-code/observability.js';
import { RemoteControlBootstrap } from './remote-control/index.js';
import { runRpcMode } from './rpc/index.js';
import { createInteractiveCommandHandler, createInteractiveSkillDownloadHandler, createSkillSyncStatusHandler, createSkillUploadHandler } from './startup/interactive-commands.js';
import { runPreflight } from './startup/preflight/index.js';
import { pruneStaleFiles } from './startup/prune-sessions.js';
import { createSessionManager } from './startup/session-selection.js';
import { applyRuntimeApiKeyAndRefreshShortcutLoggerContext, applyShortcutUserInfoToLogger, refreshShortcutUserInfoLoggerContext, resolveShortcutUserInfo } from './startup/shortcut-user-logger-context.js';
import { runForegroundUpdate } from './startup/update-action.js';
import { runStartupUpdateFlow } from './startup/update-flow.js';
import { runWindowsPythonRuntimeUpgradeIfNeeded } from './startup/windows-python-upgrade.js';
import { createWorkbookPrePromptContext } from './startup/workbook-pre-prompt.js';
let sessionRef = null;
let mcpManagerRef = null;
let sheetPoolRef = null;
const loopManager = new LoopManager((loop) => {
    if (!sessionRef)
        return;
    const suffix = loop.tickCount >= loop.maxTicks ? ' (final tick — loop auto-stopped)' : '';
    const text = `[Loop "${loop.name}" — tick #${loop.tickCount}${suffix}]\n\n${loop.prompt}`;
    // Use prompt() which starts a new turn when idle, or queues as followUp when streaming.
    sessionRef.prompt(text, { streamingBehavior: 'followUp' }).catch(() => {
        // Silently ignore — session may be shutting down
    });
});
/** Resolve CLI args into a fully-configured session options bundle plus any thinking-level fallback. */
export async function buildSessionOptions(parsed, sessionManager, modelRegistry, mcpOAuthStore, _settingsManager, approvalState, remoteControl, clientLogger, sandboxActive, devMode, startupWarnings = [], startupInfos = [], getMogRuntimeEnabled = () => isNewSheetEnabled()) {
    const options = {};
    let cliThinkingFromModel = false;
    if (sessionManager) {
        options.sessionManager = sessionManager;
    }
    // Model from CLI
    // - supports --provider <name> --model <pattern>
    // - supports --model <provider>/<pattern>
    if (parsed.model) {
        const resolved = resolveCliModel({
            cliProvider: parsed.provider,
            cliModel: parsed.model,
            modelRegistry
        });
        if (resolved.warning) {
            console.warn(chalk.yellow(`Warning: ${resolved.warning}`));
        }
        if (resolved.error) {
            console.error(chalk.red(resolved.error));
            process.exit(1);
        }
        if (resolved.model) {
            options.model = resolved.model;
            // Allow "--model <pattern>:<thinking>" as a shorthand.
            // Explicit --thinking still takes precedence (applied later).
            if (!parsed.thinking && resolved.thinkingLevel) {
                options.thinkingLevel = resolved.thinkingLevel;
                cliThinkingFromModel = true;
            }
        }
    }
    // Thinking level from CLI
    if (parsed.thinking) {
        options.thinkingLevel = parsed.thinking;
    }
    // API key from CLI - set in authStorage
    // (handled by caller before createLocalAgentSession)
    const buildRuntimeTools = async (warnings) => {
        const availableTools = [];
        const autoResizeImages = _settingsManager.getImageAutoResize();
        const workspaceRoots = _settingsManager.getWorkspaceRoots();
        const fileAccessApproval = remoteControl.createApprovalGate(createApprovalGate(FileAccessApprovalComponent, approvalState));
        const bashApproval = remoteControl.createApprovalGate(createApprovalGate(BashApprovalComponent, approvalState));
        const taskApproval = remoteControl.createApprovalGate(createBatchingApprovalGate(TaskSpawnApprovalComponent));
        const switchModeApproval = remoteControl.createApprovalGate(createApprovalGate(SwitchModeApprovalComponent, approvalState));
        const runtimeToolDeps = {
            cwd: process.cwd(),
            workspaceRoots,
            settingsManager: _settingsManager,
            filesystemApproval: fileAccessApproval,
            shellApproval: bashApproval,
            taskApproval,
            observability: clientLogger
        };
        // Shut down old resources before creating new ones (avoids orphans on reload)
        if (sheetPoolRef) {
            await sheetPoolRef.closeAll();
            sheetPoolRef = null;
        }
        if (mcpManagerRef) {
            mcpManagerRef.shutdown();
            mcpManagerRef = null;
        }
        registerRuntimeTool(availableTools, createGrepTool(process.cwd()), runtimeToolDeps);
        registerRuntimeTool(availableTools, createFindTool(process.cwd()), runtimeToolDeps);
        registerRuntimeTool(availableTools, createLsTool(process.cwd()), runtimeToolDeps);
        registerRuntimeTool(availableTools, createReadTool(process.cwd(), { autoResizeImages }), runtimeToolDeps);
        registerRuntimeTool(availableTools, createEditTool(process.cwd()), runtimeToolDeps);
        registerRuntimeTool(availableTools, createWriteTool(process.cwd()), runtimeToolDeps);
        const getRegisteredRuntimeTool = (name) => {
            const tool = availableTools.find((candidate) => candidate.name === name);
            if (!tool)
                throw new Error(`Runtime tool '${name}' was not registered.`);
            return tool;
        };
        const executableTools = [
            {
                summary: LOOP_TOOL_SUMMARY,
                definition: createLoopTool(loopManager)
            },
            {
                summary: 'Inspect the current model, thinking level, and available session settings when the user asks',
                definition: createGetSessionSettingsTool(() => sessionRef)
            },
            {
                summary: 'Change the current session model and/or thinking level for subsequent turns only when the user asks',
                definition: createUpdateSessionSettingsTool(() => sessionRef)
            },
            {
                summary: 'Read a file with runtime filesystem permissions',
                definition: getRegisteredRuntimeTool(READ)
            },
            {
                summary: 'Edit an existing file with runtime filesystem permissions',
                definition: getRegisteredRuntimeTool(EDIT)
            },
            {
                summary: 'Search file contents with runtime filesystem permissions',
                definition: getRegisteredRuntimeTool(GREP)
            },
            {
                summary: 'Find files by name with runtime filesystem permissions',
                definition: getRegisteredRuntimeTool(FIND)
            },
            {
                summary: 'List directory contents with runtime filesystem permissions',
                definition: getRegisteredRuntimeTool(LS)
            }
        ];
        availableTools.push(createGetToolInfoTool(executableTools));
        availableTools.push(createExecuteToolTool(executableTools));
        availableTools.push(createRefreshContextTool(() => sessionRef));
        const mogConfigEnabled = await getMogRuntimeEnabled();
        const mogRequested = mogConfigEnabled && isMogSdkAvailable();
        let useNewSheet = false;
        const excelApproval = remoteControl.createApprovalGate(createApprovalGate(ExcelApprovalComponent, approvalState));
        let newSheetPool = null;
        if (mogConfigEnabled && !mogRequested) {
            warnings.push('Mog is enabled but @mog-sdk/node is not installed; using COM mode.');
        }
        if (mogRequested) {
            try {
                const { SheetEnginePool } = await import('./app/new-sheet/sheet-engine.js');
                const pool = await SheetEnginePool.init();
                newSheetPool = pool;
                sheetPoolRef = pool;
                registerRuntimeTool(availableTools, createExecuteCodeTool({ platform: 'mog', pool }), runtimeToolDeps);
                useNewSheet = true;
            }
            catch (error) {
                const reason = error instanceof Error ? error.message : String(error);
                warnings.push(`Mog failed to load — reverted to COM mode. (${reason})`);
            }
        }
        if (!useNewSheet) {
            registerRuntimeTool(availableTools, createExecuteCodeTool({
                platform: 'com',
                approval: excelApproval,
                observability: createExecuteCodeApprovalObservability(clientLogger)
            }), runtimeToolDeps);
            sheetPoolRef = null;
        }
        registerRuntimeTool(availableTools, createTaskTool({
            getRuntimeTools: () => availableTools,
            notifyParent: (notification, scopeId, displayText) => {
                if (sessionRef?.sessionFile === scopeId) {
                    void sessionRef.sendCustomMessage({
                        customType: TASK_NOTIFICATION_CUSTOM_TYPE,
                        content: notification,
                        display: true,
                        details: { displayText }
                    }, { deliverAs: 'followUp', triggerTurn: true });
                }
            }
        }), runtimeToolDeps);
        availableTools.push(createSendMessageTool());
        availableTools.push(createActionSwitchModeTool(() => sessionRef, approvalState, switchModeApproval));
        availableTools.push(createPlanSwitchModeTool(() => sessionRef, approvalState, switchModeApproval));
        availableTools.push(createInstallationSwitchModeTool(() => sessionRef, approvalState, switchModeApproval));
        availableTools.push(createLlmAnalysisTool());
        if (newSheetPool) {
            // TUI mode with headless engine: capture screenshots via @mog-sdk/node
            availableTools.push(createTakeScreenshotTool({
                async captureScreenshots(ranges) {
                    if (!newSheetPool?.activeWorkbookName) {
                        return ranges.map((r) => ({ sheet: r.sheet, cell_range: r.cell_range, data: null }));
                    }
                    const wb = newSheetPool.getWorkbook(newSheetPool.activeWorkbookName);
                    const results = [];
                    for (const range of ranges) {
                        try {
                            const pngBuffer = await wb.captureScreenshot(range.sheet, range.cell_range);
                            results.push({
                                sheet: range.sheet,
                                cell_range: range.cell_range,
                                data: Buffer.from(pngBuffer).toString('base64')
                            });
                        }
                        catch (err) {
                            const msg = err instanceof Error ? err.message : String(err);
                            console.error(`[take_screenshot] captureScreenshot failed for ${range.sheet}!${range.cell_range}: ${msg}`);
                            results.push({ sheet: range.sheet, cell_range: range.cell_range, data: null });
                        }
                    }
                    return results;
                },
                analyzeWithVisionLLM: defaultScreenshotOperations.analyzeWithVisionLLM
            }));
        }
        else {
            // CLI mode without headless engine: stub (returns error for screenshot capture)
            availableTools.push(createTakeScreenshotTool());
        }
        availableTools.push(createWebSearchTool());
        availableTools.push(createWebCrawlTool());
        const mcpManager = await createMcpManager({
            cwd: process.cwd(),
            globalConfigDir: getAgentDir(),
            authStorage: modelRegistry.authStorage,
            mcpOAuthStore
        });
        if (mcpManager) {
            // MCP manager returns a generic tool shape; cast through unknown until the MCP layer exports ToolDefinition
            availableTools.push(...mcpManager.tools);
            mcpManagerRef = mcpManager;
            if (mcpManager.statusLine) {
                startupInfos.push(mcpManager.statusLine);
            }
        }
        if (sandboxActive) {
            availableTools.push(createSandboxBashTool(process.cwd(), _settingsManager, async (domain, ctx) => {
                if (!hasCustomExtensionUI(ctx)) {
                    return 'once';
                }
                const shellUI = ctx.ui;
                return shellUI.custom((_tui, uiTheme, _kb, done) => createNetworkPrompt(domain, uiTheme, done));
            }));
        }
        else {
            const shellCommandPrefix = _settingsManager.getShellCommandPrefix();
            const bashTool = createBashTool(process.cwd(), {
                commandPrefix: shellCommandPrefix,
                shellPath: _settingsManager.getShellPath(),
                resolveConnectionEnv: buildConnectionEnv
            });
            registerRuntimeTool(availableTools, bashTool, runtimeToolDeps);
        }
        return availableTools;
    };
    const availableTools = await buildRuntimeTools(startupWarnings);
    options.tools = availableTools;
    options.toolNames = availableTools.map((tool) => tool.name);
    options.reloadTools = async () => {
        const warnings = [];
        const tools = await buildRuntimeTools(warnings);
        return { tools, warnings };
    };
    return { options, cliThinkingFromModel };
}
export function isMogRuntimeEnabledForUser(userInfo) {
    if (isNewSheetForcedEnabled())
        return true;
    if (userInfo?.teamId)
        return false;
    return isNewSheetEnabled();
}
export function isFastModeAllowedForUser(userInfo) {
    return userInfo !== null && !userInfo.teamId;
}
function getFastModeDisabledReasonForUser(userInfo) {
    if (!userInfo)
        return 'login';
    if (userInfo.teamId)
        return 'team';
    return undefined;
}
function resolvePromptEngine(userInfo) {
    return isMogRuntimeEnabledForUser(userInfo) && isMogSdkAvailable() ? 'mog' : 'com';
}
async function refreshSessionForMogPolicyChange(session) {
    if (!session)
        return;
    await session.reload();
    const currentAgent = getMode(getCurrentMode());
    if (currentAgent) {
        session.applyMode([...currentAgent.tools], currentAgent.systemPrompt, currentAgent.name);
    }
}
/** Composition root: dispatch CLI args to print, RPC, interactive, or cron daemon entry points. */
export async function main(args) {
    if (args.includes('--cloud')) {
        throw new Error('Cloud mode has been removed from this runtime.');
    }
    // Fast path for cron daemon — minimal startup, no TUI, no tools
    if (args[0] === 'cron-daemon') {
        const { runCronDaemon } = await import('./startup/cron-daemon-entry.js');
        return runCronDaemon();
    }
    // Uninstall: remove registry keys, PATH entry, XLL files, installed marker
    if (args[0] === 'uninstall') {
        const { runUninstall } = await import('./startup/uninstall.js');
        runUninstall();
        return;
    }
    const offlineMode = args.includes('--offline') || isTruthyEnvFlag(process.env.SHORTCUT_OFFLINE);
    if (offlineMode) {
        process.env.SHORTCUT_OFFLINE = '1';
    }
    // Prune stale temp dirs and old session files (best-effort)
    pruneStaleFiles();
    if (await handlePackageCommand(args)) {
        return;
    }
    if (await handleMcpCliCommand(args)) {
        return;
    }
    if (await handleShortcutAuthCommand(args)) {
        return;
    }
    if (await handleConfigCommand(args)) {
        return;
    }
    // First pass: parse args to get --extension paths
    const firstPass = parseArgs(args);
    const mode = firstPass.mode ?? 'text';
    // Early load extensions to discover their CLI flags
    const cwd = process.cwd();
    const agentDir = getAgentDir();
    const settingsManager = SettingsManager.create(cwd, agentDir);
    reportSettingsErrors(settingsManager, 'startup');
    const devMode = settingsManager.getDevMode();
    const clientLogger = new ShortcutClientLogger({
        transport: new HttpShortcutClientLogTransport()
    });
    clientLogger.setBuildInfo({ version: VERSION });
    const isTelemetryEnabled = () => shouldEnableShortcutTelemetry({
        telemetryEnabled: settingsManager.getTelemetry(),
        mode
    });
    clientLogger.setEnabled(isTelemetryEnabled());
    const authBackend = new FileAuthStorageBackend();
    const mcpOAuthStore = McpOAuthStore.fromStorage(authBackend, {
        settings: settingsManager.getMcpOAuthSettings(),
        isManagedMcpOAuthProvider
    });
    const authStorage = AuthStorage.fromStorage(authBackend, {
        externalCredentialStores: [mcpOAuthStore]
    });
    const modelRegistry = new ModelRegistry(authStorage, getModelsPath());
    // Register before user-context resolution so authStorage can resolve Shortcut OAuth tokens.
    // The session id is read lazily after sessionManager is assigned below.
    let sessionManager;
    registerShortcutProvider(modelRegistry, SHORTCUT_LLM_PROXY_URL, {
        authStorageForRecovery: authStorage,
        getExtraHeaders: () => {
            const headers = {};
            if (!isTelemetryEnabled()) {
                headers[SHORTCUT_API_HEADERS.noTelemetry] = '1';
            }
            const sessionId = sessionManager?.getSessionId();
            if (sessionId)
                headers['X-Session-Id'] = sessionId;
            return Object.keys(headers).length > 0 ? headers : undefined;
        }
    });
    let shortcutUserInfo = await refreshShortcutUserInfoLoggerContext(authStorage, clientLogger);
    const mogConfigured = isNewSheetEnabled();
    const mogRuntimeEnabled = isMogRuntimeEnabledForUser(shortcutUserInfo);
    const mogSdkAvailable = mogRuntimeEnabled && isMogSdkAvailable();
    setDefaultEnginePrompts(mogSdkAvailable ? 'mog' : 'com');
    const requiresLocalXllRuntime = process.platform === 'win32' && !mogSdkAvailable;
    // Pick the initial agent based on installation state
    const needsSetup = !isInstalled();
    const startupWarnings = [];
    const startupInfos = [];
    const workspaceRoots = settingsManager.getWorkspaceRoots();
    startupInfos.push({
        message: workspaceRoots.length > 0
            ? `Approved workspaces: ${workspaceRoots.join(', ')}`
            : 'Approved workspaces: none',
        tone: 'dim'
    });
    if (mogConfigured && !mogRuntimeEnabled) {
        startupWarnings.push('Mog is disabled for team accounts; using COM mode.');
    }
    if (requiresLocalXllRuntime) {
        const upgradeReady = await runWindowsPythonRuntimeUpgradeIfNeeded({
            canPrompt: !firstPass.print && firstPass.mode === undefined && process.stdin.isTTY
        });
        if (!upgradeReady) {
            process.exit(1);
        }
    }
    // Installed packages copy their xll/ files into ~/.shortcut/agent/xll/.
    // When running from this repo, local dev uses explicit copy steps instead.
    const xllStatus = requiresLocalXllRuntime
        ? resolveStartupXll({
            sourceCheckout: isSourceCheckout(),
            needsSetup
        })
        : { kind: 'ok', updated: 0 };
    const xllStartupLog = getXllStartupLog(requiresLocalXllRuntime, xllStatus);
    if (xllStartupLog) {
        if (xllStartupLog.event === 'xll_connected') {
            clientLogger.info(xllStartupLog);
        }
        else {
            clientLogger.error(xllStartupLog);
        }
    }
    handleXllStartupStatus(xllStatus, devMode, startupWarnings);
    // Deterministic pre-flight: auto-install Git Bash, pip packages, registry key,
    // launch Excel, and run smoke test. Regardless of outcome, the installation
    // agent always runs afterward to verify everything is set up correctly.
    let preflightResult;
    if (needsSetup) {
        preflightResult = await runPreflight();
    }
    // Determine if sandbox is active (used for tool description and wiring)
    // - 'enabled': use sandbox — warn if WSL2/bwrap not installed
    // - 'disabled' (default): Git Bash, no isolation
    const sandboxMode = settingsManager.getSandboxMode();
    let sandboxAvailable = isWsl2Available() && isDistroInstalled(SANDBOX_DISTRO);
    // Auto-update sandbox rootfs if the package version changed.
    if (sandboxMode === 'enabled' && sandboxAvailable) {
        const sandboxFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
        let sandboxFrame = 0;
        const needsDownload = !existsSync(getAlpineRootfsPath());
        const spinnerMsg = needsDownload ? 'Downloading sandbox (~80 MB)...' : 'Checking sandbox...';
        const sandboxSpinner = setInterval(() => {
            process.stderr.write(`\r${sandboxFrames[sandboxFrame++ % sandboxFrames.length]} ${spinnerMsg}`);
        }, 80);
        try {
            const result = await ensureWslSandbox({
                distro: SANDBOX_DISTRO,
                wslDir: getWslDir(),
                rootfsPath: getAlpineRootfsPath(),
                version: VERSION
            });
            clearInterval(sandboxSpinner);
            process.stderr.write('\r\x1b[K');
            if (!result.installed) {
                sandboxAvailable = false;
                startupWarnings.push(`Sandbox rootfs update failed: ${result.error}`);
            }
        }
        catch (err) {
            clearInterval(sandboxSpinner);
            process.stderr.write('\r\x1b[K');
            sandboxAvailable = false;
            startupWarnings.push(`Sandbox check failed: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    const sandboxActive = sandboxMode === 'enabled' && sandboxAvailable;
    if (sandboxMode === 'enabled' && !sandboxAvailable) {
        startupWarnings.push('Sandbox enabled but not installed. Run /sandbox to set it up.');
    }
    // Validate --agent-mode against known modes
    validateAgentMode(firstPass.agentMode);
    let initialAgent;
    if (needsSetup) {
        initialAgent = installationAgent(preflightResult?.log);
    }
    else if (firstPass.agentMode === MODE.PLAN) {
        initialAgent = planAgent();
    }
    else if (firstPass.agentMode === MODE.ASK) {
        initialAgent = askAgent();
    }
    else {
        initialAgent = actionAgent();
    }
    if (initialAgent) {
        setCurrentMode(initialAgent.name);
    }
    const resourceLoader = new DefaultResourceLoader({
        cwd,
        agentDir,
        settingsManager,
        systemPrompt: initialAgent?.systemPrompt,
        additionalExtensionPaths: firstPass.extensions,
        additionalSkillPaths: firstPass.skills,
        additionalPromptTemplatePaths: firstPass.promptTemplates,
        extensionFactories: [
            ...(settingsManager.getDurableMemoryEnabled() ? [memoryExtension] : []),
            skillProposalsExtension
        ],
        noExtensions: firstPass.noExtensions,
        noSkills: firstPass.noSkills,
        noPromptTemplates: firstPass.noPromptTemplates,
        // AGENTS.md / CLAUDE.md context files are dev-only (not useful for end users)
        agentsFilesOverride: devMode ? undefined : () => ({ agentsFiles: [] })
    });
    await resourceLoader.reload();
    time('resourceLoader.reload');
    const extensionsResult = resourceLoader.getExtensions();
    for (const { path, error } of extensionsResult.errors) {
        console.error(chalk.red(`Failed to load extension "${path}": ${error}`));
    }
    // No pending provider registrations to flush — registerProvider was removed
    // from the extension API. Providers are registered directly in main.ts.
    // Keep Shortcut OAuth tokens fresh while the TUI is idle.
    // Without this, the access token (7-min TTL on staging) expires silently
    // and the next LLM request fails with 401.
    startProactiveRefresh(authStorage, SHORTCUT_PROVIDER_ID);
    // Parse args (extension flags removed — use config files instead)
    const parsed = parseArgs(args);
    if (parsed.version) {
        console.log(VERSION);
        process.exit(0);
    }
    if (parsed.help) {
        printHelp();
        process.exit(0);
    }
    settingsManager.setDangerouslySkipRuntimePermissionsOverride(parsed.dangerouslySkipRuntimePermissions ? true : undefined);
    settingsManager.setDangerouslySkipSpreadsheetPermissionsOverride(parsed.dangerouslySkipSpreadsheetPermissions ? true : undefined);
    if (parsed.listModels !== undefined) {
        const searchPattern = typeof parsed.listModels === 'string' ? parsed.listModels : undefined;
        await listModels(modelRegistry, searchPattern);
        process.exit(0);
    }
    // Read piped stdin content (if any) - skip for RPC mode (uses stdin for JSON-RPC)
    // and skip when already in print mode (subprocess callers that close stdin on DEVNULL
    // would still work, but callers that inherit an open non-TTY stdin would hang forever).
    if (parsed.mode !== 'rpc' && !parsed.print) {
        const stdinContent = await readPipedStdin();
        if (stdinContent !== undefined) {
            // Force print mode since interactive mode requires a TTY for keyboard input
            parsed.print = true;
            // Prepend stdin content to messages
            parsed.messages.unshift(stdinContent);
        }
    }
    if (parsed.export) {
        let result;
        try {
            const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
            result = await exportFromFile(parsed.export, outputPath);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : 'Failed to export session';
            console.error(chalk.red(`Error: ${message}`));
            process.exit(1);
        }
        console.log(`Exported to: ${result}`);
        process.exit(0);
    }
    if (parsed.mode === 'rpc' && parsed.fileArgs.length > 0) {
        console.error(chalk.red('Error: @file arguments are not supported in RPC mode'));
        process.exit(1);
    }
    const { initialMessage: _initMsg, initialImages } = await prepareInitialMessage(parsed.fileArgs, parsed.messages, settingsManager.getImageAutoResize());
    // Auto-start the installation flow so the user doesn't have to type anything
    const initialMessage = needsSetup && !_initMsg ? 'Start the installation process.' : _initMsg;
    const isInteractive = !parsed.print && parsed.mode === undefined;
    const approvalState = new SessionAutoApproveState(settingsManager);
    const skipVersionCheck = isTruthyEnvFlag(process.env.SHORTCUT_SKIP_VERSION_CHECK);
    const updatePromptOutcome = await runStartupUpdateFlow({
        isInteractive,
        needsSetup,
        offline: offlineMode,
        skipVersionCheck
    });
    if (updatePromptOutcome.kind === 'run-update') {
        console.log(chalk.green(`Installing ShortcutXL v${updatePromptOutcome.update.latestVersion}...`));
        const updateResult = await runForegroundUpdate({
            command: updatePromptOutcome.update.command
        });
        if (updateResult.kind === 'failed') {
            const exitDetail = updateResult.exitCode !== null
                ? `exit code ${updateResult.exitCode}`
                : updateResult.signal
                    ? `signal ${updateResult.signal}`
                    : 'unknown error';
            console.error(chalk.red(`Update failed (${exitDetail}). ShortcutXL will not launch.`));
            if (updateResult.message) {
                console.error(chalk.yellow(updateResult.message));
            }
            if (updateResult.logPath) {
                console.error(chalk.dim(`Details were written to ${updateResult.logPath}`));
            }
            process.exit(updateResult.exitCode ?? 1);
        }
        console.log(chalk.green(`Update to v${updatePromptOutcome.update.latestVersion} succeeded.`));
        console.log(chalk.green('Restart ShortcutXL to finish applying the update:'));
        console.log(chalk.bold('  shortcut'));
        process.exit(0);
    }
    if (!offlineMode && isInteractive && !needsSetup) {
        try {
            await syncInstalledShortcutXLPlugins();
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            startupWarnings.push(`ShortcutXL plugin sync failed: ${message}`);
        }
    }
    initTheme(settingsManager.getTheme(), isInteractive);
    // Create session manager based on CLI flags
    sessionManager = await createSessionManager(parsed, cwd);
    // Handle --resume: show session picker
    if (parsed.resume) {
        // Initialize keybindings so session picker respects user config
        KeybindingsManager.create();
        const selectedPath = await selectSession((onProgress) => listSessions(cwd, parsed.sessionDir, onProgress), listAllSessions);
        if (!selectedPath) {
            console.log(chalk.dim('No session selected'));
            stopThemeWatcher();
            process.exit(0);
        }
        sessionManager = openSessionManager(selectedPath);
    }
    const remoteControl = new RemoteControlBootstrap({
        settingsManager,
        autoApproveState: approvalState,
        agentDir: getAgentDir(),
        teamAccount: Boolean(shortcutUserInfo?.teamId),
        log: () => { }
    });
    const { options: sessionOptions, cliThinkingFromModel } = await buildSessionOptions(parsed, sessionManager, modelRegistry, mcpOAuthStore, settingsManager, approvalState, remoteControl, clientLogger, sandboxActive, devMode, startupWarnings, startupInfos, () => isMogRuntimeEnabledForUser(shortcutUserInfo));
    sessionOptions.settingsManager = settingsManager;
    sessionOptions.approvalState = approvalState;
    sessionOptions.authStorage = authStorage;
    sessionOptions.modelRegistry = modelRegistry;
    sessionOptions.resourceLoader = resourceLoader;
    sessionOptions.toolNames = initialAgent
        ? [...initialAgent.tools]
        : sessionOptions.tools?.map((tool) => tool.name);
    sessionOptions.modeName = initialAgent?.name;
    sessionOptions.observability = clientLogger;
    const workbookScopeRef = { current: { mode: SCOPE_UNSET } };
    // InteractiveMode owns workbook-scope mutations before each prompt. prePromptContext
    // only reads that state and can reuse the prefetched workbook state from the same send path.
    sessionOptions.prePromptContext = createWorkbookPrePromptContext(workbookScopeRef);
    sessionOptions.enableToolSummaries = isInteractive;
    // Handle CLI --api-key as runtime override (not persisted)
    if (parsed.apiKey) {
        if (!sessionOptions.model) {
            console.error(chalk.red('--api-key requires a model to be specified via --model or --provider/--model'));
            process.exit(1);
        }
        const refreshedShortcutUserInfo = await applyRuntimeApiKeyAndRefreshShortcutLoggerContext({
            authStorage,
            clientLogger,
            providerId: sessionOptions.model.provider,
            apiKey: parsed.apiKey
        });
        if (refreshedShortcutUserInfo !== undefined) {
            shortcutUserInfo = refreshedShortcutUserInfo;
        }
    }
    sessionOptions.fastModeAllowed = isFastModeAllowedForUser(shortcutUserInfo);
    sessionOptions.fastModeDisabledReason = getFastModeDisabledReasonForUser(shortcutUserInfo);
    const { session, modelFallbackMessage } = await createLocalAgentSession(sessionOptions);
    sessionRef = session;
    remoteControl.attachSession(session);
    const detachSessionEventLogging = attachShortcutSessionEventLogging(session, clientLogger);
    clientLogger.info({
        module: 'lifecycle',
        event: 'app_started',
        data: {
            surface: mode
        }
    });
    clientLogger.info({
        module: 'session',
        event: 'session_started',
        data: {
            surface: mode,
            has_existing_history: session.messages.length > 0
        }
    });
    clientLogger.info({
        module: 'lifecycle',
        event: 'app_ready',
        data: {
            surface: mode
        }
    });
    // Expose the sheet pool on the session so the collab layer can access workbooks.
    if (sheetPoolRef) {
        session.sheetPool = sheetPoolRef;
    }
    if (isInteractive) {
        const remoteControlStartup = await remoteControl.initializeInteractive();
        startupWarnings.push(...remoteControlStartup.startupWarnings);
        startupInfos.push(...remoteControlStartup.startupInfos);
    }
    // Write preflight diagnostics to the session JSONL so they end up in GCS traces.
    // Queryable by downloading the JSONL and finding the preflight_diagnostics custom entry.
    if (preflightResult?.diagnostics) {
        session.sessionManager.appendCustomEntry('preflight_diagnostics', preflightResult.diagnostics);
    }
    if (initialAgent) {
        // Installation mode always auto-approves edits so the setup flow is uninterrupted.
        if (initialAgent.name === MODE.INSTALLATION) {
            approvalState.setAutoApprove(true);
        }
    }
    // Trace hooks (always active — /trace command + HTML export after each query)
    const traceHooks = getTraceHooks(session, {
        getAccessToken: () => {
            const cred = authStorage.get('shortcut');
            return cred?.type === 'oauth' ? cred.access : undefined;
        }
    });
    // Upload session traces to Shortcut backend when telemetry is enabled.
    attachSessionUpload(session, {
        getAccessToken: () => {
            const cred = authStorage.get('shortcut');
            return cred?.type === 'oauth' ? cred.access : undefined;
        },
        isEnabled: isTelemetryEnabled,
        logger: clientLogger
    });
    if (!isInteractive && !session.model) {
        console.error(chalk.red('Login to Shortcut before running print mode:'));
        console.error('  shortcut login');
        process.exit(1);
    }
    // Clamp thinking level to model capabilities for CLI-provided thinking levels.
    // This covers both --thinking <level> and --model <pattern>:<thinking>.
    const cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;
    if (session.model && cliThinkingOverride) {
        let effectiveThinking = session.thinkingLevel;
        if (!session.model.reasoning) {
            effectiveThinking = 'off';
        }
        else if (effectiveThinking === 'xhigh' && !supportsXhigh(session.model)) {
            effectiveThinking = 'high';
        }
        if (effectiveThinking !== session.thinkingLevel) {
            session.setThinkingLevel(effectiveThinking);
        }
    }
    // In non-interactive modes, print startup messages to stderr since there's no TUI
    if (!isInteractive) {
        for (const info of startupInfos) {
            const infoMessage = typeof info === 'string' ? info : info.message;
            const infoTone = typeof info === 'string' ? 'dim' : (info.tone ?? 'dim');
            console.warn(infoTone === 'success' ? chalk.green(infoMessage) : chalk.dim(infoMessage));
        }
        for (const warning of startupWarnings) {
            console.warn(chalk.yellow(warning));
        }
    }
    // Clean up MCP server child processes on exit.
    // Use process.once to avoid listener accumulation if the session restarts.
    if (mcpManagerRef) {
        const cleanupMcp = () => mcpManagerRef?.shutdown();
        process.once('SIGINT', cleanupMcp);
        process.once('SIGTERM', cleanupMcp);
    }
    // Clean up loop timers on exit.
    const cleanupLoops = () => loopManager.stopAll();
    process.once('SIGINT', cleanupLoops);
    process.once('SIGTERM', cleanupLoops);
    const cleanupClientLogger = () => {
        detachSessionEventLogging();
        void clientLogger.destroy();
    };
    const cleanupRemoteControl = () => {
        void remoteControl.close();
    };
    process.once('SIGINT', cleanupClientLogger);
    process.once('SIGTERM', cleanupClientLogger);
    process.once('SIGINT', cleanupRemoteControl);
    process.once('SIGTERM', cleanupRemoteControl);
    if (mode === 'rpc') {
        await runRpcMode(session);
    }
    else if (isInteractive) {
        printTimings();
        const setRemoteControlTeamAccount = (teamAccount) => {
            void remoteControl.setTeamAccount(teamAccount).catch((error) => {
                clientLogger.warn({
                    module: 'auth',
                    event: 'remote_control_team_account_policy_update_failed',
                    data: {
                        team_account: teamAccount,
                        message: error instanceof Error ? error.message : String(error)
                    }
                });
            });
        };
        const mode = new InteractiveMode(createInteractiveSessionClient(session), {
            workbookScopeRef,
            workbookScopeDeps: {
                fetchState: fetchWorkbookConnectionState
            },
            initialMode: initialAgent?.name,
            sandboxActive,
            devMode,
            modelFallbackMessage,
            startupWarnings,
            startupInfos,
            initialMessage,
            initialImages,
            initialMessages: parsed.messages,
            verbose: parsed.verbose,
            fetchCreditBalance: async (apiKey) => {
                const model = session.model;
                if (!model || model.provider !== SHORTCUT_PROVIDER_ID)
                    return 'not_applicable';
                return fetchCreditBalance(apiKey);
            },
            // TODO: Re-enable when we support multiple providers
            oauthProviderOrder: [SHORTCUT_PROVIDER_ID],
            extraSlashCommands: [
                { name: 'loop', description: 'Start a recurring task (e.g. /loop 5m check staging)' },
                { name: 'mcp', description: 'View connected MCP servers, tools, and config' },
                { name: 'sandbox', description: 'Set up or toggle WSL2 sandbox for bash isolation' },
                { name: 'connect', description: 'Manage external service connections (databases, APIs)' },
                {
                    name: 'workspace',
                    description: 'List, add, remove, or open project workspace roots'
                },
                {
                    name: 'dev',
                    description: 'Toggle developer mode (AGENTS.md, dev resources, verbose logging)'
                },
                {
                    name: 'mog',
                    description: 'Toggle the Mog spreadsheet engine'
                },
                { name: 'action', description: 'Switch to action mode (default)' },
                { name: 'plan', description: 'Switch to plan mode (read-only planning)' },
                { name: 'ask', description: 'Switch to ask mode (read-only analysis)' },
                { name: 'manage', description: 'Switch to manage mode (delegation-only orchestration)' },
                { name: 'docs', description: 'Open documentation in browser' }
            ],
            slashCommands: [createShortySlashCommand(remoteControl.getPhoneManager())],
            onDevCommand: createInteractiveCommandHandler({
                getMcpManager: () => mcpManagerRef,
                authStorage,
                mcpOAuthStore,
                session,
                settingsManager,
                traceHooks
            }),
            onAgentEnd: traceHooks.onAgentEnd,
            onReady: ({ setStatus, setEditorBadge }) => {
                startCronStatusPolling(setStatus);
                startLoopStatusPolling(loopManager, setStatus, setEditorBadge);
                startDurableMemoryStatusUpdater(setStatus, () => { });
                startSkillProposalStatusUpdater(setStatus, () => { });
            },
            // Re-reads token at call time so /login mid-session is picked up.
            downloadSkillsWithApproval: createInteractiveSkillDownloadHandler(authStorage),
            uploadSkills: createSkillUploadHandler(authStorage),
            checkSkillsSyncStatus: createSkillSyncStatusHandler(authStorage),
            onTelemetryChanged: (enabled) => {
                if (enabled) {
                    clientLogger.setEnabled(true);
                    clientLogger.info(getTelemetryToggleLog(true));
                    return;
                }
                clientLogger.info(getTelemetryToggleLog(false));
                void clientLogger.disable();
            },
            onProviderLoginStarted: async (providerId) => {
                if (providerId !== SHORTCUT_PROVIDER_ID)
                    return;
                clientLogger.info({
                    module: 'auth',
                    event: 'login_started',
                    data: { provider_id: providerId }
                });
            },
            onProviderLoggedIn: async (providerId, userId) => {
                if (providerId !== SHORTCUT_PROVIDER_ID) {
                    if (isHostedMcpOAuthProviderId(providerId)) {
                        await sessionRef?.reload();
                    }
                    return;
                }
                const previousMogRuntimeEnabled = isMogRuntimeEnabledForUser(shortcutUserInfo);
                const userInfo = await resolveShortcutUserInfo(authStorage);
                shortcutUserInfo = userInfo;
                setDefaultEnginePrompts(resolvePromptEngine(shortcutUserInfo));
                const resolvedUserId = userInfo?.userId ?? userId;
                if (userInfo) {
                    applyShortcutUserInfoToLogger(clientLogger, userInfo);
                }
                else if (resolvedUserId) {
                    clientLogger.setUserId(resolvedUserId);
                    clientLogger.clearUserContext();
                }
                else {
                    applyShortcutUserInfoToLogger(clientLogger, null);
                }
                clientLogger.info({
                    module: 'auth',
                    event: 'login_succeeded',
                    data: {
                        provider_id: providerId,
                        has_user_id: resolvedUserId != null
                    }
                });
                sessionRef?.setFastModeAllowed(isFastModeAllowedForUser(userInfo), getFastModeDisabledReasonForUser(userInfo) ?? 'team');
                setRemoteControlTeamAccount(Boolean(userInfo?.teamId));
                if (isMogRuntimeEnabledForUser(shortcutUserInfo) !== previousMogRuntimeEnabled) {
                    await refreshSessionForMogPolicyChange(sessionRef);
                }
            },
            onProviderLoginFailed: async (providerId, message) => {
                if (providerId !== SHORTCUT_PROVIDER_ID)
                    return;
                clientLogger.warn({
                    module: 'auth',
                    event: 'login_failed',
                    data: {
                        provider_id: providerId,
                        message
                    }
                });
            },
            onProviderLoggedOut: async (providerId) => {
                if (providerId !== SHORTCUT_PROVIDER_ID) {
                    if (isHostedMcpOAuthProviderId(providerId)) {
                        mcpOAuthStore.clear(providerId);
                        await sessionRef?.reload();
                    }
                    return;
                }
                clientLogger.clearUserId();
                clientLogger.clearUserContext();
                clientLogger.info({
                    module: 'auth',
                    event: 'logout_succeeded',
                    data: { provider_id: providerId }
                });
                const previousMogRuntimeEnabled = isMogRuntimeEnabledForUser(shortcutUserInfo);
                shortcutUserInfo = null;
                setDefaultEnginePrompts(resolvePromptEngine(shortcutUserInfo));
                sessionRef?.setFastModeAllowed(false, 'login');
                setRemoteControlTeamAccount(false);
                if (isMogRuntimeEnabledForUser(shortcutUserInfo) !== previousMogRuntimeEnabled) {
                    await refreshSessionForMogPolicyChange(sessionRef);
                }
            }
        });
        await runInteractiveShell(parsed.watch, mode, remoteControl.getPhoneManager());
    }
    else {
        await runPrintMode(createPrintModeSessionClient(session), {
            mode,
            messages: parsed.messages,
            initialMessage,
            initialImages,
            fetchBalance: async () => {
                const model = session.model;
                if (!model || model.provider !== SHORTCUT_PROVIDER_ID)
                    return null;
                const apiKey = await session.modelRegistry.getApiKey(model);
                if (!apiKey)
                    return null;
                return fetchCreditBalance(apiKey);
            }
        });
        detachSessionEventLogging();
        await clientLogger.destroy();
        stopThemeWatcher();
        if (process.stdout.writableLength > 0) {
            await new Promise((resolve) => process.stdout.once('drain', resolve));
        }
        process.exit(0);
    }
}
//# sourceMappingURL=main.js.map