#!/usr/bin/env node
import { Buffer } from "node:buffer";
import { spawn, type ChildProcess } from "node:child_process";
import { createConnection } from "node:net";
import { fileURLToPath } from "node:url";
import { dirname, join, resolve as resolvePath } from "node:path";
import { existsSync } from "node:fs";
import type { Readable, Writable } from "node:stream";
import {
  formatAccessHintLines,
  isValidBindHost,
  localUrlForPort,
  networkUrlsForBindHost,
  probeHostForBindHost,
  urlForHost,
} from "./cli/networkHints.js";
import {
  shouldSuppressServerOutputLine,
  type ServerOutputFilterContext,
} from "./cli/startupOutput.js";
import { startIdentityProxy } from "./proxy/identityProxy.js";
import { findPidsByPort, killPid, openUrlCommand } from "./proxy/platformCommands.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// v3 defaults the public entrypoint to 9527. The legacy 25947 alias can be
// enabled explicitly for older AI-tool and MCP configs. The TanStack Start
// runtime stays on a private upstream port and public ports reverse-proxy to
// that same runtime, so logs, sessions, and MCP state remain unified.
const DEFAULT_PORT = 9527;
const LEGACY_ALIAS_PORT = 25947;
const DEFAULT_UPSTREAM_PORT = 9529;
const LOCAL_PROBE_TIMEOUT_MS = 2000;
const BRANDED_WINDOWS_RUNTIME_EXE = "agent-inspector.exe";
const DEFAULT_CAPTURE_MODE = "simple";

type CaptureMode = "simple" | "full";

process.title = "Agent Inspector";

/**
 * Subcommand router. The legacy one-liner UX (`agent-inspector` with no args,
 * or `agent-inspector start`) starts the proxy. `agent-inspector onboard` runs
 * the guided skill install. Unknown subcommands fall back to the legacy
 * behavior so a stray positional arg doesn't break an old invocation.
 */
const subcommand = process.argv[2];

if (subcommand === "onboard") {
  const { runOnboard } = await import("./cli/onboard.js");
  const code = await runOnboard(process.argv.slice(3));
  process.exit(code);
}

if (subcommand === "doctor") {
  const { runDoctor } = await import("./cli/doctor.js");
  const code = await runDoctor(process.argv.slice(3));
  process.exit(code);
}

await runStart(process.argv.slice(2));

// -----------------------------------------------------------------------------
// Legacy `start` behavior — start the proxy on the configured port. Extracted
// into a function so the router above can keep the top-level flow readable.
// -----------------------------------------------------------------------------
async function isInspectorHealthy(port: number, host?: string): Promise<boolean> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
  try {
    const response = await fetch(`${urlForHost(port, probeHostForBindHost(host))}/api/health`, {
      cache: "no-store",
      signal: controller.signal,
    });
    return response.ok;
  } catch {
    return false;
  } finally {
    clearTimeout(timeout);
  }
}

async function getRunningCaptureMode(port: number, host?: string): Promise<CaptureMode | null> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
  try {
    const response = await fetch(`${urlForHost(port, probeHostForBindHost(host))}/api/config`, {
      cache: "no-store",
      signal: controller.signal,
    });
    if (!response.ok) return null;
    const raw: unknown = await response.json();
    return readCaptureMode(raw);
  } catch {
    return null;
  } finally {
    clearTimeout(timeout);
  }
}

function isPortAcceptingConnections(port: number, host?: string): Promise<boolean> {
  return new Promise((resolve) => {
    const socket = createConnection({ host: probeHostForBindHost(host), port });
    const finish = (value: boolean): void => {
      socket.removeAllListeners();
      socket.destroy();
      resolve(value);
    };

    socket.setTimeout(LOCAL_PROBE_TIMEOUT_MS);
    socket.once("connect", () => finish(true));
    socket.once("timeout", () => finish(false));
    socket.once("error", () => finish(false));
  });
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function waitForInspectorHealthy(
  port: number,
  timeoutMs: number,
  host?: string,
): Promise<boolean> {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    if (await isInspectorHealthy(port, host)) return true;
    await sleep(250);
  }
  return false;
}

