// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. actions module for scripts/src/platform-infra-sub2api-codex.ts.

// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:820-1316 for #903.

import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
  codexPoolSentinelSummary,
  codexPoolSentinelRuntimeImage,
  readCodexPoolSentinelConfig,
  renderCodexPoolSentinelManifest,
  type CodexPoolSentinelConfig,
  type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";

import type { CodexPoolConfig, ConfirmOptions, DisclosureOptions, SentinelImageOptions, SentinelProbeOptions, SentinelReportOptions, SyncOptions, TraceOptions } from "./types";
import { desiredAccountNames } from "./accounts";
import { collectCodexProfiles, readCodexPoolConfig } from "./config";
import { codexPoolSentinelProbeConfigFingerprint, fingerprint } from "./config-utils";
import { apiKeyPreview, codexConsumerBaseUrl, fetchPoolApiKey, probePublicModels, validatePublicGatewayWithKey, writeLocalCodexConfig } from "./local-codex";
import { manualBindingSourcePlan, poolTarget, prepareTargetPublicExposureSecret, protectedManualAccountNamesForTarget, resolvedManualAccountProtections, secretMaterialSummary, sentinelProfileSecrets, targetFrpPublicExposure, targetPublicExposureApplyScript, targetPublicExposureSummary } from "./public-exposure";
import { codexPoolConfigSummary, compactProfile, compactSentinelProbeResult, redactProfile, renderSub2ApiTempUnschedulableCredentials } from "./redaction";
import { boolField, capture, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote";
import { cleanupProbesScript, sentinelImageBuildScript, sentinelImageStatusScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts";
import { codexPoolSyncSummary, codexPoolValidationSummary, renderCodexPoolPlan, renderCodexPoolSentinelProbeResult, renderCodexPoolSyncResult, renderCodexPoolValidateResult, renderSentinelReport, renderTraceReport, renderedCliResult } from "./render";
import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId, targetFlag } from "./runtime-target";
import { codexPoolConfigPath, serviceName, sub2apiConfigPath } from "./types";

export function codexPoolPlan(options?: DisclosureOptions): Record<string, unknown> {
  const resolvedOptions = options ?? { full: false, raw: false, targetId: defaultCodexPoolRuntimeTargetId() };
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(resolvedOptions.targetId);
  const profiles = collectCodexProfiles();
  const ok = profiles.length > 0 && profiles.every((profile) => profile.ok);
  const consumerBaseUrl = runtimeTarget.publicBaseUrl === null ? null : codexConsumerBaseUrl(pool, runtimeTarget);
  return {
    ok,
    action: "platform-infra-sub2api-codex-pool-plan",
    source: {
      directory: join(homedir(), ".codex"),
      configPattern: "YAML-selected config files under ~/.codex",
      authPattern: "YAML-selected auth files under ~/.codex",
      valuesPrinted: false,
    },
    target: poolTarget(pool, runtimeTarget),
    config: {
      path: codexPoolConfigPath,
      pool: options.full ? pool : codexPoolConfigSummary(pool, runtimeTarget),
    },
    profiles: options.full ? profiles.map(redactProfile) : profiles.map(compactProfile),
    decision: {
      accountType: "openai/apikey",
      grouping: `All discovered Codex profiles are bound to one Sub2API group named ${pool.groupName}.`,
      unifiedApiKey: runtimeTarget.runtimeMode === "host-docker"
        ? `The client-facing API_KEY is controlled by host-Docker env source ${runtimeTarget.hostDockerEnvPath}.${pool.apiKeySecretKey}.`
        : `The client-facing API_KEY is controlled by k3s Secret ${runtimeTarget.namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}.`,
      sentinel: pool.sentinel.monitor.enabled
        ? `Account sentinel is enabled as k8s CronJob ${runtimeTarget.namespace}/${pool.sentinel.cronJobName}; actions.enabled=${pool.sentinel.actions.enabled}.`
        : "Account sentinel monitoring is disabled by YAML.",
      publicExposure: runtimeTarget.publicBaseUrl === null
        ? `Target-level public exposure is disabled or absent in ${sub2apiConfigPath}.targets[${runtimeTarget.id}].publicExposure.`
        : `Codex consumers for target ${runtimeTarget.id} use target-level public exposure ${consumerBaseUrl}.`,
      idempotency: "sync reuses the group, account names, and k3s Secret when they already exist; credentials are updated from the current local Codex files for YAML-managed profiles only; managed accounts missing from YAML are preserved unless --prune-removed is explicitly provided.",
      configPolicy: "UniDesk-owned durable configuration remains YAML-first; local ~/.codex files and runtime Secrets are not committed.",
      manualAccountProtection: pool.manualAccounts.protected.length === 0
        ? "No manual Sub2API accounts are protected by YAML."
        : `${pool.manualAccounts.protected.length} manual Sub2API account(s) are protected from UniDesk-managed credentials, prune, sentinel probe, and sentinel freeze paths; only explicitly declared proxy/group bindings are reconciled.`,
    },
    next: ok
      ? { sync: `bun scripts/cli.ts platform-infra sub2api codex-pool sync${targetFlag(runtimeTarget)} --confirm` }
      : { fix: "Ensure every discovered config.toml profile has a base_url and either auth.json OPENAI_API_KEY or the configured env_key present in this shell." },
  };
}

export async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const profiles = collectCodexProfiles();
  const planOk = (profiles.length > 0 && profiles.every((profile) => profile.ok)) || (profiles.length === 0 && pool.manualAccounts.protected.length > 0);
  if (!options.confirm || !planOk) {
    const plan = {
      ...codexPoolPlan(options),
      ok: !options.confirm ? planOk : false,
      mode: options.confirm ? "blocked-invalid-local-profile" : "dry-run",
      next: options.confirm
        ? { fix: "Repair invalid local Codex profiles, then rerun sync --confirm." }
        : { confirm: "bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm" },
    };
    return options.full || options.raw ? plan : renderCodexPoolPlan(plan);
  }

  const sentinelImage = pool.sentinel.monitor.enabled && runtimeTarget.sentinelEnabled
    ? await runCodexPoolSentinelImage(config, pool, { action: "build", confirm: true, dryRun: false, full: options.full, raw: false, targetId: options.targetId })
    : { ok: true, mode: runtimeTarget.sentinelEnabled ? "skipped-monitor-disabled" : "skipped-target-disabled" };
  if (sentinelImage.ok !== true) {
    return {
      ok: false,
      action: "platform-infra-sub2api-codex-pool-sync",
      mode: "blocked-sentinel-image",
      sentinelImage,
      next: {
        image: `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build${targetFlag(runtimeTarget)} --confirm`,
      },
    };
  }

  const payload = {
    pruneRemoved: options.pruneRemoved,
    sentinel: {
      enabledForTarget: runtimeTarget.sentinelEnabled,
      manifest: runtimeTarget.sentinelEnabled
        ? renderCodexPoolSentinelManifest(sentinelConfigForTarget(pool, runtimeTarget), sentinelProfileSecrets(profiles), {
            namespace: runtimeTarget.namespace,
            serviceName: runtimeTarget.serviceName,
            serviceDns: runtimeTarget.serviceDns,
            appSecretName: runtimeTarget.appSecretName,
            adminEmailDefault: pool.adminEmailDefault,
            proxy: runtimeTarget.egressProxy?.applyToSentinel ? {
              httpProxy: runtimeTarget.egressProxy.httpProxy,
              noProxy: runtimeTarget.egressProxy.noProxy,
            } : null,
          })
        : null,
      summary: codexPoolSentinelSummary(sentinelConfigForTarget(pool, runtimeTarget)),
    },
    pool: {
      groupName: pool.groupName,
      groupDescription: pool.groupDescription,
      apiKeyName: pool.apiKeyName,
      apiKeySecretName: pool.apiKeySecretName,
      apiKeySecretKey: pool.apiKeySecretKey,
      adminEmailDefault: pool.adminEmailDefault,
      minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
      minOwnerConcurrency: pool.minOwnerConcurrency,
      defaultAccountPriority: pool.defaultAccountPriority,
      defaultAccountCapacity: pool.defaultAccountCapacity,
      defaultAccountLoadFactor: pool.defaultAccountLoadFactor,
      defaultSentinelProtect: pool.defaultSentinelProtect,
    },
    manualAccounts: {
      bindingSources: pool.manualAccounts.bindingSources.items.map(manualBindingSourcePlan),
      protected: resolvedManualAccountProtections(pool, runtimeTarget),
    },
    profiles: profiles.map((profile) => ({
      profile: profile.profile,
      accountName: profile.accountName,
      configFile: profile.configFile,
      authFile: profile.authFile,
      provider: profile.provider,
      baseUrl: profile.baseUrl,
      wireApi: profile.wireApi,
      model: profile.model,
      apiKey: profile.apiKey,
      apiKeySource: profile.apiKeySource,
      apiKeyFingerprint: fingerprint(profile.apiKey ?? ""),
      sentinelProbeConfigFingerprint: codexPoolSentinelProbeConfigFingerprint({
        accountName: profile.accountName,
        profile: profile.profile,
        baseUrl: profile.baseUrl,
        apiKeyFingerprint: fingerprint(profile.apiKey ?? ""),
        upstreamUserAgent: profile.upstreamUserAgent,
        openaiResponsesWebSocketsV2Mode: profile.openaiResponsesWebSocketsV2Mode,
        trustUpstream: profile.trustUpstream,
        sentinelProtect: profile.sentinelProtect,
      }),
      openaiResponsesWebSocketsV2Mode: profile.openaiResponsesWebSocketsV2Mode,
      upstreamUserAgent: profile.upstreamUserAgent,
      trustUpstream: profile.trustUpstream,
      sentinelProtect: profile.sentinelProtect,
      priority: profile.priority,
      capacity: profile.capacity,
      loadFactor: profile.loadFactor,
      tempUnschedulable: profile.tempUnschedulable,
      tempUnschedulableCredentials: renderSub2ApiTempUnschedulableCredentials(profile.tempUnschedulable),
    })),
  };
  const result = await runRemoteCodexPoolScript(config, "sync", syncScript(payload, pool, runtimeTarget), runtimeTarget);
  const parsed = parseJsonOutput(result.stdout);
  if (options.raw) {
    return {
      ok: result.exitCode === 0 && boolField(parsed, "ok", false),
      action: "platform-infra-sub2api-codex-pool-sync",
      remote: compactCapture(result, { full: true }),
      parsed,
    };
  }
  const response = {
    ok: result.exitCode === 0 && boolField(parsed, "ok", false),
    action: "platform-infra-sub2api-codex-pool-sync",
    target: {
      id: runtimeTarget.id,
      route: runtimeTarget.route,
      namespace: runtimeTarget.namespace,
    },
    local: {
      profileCount: profiles.length,
      pruneRemoved: options.pruneRemoved,
      invalidProfiles: profiles.filter((profile) => !profile.ok).map(compactProfile),
      profiles: options.full ? profiles.map(redactProfile) : undefined,
      valuesPrinted: false,
    },
    sentinelImage,
    remote: parsed === null
      ? compactCapture(result, { full: options.full || result.exitCode !== 0 })
      : options.full ? parsed : codexPoolSyncSummary(parsed),
    next: {
      validate: `bun scripts/cli.ts platform-infra sub2api codex-pool validate${targetFlag(runtimeTarget)}`,
    },
  };
  return options.full ? response : renderCodexPoolSyncResult(response);
}

