"use strict";
// src/cli/install/Phases.ts
// All install-wizard phases as Phase objects with stable ids.
//
// Phase taxonomy:
//   00-09  : welcome / detection / product selection (always)
//   10-19  : openlife-core block (when core is selected)
//   20-29  : openlife-agent block (when agent is selected)
//
// The runner walks phases in id order, skipping those whose `when` returns false.
// Each phase ends with `io.pause()` so the user controls progression with Enter.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ALL_PHASES = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const InstallBanner_1 = require("../InstallBanner");
const MatrixTheme_1 = require("../MatrixTheme");
const InstallFlow_1 = require("../InstallFlow");
const Multiselect_1 = require("./Multiselect");
const OAuthRunner_1 = require("./OAuthRunner");
const ProvidersCatalog_1 = require("./ProvidersCatalog");
const types_1 = require("./types");
const isFullMode = (ctx) => ctx.installMode === 'full' && !ctx.aborted;
// ════════════════════════════════════════════════════════════════════════
//  Section A · Welcome & Product Selection (always run)
// ════════════════════════════════════════════════════════════════════════
const phaseBanner = {
    id: '00-banner',
    label: 'Welcome',
    async run(_ctx, io) {
        process.stdout.write((0, InstallBanner_1.installationBanner)() + '\n\n');
        io.print('Welcome to the OpenLife installer.');
        io.hint('This wizard walks you through setup one step at a time.');
        io.hint('Press Enter to advance · Ctrl-C to abort any time.');
        io.print('');
        io.print('What you can install:');
        io.print('  • openlife-core  — local interactive CLI (framework profile)');
        io.print('  • openlife-agent — long-running daemon with Telegram gateway');
        io.print('');
        io.hint('Both can be installed together (core runs first, then agent).');
        await io.pause();
    },
};
const phaseInstallMode = {
    id: '00b-install-mode',
    label: 'Installation mode',
    async run(ctx, io) {
        io.print('');
        const idx = await io.choice('Installation mode:', [
            'Quick  — intelligent defaults, 2-3 essential questions (~2 min)',
            'Full   — guided wizard, configure everything (~10 min)',
        ], 0);
        ctx.installMode = idx === 0 ? 'quick' : 'full';
        io.print(`  ✓ mode: ${ctx.installMode}`);
        await io.pause();
    },
};
const phaseExistingInstall = {
    id: '01-existing-install',
    label: 'Pre-existing install check',
    async run(ctx, io) {
        const manifestPath = path.join(ctx.root, '.openlife', 'install-manifest.json');
        if (!fs.existsSync(manifestPath)) {
            io.print('No previous install detected — clean slate.');
            await io.pause();
            return;
        }
        io.warn(`Existing install detected at ${manifestPath}`);
        const idx = await io.choice('What do you want to do?', [
            'abort   — leave the existing install untouched (recommended)',
            'reinstall — overwrite everything',
            'repair  — re-run install over existing files (keeps state)',
        ], 0);
        if (idx === 0) {
            ctx.aborted = true;
            ctx.abortReason = 'pre_existing_install_abort';
            return;
        }
        ctx.preExisting = idx === 1 ? 'reinstall' : 'repair';
        io.print(`Selected: ${ctx.preExisting}`);
        await io.pause();
    },
};
const phaseProductSelection = {
    id: '02-product-select',
    label: 'Choose what to install',
    when: (ctx) => ctx.products.length === 0 && !ctx.aborted,
    async run(ctx, io) {
        io.print('Pick the OpenLife products you want to install on this machine.');
        io.hint('Select with SPACE, confirm with ENTER. Both = full setup.');
        io.print('');
        const products = await (0, Multiselect_1.multiselect)({
            title: 'Products',
            items: [
                {
                    value: 'openlife-core',
                    label: 'openlife-core',
                    description: 'Local CLI · multi-provider chat, agents, skills, workflows',
                    preselected: true,
                },
                {
                    value: 'openlife-agent',
                    label: 'openlife-agent',
                    description: 'Long-running daemon · Telegram gateway, governance, scheduler',
                    preselected: false,
                },
            ],
            minSelections: 1,
        });
        if (!products || products.length === 0) {
            ctx.aborted = true;
            ctx.abortReason = 'no_product_selected';
            return;
        }
        // Always install core BEFORE agent when both selected (sequential).
        ctx.products = products.sort((a, b) => (a === 'openlife-core' ? -1 : b === 'openlife-core' ? 1 : 0));
        io.print('');
        io.print(`Plan: ${ctx.products.join(' → ')}`);
        if (ctx.products.length > 1) {
            io.hint('Sequential install — openlife-core runs first, then openlife-agent.');
        }
        await io.pause();
    },
};
const phaseProjectMode = {
    id: '03-project-mode',
    label: 'Project mode (greenfield vs brownfield)',
    async run(ctx, io) {
        io.divider();
        io.print('Project mode selection');
        io.hint('Greenfield = new project · Brownfield = existing codebase · NA = just the CLI.');
        io.print('');
        // Heuristic suggestion based on cwd contents.
        const { detectMode, detectStack, recommendWorkflow } = await Promise.resolve().then(() => __importStar(require('../../orchestrator/ProjectMetadata')));
        const suggested = detectMode(ctx.root);
        const stack = detectStack(ctx.root);
        if (stack.length > 0) {
            io.hint(`Auto-detected stack: ${stack.join(', ')} → suggesting ${suggested}.`);
        }
        else {
            io.hint('No stack detected — suggesting greenfield (new project).');
        }
        io.print('');
        const options = [
            'greenfield      — new project from scratch',
            'brownfield      — existing codebase (will run discovery audit)',
            'not-applicable  — just install the CLI, skip method workflows',
        ];
        const defaultIdx = suggested === 'brownfield' ? 1 : suggested === 'not-applicable' ? 2 : 0;
        const idx = await io.choice('Project mode', options, defaultIdx);
        const mode = ['greenfield', 'brownfield', 'not-applicable'][idx];
        ctx.projectMode = mode;
        io.print(`  ✓ project mode: ${mode}`);
        if (mode !== 'not-applicable') {
            const recommended = recommendWorkflow(mode, stack);
            io.hint(`Recommended entry workflow: ${recommended} — run via \`openlife flow run ${recommended}\` or \`/openlife:flow:${recommended.replace('-development-cycle', '-cycle').replace('continuous-deployment', 'release').replace('epic-orchestration', 'epic')}\`.`);
        }
        // Persist to .openlife/project.json immediately — separate from .env so
        // the wizard's confirm phase doesn't need to know about it.
        const { buildMetadata, writeProjectMetadata } = await Promise.resolve().then(() => __importStar(require('../../orchestrator/ProjectMetadata')));
        const meta = buildMetadata(mode, ctx.root);
        writeProjectMetadata(meta, ctx.root);
        io.print(`  ✓ saved to .openlife/project.json`);
        await io.pause();
    },
};
// ════════════════════════════════════════════════════════════════════════
//  Section B · openlife-core block
// ════════════════════════════════════════════════════════════════════════
const isCore = (ctx) => ctx.products.includes('openlife-core') && !ctx.aborted;
const phaseCoreEnter = {
    id: '10-core-enter',
    label: 'openlife-core · enter',
    when: isCore,
    async run(ctx, io) {
        ctx.currentProduct = 'openlife-core';
        io.divider();
        io.print(`${(0, MatrixTheme_1.paint)('▣ openlife-core', MatrixTheme_1.MATRIX.head)}  — local interactive CLI`);
        io.hint('Steps: hosts → providers → keys → model chain → doctor → install.');
        await io.pause();
    },
};
const phaseCoreHosts = {
    id: '11-core-hosts',
    label: 'openlife-core · host CLIs',
    when: isCore,
    async run(ctx, io) {
        const detected = (0, InstallFlow_1.detectHostFromEnv)();
        if (detected)
            io.hint(`(auto-detected current host: ${detected})`);
        io.print('Pick the host CLI(s) you want OpenLife wired into.');
        io.hint('You can pick more than one — OpenLife installs into each.');
        io.print('');
        const hosts = await (0, Multiselect_1.multiselect)({
            title: 'Host CLIs',
            items: InstallFlow_1.VALID_HOSTS.map((h) => ({
                value: h,
                label: h,
                description: hostDescription(h),
                preselected: h === (detected || InstallFlow_1.DEFAULT_HOST),
            })),
            minSelections: 1,
        });
        if (!hosts || hosts.length === 0) {
            ctx.warnings.push('NO_HOST_SELECTED: defaulting to claude-code');
            ctx.coreHosts = [InstallFlow_1.DEFAULT_HOST];
        }
        else {
            ctx.coreHosts = hosts;
        }
        io.print(`Selected hosts: ${ctx.coreHosts.join(', ')}`);
        await io.pause();
    },
};
// Quick-mode defaults for core — runs instead of the detailed provider/model/doctor phases.
const phaseQuickCoreDefaults = {
    id: '11b-quick-core-defaults',
    label: 'openlife-core · quick defaults',
    when: (ctx) => isCore(ctx) && !isFullMode(ctx),
    async run(ctx, io) {
        ctx.coreProviders = ['anthropic'];
        ctx.coreSkipDoctor = true;
        io.print('  ✓ Provider: anthropic (default)');
        io.print('  ✓ Model chain: defaults');
        io.print('  ✓ Doctor: skipped (run `openlife system doctor` anytime)');
        await io.pause();
    },
};
const phaseCoreProviders = {
    id: '12-core-providers',
    label: 'openlife-core · providers',
    when: (ctx) => isCore(ctx) && isFullMode(ctx),
    async run(ctx, io) {
        io.print('Pick the LLM providers you want available in the CLI.');
        io.hint('SPACE to toggle, A = all, N = none, ENTER to confirm.');
        io.hint('You can add more later with `openlife auth <provider>`.');
        io.print('');
        const items = ProvidersCatalog_1.PROVIDERS_CATALOG.map((p) => ({
            value: p.id,
            label: p.label,
            description: p.description,
            group: p.tier,
            // Pre-select Tier 1 essentials by default.
            preselected: p.tier === 'tier-1',
        }));
        const providers = await (0, Multiselect_1.multiselect)({
            title: 'LLM Providers',
            items,
            groupOrder: ['tier-1', 'tier-2', 'tier-3', 'tier-4', 'tier-5'],
            groupLabels: ProvidersCatalog_1.TIER_LABELS,
            minSelections: 1,
        });
        if (!providers || providers.length === 0) {
            ctx.warnings.push('NO_PROVIDER_SELECTED: defaulting to openrouter');
            ctx.coreProviders = ['openrouter'];
        }
        else {
            ctx.coreProviders = providers;
        }
        io.print(`Selected ${ctx.coreProviders.length} provider(s): ${ctx.coreProviders.join(', ')}`);
        await io.pause();
    },
};
const phaseCoreApiKeys = {
    id: '13-core-api-keys',
    label: 'openlife-core · API keys',
    when: (ctx) => isCore(ctx) && ctx.coreProviders.some((p) => !!(0, ProvidersCatalog_1.getProvider)(p).envVar),
    async run(ctx, io) {
        io.print('Paste API keys for each provider (Enter to skip).');
        io.hint('Keys are written to .env only after the final confirmation.');
        io.print('');
        for (const id of ctx.coreProviders) {
            const provider = (0, ProvidersCatalog_1.getProvider)(id);
            if (!provider.envVar) {
                // Local / OAuth / router-only — no api key field.
                io.hint(`${provider.label}: no API key required (${provider.tags.join(', ')})`);
                continue;
            }
            const key = await io.secret(`${provider.envVar} (Enter to skip)`);
            if (key) {
                ctx.coreApiKeys[id] = key;
                io.print(`  ✓ ${provider.envVar} captured (will save on confirm)`);
            }
            else {
                io.hint(`  · ${provider.envVar} skipped`);
            }
        }
        io.print('');
        await io.pause();
    },
};
const phaseCoreModelChain = {
    id: '14-core-model-chain',
    label: 'openlife-core · model chain',
    when: (ctx) => isCore(ctx) && isFullMode(ctx),
    async run(ctx, io) {
        io.print('Optionally set a custom model chain (primary,fallback1,fallback2…).');
        io.hint('Format: provider/model · ex: gemini-api/gemini-3.1-flash,openai-api/gpt-5.5');
        io.hint('Enter blank to use the OpenLife defaults.');
        io.print('');
        const raw = await io.text('Model chain (blank = defaults)');
        if (raw) {
            const parts = raw.split(',').map((m) => m.trim()).filter(Boolean);
            if (parts.length === 0) {
                io.warn('No valid models parsed — using defaults');
            }
            else {
                ctx.coreModelChain = parts;
                io.print(`Model chain: ${parts.join(' → ')}`);
            }
        }
        else {
            io.hint('Using defaults: gemini-api/gemini-3.1-flash-lite-preview → openai-api/gpt-5.4-mini → ollama/qwen2.5-coder:7b');
        }
        await io.pause();
    },
};
const phaseCoreDoctor = {
    id: '15-core-doctor',
    label: 'openlife-core · post-install doctor',
    when: (ctx) => isCore(ctx) && isFullMode(ctx),
    async run(ctx, io) {
        io.print('After install, run `openlife system doctor` to verify the setup.');
        io.hint('Doctor checks API keys, model chain reachability, host paths, runtime state.');
        io.print('');
        const skip = await io.yesNo('Skip the post-install doctor run?', false);
        ctx.coreSkipDoctor = skip;
        if (skip) {
            ctx.warnings.push('DOCTOR_SKIPPED: run `openlife system doctor` manually to verify');
        }
        await io.pause();
    },
};
const phaseCoreConfirm = {
    id: '16-core-confirm',
    label: 'openlife-core · confirm & install',
    when: isCore,
    async run(ctx, io) {
        io.divider();
        io.header('Review openlife-core configuration');
        io.print(`Hosts            : ${ctx.coreHosts.join(', ')}`);
        io.print(`Providers (${ctx.coreProviders.length})    : ${ctx.coreProviders.join(', ')}`);
        const keyNames = Object.keys(ctx.coreApiKeys).filter((k) => ctx.coreApiKeys[k]);
        io.print(`API keys captured: ${keyNames.length ? keyNames.join(', ') : '(none)'}`);
        io.print(`Model chain      : ${ctx.coreModelChain ? ctx.coreModelChain.join(' → ') : '(defaults)'}`);
        io.print(`Skip doctor      : ${ctx.coreSkipDoctor ? 'yes' : 'no'}`);
        io.print('');
        const ok = await io.yesNo('Apply this configuration?', true);
        if (!ok) {
            ctx.aborted = true;
            ctx.abortReason = 'core_confirm_rejected';
            return;
        }
        // Persist API keys to .env (only now — never before confirm).
        if (keyNames.length > 0) {
            const { saveApiKeysToEnv } = await Promise.resolve().then(() => __importStar(require('../InstallModules')));
            const keyMap = mapApiKeysForLegacyHelper(ctx.coreApiKeys);
            saveApiKeysToEnv(ctx.root, keyMap);
            io.print(`✓ API keys persisted to ${path.join(ctx.root, '.env')}`);
        }
        // Run InstallFlow for the core profile across all chosen hosts.
        const { InstallFlow } = await Promise.resolve().then(() => __importStar(require('../InstallFlow')));
        const flow = new InstallFlow(ctx.root);
        for (const host of ctx.coreHosts) {
            io.print('');
            io.print(`▸ Installing into host: ${host}`);
            const result = flow.run({
                profile: 'framework',
                host,
                skipDoctor: ctx.coreSkipDoctor,
                modelOrder: ctx.coreModelChain,
            });
            for (const line of flow.renderSummary(result))
                io.print('  ' + line);
        }
        await io.pause();
    },
};
const phaseCoreComplete = {
    id: '17-core-complete',
    label: 'openlife-core · complete',
    when: isCore,
    async run(_ctx, io) {
        io.print('');
        renderCompletionCard(io, 'openlife-core installed', [
            'Run `openlife ask "hello"` to test the chat',
            'Run `openlife status` for a runtime snapshot',
            'Run `openlife --help` for the full command surface',
            'To undo: `openlife uninstall --host claude-code`',
        ]);
        await io.pause();
    },
};
// ════════════════════════════════════════════════════════════════════════
//  Section C · openlife-agent block
// ════════════════════════════════════════════════════════════════════════
const isAgent = (ctx) => ctx.products.includes('openlife-agent') && !ctx.aborted;
const phaseAgentEnter = {
    id: '20-agent-enter',
    label: 'openlife-agent · enter',
    when: isAgent,
    async run(ctx, io) {
        ctx.currentProduct = 'openlife-agent';
        io.divider();
        io.print(`${(0, MatrixTheme_1.paint)('▣ openlife-agent', MatrixTheme_1.MATRIX.head)}  — long-running daemon`);
        io.hint('Steps: auth-per-provider → Telegram → service mode → install.');
        io.print('');
        io.hint('Tip: agent profile lets you pick API key OR OAuth per provider.');
        io.warn('Anthropic OAuth is NOT permitted — Claude requires API key.');
        await io.pause();
    },
};
// Quick-mode defaults for agent — runs instead of detailed auth/telegram-mode/service-mode phases.
const phaseQuickAgentDefaults = {
    id: '20b-quick-agent-defaults',
    label: 'openlife-agent · quick defaults',
    when: (ctx) => isAgent(ctx) && !isFullMode(ctx),
    async run(ctx, io) {
        ctx.agentTelegramMode = 'auto';
        ctx.agentServiceMode = 'manual';
        io.print('  ✓ Auth strategy: api-key (default)');
        io.print('  ✓ Telegram mode: auto');
        io.print('  ✓ Service mode: manual (run `openlife agent start --daemon`)');
        await io.pause();
    },
};
const phaseAgentAuthStrategy = {
    id: '21-agent-auth-strategy',
    label: 'openlife-agent · auth strategy per provider',
    when: (ctx) => isAgent(ctx) && isFullMode(ctx),
    async run(ctx, io) {
        io.print('For the autonomous daemon, choose how each provider authenticates.');
        io.hint('OAuth uses your subscription (Plus/Pro). API key uses billed credits.');
        io.print('');
        // Reuse the provider list from the core block when available; otherwise
        // ask the user to pick agent-profile providers fresh.
        let providers = ctx.coreProviders.length > 0
            ? [...ctx.coreProviders]
            : [];
        if (providers.length === 0) {
            io.hint('No providers carried from openlife-core — picking fresh for agent.');
            const items = ProvidersCatalog_1.PROVIDERS_CATALOG.map((p) => ({
                value: p.id,
                label: p.label,
                description: p.description,
                group: p.tier,
                preselected: p.tier === 'tier-1' && (p.id === 'openai-api' || p.id === 'gemini-api' || p.id === 'anthropic'),
            }));
            const picked = await (0, Multiselect_1.multiselect)({
                title: 'Providers for the agent',
                items,
                groupOrder: ['tier-1', 'tier-2', 'tier-3', 'tier-4', 'tier-5'],
                groupLabels: ProvidersCatalog_1.TIER_LABELS,
                minSelections: 1,
            });
            providers = picked || [];
        }
        if (providers.length === 0) {
            ctx.warnings.push('AGENT_NO_PROVIDERS: agent will rely on whatever .env contains');
            await io.pause();
            return;
        }
        io.print(`Providers to configure: ${providers.length}`);
        io.print('');
        const decisions = [];
        for (const id of providers) {
            const provider = (0, ProvidersCatalog_1.getProvider)(id);
            const oauthAllowed = types_1.OAUTH_ENABLED_PROVIDERS.includes(id);
            const noApiKey = !provider.envVar;
            // Build available options dynamically.
            const opts = [];
            if (provider.envVar) {
                opts.push({ strategy: 'api-key', label: `API key — ${provider.envVar}` });
            }
            else {
                opts.push({
                    strategy: 'api-key',
                    label: 'API key',
                    disabled: true,
                    reason: 'this provider has no API-key option',
                });
            }
            if (oauthAllowed) {
                opts.push({ strategy: 'oauth', label: `OAuth via \`${provider.oauthCli} login\`` });
            }
            else if (id === 'anthropic') {
                opts.push({
                    strategy: 'oauth',
                    label: 'OAuth',
                    disabled: true,
                    reason: 'Anthropic OAuth is NOT permitted (policy)',
                });
            }
            else {
                opts.push({
                    strategy: 'oauth',
                    label: 'OAuth',
                    disabled: true,
                    reason: 'OAuth not available for this provider',
                });
            }
            opts.push({ strategy: 'skip', label: 'Skip — configure later' });
            io.print(`── ${provider.label} ──`);
            const enabledOpts = opts.filter(o => !o.disabled);
            const labels = opts.map((o) => o.disabled ? `${o.label}  (disabled: ${o.reason})` : o.label);
            const defaultIdx = opts.findIndex(o => !o.disabled);
            const idx = await io.choice('Auth strategy', labels, defaultIdx >= 0 ? defaultIdx : 0);
            const chosen = opts[idx];
            if (chosen.disabled) {
                io.warn(`Option "${chosen.label}" is disabled — defaulting to skip.`);
                decisions.push({ provider: id, strategy: 'skip' });
                continue;
            }
            decisions.push({ provider: id, strategy: chosen.strategy });
            io.print(`  → ${chosen.strategy}`);
        }
        ctx.agentAuthDecisions = decisions;
        io.print('');
        await io.pause();
    },
};
const phaseAgentOAuth = {
    id: '22-agent-oauth',
    label: 'openlife-agent · run OAuth flows',
    when: (ctx) => isAgent(ctx) && isFullMode(ctx) && ctx.agentAuthDecisions.some((d) => d.strategy === 'oauth'),
    async run(ctx, io) {
        const oauthDecisions = ctx.agentAuthDecisions.filter((d) => d.strategy === 'oauth');
        io.print(`Running OAuth for ${oauthDecisions.length} provider(s).`);
        io.hint('Each step launches the vendor CLI — complete the browser flow when prompted.');
        io.print('');
        for (const decision of oauthDecisions) {
            const provider = (0, ProvidersCatalog_1.getProvider)(decision.provider);
            io.print(`── OAuth: ${provider.label} ──`);
            const proceed = await io.yesNo(`Run \`${provider.oauthCli} login\` now?`, true);
            if (!proceed) {
                ctx.warnings.push(`OAUTH_DEFERRED_${decision.provider}: run \`${provider.oauthCli} login\` manually later`);
                io.hint('  · deferred — run later manually');
                continue;
            }
            const result = await (0, OAuthRunner_1.runOAuth)(decision.provider);
            ctx.agentOAuthResults.push(result);
            if (result.ok) {
                io.print(`  ✓ ${result.detail}`);
            }
            else {
                io.err(`  ${result.detail}`);
                ctx.warnings.push(`OAUTH_FAILED_${decision.provider}: ${result.detail}`);
            }
        }
        io.print('');
        await io.pause();
    },
};
const phaseAgentApiKeys = {
    id: '23-agent-api-keys',
    label: 'openlife-agent · API keys',
    when: (ctx) => isAgent(ctx) &&
        ctx.agentAuthDecisions.some((d) => d.strategy === 'api-key'),
    async run(ctx, io) {
        const apiKeyDecisions = ctx.agentAuthDecisions.filter((d) => d.strategy === 'api-key');
        io.print(`Collecting API keys for ${apiKeyDecisions.length} provider(s).`);
        io.hint('Paste the key when prompted (input masked). Enter to skip.');
        io.print('');
        for (const decision of apiKeyDecisions) {
            const provider = (0, ProvidersCatalog_1.getProvider)(decision.provider);
            if (!provider.envVar) {
                io.hint(`${provider.label}: no env var configured — skipping`);
                continue;
            }
            // Skip if openlife-core already collected this same key.
            if (ctx.coreApiKeys[decision.provider]) {
                io.print(`  ✓ ${provider.envVar} reused from openlife-core`);
                continue;
            }
            const key = await io.secret(`${provider.envVar} (Enter to skip)`);
            if (key) {
                ctx.agentApiKeys[decision.provider] = key;
                io.print(`  ✓ ${provider.envVar} captured`);
            }
            else {
                io.hint(`  · ${provider.envVar} skipped`);
                ctx.warnings.push(`AGENT_NO_KEY_${decision.provider}: agent will fall back to other providers`);
            }
        }
        io.print('');
        await io.pause();
    },
};
const phaseAgentTelegramToken = {
    id: '24-agent-telegram-token',
    label: 'openlife-agent · Telegram bot token',
    when: isAgent,
    async run(ctx, io) {
        io.print('Configure Telegram — the autonomous daemon\'s primary gateway.');
        io.hint('Get a token from @BotFather (https://t.me/BotFather → /newbot).');
        io.print('');
        const token = await io.secret('TELEGRAM_BOT_TOKEN (Enter to skip)');
        if (!token) {
            ctx.warnings.push('TELEGRAM_NO_TOKEN: agent will run without Telegram gateway until you add it');
            await io.pause();
            return;
        }
        // Validate format + getMe probe.
        const { validateTelegramToken } = await Promise.resolve().then(() => __importStar(require('../InstallModules')));
        const validation = validateTelegramToken(token);
        if (!validation.ok) {
            io.err(`Token validation failed: ${validation.detail}`);
            const proceed = await io.yesNo('Save it anyway?', false);
            if (!proceed) {
                ctx.warnings.push(`TELEGRAM_TOKEN_INVALID: ${validation.detail} — not saved`);
                await io.pause();
                return;
            }
        }
        else {
            io.print(`  ✓ ${validation.detail}`);
        }
        ctx.agentTelegramToken = token;
        await io.pause();
    },
};
const phaseAgentTelegramUserId = {
    id: '25-agent-telegram-user',
    label: 'openlife-agent · allowed Telegram user ID',
    when: (ctx) => isAgent(ctx) && !!ctx.agentTelegramToken,
    async run(ctx, io) {
        io.print('Configure who can message the bot.');
        io.hint('Single-user by design. Get your user ID from @userinfobot.');
        io.print('');
        const userId = await io.text('OPENLIFE_TELEGRAM_ALLOWED_USER_ID', '1344110010');
        ctx.agentTelegramUserId = userId;
        io.print(`  ✓ allowed user: ${userId}`);
        await io.pause();
    },
};
const phaseAgentTelegramMode = {
    id: '26-agent-telegram-mode',
    label: 'openlife-agent · Telegram delivery mode',
    when: (ctx) => isAgent(ctx) && isFullMode(ctx) && !!ctx.agentTelegramToken,
    async run(ctx, io) {
        io.print('Choose Telegram delivery mode.');
        io.hint('auto detects from env (PORT, RAILWAY_*) — picks polling locally, webhook in prod.');
        io.print('');
        const idx = await io.choice('Delivery mode', [
            'auto    — detect based on environment (recommended)',
            'polling — long-poll (good for local dev, NAT-friendly)',
            'webhook — push (requires public URL, good for Railway/Render)',
        ], 0);
        const mode = idx === 0 ? 'auto' : idx === 1 ? 'polling' : 'webhook';
        ctx.agentTelegramMode = mode;
        io.print(`  ✓ mode: ${mode}`);
        await io.pause();
    },
};
const phaseAgentServiceMode = {
    id: '27-agent-service-mode',
    label: 'openlife-agent · supervisor / service mode',
    when: (ctx) => isAgent(ctx) && isFullMode(ctx),
    async run(ctx, io) {
        io.print('How should the daemon be supervised?');
        io.hint('manual = no supervisor (you run `openlife agent start` yourself).');
        io.print('');
        const idx = await io.choice('Service mode', [
            'manual  — run by hand with `openlife agent start --daemon`',
            'nohup   — background process via nohup (Linux/macOS)',
            'systemd — register a systemd user unit (Linux)',
            'pm2     — managed by pm2 (multi-platform, requires `pm2` installed)',
        ], 0);
        const mode = idx === 0 ? 'manual' : idx === 1 ? 'nohup' : idx === 2 ? 'systemd' : 'pm2';
        ctx.agentServiceMode = mode;
        io.print(`  ✓ supervisor: ${mode}`);
        if (mode !== 'manual') {
            ctx.warnings.push(`SERVICE_MODE_${mode.toUpperCase()}: supervisor wiring is a hint — finalize manually as needed`);
        }
        await io.pause();
    },
};
const phaseAgentConfirm = {
    id: '28-agent-confirm',
    label: 'openlife-agent · confirm & install',
    when: isAgent,
    async run(ctx, io) {
        io.divider();
        io.header('Review openlife-agent configuration');
        const authSummary = ctx.agentAuthDecisions
            .map((d) => `${d.provider}=${d.strategy}`)
            .join(', ') || '(none)';
        const keyNames = Object.keys(ctx.agentApiKeys).filter((k) => ctx.agentApiKeys[k]);
        io.print(`Auth decisions   : ${authSummary}`);
        io.print(`OAuth flows ran  : ${ctx.agentOAuthResults.length} (${ctx.agentOAuthResults.filter((r) => r.ok).length} ok)`);
        io.print(`API keys captured: ${keyNames.length ? keyNames.join(', ') : '(none)'}`);
        io.print(`Telegram token   : ${ctx.agentTelegramToken ? '✓ (validated)' : '(none)'}`);
        io.print(`Allowed user     : ${ctx.agentTelegramUserId || '(default 1344110010)'}`);
        io.print(`Telegram mode    : ${ctx.agentTelegramMode || 'auto'}`);
        io.print(`Service mode     : ${ctx.agentServiceMode || 'manual'}`);
        io.print('');
        const ok = await io.yesNo('Apply this configuration?', true);
        if (!ok) {
            ctx.aborted = true;
            ctx.abortReason = 'agent_confirm_rejected';
            return;
        }
        // Persist API keys collected by the agent block.
        if (keyNames.length > 0) {
            const { saveApiKeysToEnv } = await Promise.resolve().then(() => __importStar(require('../InstallModules')));
            const keyMap = mapApiKeysForLegacyHelper(ctx.agentApiKeys);
            saveApiKeysToEnv(ctx.root, keyMap);
            io.print(`✓ API keys persisted to ${path.join(ctx.root, '.env')}`);
        }
        // Persist Telegram config + extras that don't fit the legacy helper.
        if (ctx.agentTelegramToken) {
            const { saveTelegramConfig } = await Promise.resolve().then(() => __importStar(require('../InstallModules')));
            saveTelegramConfig(ctx.root, ctx.agentTelegramToken, ctx.agentTelegramUserId);
            writeExtraEnv(ctx.root, {
                OPENLIFE_TELEGRAM_ALLOWED_USER_ID: ctx.agentTelegramUserId || '1344110010',
                OPENLIFE_TELEGRAM_MODE: ctx.agentTelegramMode || 'auto',
            });
            io.print('✓ Telegram config saved to .env');
        }
        // Run InstallFlow for autonomous profile across all chosen hosts.
        // Agent runs across the same hosts the core picked. If core wasn't run,
        // default to the auto-detected host.
        const hosts = ctx.coreHosts.length > 0
            ? ctx.coreHosts
            : [((0, InstallFlow_1.detectHostFromEnv)() || InstallFlow_1.DEFAULT_HOST)];
        const { InstallFlow } = await Promise.resolve().then(() => __importStar(require('../InstallFlow')));
        const flow = new InstallFlow(ctx.root);
        for (const host of hosts) {
            io.print('');
            io.print(`▸ Installing agent into host: ${host}`);
            const result = flow.run({
                profile: 'autonomous',
                host,
                skipDoctor: ctx.coreSkipDoctor, // honor the user's earlier choice
                modelOrder: ctx.coreModelChain,
            });
            for (const line of flow.renderSummary(result))
                io.print('  ' + line);
        }
        await io.pause();
    },
};
const phaseAgentVerify = {
    id: '29-agent-verify',
    label: 'openlife-agent · verify',
    when: isAgent,
    async run(ctx, io) {
        io.print('Verifying agent install…');
        const checks = [];
        const manifestPath = path.join(ctx.root, '.openlife', 'install-manifest.json');
        checks.push({
            name: 'install-manifest',
            ok: fs.existsSync(manifestPath),
            detail: manifestPath,
        });
        const envPath = path.join(ctx.root, '.env');
        const envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
        checks.push({
            name: 'telegram-token',
            ok: envContent.includes('TELEGRAM_BOT_TOKEN='),
            detail: 'TELEGRAM_BOT_TOKEN present in .env',
        });
        checks.push({
            name: 'allowed-user',
            ok: envContent.includes('OPENLIFE_TELEGRAM_ALLOWED_USER_ID='),
            detail: 'OPENLIFE_TELEGRAM_ALLOWED_USER_ID present in .env',
        });
        // Probe `openlife status` if the dist build is available.
        try {
            const out = (0, child_process_1.execFileSync)('node', ['bin/openlife.js', 'status', '--json'], {
                cwd: ctx.root,
                encoding: 'utf-8',
                timeout: 10_000,
            }).trim();
            checks.push({
                name: 'openlife-status',
                ok: out.length > 0 && out.startsWith('{'),
                detail: `status responded (${out.length} bytes)`,
            });
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            checks.push({
                name: 'openlife-status',
                ok: false,
                detail: `status probe failed: ${msg.slice(0, 120)}`,
            });
        }
        for (const c of checks) {
            if (c.ok)
                io.print(`  ✓ ${c.name}: ${c.detail}`);
            else
                io.err(`  ${c.name}: ${c.detail}`);
        }
        if (checks.some((c) => !c.ok)) {
            ctx.warnings.push('AGENT_VERIFY_PARTIAL: some checks failed — review .env and run `openlife system doctor`');
        }
        io.print('');
        await io.pause();
    },
};
const phaseAgentComplete = {
    id: '30-agent-complete',
    label: 'openlife-agent · complete',
    when: isAgent,
    async run(ctx, io) {
        io.print('');
        const next = [
            'Run `openlife agent start --daemon` to launch the daemon',
            'Run `openlife agent status` to confirm the heartbeat',
            'Send a message to your Telegram bot to test the gateway',
            'To undo: `openlife uninstall --host claude-code`',
        ];
        if (ctx.agentServiceMode === 'systemd') {
            next.push('See docs/install/openlife-agent.md for the systemd unit template');
        }
        else if (ctx.agentServiceMode === 'pm2') {
            next.push('Run `pm2 start "openlife agent start --daemon" --name openlife-agent`');
        }
        else if (ctx.agentServiceMode === 'nohup') {
            next.push('Run `nohup openlife agent start --daemon > openlife.log 2>&1 &`');
        }
        renderCompletionCard(io, 'openlife-agent installed', next);
        await io.pause();
    },
};
// ════════════════════════════════════════════════════════════════════════
//  Phase registry — ordered list consumed by InstallWizardV2 PhaseRunner
// ════════════════════════════════════════════════════════════════════════
exports.ALL_PHASES = Object.freeze([
    phaseBanner,
    phaseInstallMode,
    phaseExistingInstall,
    phaseProductSelection,
    phaseProjectMode,
    phaseCoreEnter,
    phaseCoreHosts,
    phaseQuickCoreDefaults, // quick: sets provider+doctor defaults, skips 12/14/15
    phaseCoreProviders, // full only
    phaseCoreApiKeys,
    phaseCoreModelChain, // full only
    phaseCoreDoctor, // full only
    phaseCoreConfirm,
    phaseCoreComplete,
    phaseAgentEnter,
    phaseQuickAgentDefaults, // quick: sets telegramMode+serviceMode defaults, skips 21/22/26/27
    phaseAgentAuthStrategy, // full only
    phaseAgentOAuth, // full only
    phaseAgentApiKeys,
    phaseAgentTelegramToken,
    phaseAgentTelegramUserId,
    phaseAgentTelegramMode, // full only
    phaseAgentServiceMode, // full only
    phaseAgentConfirm,
    phaseAgentVerify,
    phaseAgentComplete,
]);
// ════════════════════════════════════════════════════════════════════════
//  Helpers
// ════════════════════════════════════════════════════════════════════════
function hostDescription(host) {
    switch (host) {
        case 'claude-code': return 'Anthropic Claude Code (fully supported)';
        case 'gemini-cli': return 'Google Gemini CLI';
        case 'codex': return 'OpenAI Codex CLI';
        default: return host;
    }
}
/**
 * Maps the new ProviderId-keyed ApiKeyMap to the legacy InstallModules
 * { openai, anthropic, gemini, openrouter, elevenlabs } shape so we can
 * reuse `saveApiKeysToEnv` without refactoring it.
 *
 * Providers not in the legacy map still get their env var written by writeExtraEnv.
 */
