/**
 * `olam bootstrap` — the SOLE on-ramp for olam's image stack.
 *
 * Phase C of the olam-fresh-install-self-bootstrap epic, implementing
 * the centerpiece of the user goal: `npm install -g @pleri/olam-cli &&
 * olam init && olam bootstrap` lands the operator at a working stack
 * with all three images pulled by digest, protocol-version handshake
 * verified, and services started + authenticated.
 *
 * Steps:
 *   1. docker info smoke (fail-fast with remedy if daemon missing).
 *   2. Parallel pull 3 images by digest from image-digests.json
 *      (shipped in the npm tarball by Phase B). Retry-throttle-coalesce
 *      pattern adopted from packages/auth-client (withCredential +
 *      coalesce.ts) per Decision #16: in-flight Map deduplicates
 *      concurrent identical-digest pulls; bounded retry.
 *   3. For each pulled image, run `docker inspect` + checkProtocolOverlap
 *      (Decision #14 handshake). Refuse on no-overlap.
 *   4. olam host-cp start (no-ops if already running).
 *   5. olam auth up (idempotent).
 *   6. olam auth login (interactive PKCE — 5-min timeout, "still
 *      waiting" reprint every 60s, Ctrl+C cancellation).
 *   7. Optional --with-smoke: olam create --workspace smoke
 *      --task "smoke test".
 *
 * Idempotent re-run: when all images present + matching digests +
 * services healthy, prints a brief status table and exits 0 in ≤30s.
 */
import { spawnSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import ora, {} from 'ora';
import pc from 'picocolors';
import { pullImageWithRetry, realDocker, } from '@olam/adapters';
import { installRoot } from '../install-root.js';
import { EXIT_BOOTSTRAP_PULL_FAILED, EXIT_GENERIC_ERROR, EXIT_PROTOCOL_MISMATCH, } from '../exit-codes.js';
import { checkProtocolOverlap, parseProtocolVersionsLabel } from '../protocol-version.js';
import { printError, printSuccess, printInfo, printHeader } from '../output.js';
import { readConfig } from '../lib/config.js';
import { runBootstrapKubernetes, DEFAULT_K3S_CLUSTER } from '../lib/bootstrap-kubernetes.js';
// Re-export so existing call sites (upgrade.ts, auth-upgrade.ts, tests)
// keep importing from `./bootstrap.js` unchanged. The implementation
// moved to @olam/adapters/docker/pull.ts so the adapter layer can
// pull-before-createContainer without inverting the cli↔adapters dep
// direction.
export { pullImageWithRetry, realDocker, };
/** Image-digests.json schema validation + load. */
export function loadImageDigests(installRootDir = installRoot()) {
    const digestsPath = join(installRootDir, 'dist', 'image-digests.json');
    if (!existsSync(digestsPath)) {
        throw new Error(`image-digests.json missing at ${digestsPath}. ` +
            `Re-run \`npm install -g @pleri/olam-cli@<version>\` to refresh the tarball.`);
    }
    let parsed;
    try {
        parsed = JSON.parse(readFileSync(digestsPath, 'utf8'));
    }
    catch (err) {
        throw new Error(`image-digests.json is not valid JSON: ${err.message}`);
    }
    if (typeof parsed !== 'object' || parsed === null) {
        throw new Error('image-digests.json is not an object');
    }
    const obj = parsed;
    const required = ['host-cp', 'auth', 'devbox'];
    const missing = required.filter((k) => typeof obj[k] !== 'string' || !obj[k]);
    if (missing.length > 0) {
        throw new Error(`image-digests.json missing required key(s): ${missing.join(', ')}. ` +
            `Tarball may be corrupted; reinstall the CLI.`);
    }
    return parsed;
}
const REAL_OLAM_SUBCOMMAND = (args) => {
    const result = spawnSync(process.execPath, [
        process.argv[1] ?? 'olam',
        ...args,
    ], {
        encoding: 'utf8',
        stdio: ['ignore', 'pipe', 'pipe'],
    });
    return {
        exitCode: result.status ?? -1,
        stdout: result.stdout ?? '',
        stderr: result.stderr ?? '',
    };
};
export async function runBootstrap(opts, deps = {}) {
    // Substrate-aware dispatch. k3s is the default substrate as of
    // feat/cli-bootstrap-k3s-default; compose remains supported for existing
    // installs whose ~/.olam/config.json still declares substrate=compose.
    const cfg = readConfig(deps.configPath ? { configPath: deps.configPath } : {});
    if (cfg.host.substrate === 'kubernetes') {
        const k8s = await runBootstrapKubernetes({
            cluster: opts.k3sCluster ?? DEFAULT_K3S_CLUSTER,
            autoInstall: opts.autoInstall ?? false,
            skipObservability: opts.skipObservability ?? false,
            skipClusterCreate: opts.skipClusterCreate ?? false,
        });
        return { exitCode: k8s.exitCode, summary: k8s.summary };
    }
    // Compose substrate — existing flow.
    const docker = deps.docker ?? realDocker;
    const digests = deps.digests ?? loadImageDigests();
    const registry = opts.registry ?? deps.registry ?? digests.$registry ?? 'ghcr.io/pleri';
    printHeader('olam bootstrap');
    // Step 1: docker daemon smoke.
    const infoSpinner = ora('Checking docker daemon').start();
    const info = await docker.info();
    if (info.exitCode !== 0) {
        infoSpinner.fail('docker daemon not reachable');
        process.stderr.write(`${pc.red('error')} docker info exited with ${info.exitCode}.\n` +
            '  Ensure Docker Desktop / Colima / Rancher is running, then retry.\n' +
            `  stderr: ${info.stderr.split('\n')[0] ?? ''}\n`);
        return { exitCode: EXIT_GENERIC_ERROR, summary: 'docker daemon not reachable' };
    }
    infoSpinner.succeed('docker daemon reachable');
    // Step 2: parallel pull all images by digest.
    // mcp-auth is OPTIONAL per the type def — image-digests.json from
    // older CLI tarballs may not include it. Bootstrap stays
    // backward-compat by skipping the mcp-auth entries when the digest
    // is absent. Newer tarballs include the field, so the consumer side
    // (services.ts → MCP_AUTH_LOCAL_TAG = 'olam-mcp-auth:local') gets
    // its expected canonical tag created.
    const imageRefs = [
        { name: 'host-cp', ref: `${registry}/olam-host-cp@${digests['host-cp']}` },
        { name: 'auth', ref: `${registry}/olam-auth@${digests.auth}` },
        { name: 'devbox', ref: `${registry}/olam-devbox@${digests.devbox}` },
    ];
    if (digests['mcp-auth']) {
        imageRefs.push({ name: 'mcp-auth', ref: `${registry}/olam-mcp-auth@${digests['mcp-auth']}` });
    }
    // kg-service: optional (older tarballs don't ship it; opt-out via --skip-kg-service).
    if (digests['kg-service'] && !opts.skipKgService) {
        imageRefs.push({ name: 'kg-service', ref: `${registry}/olam-kg-service@${digests['kg-service']}` });
    }
    // memory-service: optional (older tarballs don't ship it). When present,
    // pull and tag as :local so MemoryServiceContainerController resolves it
    // via the local→dev→published fallback chain without touching :latest.
    // Skipped when --skip-memory is set (caller doesn't want memory-service).
    if (digests['memory-service'] && !opts.skipMemory) {
        imageRefs.push({ name: 'memory-service', ref: `${registry}/olam-memory-service@${digests['memory-service']}` });
    }
    // devbox-base: optional (older tarballs don't ship it). When present,
    // we MUST pull AND tag it as `olam-devbox:base` so the new
    // `DEFAULT_DEVBOX_IMAGE = olam-devbox:base` resolves on a fresh
    // `olam bootstrap` host. Without this, the operator's first
    // `olam create` after bootstrap fails with `404 No such image:
    // olam-devbox:base` (e2e workflow run 25709257945, fresh-install
    // matrix). Mirrors the upgrade.ts:1075-1080 logic exactly.
    if (digests['devbox-base']) {
        imageRefs.push({
            name: 'devbox-base',
            ref: `${registry}/olam-devbox@${digests['devbox-base']}`,
        });
    }
    const pullSpinner = ora(`Pulling images (${imageRefs.length} parallel)`).start();
    const pullStart = Date.now();
    const pullResults = await Promise.all(imageRefs.map(async ({ name, ref }) => ({
        name,
        ref,
        result: await pullImageWithRetry(ref, docker),
    })));
    const pullElapsed = ((Date.now() - pullStart) / 1000).toFixed(1);
    const failed = pullResults.filter((r) => r.result.exitCode !== 0);
    if (failed.length > 0) {
        pullSpinner.fail(`Pull failed for ${failed.map((r) => r.name).join(', ')}`);
        for (const f of failed) {
            process.stderr.write(`  ${pc.red(f.name)} (${f.ref}):\n` +
                `    exit=${f.result.exitCode}\n` +
                `    stderr: ${f.result.stderr.split('\n')[0] ?? '(empty)'}\n`);
        }
        process.stderr.write('\n  Remedy: `olam bootstrap` is the only on-ramp. Re-run after\n' +
            '  resolving network / GHCR availability. If a specific image is\n' +
            '  permanently unavailable, file a bug at github.com/pleri/olam/issues.\n');
        return {
            exitCode: EXIT_BOOTSTRAP_PULL_FAILED,
            summary: `pull failed: ${failed.map((r) => r.name).join(', ')}`,
        };
    }
    pullSpinner.succeed(`Pulled ${imageRefs.length} images in ${pullElapsed}s`);
    // Step 3: protocol-version handshake — refuse on no-overlap.
    const handshakeSpinner = ora('Verifying olam.protocol.versions handshake').start();
    for (const { name, ref } of imageRefs) {
        const inspect = await docker.inspectLabel(ref, 'olam.protocol.versions');
        if (inspect.exitCode !== 0) {
            handshakeSpinner.fail(`Could not inspect ${name}`);
            process.stderr.write(`${pc.red('error')} docker inspect ${ref} failed: ${inspect.stderr}\n`);
            return { exitCode: EXIT_GENERIC_ERROR, summary: `inspect failed: ${name}` };
        }
        const labelValue = (inspect.stdout || '').trim();
        const versions = parseProtocolVersionsLabel(labelValue === '<no value>' ? '' : labelValue);
        const decision = checkProtocolOverlap(versions);
        if (!decision.compatible) {
            handshakeSpinner.fail(`Protocol mismatch on ${name}`);
            process.stderr.write(`${pc.red('error')} ${decision.remedy}\n`);
            return {
                exitCode: EXIT_PROTOCOL_MISMATCH,
                summary: `protocol mismatch: ${name}`,
            };
        }
    }
    handshakeSpinner.succeed(`Protocol handshake passed (all ${imageRefs.length} images)`);
    // Step 3b: alias the digest-pinned pulls as `<image>:latest`. compose.yaml
    // references services by tag (`ghcr.io/pleri/olam-host-cp:latest`), not
    // by digest, so without this step `compose up` would either pull `:latest`
    // again from the registry (wasted bandwidth, no digest pinning) or fail
    // when the operator is offline / GHCR is rate-limited. Re-tagging is
    // local-only and instant.
    const tagSpinner = ora('Aliasing pulled digests as :latest').start();
    // Tag targets are deliberately heterogeneous — each consumer reads
    // a different canonical name:
    //   - host-cp's compose.yaml says `image: ghcr.io/pleri/olam-host-cp:latest`
    //   - AuthContainerController hard-codes DEFAULT_IMAGE = 'olam-auth:local'
    //     (the legacy local-build tag; predates the published image)
    //   - devbox: `olam create` runs `docker run olam-devbox:latest` directly
    //     (DEFAULT_DEVBOX_IMAGE in packages/core/src/world/devbox-image.ts).
    //     Without the bare-name alias, create fails with "no such image" even
    //     after bootstrap pulled the digest. PR #281 E2E run 25534306717
    //     surfaced this when `--workspace` was finally seeded — the registry-
    //     prefixed tag isn't what the runtime looks up.
    // Future cleanup: pick one canonical scheme + migrate consumers.
    const tagPlan = [
        { name: 'host-cp', from: imageRefs[0].ref, to: `${registry}/olam-host-cp:latest` },
        { name: 'auth', from: imageRefs[1].ref, to: 'olam-auth:local' },
        { name: 'devbox', from: imageRefs[2].ref, to: 'olam-devbox:latest' },
    ];
    // mcp-auth: only tag when the operator's image-digests.json carried
    // the digest (older tarballs don't). services.ts's
    // MCP_AUTH_LOCAL_TAG ('olam-mcp-auth:local') is the consumer; without
    // this tag, `olam auth up` fails with "mcp-auth image
    // 'olam-mcp-auth:local' not found locally" and bootstrap is broken
    // for the D3 conformance smoke + every fresh-machine install.
    const mcpAuthRef = imageRefs.find((r) => r.name === 'mcp-auth');
    if (mcpAuthRef) {
        tagPlan.push({ name: 'mcp-auth', from: mcpAuthRef.ref, to: 'olam-mcp-auth:local' });
    }
    // kg-service: tag the published image as the local canonical
    // (kg-service-container.ts resolves :local → :dev → :latest in that
    // order). Without this, fresh-bootstrap operators get "kg-service image
    // not found locally" on `olam services up`.
    const kgServiceRef = imageRefs.find((r) => r.name === 'kg-service');
    if (kgServiceRef) {
        tagPlan.push({ name: 'kg-service', from: kgServiceRef.ref, to: 'olam-kg-service:local' });
    }
    // memory-service: tag the published image as :local so
    // MemoryServiceContainerController's local→dev→published fallback
    // resolves without touching the registry :latest tag. Mirrors the
    // kg-service pattern above.
    const memoryServiceRef = imageRefs.find((r) => r.name === 'memory-service');
    if (memoryServiceRef) {
        tagPlan.push({ name: 'memory-service', from: memoryServiceRef.ref, to: 'olam-memory-service:local' });
    }
    // devbox-base: tag both bare (`olam-devbox:base`) and
    // registry-prefixed variants. `olam create`'s
    // DEFAULT_DEVBOX_IMAGE = 'olam-devbox:base' (devbox-image.ts) is the
    // consumer; without these tags, every fresh-bootstrap operator's
    // first `olam create` fails with `404 No such image:
    // olam-devbox:base`. Mirrors upgrade.ts:1077-1078 exactly.
    const devboxBaseRef = imageRefs.find((r) => r.name === 'devbox-base');
    if (devboxBaseRef) {
        tagPlan.push({ name: 'devbox-base (bare)', from: devboxBaseRef.ref, to: 'olam-devbox:base' });
        tagPlan.push({
            name: 'devbox-base (registry)',
            from: devboxBaseRef.ref,
            to: `${registry}/olam-devbox:base`,
        });
    }
    for (const { name, from, to } of tagPlan) {
        const result = await docker.tag(from, to);
        if (result.exitCode !== 0) {
            tagSpinner.fail(`Failed to tag ${name}`);
            process.stderr.write(`${pc.red('error')} docker tag ${from} ${to} failed: ${result.stderr}\n`);
            return { exitCode: EXIT_GENERIC_ERROR, summary: `tag failed: ${name}` };
        }
    }
    tagSpinner.succeed('Aliased 3 images as :latest');
    // Steps 4-6: services + auth-login (C3 — scoped to wiring, not re-implementing).
    const runOlam = deps.runOlamSubcommand ?? REAL_OLAM_SUBCOMMAND;
    const hostCpSpinner = ora('Starting host-cp').start();
    const hostCp = runOlam(['host-cp', 'start']);
    if (hostCp.exitCode !== 0) {
        hostCpSpinner.fail('host-cp start failed');
        process.stderr.write(`  stderr: ${hostCp.stderr.split('\n').slice(0, 20).join('\n  ')}\n`);
        return { exitCode: EXIT_GENERIC_ERROR, summary: 'host-cp start failed' };
    }
    hostCpSpinner.succeed('host-cp running');
    const authUpSpinner = ora('Starting auth-service').start();
    const authUp = runOlam(['auth', 'up']);
    if (authUp.exitCode !== 0) {
        authUpSpinner.fail('auth up failed');
        // Print BOTH stdout + stderr. servicesUp's helpers print success +
        // warnings via console.log (→ stdout); only printError goes to
        // stderr. The most-common failure mode — "olam-auth started but
        // /health did not respond within 15s" — is a printWarning to
        // stdout. Pre-fix the bootstrap displayed only stderr, so that
        // diagnostic was invisible. Operators saw `stderr:` (empty) and
        // had no pointer to the real cause.
        if (authUp.stdout.trim().length > 0) {
            process.stderr.write(`  stdout: ${authUp.stdout.split('\n').slice(0, 20).join('\n  ')}\n`);
        }
        if (authUp.stderr.trim().length > 0) {
            process.stderr.write(`  stderr: ${authUp.stderr.split('\n').slice(0, 20).join('\n  ')}\n`);
        }
        if (authUp.stdout.trim().length === 0 && authUp.stderr.trim().length === 0) {
            process.stderr.write(`  (no stdout/stderr from olam auth up — exit code ${authUp.exitCode})\n`);
        }
        return { exitCode: EXIT_GENERIC_ERROR, summary: 'auth up failed' };
    }
    authUpSpinner.succeed('auth-service running');
    // Step 5b: olam-postgres singleton + olam-shared network (Phase A of
    // olam-hybrid-shared-postgres plan — see plan task A6).
    //
    // Brings up the shared postgres service that worlds will use for their
    // per-world DB namespace (CREATE DATABASE x TEMPLATE atlas_*_seed at
    // world create time). Mirrors host-cp + auth-service shape: host-level
    // service, started once per machine, survives across world creates.
    //
    // Skipped via --skip-postgres-singleton or in CI where
    // OLAM_BOOTSTRAP_SKIP_POSTGRES=1 is set. Operators with existing host
    // postgres can opt out via ~/.olam/postgres.toml (currently surfaces in
    // a future tracker task — A6 ships singleton-only).
    if (opts.skipPostgresSingleton || process.env.OLAM_BOOTSTRAP_SKIP_POSTGRES === '1') {
        printInfo('postgres singleton', 'skipped');
    }
    else {
        const pgSpinner = ora('Starting olam-postgres singleton').start();
        try {
            const { ensureOlamPostgresSingleton } = await import('@olam/core/src/auth/postgres-init-helpers.js');
            const result = await ensureOlamPostgresSingleton();
            pgSpinner.succeed(`olam-postgres ${result.state} on network ${result.network} → 127.0.0.1:${result.hostPort}`);
        }
        catch (err) {
            pgSpinner.fail('olam-postgres singleton bring-up failed');
            process.stderr.write(`  ${err instanceof Error ? err.message : String(err)}\n`);
            process.stderr.write(`  Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_POSTGRES=1.\n`);
            return { exitCode: EXIT_GENERIC_ERROR, summary: 'postgres singleton bring-up failed' };
        }
    }
    // Step 5c: agent-memory Docker container (memory-service-as-docker-peripheral
    // Phase B). Brings up `olam-memory-service` via `MemoryServiceContainerController`,
    // identical substrate to kg-service / auth-service / mcp-auth-service. Worlds
    // reach it via `host.docker.internal:3111` (wire protocol unchanged from the
    // Phase A host-process model).
    //
    // Skipped via --skip-memory or in CI where OLAM_BOOTSTRAP_SKIP_MEMORY=1
    // is set. Operators without Docker can opt out the same way.
    if (opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === '1') {
        printInfo('agent-memory', 'skipped');
    }
    else {
        const memSpinner = ora('Starting agent-memory Docker container').start();
        const mem = runOlam(['memory', 'start']);
        if (mem.exitCode !== 0) {
            memSpinner.fail('agent-memory start failed');
            if (mem.stdout.trim().length > 0) {
                process.stderr.write(`  stdout: ${mem.stdout.split('\n').slice(0, 20).join('\n  ')}\n`);
            }
            if (mem.stderr.trim().length > 0) {
                process.stderr.write(`  stderr: ${mem.stderr.split('\n').slice(0, 20).join('\n  ')}\n`);
            }
            process.stderr.write(`  Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_MEMORY=1.\n`);
            return { exitCode: EXIT_GENERIC_ERROR, summary: 'agent-memory start failed' };
        }
        memSpinner.succeed('agent-memory running');
    }
    // Step 6: auth login (interactive). Skipped via --skip-auth-login or in
    // CI where OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN=1 is set. Gives the operator
    // a clear bypass; production bootstrap drops them into PKCE.
    if (opts.skipAuthLogin || process.env.OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN === '1') {
        printInfo('auth login', 'skipped');
    }
    else {
        printHeader('Authenticate with Anthropic');
        process.stdout.write('  This opens an OAuth PKCE flow against Anthropic. The auth-service\n' +
            '  will hold your refresh token; per-world access tokens are issued\n' +
            '  on demand and rotated every 6h.\n\n' +
            '  Press Ctrl+C to cancel — re-run `olam bootstrap` to retry.\n\n');
        const login = runOlam(['auth', 'login']);
        if (login.exitCode !== 0) {
            printError('auth login failed (or was cancelled)');
            process.stderr.write(`  Re-run \`olam bootstrap\` after resolving.\n`);
            return { exitCode: EXIT_GENERIC_ERROR, summary: 'auth login failed' };
        }
        printSuccess('auth login complete');
    }
    // Step 7 (optional): --with-smoke creates a smoke-test world.
    if (opts.withSmoke) {
        printHeader('Smoke test');
        const smoke = runOlam([
            'create',
            '--workspace',
            'smoke',
            '--task',
            'smoke test from olam bootstrap',
        ]);
        if (smoke.exitCode !== 0) {
            printError('smoke world create failed');
            process.stderr.write(`  stderr: ${smoke.stderr.split('\n').slice(0, 20).join('\n  ')}\n`);
            return { exitCode: EXIT_GENERIC_ERROR, summary: 'smoke create failed' };
        }
        printSuccess('Smoke world created');
    }
    printHeader('Stack ready');
    printInfo('olam-host-cp', `running (${digests['host-cp'].slice(0, 19)}…)`);
    printInfo('olam-auth', `running (${digests.auth.slice(0, 19)}…)`);
    const memorySkipped = opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === '1';
    printInfo('agent-memory', memorySkipped ? 'skipped' : 'running');
    printInfo('olam-devbox', `pulled (${digests.devbox.slice(0, 19)}…)`);
    printInfo('next', '`olam create --task "your task"` to spawn a world');
    return { exitCode: 0, summary: 'stack ready' };
}
// ── Commander registration ────────────────────────────────────────
/**
 * Build a Commander Command for the substrate bring-up primitive.
 *
 * `olam setup` (registered in commands/setup.ts) is the operator-facing
 * wizard; it invokes `olam bootstrap` as a subprocess to bring the substrate
 * up. This builder is parameterised so future aliases can share the
 * implementation without duplication.
 */
function buildSetupCommand(program, name) {
    return program
        .command(name)
        .description('Bring up the olam substrate. Substrate-aware: on kubernetes (default), ' +
        'detects tools, auto-installs missing brew formulae on macOS, ' +
        'generates secrets, creates a k3d cluster, installs observability ' +
        '(Loki + Promtail + Grafana + Prometheus + Kyverno), applies ' +
        'peripheral-services manifests (IngressRoutes + ServiceMonitors + ' +
        'dashboards), and runs the `olam upgrade` flow for host-cp + ' +
        'peripherals + rollout. On compose, pulls 3 images by digest + ' +
        'starts host-cp + auth. The full operator-onboarding wizard is ' +
        '`olam setup` — this command is the substrate primitive it calls.')
        .option('--with-smoke', 'After setup, create a smoke-test world to verify end-to-end')
        .option('--skip-auth-login', 'Skip the interactive PKCE step (CI / scripted use only)')
        .option('--skip-postgres-singleton', 'Skip the olam-postgres singleton bring-up (operators with host postgres opt-out, or CI)')
        .option('--skip-memory', 'Skip the agent-memory Docker container bring-up (CI / scripted use, or hosts without Docker)')
        .option('--skip-kg-service', 'Skip the olam-kg-service container pull + start (classifier-augmented retrieval; v2 feature)')
        .option('--registry <ref>', 'Override the registry prefix (default: read from image-digests.json or fall back to ghcr.io/pleri)')
        // ── k3s substrate options ─────────────────────────────────────────
        .option('--k3s-cluster <name>', `[k3s] k3d cluster name (default: ${DEFAULT_K3S_CLUSTER})`, DEFAULT_K3S_CLUSTER)
        .option('--auto-install', '[k3s] auto-install missing tools via brew on macOS (default: ON for macOS)', undefined)
        .option('--no-auto-install', '[k3s] disable auto-install (operator runs brew themselves)')
        .option('--skip-observability', '[k3s] skip Loki/Promtail/Grafana/Prom install (already in place)', false)
        .option('--skip-cluster-create', '[k3s] cluster already exists; install substrate only', false)
        .action(async (opts) => {
        try {
            const result = await runBootstrap({
                withSmoke: opts.withSmoke === true,
                skipAuthLogin: opts.skipAuthLogin === true,
                skipPostgresSingleton: opts.skipPostgresSingleton === true,
                skipMemory: opts.skipMemory === true,
                skipKgService: opts.skipKgService === true,
                registry: opts.registry,
                k3sCluster: opts.k3sCluster,
                // Tri-state: undefined = let runBootstrapKubernetes apply the
                // platform-default (ON for macOS, OFF otherwise). `--no-auto-install`
                // sets autoInstall=false explicitly.
                autoInstall: opts.autoInstall,
                skipObservability: opts.skipObservability === true,
                skipClusterCreate: opts.skipClusterCreate === true,
            });
            process.exitCode = result.exitCode;
        }
        catch (err) {
            printError(err instanceof Error ? err.message : String(err));
            process.exitCode = EXIT_GENERIC_ERROR;
        }
    });
}
export function registerBootstrap(program) {
    // `olam setup` is registered separately in commands/setup.ts as the 7-phase
    // operator-onboarding wizard (system check → bootstrap → shell init →
    // auth → doctor). It calls `olam bootstrap` as Phase 3. This command stays
    // as the lower-level substrate bring-up primitive; operators can call it
    // directly to re-run just the substrate steps.
    buildSetupCommand(program, 'bootstrap');
}
//# sourceMappingURL=bootstrap.js.map