export async function codexPoolSentinelImage(config: UniDeskConfig, options: SentinelImageOptions): Promise<Record<string, unknown>> {
  const pool = readCodexPoolConfig();
  return await runCodexPoolSentinelImage(config, pool, options);
}

export async function runCodexPoolSentinelImage(config: UniDeskConfig, pool: CodexPoolConfig, options: SentinelImageOptions): Promise<Record<string, unknown>> {
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const target = codexPoolSentinelRuntimeImage(pool.sentinel);
  void config;
  if (!runtimeTarget.sentinelEnabled) {
    return {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-sentinel-image",
      mode: "skipped-target-disabled",
      target: {
        id: runtimeTarget.id,
        namespace: runtimeTarget.namespace,
      },
      image: target,
      mutation: false,
      valuesPrinted: false,
    };
  }
  const summary = {
    ok: true,
    mode: options.action === "status" ? "status" : options.dryRun ? "dry-run" : "build",
    target: {
      id: runtimeTarget.id,
      namespace: runtimeTarget.namespace,
    },
    image: target.runtimeImage,
    baseImage: target.baseImage,
    openaiPythonVersion: pool.sentinel.sdk.openaiPythonVersion,
    sdkInstall: "prebuilt-runtime-image",
    dockerRequired: options.action === "status" || !options.dryRun,
    registryPushRequired: options.action === "build" && !options.dryRun,
    mutation: options.action === "build" && !options.dryRun,
    valuesPrinted: false,
  };
  const script = options.action === "build" && !options.dryRun
    ? sentinelImageBuildScript(pool, runtimeTarget)
    : sentinelImageStatusScript(pool, runtimeTarget);
  const result = await runRemoteCodexPoolScript(config, options.action === "build" && !options.dryRun ? "sentinel-image-build" : "sentinel-image-status", script, runtimeTarget);
  const parsed = parseJsonOutput(result.stdout);
  const ok = result.exitCode === 0 && boolField(parsed, "ok", options.dryRun);
  if (options.raw) {
    return {
      ok,
      action: "platform-infra-sub2api-codex-pool-sentinel-image",
      mode: summary.mode,
      image: target,
      remote: compactCapture(result, { full: true }),
      parsed: parsed ?? summary,
    };
  }
  return {
    ok,
    action: "platform-infra-sub2api-codex-pool-sentinel-image",
    mode: summary.mode,
    image: target,
    summary: parsed === null ? summary : { ...summary, ...parsed },
    remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }),
  };
}