function openBrowser(targetUrl: string): void {
  const command = openUrlCommand(targetUrl);
  if (command === null) return;
  const browserProcess = spawn(command.command, command.args, {
    stdio: "ignore",
    detached: true,
    windowsHide: true,
  });
  browserProcess.once("error", () => {
    // Opening a browser is best-effort. Minimal Linux/container images may not
    // include xdg-open, and that should not make the CLI look unhealthy.
  });
  browserProcess.unref();
}

function waitForProcessExit(child: ChildProcess): Promise<number> {
  return new Promise((resolve) => {
    child.once("exit", (code) => {
      resolve(code ?? 1);
    });
    child.once("error", () => {
      resolve(1);
    });
  });
}

function pipeServerOutput(child: ChildProcess, context: ServerOutputFilterContext): void {
  pipeServerOutputStream(child.stdout, process.stdout, context);
  pipeServerOutputStream(child.stderr, process.stderr, context);
}

function pipeServerOutputStream(
  stream: Readable | null,
  target: Writable,
  context: ServerOutputFilterContext,
): void {
  if (stream === null) return;

  let buffered = "";
  stream.on("data", (chunk: unknown) => {
    const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
    const lines = `${buffered}${text}`.split(/\r?\n/);
    buffered = lines.pop() ?? "";
    for (const line of lines) {
      if (!shouldSuppressServerOutputLine(line, context)) {
        target.write(`${line}\n`);
      }
    }
  });
  stream.on("end", () => {
    if (buffered.length === 0) return;
    if (!shouldSuppressServerOutputLine(buffered, context)) {
      target.write(buffered);
    }
    buffered = "";
  });
}

type ServerCommand = {
  command: string;
  args: string[];
};

function resolveServerCommand(outputDir: string, serverPath: string): ServerCommand {
  const brandedRuntime = join(outputDir, BRANDED_WINDOWS_RUNTIME_EXE);
  return process.platform === "win32" && existsSync(brandedRuntime)
    ? { command: brandedRuntime, args: [serverPath] }
    : { command: process.execPath, args: [serverPath] };
}

function parseCaptureMode(value: string | undefined): CaptureMode | null {
  switch (value) {
    case undefined:
      return null;
    case "simple":
      return "simple";
    case "full":
      return "full";
    default:
      return null;
  }
}

function readCaptureMode(raw: unknown): CaptureMode | null {
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
  const descriptor = Object.getOwnPropertyDescriptor(raw, "captureMode");
  const value: unknown = descriptor?.value;
  return typeof value === "string" ? parseCaptureMode(value) : null;
}

function printAccessHints(port: number, host?: string): void {
  const networkUrls = networkUrlsForBindHost(port, host);
  for (const line of formatAccessHintLines(port, networkUrls, host)) {
    console.log(line);
  }
}

type BackgroundSupervisorOptions = {
  port: number;
  host: string | undefined;
  configDir: string | undefined;
  providersJson: string | undefined;
  captureMode: CaptureMode;
  enableIdentityProxy: boolean;
  legacyAliasEnabled: boolean;
};

function buildBackgroundSupervisorArgs(options: BackgroundSupervisorOptions): string[] {
  const args = ["--no-open", "--port", String(options.port), "--mode", options.captureMode];
  if (options.host !== undefined) {
    args.push("--host", options.host);
  }
  if (options.configDir !== undefined) {
    args.push("--config-dir", options.configDir);
  }
  if (options.providersJson !== undefined) {
    args.push("--providers", options.providersJson);
  }
  if (!options.enableIdentityProxy) {
    args.push("--no-identity-proxy");
  }
  if (options.legacyAliasEnabled) {
    args.push("--legacy-port");
  }
  return args;
}

function pickUpstreamPort(publicPort: number, legacyAliasPort: number | null): number {
  const nearbyPort = publicPort + 2 <= 65535 ? publicPort + 2 : publicPort - 2;
  const candidates =
    publicPort === DEFAULT_PORT
      ? [DEFAULT_UPSTREAM_PORT, nearbyPort, LEGACY_ALIAS_PORT + 2]
      : [nearbyPort, DEFAULT_UPSTREAM_PORT, LEGACY_ALIAS_PORT + 2];
  for (const candidate of candidates) {
    if (
      Number.isInteger(candidate) &&
      candidate > 0 &&
      candidate <= 65535 &&
      candidate !== publicPort &&
      candidate !== legacyAliasPort
    ) {
      return candidate;
    }
  }
  return DEFAULT_UPSTREAM_PORT;
}