function mapApiKeysForLegacyHelper(input) {
    const out = {};
    if (input['openai-api'])
        out.openai = input['openai-api'];
    if (input['anthropic'])
        out.anthropic = input['anthropic'];
    if (input['gemini-api'])
        out.gemini = input['gemini-api'];
    if (input['openrouter'])
        out.openrouter = input['openrouter'];
    return out;
}
/**
 * Append-or-replace env vars that don't fit the legacy ApiKeyMap.
 * Read .env, strip matching lines, write back with new values appended.
 */
function writeExtraEnv(root, kv) {
    const envPath = path.join(root, '.env');
    const current = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
    const keys = new Set(Object.keys(kv));
    const lines = current
        .split('\n')
        .filter((l) => {
        const eq = l.indexOf('=');
        if (eq < 0)
            return l.length > 0;
        return !keys.has(l.slice(0, eq));
    });
    for (const [k, v] of Object.entries(kv)) {
        lines.push(`${k}=${v}`);
    }
    fs.writeFileSync(envPath, lines.join('\n') + '\n', 'utf-8');
}
function renderCompletionCard(io, title, nextSteps) {
    const useColor = (0, MatrixTheme_1.supportsColor)();
    const head = useColor ? MatrixTheme_1.MATRIX.head : '';
    const body = useColor ? MatrixTheme_1.MATRIX.body : '';
    const reset = useColor ? MatrixTheme_1.ANSI.reset : '';
    io.divider();
    io.print(`${head}✓ ${title}${reset}`);
    io.print('');
    io.print('Next steps:');
    for (const step of nextSteps) {
        io.print(`  ${body}• ${step}${reset}`);
    }
    io.print('');
}