function sentinelConfigForTarget(pool: CodexPoolConfig, target: ReturnType<typeof codexPoolRuntimeTarget>): CodexPoolConfig["sentinel"] {
  return {
    ...pool.sentinel,
    protectedManualAccounts: protectedManualAccountNamesForTarget(pool, target),
  };
}

export async function codexPoolValidate(config: UniDeskConfig, options: DisclosureOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const result = await runRemoteCodexPoolScript(config, "validate", validateScript(pool, runtimeTarget), runtimeTarget);
  const parsed = parseJsonOutput(result.stdout);
  if (options.raw) {
    return {
      ok: result.exitCode === 0 && boolField(parsed, "ok", false),
      action: "platform-infra-sub2api-codex-pool-validate",
      remote: compactCapture(result, { full: true }),
      parsed,
    };
  }
  const response = {
    ok: result.exitCode === 0 && boolField(parsed, "ok", false),
    action: "platform-infra-sub2api-codex-pool-validate",
    target: {
      id: runtimeTarget.id,
      route: runtimeTarget.route,
      namespace: runtimeTarget.namespace,
    },
    summary: options.full ? parsed : codexPoolValidationSummary(parsed),
    remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }),
  };
  return options.full ? response : renderCodexPoolValidateResult(response);
}

export async function codexPoolTrace(config: UniDeskConfig, options: TraceOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const result = await capture(config, runtimeTarget.route, ["sh"], traceScript(pool, options, runtimeTarget));
  const parsed = parseJsonOutput(result.stdout);
  const ok = result.exitCode === 0 && boolField(parsed, "ok", false);
  if (options.raw) {
    return {
      ok,
      action: "platform-infra-sub2api-codex-pool-trace",
      remote: compactCapture(result, { full: true }),
      trace: parsed,
      valuesPrinted: false,
    };
  }
  const text = renderTraceReport(parsed, {
    requestId: options.requestId ?? "",
    showLines: options.showLines,
    remote: compactCapture(result, { full: result.exitCode !== 0 || parsed === null }),
  });
  return renderedCliResult(ok, "platform-infra sub2api codex-pool trace", text);
}