/** Try to start the identity proxy. Returns `null` (and prints a warning)
 *  if the listen port is already in use by another process — e.g. when
 *  restarting a previous instance that didn't shut down cleanly. The
 *  TanStack Start server keeps running either way; clients just lose the
 *  per-PID attribution until they fall back to direct port `port` access. */
async function tryStartIdentityProxy(
  identityPort: number,
  upstreamPort: number,
  upstreamHost: string | undefined,
): Promise<import("node:http").Server | null> {
  try {
    const server = await startIdentityProxy({
      listenPort: identityPort,
      upstreamHost: upstreamHost ?? "127.0.0.1",
      upstreamPort,
    });
    return server;
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    console.warn(
      `[identity-proxy] Could not listen on port ${identityPort}: ${message}. ` +
        `Falling back to OS-scan heuristic on port ${upstreamPort}. ` +
        `Pass --identity-proxy-port <n> to choose another port, or --no-identity-proxy to disable.`,
    );
    return null;
  }
}

async function runStart(args: string[]): Promise<void> {
  const envPort = process.env["AGENT_INSPECTOR_PORT"] ?? process.env["PORT"];
  const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
  const envHost = process.env["NITRO_HOST"] ?? process.env["HOST"];
  const envMode =
    process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];

  let port = portDefault;
  let host = envHost !== undefined && envHost.trim().length > 0 ? envHost.trim() : undefined;
  let open = true;
  let openWasSpecified = false;
  let background = false;
  let forceRestart = false;
  let configDir: string | undefined;
  let providersJson: string | undefined;
  let captureMode: CaptureMode = DEFAULT_CAPTURE_MODE;
  let captureModeWasSpecified = false;
  let enableIdentityProxy = true;
  let legacyAliasEnabled = false;

  if (envMode !== undefined && envMode !== "") {
    const parsedMode = parseCaptureMode(envMode);
    if (parsedMode === null) {
      console.error(`Invalid capture mode: ${envMode}. Use simple or full.`);
      process.exitCode = 1;
      return;
    }
    captureMode = parsedMode;
    captureModeWasSpecified = true;
  }

  if (host !== undefined && !isValidBindHost(host)) {
    console.error(`Invalid host: ${host}. Use an IP or hostname without protocol/path.`);
    process.exitCode = 1;
    return;
  }

  for (let i = 0; i < args.length; i++) {
    const arg = args[i] ?? "";
    if (arg.startsWith("--host=")) {
      const value = arg.slice(arg.indexOf("=") + 1).trim();
      if (!isValidBindHost(value)) {
        console.error(`Invalid host: ${value}. Use an IP or hostname without protocol/path.`);
        process.exitCode = 1;
        return;
      }
      host = value;
      continue;
    }
    if (arg.startsWith("--mode=") || arg.startsWith("--capture-mode=")) {
      const value = arg.slice(arg.indexOf("=") + 1);
      const parsedMode = parseCaptureMode(value);
      if (parsedMode === null) {
        console.error(`Invalid capture mode: ${value}. Use simple or full.`);
        process.exitCode = 1;
        return;
      }
      captureMode = parsedMode;
      captureModeWasSpecified = true;
      continue;
    }
    switch (arg) {
      case "--port":
      case "-p":
        port = Number(args[i + 1]);
        i++;
        break;
      case "--host":
      case "-H": {
        const value = args[i + 1]?.trim() ?? "";
        if (!isValidBindHost(value)) {
          console.error(`Invalid host: ${value}. Use an IP or hostname without protocol/path.`);
          process.exitCode = 1;
          return;
        }
        host = value;
        i++;
        break;
      }
      case "--no-open":
        open = false;
        openWasSpecified = true;
        break;
      case "--open":
        open = true;
        openWasSpecified = true;
        break;
      case "--force-restart":
      case "--restart":
        forceRestart = true;
        break;
      case "--background":
        background = true;
        break;
      case "--config-dir":
        configDir = args[i + 1];
        i++;
        break;
      case "--providers":
        providersJson = args[i + 1];
        i++;
        break;
      case "--mode":
      case "--capture-mode": {
        const value = args[i + 1];
        const parsedMode = parseCaptureMode(value);
        if (parsedMode === null) {
          console.error(`Invalid capture mode: ${String(value)}. Use simple or full.`);
          process.exitCode = 1;
          return;
        }
        captureMode = parsedMode;
        captureModeWasSpecified = true;
        i++;
        break;
      }
      case "--no-identity-proxy":
        enableIdentityProxy = false;
        break;
      case "--identity-proxy-port": {
        const value = args[i + 1];
        const parsed = Number(value);
        if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
          console.error(`Invalid identity-proxy port: ${String(value)}.`);
          process.exitCode = 1;
          return;
        }
        port = parsed;
        i++;
        break;
      }
      case "--no-legacy-port":
        legacyAliasEnabled = false;
        break;
      case "--legacy-port":
        legacyAliasEnabled = true;
        break;
      default:
        break;
    }
  }

  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
    console.error(`Invalid port: ${String(port)}. Use --port <1-65535>.`);
    process.exitCode = 1;
    return;
  }

  /**
   * Check if a port is in use and kill the process using it
   */
  function killProcessOnPort(targetPort: number): void {
    const pids = findPidsByPort(targetPort);
    for (const pid of pids) {
      console.log(`Killing process ${pid} on port ${targetPort}...`);
      killPid(pid);
    }
  }

  const legacyAliasPort =
    enableIdentityProxy && legacyAliasEnabled && port === DEFAULT_PORT ? LEGACY_ALIAS_PORT : null;

  // TanStack Start listens on the private upstream port; identity proxies own
  // the public-facing port(s). When the identity proxy is disabled we fall
  // back to TanStack listening on the public port directly.
  const upstreamPort = enableIdentityProxy ? pickUpstreamPort(port, legacyAliasPort) : port;
  process.env["PORT"] = String(upstreamPort);

  const url = urlForHost(port, host);

  if (!forceRestart && (await isInspectorHealthy(port, host))) {
    console.log(`agent-inspector is already running at ${url}`);
    printAccessHints(port, host);
    if (captureModeWasSpecified) {
      const runningMode = await getRunningCaptureMode(port, host);
      if (runningMode !== null && runningMode !== captureMode) {
        console.log(`Existing instance capture mode is ${runningMode}; requested ${captureMode}.`);
        console.log(`Use --force-restart to restart with ${captureMode} mode.`);
      }
    }
    console.log(`Use --force-restart to restart the existing instance.`);
    if (open && openWasSpecified) {
      openBrowser(url);
    }
    return;
  }

  if (!forceRestart && (await isPortAcceptingConnections(port, host))) {
    console.error(`Port ${port} is already in use, but it is not a healthy agent-inspector.`);
    console.error(`Stop that process, choose --port <n>, or re-run with --force-restart.`);
    process.exitCode = 1;
    return;
  }

  if (forceRestart) {
    const restartPorts = new Set<number>();
    restartPorts.add(port);
    if (enableIdentityProxy) {
      restartPorts.add(upstreamPort);
      if (legacyAliasPort !== null) restartPorts.add(legacyAliasPort);
    }
    for (const targetPort of restartPorts) {
      killProcessOnPort(targetPort);
    }
  }

  if (background && enableIdentityProxy) {
    const cliEntry = process.argv[1];
    if (cliEntry === undefined || cliEntry.length === 0) {
      console.error("Cannot resolve agent-inspector CLI entry for background supervisor.");
      process.exitCode = 1;
      return;
    }

    const supervisorArgs = buildBackgroundSupervisorArgs({
      port,
      host,
      configDir,
      providersJson,
      captureMode,
      enableIdentityProxy,
      legacyAliasEnabled,
    });
    const supervisorProcess = spawn(process.execPath, [cliEntry, ...supervisorArgs], {
      stdio: "ignore",
      detached: true,
      env: process.env,
      windowsHide: true,
    });
    supervisorProcess.once("error", (err) => {
      console.error(`Failed to start Agent Inspector background supervisor: ${err.message}`);
    });
    supervisorProcess.unref();

    if (await waitForInspectorHealthy(port, 10000, host)) {
      console.log(`agent-inspector background server is ready at ${url}`);
      printAccessHints(port, host);
      if (legacyAliasPort !== null) {
        console.log(`   Legacy proxy:  http://localhost:${legacyAliasPort}/proxy`);
        console.log(`   Legacy MCP:    http://localhost:${legacyAliasPort}/api/mcp`);
      }
      console.log(``);
      console.log(`Route AI coding tools through the identity proxy for accurate PID attribution:`);
      console.log(`   Claude Code:  ANTHROPIC_BASE_URL=http://localhost:${port}/proxy claude`);
      console.log(`   OpenCode:     LLM_BASE_URL=http://localhost:${port}/proxy opencode`);
      console.log(`   MiMo Code:    OPENAI_BASE_URL=http://localhost:${port}/proxy mimo`);
      if (open && openWasSpecified) {
        openBrowser(url);
      }
      return;
    }

    console.error(`agent-inspector background server did not become ready at ${url}.`);
    process.exitCode = 1;
    return;
  }

  console.log(`Server running at ${url}`);
  console.log(`   Capture mode: ${captureMode}`);
  console.log(`   Proxy: ${url}/proxy`);
  printAccessHints(port, host);
  if (legacyAliasPort !== null) {
    console.log(`   Legacy UI:      http://localhost:${legacyAliasPort}`);
    console.log(`   Legacy proxy:   http://localhost:${legacyAliasPort}/proxy`);
    console.log(`   Legacy MCP:     http://localhost:${legacyAliasPort}/api/mcp`);
  }
  console.log(``);
  console.log(`Route AI coding tools through the proxy:`);
  console.log(`   Claude Code:  ANTHROPIC_BASE_URL=${url}/proxy claude`);
  console.log(`   OpenCode:     LLM_BASE_URL=${url}/proxy opencode`);
  console.log(`   MiMo Code:    OPENAI_BASE_URL=${url}/proxy mimo`);
  console.log(
    `   Direct HTTP:  curl ${url}/proxy/v1/messages -d '{"model":"...","messages":[...]}'`,
  );
  console.log(
    `   Remote/container tools: use the Network proxy URL above when shown; otherwise restart with --host 0.0.0.0 or a reachable IP.`,
  );
  console.log(``);
  console.log(`Routing environment variables:`);
  console.log(`   ROUTES            JSON map of model prefix -> upstream URL`);
  console.log(`   DEFAULT_UPSTREAM  Fallback upstream for unmatched models`);
  console.log(
    `   Example: ROUTES='{"claude-":"https://api.anthropic.com","MiniMax":"https://api.minimaxi.com/anthropic"}'`,
  );

  if (open) {
    openBrowser(url);
  }

  // Compute server path
  const outputDir = __dirname;
  const serverPath = join(outputDir, "../.output/server/index.mjs");
  const serverCommand = resolveServerCommand(outputDir, serverPath);

  // Start the server with the branded Windows runtime when postinstall created it.
  const serverEnv = { ...process.env };
  if (configDir !== undefined) {
    // Normalize MSYS / Git Bash paths to Windows native form.
    // On Windows, `path.join('/c/Users/foo')` becomes `\c\Users\foo`
    // (leading slash converted to backslash).
    // Child processes spawned by `spawn()` won't follow that style, so
    // rewrite `\c\...` (or any `\x\...` drive) to `C:\...` before
    // handing the path to the proxy server. No-op on already-native
    // Windows paths and on non-Windows platforms.
    let resolvedPath = join(configDir);
    const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
    if (msysMatch !== null) {
      const drive = (msysMatch[1] ?? "").toUpperCase();
      const rest = msysMatch[2] ?? "";
      resolvedPath = `${drive}:\\${rest}`;
    } else {
      resolvedPath = resolvePath(resolvedPath);
    }
    serverEnv["AGENT_INSPECTOR_CONFIG_DIR"] = resolvedPath;
  }
  if (providersJson !== undefined) {
    serverEnv["AGENT_INSPECTOR_PROVIDERS_JSON"] = providersJson;
  }
  if (host !== undefined) {
    serverEnv["HOST"] = host;
    serverEnv["NITRO_HOST"] = host;
  }
  serverEnv["AGENT_INSPECTOR_PUBLIC_PORT"] = String(port);
  serverEnv["PROXY_PORT"] = String(port);
  serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
  const serverProcess = spawn(serverCommand.command, serverCommand.args, {
    stdio: background ? ["ignore", "ignore", "ignore"] : ["ignore", "pipe", "pipe"],
    detached: background,
    env: serverEnv,
    windowsHide: background,
  });
  serverProcess.once("error", (err) => {
    console.error(`Failed to start Agent Inspector runtime: ${err.message}`);
  });
  const serverExit = waitForProcessExit(serverProcess);
  if (!background) {
    pipeServerOutput(serverProcess, {
      enableIdentityProxy,
      publicPort: port,
      upstreamPort,
    });
  }

  // Identity proxy — a native Node HTTP server that sits in front of
  // TanStack Start and looks up each client's PID via the OS connection
  // table, injecting the result as `X-Agent-Inspector-Client-*` headers.
  // This lets AI tools (which can't easily set those headers themselves)
  // still get accurate session attribution. Started only after TanStack
  // Start is healthy, so the upstream is ready by the time clients connect.
  const identityProxies: Array<{
    label: "primary" | "legacy";
    port: number;
    server: import("node:http").Server;
  }> = [];
  if (enableIdentityProxy) {
    const primaryProxy = await tryStartIdentityProxy(port, upstreamPort, host);
    if (primaryProxy !== null) {
      identityProxies.push({ label: "primary", port, server: primaryProxy });
    }
    if (legacyAliasPort !== null) {
      const legacyProxy = await tryStartIdentityProxy(legacyAliasPort, upstreamPort, host);
      if (legacyProxy !== null) {
        identityProxies.push({ label: "legacy", port: legacyAliasPort, server: legacyProxy });
      }
    }
  }

  if (background) {
    serverProcess.unref();
    // Probe the upstream TanStack port directly — the identity proxy is
    // not started yet so the public port is not yet accepting traffic.
    if (await waitForInspectorHealthy(upstreamPort, 5000, host)) {
      console.log(`agent-inspector background server is ready at ${url}`);
      if (identityProxies.length > 0) {
        console.log(`Identity proxy ready on port ${port} (PID-attributing proxy).`);
      }
      printAccessHints(port, host);
      if (legacyAliasPort !== null) {
        console.log(`   Legacy proxy:  http://localhost:${legacyAliasPort}/proxy`);
        console.log(`   Legacy MCP:    http://localhost:${legacyAliasPort}/api/mcp`);
      }
      if (identityProxies.length > 0) {
        console.log(
          ``,
          `Route AI coding tools through the identity proxy for accurate PID attribution:`,
          `   Claude Code:  ANTHROPIC_BASE_URL=http://localhost:${port}/proxy claude`,
          `   OpenCode:     LLM_BASE_URL=http://localhost:${port}/proxy opencode`,
          `   MiMo Code:    OPENAI_BASE_URL=http://localhost:${port}/proxy mimo`,
          legacyAliasPort === null
            ? `   Override the port with --port <n> when needed.`
            : `   Legacy configs using http://localhost:${legacyAliasPort}/proxy still work.`,
        );
      }
      return;
    }
    console.error(`agent-inspector background server did not become ready at ${url}.`);
    process.exitCode = 1;
    return;
  }

  if (identityProxies.length > 0) {
    console.log(``);
    console.log(`Identity proxy ready at ${url} (PID-attributing public entrypoint).`);
    if (legacyAliasPort !== null) {
      const legacyReady = identityProxies.some((proxy) => proxy.label === "legacy");
      if (legacyReady) {
        console.log(`   Legacy alias ready at http://localhost:${legacyAliasPort}.`);
      }
    }
    if (upstreamPort !== port) {
      console.log(`   Internal runtime port ${upstreamPort} is hidden behind the public proxy.`);
    }
  }

  process.exitCode = await serverExit;

  for (const proxy of identityProxies) {
    await new Promise<void>((resolve) => proxy.server.close(() => resolve()));
  }
}