export async function codexPoolSentinelReport(config: UniDeskConfig, options: SentinelReportOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const result = await capture(config, runtimeTarget.route, ["sh"], sentinelReportScript(pool, options.events, runtimeTarget));
  const parsed = parseJsonOutput(result.stdout);
  const ok = result.exitCode === 0 && boolField(parsed, "ok", false);
  if (options.raw) {
    return {
      ok,
      action: "platform-infra-sub2api-codex-pool-sentinel-report",
      remote: compactCapture(result, { full: true }),
      report: parsed,
      valuesPrinted: false,
    };
  }
  const text = renderSentinelReport(parsed, {
    events: options.events,
    full: options.full,
    remote: compactCapture(result, { full: result.exitCode !== 0 || parsed === null }),
  });
  return renderedCliResult(ok, "platform-infra sub2api codex-pool sentinel-report", text);
}

export async function codexPoolSentinelProbe(config: UniDeskConfig, options: SentinelProbeOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const protectedNames = new Set(protectedManualAccountNamesForTarget(pool, runtimeTarget).map((account) => account.toLowerCase()));
  const protectedRequested = options.accounts.filter((account) => protectedNames.has(account.toLowerCase()));
  if (protectedRequested.length > 0) {
    return {
      ok: false,
      action: "platform-infra-sub2api-codex-pool-sentinel-probe",
      error: "account-protected-manual",
      protected: protectedRequested,
      message: "Protected manual Sub2API accounts are not YAML-managed and must not be probed by the UniDesk sentinel.",
      valuesPrinted: false,
    };
  }
  const configuredAccounts = desiredAccountNames(pool);
  const missing = options.accounts.filter((account) => !configuredAccounts.includes(account));
  if (missing.length > 0) {
    return {
      ok: false,
      action: "platform-infra-sub2api-codex-pool-sentinel-probe",
      error: "account-not-in-yaml",
      missing,
      configuredAccounts,
      valuesPrinted: false,
    };
  }
  if (!options.confirm) {
    return {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-sentinel-probe",
      mode: "dry-run",
      target: poolTarget(pool, runtimeTarget),
      accounts: options.accounts,
      effect: "Would create one Kubernetes Job from the managed sentinel CronJob and force an immediate marker probe for the requested account(s).",
      next: {
        confirm: `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe${targetFlag(runtimeTarget)} --account ${options.accounts.join(",")} --confirm`,
      },
      valuesPrinted: false,
    };
  }
  const payload = {
    accounts: options.accounts,
  };
  const result = await runRemoteCodexPoolScript(config, "sentinel-probe", sentinelProbeScript(payload, pool, runtimeTarget), runtimeTarget);
  const parsed = parseJsonOutput(result.stdout);
  const summary = compactSentinelProbeResult(parsed);
  const ok = summary?.ok === true || (result.exitCode === 0 && boolField(parsed, "ok", false));
  if (options.raw) {
    return {
      ok,
      action: "platform-infra-sub2api-codex-pool-sentinel-probe",
      remote: compactCapture(result, { full: true }),
      parsed,
    };
  }
  const response = {
    ok,
    action: "platform-infra-sub2api-codex-pool-sentinel-probe",
    target: {
      id: runtimeTarget.id,
      route: runtimeTarget.route,
      namespace: runtimeTarget.namespace,
    },
    summary: options.full ? parsed : summary,
    remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }),
  };
  return options.full ? response : renderCodexPoolSentinelProbeResult(response);
}

export async function codexPoolCleanupProbes(config: UniDeskConfig, options: ConfirmOptions): Promise<Record<string, unknown>> {
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  if (!options.confirm) {
    return {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-cleanup-probes",
      mode: "dry-run",
      target: poolTarget(readCodexPoolConfig(), runtimeTarget),
      scope: "Only deletes temporary resources whose names start with unidesk-probe-.",
      next: { confirm: `bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes${targetFlag(runtimeTarget)} --confirm` },
      valuesPrinted: false,
    };
  }
  const pool = readCodexPoolConfig();
  const result = await capture(config, runtimeTarget.route, ["sh"], cleanupProbesScript(pool, runtimeTarget));
  const parsed = parseJsonOutput(result.stdout);
  if (options.raw) {
    return {
      ok: result.exitCode === 0 && boolField(parsed, "ok", false),
      action: "platform-infra-sub2api-codex-pool-cleanup-probes",
      remote: compactCapture(result, { full: true }),
      parsed,
    };
  }
  return {
    ok: result.exitCode === 0 && boolField(parsed, "ok", false),
    action: "platform-infra-sub2api-codex-pool-cleanup-probes",
    summary: parsed,
    remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }),
  };
}

export async function codexPoolExpose(config: UniDeskConfig, options: ConfirmOptions): Promise<Record<string, unknown>> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  if (runtimeTarget.publicExposure === null || !runtimeTarget.publicExposure.enabled) {
    return {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-expose",
      mode: "disabled-by-yaml",
      target: poolTarget(pool, runtimeTarget),
      source: `${sub2apiConfigPath}.targets[${runtimeTarget.id}].publicExposure`,
    };
  }
  if (!options.confirm) {
    return {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-expose",
      mode: "dry-run",
      target: poolTarget(pool, runtimeTarget),
      publicExposure: targetPublicExposureSummary(runtimeTarget),
      next: {
        confirm: `bun scripts/cli.ts platform-infra sub2api codex-pool expose${targetFlag(runtimeTarget)} --confirm`,
      },
    };
  }
  if (runtimeTarget.publicExposure.mode === "pk01-local") {
    const publicProbe = await probePublicModels(pool, "without-api-key", undefined, runtimeTarget);
    const ok = publicProbe.httpStatus === 401;
    return {
      ok,
      action: "platform-infra-sub2api-codex-pool-expose",
      mode: "pk01-local-managed-by-sub2api-apply",
      publicExposure: targetPublicExposureSummary(runtimeTarget),
      publicProbe,
      next: {
        apply: `bun scripts/cli.ts platform-infra sub2api apply${targetFlag(runtimeTarget)} --confirm --wait`,
        validate: `bun scripts/cli.ts platform-infra sub2api validate${targetFlag(runtimeTarget)}`,
        configureLocal: `bun scripts/cli.ts platform-infra sub2api codex-pool configure-local${targetFlag(runtimeTarget)} --confirm`,
      },
    };
  }
  const secretMaterial = prepareTargetPublicExposureSecret(runtimeTarget);
  const caddyResult = await applyPk01CaddyBlock(config, serviceName, targetFrpPublicExposure(runtimeTarget));
  const remoteResult = await capture(config, runtimeTarget.route, ["sh"], targetPublicExposureApplyScript(runtimeTarget, secretMaterial));
  const parsed = parseJsonOutput(remoteResult.stdout);
  const publicProbe = await probePublicModels(pool, "without-api-key", undefined, runtimeTarget);
  const ok = caddyResult.ok === true && remoteResult.exitCode === 0 && boolField(parsed, "ok", false) && publicProbe.httpStatus === 401;
  if (options.raw) {
    return {
      ok,
      action: "platform-infra-sub2api-codex-pool-expose",
      frpcSecret: secretMaterialSummary(secretMaterial),
      pk01Caddy: caddyResult,
      remote: compactCapture(remoteResult, { full: true }),
      parsed,
      publicProbe,
    };
  }
  return {
    ok,
    action: "platform-infra-sub2api-codex-pool-expose",
    mode: "confirmed",
    publicExposure: targetPublicExposureSummary(runtimeTarget),
    frpcSecret: secretMaterialSummary(secretMaterial),
    pk01Caddy: caddyResult,
    remote: parsed ?? compactCapture(remoteResult, { full: options.full || remoteResult.exitCode !== 0 }),
    publicProbe,
    next: {
      configureLocal: `bun scripts/cli.ts platform-infra sub2api codex-pool configure-local${targetFlag(runtimeTarget)} --confirm`,
      validate: `bun scripts/cli.ts platform-infra sub2api codex-pool validate${targetFlag(runtimeTarget)}`,
    },
  };
}

export async function codexPoolConfigureLocal(config: UniDeskConfig, options: ConfirmOptions): Promise<Record<string, unknown>> {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
  const consumerExposureAvailable = runtimeTarget.publicBaseUrl !== null;
  const codexDir = join(homedir(), ".codex");
  const configPath = join(codexDir, "config.toml");
  const authPath = join(codexDir, "auth.json");
  const backupConfigPath = join(codexDir, `config.toml.${pool.localCodex.backupSuffix}`);
  const backupAuthPath = join(codexDir, `auth.json.${pool.localCodex.backupSuffix}`);
  if (!options.confirm) {
    return {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-configure-local",
      mode: "dry-run",
      target: {
        codexDir,
        configPath,
        authPath,
        backupConfigPath,
        backupAuthPath,
        baseUrl: consumerExposureAvailable ? codexConsumerBaseUrl(pool, runtimeTarget) : null,
        providerName: pool.localCodex.providerName,
        wireApi: pool.localCodex.wireApi,
        modelContextWindow: pool.localCodex.modelContextWindow,
        modelAutoCompactTokenLimit: pool.localCodex.modelAutoCompactTokenLimit,
        supportsWebSockets: pool.localCodex.supportsWebSockets,
        responsesWebSocketsV2: pool.localCodex.responsesWebSocketsV2,
        responsesSmokeModel: pool.localCodex.responsesSmokeModel,
        valuesPrinted: false,
      },
      next: {
        confirm: `bun scripts/cli.ts platform-infra sub2api codex-pool configure-local${targetFlag(runtimeTarget)} --confirm`,
      },
    };
  }
  if (!consumerExposureAvailable) throw new Error(`${sub2apiConfigPath}.targets[${runtimeTarget.id}].publicExposure.enabled must be true; configure-local needs one consumer URL`);
  const keyResult = await fetchPoolApiKey(config, pool, runtimeTarget);
  if (keyResult.apiKey === null) {
    return {
      ok: false,
      action: "platform-infra-sub2api-codex-pool-configure-local",
      error: keyResult.error ?? "pool API key missing",
      valuesPrinted: false,
    };
  }
  const writeResult = writeLocalCodexConfig(pool, keyResult.apiKey, runtimeTarget);
  const validateResult = await validatePublicGatewayWithKey(pool, keyResult.apiKey, runtimeTarget);
  return {
    ok: writeResult.ok && validateResult.ok,
    action: "platform-infra-sub2api-codex-pool-configure-local",
    mode: "confirmed",
    local: writeResult,
    validation: validateResult,
    apiKey: {
      secret: `${runtimeTarget.namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}`,
      keyPreview: apiKeyPreview(keyResult.apiKey),
      apiKeyFingerprint: fingerprint(keyResult.apiKey),
      valuesPrinted: false,
    },
  };
}
