#!/usr/bin/env python3
"""Command-line control plane for the Codex MMO profile runtime."""

from __future__ import annotations

import argparse
import difflib
import os
import re
import subprocess
import sys
import textwrap
import traceback
from pathlib import Path
from typing import Any, Never

from mmo_catalog import (
    catalog_data,
    catalog_summary,
    discover_codex,
    discover_opencode_go,
    discover_opencode_zen,
    discover_openrouter,
    discover_zai,
    find_model,
    list_models,
    local_inventory_report,
    refresh_discovery,
    verify_catalog,
)
from mmo_cli_output import (
    emit_error,
    emit_json,
    emit_scalar,
    emit_structured,
    emit_tty_success,
    emit_usage_error,
    progress,
    stdout_is_tty,
)
from mmo_diagnostics import (
    doctor,
    profile_validation_report,
    smoke_profile,
    tool_mcp_status,
)
from mmo_eval import (
    compare_runs,
    discover_suites,
    list_runs,
    load_run,
    run_evaluation,
    validate_suite,
)
from mmo_gateway import (
    ensure_gateway,
    gateway_models,
    gateway_status,
    list_gateways,
    stop_gateway,
    stop_idle_gateways,
)
from mmo_profiles import (
    active_profile_id,
    clone_profile,
    discover_profiles,
    install_profile_pack,
    load_settings,
    profile_summary,
    remove_profile,
    resolve_profile,
    set_active_profile,
)
from mmo_runtime import (
    cancel_job,
    cancel_session,
    clean_state,
    compact_session,
    continue_session,
    detach_session,
    iter_session_runs,
    iter_sessions,
    launch_interactive,
    list_jobs,
    load_session,
    pause_session,
    public_run,
    public_session,
    read_result,
    resolve_resume_session,
    resume_interactive,
    run_root_exec,
    stop_session,
    wait_for_jobs,
)
from mmo_snapshot import compile_profile
from mmo_tool_mcp import (
    load_tool_mcp_registry_with_sources,
)
from mmo_util import (
    filtered_environment,
    package_version,
    shell_exit_status,
)


class CLIUsageError(Exception):
    """A command-line contract error that should exit with status 2."""

    def __init__(self, message: str, *, hint: str | None = None) -> None:
        super().__init__(message)
        self.hint = hint


class MMOArgumentParser(argparse.ArgumentParser):
    """Argparse with stable no-color output and catchable usage failures."""

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        kwargs.setdefault("allow_abbrev", False)
        if sys.version_info >= (3, 14):
            kwargs.setdefault("color", False)
        super().__init__(*args, **kwargs)

    def error(self, message: str) -> Never:
        raise CLIUsageError(message)


def _bindings(values: list[str] | None) -> dict[str, str]:
    result: dict[str, str] = {}
    for raw in values or []:
        if "=" not in raw:
            raise ValueError(f"invalid binding {raw!r}; use AGENT=MODEL")
        agent, model = raw.split("=", 1)
        if not agent or not model:
            raise ValueError(f"invalid binding {raw!r}; use AGENT=MODEL")
        if agent in result:
            raise ValueError(f"duplicate binding for agent {agent!r}")
        result[agent] = model
    return result


_CODEX_OPTIONS_WITH_VALUES = frozenset(
    {
        "-a",
        "--add-dir",
        "--ask-for-approval",
        "-C",
        "--cd",
        "-c",
        "--config",
        "--disable",
        "--enable",
        "--local-provider",
        "-m",
        "--model",
        "-p",
        "--profile",
        "--remote",
        "--remote-auth-token-env",
        "-s",
        "--sandbox",
    }
)
_CODEX_LONG_OPTIONS_WITH_VALUES = tuple(
    option for option in _CODEX_OPTIONS_WITH_VALUES if option.startswith("--")
)
_CODEX_SHORT_OPTIONS_WITH_VALUES = tuple(
    option for option in _CODEX_OPTIONS_WITH_VALUES if option.startswith("-") and len(option) == 2
)


def _codex_subcommand(arguments: list[str]) -> str | None:
    """Return the first Codex positional after its documented global options."""

    index = 0
    while index < len(arguments):
        token = arguments[index]
        if token == "--":
            return None
        if (
            token in {"-i", "--image"}
            or token.startswith("--image=")
            or (token.startswith("-i") and token != "-i")
        ):
            index += 1
            if token in {"-i", "--image"} and index < len(arguments):
                index += 1
            while index < len(arguments) and not arguments[index].startswith("-"):
                index += 1
            continue
        if token in _CODEX_OPTIONS_WITH_VALUES:
            index += 2
            continue
        if any(token.startswith(option + "=") for option in _CODEX_LONG_OPTIONS_WITH_VALUES):
            index += 1
            continue
        if any(
            token.startswith(option) and token != option
            for option in _CODEX_SHORT_OPTIONS_WITH_VALUES
        ):
            index += 1
            continue
        if token.startswith("-"):
            index += 1
            continue
        return token
    return None


def _tail(path: Path, lines: int) -> str:
    if not path.is_file():
        return ""
    values = path.read_text(encoding="utf-8", errors="replace").splitlines()
    return "\n".join(values[-lines:])


def _bounded_int(minimum: int, maximum: int) -> Any:
    def parse(value: str) -> int:
        try:
            parsed = int(value)
        except ValueError as exc:
            raise argparse.ArgumentTypeError("must be an integer") from exc
        if not minimum <= parsed <= maximum:
            raise argparse.ArgumentTypeError(f"must be between {minimum} and {maximum}")
        return parsed

    return parse


def _bounded_float(minimum: float, maximum: float) -> Any:
    def parse(value: str) -> float:
        try:
            parsed = float(value)
        except ValueError as exc:
            raise argparse.ArgumentTypeError("must be a number") from exc
        if not minimum <= parsed <= maximum:
            raise argparse.ArgumentTypeError(f"must be between {minimum:g} and {maximum:g}")
        return parsed

    return parse


def _configured_codex_context(binary: str | None, home: Path | None) -> tuple[str, Path | None]:
    """Apply explicit option, environment, then settings precedence."""

    settings = load_settings()
    selected_binary = (
        binary or os.environ.get("MMO_CODEX_BIN") or str(settings.get("codex_bin", "codex"))
    )
    if home is not None:
        selected_home = home.expanduser()
    elif os.environ.get("CODEX_HOME"):
        selected_home = Path(os.environ["CODEX_HOME"]).expanduser()
    else:
        selected_home = Path(str(settings.get("base_codex_home", "~/.codex"))).expanduser()
    return selected_binary, selected_home


def _parser(*, prog: str = "codex-mmoctl") -> MMOArgumentParser:
    first_workflow = (
        (prog, "Launch a new interactive session.")
        if prog == "codex-mmo"
        else (f"{prog} profile list", "Inspect installed profiles.")
    )
    workflow_lines = "\n".join(
        f"  {invocation:<36} {description}"
        for invocation, description in (
            first_workflow,
            (f"{prog} resume --last", "Reattach to the latest resumable session here."),
            (f"{prog} session list", "Inspect retained session state."),
            (f"{prog} jobs list --session ID", "Inspect workers belonging to one session."),
        )
    )
    parser = MMOArgumentParser(
        prog=prog,
        description=(
            "Run persistent Codex MMO sessions and inspect their profiles, workers, "
            "gateways, and retained evidence."
        ),
        epilog=(
            "Common workflows:\n"
            f"{workflow_lines}\n\n"
            f"Run '{prog} COMMAND --help' for command-specific usage. Structured commands\n"
            "use human output on a terminal and JSON when redirected; --json forces JSON."
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "--json",
        action="store_true",
        dest="global_json",
        help="force structured JSON output where the command supports it",
    )
    parser.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        help="suppress MMO progress messages; requested output and errors remain",
    )
    parser.add_argument(
        "--debug",
        action="store_true",
        help="include exception details and tracebacks when a command fails",
    )
    parser.add_argument(
        "--version",
        action="store_true",
        dest="show_version",
        help="print the Codex MMO version and exit",
    )
    sub = parser.add_subparsers(dest="command", metavar="COMMAND")

    def command(
        actions: argparse._SubParsersAction[Any],
        name: str,
        summary: str,
        *,
        description: str | None = None,
        epilog: str | None = None,
    ) -> MMOArgumentParser:
        return actions.add_parser(
            name,
            help=summary,
            description=description or summary.capitalize() + ".",
            epilog=epilog,
            formatter_class=argparse.RawDescriptionHelpFormatter,
        )

    command(sub, "version", "print the Codex MMO package version")

    run = command(
        sub,
        "run",
        "launch a new interactive Codex session",
        description=(
            "Create a new immutable MMO session and attach the stock Codex TUI. "
            "Use resume to reattach to existing work."
        ),
        epilog=f"Pass Codex-owned options after '--', for example:\n  {prog} run -- --search",
    )
    run.add_argument("--profile", "-p", metavar="PROFILE", help="profile ID (default: active)")
    run.add_argument(
        "--cwd", "-C", metavar="PATH", default=os.getcwd(), help="session working directory"
    )
    run.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="override one profile agent binding; repeat for multiple agents",
    )
    run.add_argument(
        "codex_args",
        metavar="CODEX_ARG",
        nargs=argparse.REMAINDER,
        help="arguments forwarded to Codex; put '--' before Codex-owned options",
    )

    resume = command(
        sub,
        "resume",
        "reattach to a resumable persistent MMO session",
        description=(
            "Reattach to the exact retained session and current root-thread generation. "
            "Terminal sessions cannot be resumed."
        ),
    )
    resume.add_argument(
        "identifier", metavar="SESSION_OR_THREAD_ID", nargs="?", help="session or root thread ID"
    )
    resume.add_argument(
        "--last", action="store_true", help="select the most recently active resumable session"
    )
    resume.add_argument(
        "--all",
        action="store_true",
        dest="all_cwds",
        help="with --last, search every working directory instead of the current one",
    )
    resume.add_argument(
        "--allow-tainted",
        action="store_true",
        help="allow reattachment to state marked unsafe after a boundary violation",
    )

    execute = command(
        sub,
        "exec",
        "run one noninteractive root task",
        description=(
            "Run one task through the persistent app-server host without a TUI. Supply PROMPT "
            "or pipe non-empty UTF-8 text on stdin."
        ),
        epilog=f"Example:\n  printf '%s\\n' 'Inspect the failure' | {prog} exec --profile PROFILE",
    )
    execute.add_argument("prompt", metavar="PROMPT", nargs="?", help="task prompt")
    execute.add_argument("--profile", "-p", metavar="PROFILE", help="profile ID (default: active)")
    execute.add_argument(
        "--cwd", "-C", metavar="PATH", default=os.getcwd(), help="task working directory"
    )
    execute.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="override one profile agent binding; repeat for multiple agents",
    )
    execute.add_argument(
        "--image", metavar="PATH", action="append", default=[], help="attach an image; repeatable"
    )
    execute.add_argument(
        "--wall-timeout",
        metavar="SECONDS",
        type=_bounded_int(1, 172_800),
        help="detach the caller after this harness limit while preserving active work (1-172800)",
    )
    execute.add_argument(
        "--sandbox",
        choices=["read-only", "workspace-write"],
        default=None,
        help="override the root task sandbox mode",
    )

    profile = command(sub, "profile", "inspect and manage composition profiles")
    ps = profile.add_subparsers(dest="profile_command", required=True, metavar="ACTION")
    command(ps, "list", "list installed profiles")
    command(ps, "current", "print the active profile ID")
    show = command(ps, "show", "show one profile")
    show.add_argument("profile", metavar="PROFILE", help="profile ID or profile-pack path")
    show.add_argument(
        "--resolved", action="store_true", help="include inherited defaults and resolved bindings"
    )
    validate = command(ps, "validate", "validate one profile without launching models")
    validate.add_argument(
        "profile", metavar="PROFILE", nargs="?", help="profile ID (default: active)"
    )
    validate.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="validate a binding override",
    )
    compile_p = command(ps, "compile", "compile one immutable profile snapshot")
    compile_p.add_argument(
        "profile", metavar="PROFILE", nargs="?", help="profile ID (default: active)"
    )
    compile_p.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="compile a binding override",
    )
    compile_p.add_argument(
        "--force",
        action="store_true",
        help="rebuild the snapshot even when an identical one exists",
    )
    use = command(ps, "use", "select the default profile")
    use.add_argument("profile", metavar="PROFILE", help="installed profile ID")
    install = command(ps, "install", "install a static profile pack")
    install.add_argument("source", metavar="PATH", help="profile-pack directory")
    install.add_argument(
        "--replace", action="store_true", help="replace an installed user profile with the same ID"
    )
    remove = command(ps, "remove", "remove an installed user profile")
    remove.add_argument("profile", metavar="PROFILE", help="user profile ID")
    clone = command(ps, "clone", "clone a profile into user configuration")
    clone.add_argument("source", metavar="SOURCE", help="source profile ID")
    clone.add_argument("destination", metavar="DESTINATION", help="new user profile ID")
    clone.add_argument(
        "--replace", action="store_true", help="replace an existing destination profile"
    )
    doctor_p = command(ps, "doctor", "diagnose one profile and its runtime dependencies")
    doctor_p.add_argument(
        "profile", metavar="PROFILE", nargs="?", help="profile ID (default: active)"
    )
    doctor_p.add_argument(
        "--live", action="store_true", help="check live binaries, credentials, and routes"
    )
    doctor_p.add_argument(
        "--probe", action="store_true", help="with --live, send a real root-model probe request"
    )
    doctor_p.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="diagnose a binding override",
    )
    smoke = command(ps, "smoke", "exercise a profile through its declared execution backends")
    smoke.add_argument("profile", metavar="PROFILE", nargs="?", help="profile ID (default: active)")
    smoke.add_argument(
        "--cwd", "-C", metavar="PATH", default=os.getcwd(), help="temporary smoke workspace parent"
    )
    smoke.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="smoke-test a binding override",
    )
    smoke_selection = smoke.add_mutually_exclusive_group()
    smoke_selection.add_argument(
        "--root-only", action="store_true", help="exercise only the root agent"
    )
    smoke_selection.add_argument(
        "--workers-only", action="store_true", help="exercise only worker agents"
    )

    tool_mcp = command(
        sub,
        "tool-mcp",
        "inspect operator-owned third-party Tool MCP definitions",
    )
    tms = tool_mcp.add_subparsers(dest="tool_mcp_command", required=True, metavar="ACTION")
    command(tms, "list", "list configured Tool MCP servers")
    tool_mcp_show = command(tms, "show", "show one server definition and readiness report")
    tool_mcp_show.add_argument("server", metavar="SERVER", help="Tool MCP server ID")
    tool_mcp_validate = command(tms, "validate", "validate one or all Tool MCP definitions")
    tool_mcp_validate.add_argument(
        "server", metavar="SERVER", nargs="?", help="server ID (default: all)"
    )

    catalog = command(sub, "catalog", "inspect and verify the provider/model catalog")
    cs = catalog.add_subparsers(dest="catalog_command", required=True, metavar="ACTION")
    summary = command(cs, "summary", "summarize routes, models, and resource groups")
    summary.add_argument("--profile", metavar="PROFILE", help="apply one profile's catalog overlay")
    models = command(cs, "models", "list catalog models")
    models.add_argument("--profile", metavar="PROFILE", help="apply one profile's catalog overlay")
    models.add_argument("--route", metavar="ROUTE", help="filter by route ID")
    models.add_argument("--inventory", metavar="INVENTORY", help="filter by inventory source")
    models.add_argument("--query", metavar="TEXT", help="case-insensitive model search")
    models.add_argument("--availability", metavar="STATUS", help="filter by observed availability")
    compatibility = models.add_mutually_exclusive_group()
    compatibility.add_argument(
        "--agent-compatible", action="store_true", help="show only executable agent models"
    )
    compatibility.add_argument(
        "--catalog-only", action="store_true", help="show only inventory-only models"
    )
    model = command(cs, "model", "show one route-qualified model")
    model.add_argument("key", metavar="ROUTE__MODEL_KEY", help="route-qualified model key")
    model.add_argument("--profile", metavar="PROFILE", help="apply one profile's catalog overlay")
    routes = command(cs, "routes", "list catalog routes")
    routes.add_argument("--profile", metavar="PROFILE", help="apply one profile's catalog overlay")
    resources = command(cs, "resources", "list shared capacity resource groups")
    resources.add_argument(
        "--profile", metavar="PROFILE", help="apply one profile's catalog overlay"
    )
    command(cs, "inventory", "verify the bundled local inventory snapshot")
    verify = command(cs, "verify", "verify local and optional live catalog evidence")
    verify.add_argument(
        "--remote", action="store_true", help="query configured remote provider catalogs"
    )
    verify.add_argument(
        "--codex", action="store_true", help="query the installed Codex model catalog"
    )
    verify.add_argument("--opencode-url", metavar="URL", help="override the OpenCode Go models URL")
    verify.add_argument(
        "--opencode-zen-url", metavar="URL", help="override the OpenCode Zen models URL"
    )
    verify.add_argument(
        "--openrouter-url", metavar="URL", help="override the OpenRouter models URL"
    )
    verify.add_argument(
        "--zai-coding-url", metavar="URL", help="override the Z.AI Coding models URL"
    )
    verify.add_argument("--codex-bin", metavar="PATH", help="Codex executable override")
    verify.add_argument("--codex-home", metavar="PATH", type=Path, help="base Codex home override")
    verify.add_argument(
        "--timeout",
        metavar="SECONDS",
        type=_bounded_float(0.1, 300.0),
        default=10.0,
        help="per-request timeout (default: 10)",
    )
    discover = command(cs, "discover", "query one provider's live model inventory")
    discover.add_argument(
        "source",
        metavar="SOURCE",
        choices=[
            "codex",
            "opencode-go",
            "opencode-zen",
            "openrouter",
            "zai-api",
            "zai-coding-plan",
        ],
        help="provider inventory to query",
    )
    discover.add_argument("--url", metavar="URL", help="provider URL override where supported")
    discover.add_argument("--opencode-url", metavar="URL", help="OpenCode Go URL override")
    discover.add_argument("--opencode-zen-url", metavar="URL", help="OpenCode Zen URL override")
    discover.add_argument("--codex-bin", metavar="PATH", help="Codex executable override")
    discover.add_argument(
        "--codex-home", metavar="PATH", type=Path, help="base Codex home override"
    )
    discover.add_argument(
        "--timeout",
        metavar="SECONDS",
        type=_bounded_float(0.1, 300.0),
        help="request timeout for remote sources (default: 10)",
    )
    refresh = command(cs, "refresh", "refresh retained discovery evidence")
    refresh.add_argument("--no-remote", action="store_true", help="skip remote provider discovery")
    refresh.add_argument("--no-codex", action="store_true", help="skip installed Codex discovery")
    refresh.add_argument(
        "--install-codex-overlay",
        action="store_true",
        help="install the successfully discovered Codex catalog overlay",
    )
    refresh.add_argument(
        "--opencode-url", metavar="URL", help="override the OpenCode Go models URL"
    )
    refresh.add_argument(
        "--opencode-zen-url", metavar="URL", help="override the OpenCode Zen models URL"
    )
    refresh.add_argument(
        "--openrouter-url", metavar="URL", help="override the OpenRouter models URL"
    )
    refresh.add_argument(
        "--zai-coding-url", metavar="URL", help="override the Z.AI Coding models URL"
    )
    refresh.add_argument("--codex-bin", metavar="PATH", help="Codex executable override")
    refresh.add_argument("--codex-home", metavar="PATH", type=Path, help="base Codex home override")
    refresh.add_argument(
        "--timeout",
        metavar="SECONDS",
        type=_bounded_float(0.1, 300.0),
        default=10.0,
        help="per-request timeout (default: 10)",
    )

    gateway = command(sub, "gateway", "inspect and control local Switchyard gateways")
    gs = gateway.add_subparsers(dest="gateway_command", required=True, metavar="ACTION")
    command(gs, "list", "list retained gateway processes")
    gateway_summaries = {
        "start": "start the gateway required by a profile",
        "stop": "stop the gateway used by a profile",
        "status": "show gateway status for a profile",
        "models": "query models exposed by a profile gateway",
        "logs": "print the tail of a profile gateway log",
    }
    for name, summary_text in gateway_summaries.items():
        item = command(gs, name, summary_text)
        item.add_argument(
            "profile", metavar="PROFILE", nargs="?", help="profile ID (default: active)"
        )
        if name == "logs":
            item.add_argument(
                "--lines",
                metavar="COUNT",
                type=_bounded_int(1, 100_000),
                default=100,
                help="lines to print (default: 100)",
            )
    command(gs, "stop-idle", "stop gateways whose idle timeout has elapsed")

    session = command(sub, "session", "inspect and control persistent MMO sessions")
    ss = session.add_subparsers(dest="session_command", required=True, metavar="ACTION")
    list_s = command(ss, "list", "list retained sessions, newest activity first")
    list_s.add_argument(
        "--limit",
        metavar="COUNT",
        type=_bounded_int(1, 10_000),
        default=50,
        help="maximum sessions (default: 50)",
    )
    show_s = command(ss, "show", "show lifecycle and recovery details for one session")
    show_s.add_argument("session_id", metavar="SESSION_ID", help="MMO session ID")
    runs_s = command(ss, "runs", "list the immutable run belonging to one session")
    runs_s.add_argument("session_id", metavar="SESSION_ID", help="MMO session ID")
    runs_s.add_argument(
        "--limit",
        metavar="COUNT",
        type=_bounded_int(1, 10_000),
        default=50,
        help="maximum runs (default: 50)",
    )
    cancel_s = command(ss, "cancel", "immediately terminate a session while retaining evidence")
    cancel_s.add_argument("session_id", metavar="SESSION_ID", help="MMO session ID")
    detach_s = command(ss, "detach", "disconnect the client while admitted work continues")
    detach_s.add_argument("session_id", metavar="SESSION_ID", help="MMO session ID")
    pause_s = command(ss, "pause", "checkpoint and cold-pause a session and its workers")
    pause_s.add_argument("session_id", metavar="SESSION_ID", help="MMO session ID")
    compact_s = command(ss, "compact", "compact a paused root thread and return it to cold pause")
    compact_s.add_argument("session_id", metavar="SESSION_ID", help="paused MMO session ID")
    continue_s = command(ss, "continue", "reactivate the same paused root and worker threads")
    continue_s.add_argument("session_id", metavar="SESSION_ID", help="paused MMO session ID")
    continue_s.add_argument(
        "--input", metavar="TEXT", help="new operator input delivered on reactivation"
    )
    continue_s.add_argument(
        "--goal-token-budget",
        metavar="TOKENS",
        type=_bounded_int(10_000, 100_000_000),
        help="increase the total goal budget within the compiled ceiling",
    )
    stop_s = command(ss, "stop", "request evidence finalization, then retire every host")
    stop_s.add_argument("session_id", metavar="SESSION_ID", help="MMO session ID")
    stop_s.add_argument(
        "--grace",
        metavar="SECONDS",
        type=_bounded_int(0, 3600),
        default=120,
        help="finalization grace before forced retirement (default: 120)",
    )

    jobs = command(sub, "jobs", "inspect worker jobs and retrieve retained evidence")
    js = jobs.add_subparsers(dest="jobs_command", required=True, metavar="ACTION")
    list_j = command(js, "list", "list retained jobs, newest first")
    list_j.add_argument("--session", metavar="SESSION_ID", help="filter by session")
    list_j.add_argument("--run", metavar="RUN_ID", help="filter by immutable run")
    list_j.add_argument(
        "--limit",
        metavar="COUNT",
        type=_bounded_int(1, 10_000),
        default=50,
        help="maximum jobs (default: 50)",
    )
    status_j = command(js, "status", "show one or more jobs in the supplied order")
    status_j.add_argument("job_ids", metavar="JOB_ID", nargs="+", help="job IDs")
    result_j = command(
        js,
        "result",
        "read one losslessly paged job result",
        epilog="Follow next_cursor until it is null; concatenate text pages in cursor order.",
    )
    result_j.add_argument("job_id", metavar="JOB_ID", help="job ID")
    result_j.add_argument(
        "--cursor",
        metavar="OFFSET",
        type=_bounded_int(0, 2_147_483_647),
        default=0,
        help="character cursor (default: 0)",
    )
    result_j.add_argument(
        "--max-chars",
        metavar="COUNT",
        type=_bounded_int(500, 500_000),
        help="maximum characters in this page (500-500000)",
    )
    wait_j = command(js, "wait", "wait briefly for one or more jobs without discarding work")
    wait_j.add_argument("job_ids", metavar="JOB_ID", nargs="+", help="job IDs")
    wait_j.add_argument(
        "--timeout",
        metavar="SECONDS",
        type=_bounded_int(0, 120),
        default=30,
        help="caller wait limit (default: 30; maximum: 120)",
    )
    wait_j.add_argument("--session", metavar="SESSION_ID", required=True, help="owning session ID")
    cancel_j = command(js, "cancel", "cancel a job and, by default, its descendants")
    cancel_j.add_argument("job_id", metavar="JOB_ID", help="job ID")
    cancel_j.add_argument("--no-cascade", action="store_true", help="leave descendant jobs running")
    cancel_j.add_argument("--reason", metavar="TEXT", help="operator cancellation reason")

    eval_p = command(sub, "eval", "validate and run profile evaluation suites")
    es = eval_p.add_subparsers(dest="eval_command", required=True, metavar="ACTION")
    command(es, "suites", "list installed evaluation suites")
    ev = command(es, "validate", "validate one evaluation suite without running it")
    ev.add_argument("suite", metavar="SUITE", help="suite ID or path")
    er = command(es, "run", "run one profile against an evaluation suite")
    er.add_argument("--profile", "-p", metavar="PROFILE", required=True, help="profile ID")
    er.add_argument(
        "--suite",
        metavar="SUITE",
        default="codex-harness",
        help="suite ID (default: codex-harness)",
    )
    er.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="override one profile binding; repeatable",
    )
    er.add_argument(
        "--wall-timeout",
        metavar="SECONDS",
        type=_bounded_int(1, 172_800),
        help="override each task's external harness limit",
    )
    er.add_argument(
        "--trial-mode",
        choices=("development", "release"),
        default="development",
        help="use the suite's development or release trial count (default: development)",
    )
    er.add_argument(
        "--dry-run", action="store_true", help="validate and plan trials without launching agents"
    )
    el = command(es, "list", "list retained evaluation runs")
    el.add_argument(
        "--limit",
        metavar="COUNT",
        type=_bounded_int(1, 10_000),
        default=100,
        help="maximum runs (default: 100)",
    )
    eshow = command(es, "show", "show one retained evaluation run")
    eshow.add_argument("run_id", metavar="RUN_ID", help="evaluation run ID")
    ecompare = command(es, "compare", "compare two or more retained evaluation runs")
    ecompare.add_argument("run_ids", metavar="RUN_ID", nargs="+", help="at least two run IDs")

    doc = command(sub, "doctor", "diagnose the active or selected profile")
    doc.add_argument("--profile", "-p", metavar="PROFILE", help="profile ID (default: active)")
    doc.add_argument(
        "--live", action="store_true", help="check live binaries, credentials, and routes"
    )
    doc.add_argument(
        "--probe", action="store_true", help="with --live, send a real root-model probe request"
    )
    doc.add_argument(
        "--bind",
        metavar="AGENT=MODEL",
        action="append",
        default=[],
        help="diagnose a binding override",
    )
    validate_all = command(
        sub, "validate", "validate profiles, catalog inventory, and evaluation suites"
    )
    validate_all.add_argument(
        "--all-profiles",
        action="store_true",
        help="validate every installed profile instead of only the active profile",
    )
    clean = command(
        sub,
        "clean",
        "delete terminal jobs and sessions older than their retention thresholds",
        description=(
            "Permanently delete only terminal job and session state older than the selected "
            "thresholds. Active work is never eligible. Use --dry-run to preview counts."
        ),
    )
    clean.add_argument(
        "--job-days",
        metavar="DAYS",
        type=_bounded_int(0, 36_500),
        help="job retention threshold (default: settings.toml)",
    )
    clean.add_argument(
        "--session-days",
        metavar="DAYS",
        type=_bounded_int(0, 36_500),
        help="session retention threshold (default: settings.toml)",
    )
    clean.add_argument(
        "--dry-run", action="store_true", help="count eligible records without deleting them"
    )
    command(sub, "prompt", "print the reusable orchestration prompt")

    auth = command(sub, "auth", "manage built-in Codex authentication in the base Codex home")
    aus = auth.add_subparsers(dest="auth_command", required=True, metavar="ACTION")
    command(aus, "status", "show Codex login status for the configured base home")
    command(aus, "login", "run the interactive Codex login flow in the configured base home")
    return parser


_GLOBAL_FLAGS = frozenset({"--json", "-q", "--quiet", "--debug"})


def _subparser_action(parser: argparse.ArgumentParser) -> argparse._SubParsersAction[Any] | None:
    return next(
        (action for action in parser._actions if isinstance(action, argparse._SubParsersAction)),
        None,
    )


def _normalize_global_options(argv: list[str]) -> list[str]:
    """Allow MMO global flags anywhere before the explicit passthrough separator."""

    global_flags: list[str] = []
    remaining: list[str] = []
    passthrough = False
    for token in argv:
        if token == "--":
            passthrough = True
            remaining.append(token)
        elif not passthrough and token in _GLOBAL_FLAGS:
            if token not in global_flags:
                global_flags.append(token)
        else:
            remaining.append(token)
    return [*global_flags, *remaining]


def _route_argv(
    parser: argparse.ArgumentParser,
    argv: list[str],
    *,
    implicit_run: bool,
) -> list[str]:
    normalized = _normalize_global_options(argv)
    if not implicit_run:
        return normalized
    split = 0
    while split < len(normalized) and normalized[split] in _GLOBAL_FLAGS:
        split += 1
    globals_prefix = normalized[:split]
    command_argv = normalized[split:]
    top_level = _subparser_action(parser)
    choices = set(top_level.choices) if top_level is not None else set()
    if not command_argv:
        return [*globals_prefix, "run"]
    first = command_argv[0]
    if first in {"-h", "--help", "--version"} or first in choices:
        return normalized
    return [*globals_prefix, "run", *command_argv]


def _selected_parser(
    parser: argparse.ArgumentParser,
    argv: list[str],
) -> argparse.ArgumentParser:
    current = parser
    for token in argv:
        if token == "--":
            break
        if token in _GLOBAL_FLAGS or token == "--version":
            continue
        action = _subparser_action(current)
        if action is not None and token in action.choices:
            current = action.choices[token]
    return current


def _usage_hint(
    message: str,
    selected: argparse.ArgumentParser,
    root: argparse.ArgumentParser,
) -> str | None:
    candidates: list[str] = []
    action = _subparser_action(selected)
    if action is not None:
        candidates.extend(action.choices)
    for parser in (selected, root):
        candidates.extend(parser._option_string_actions)
    invalid_choice = re.search(r"invalid choice: ['\"]([^'\"]+)['\"]", message)
    unknown_option = re.search(r"unrecognized arguments?:\s+(--?[A-Za-z0-9][\w-]*)", message)
    value = invalid_choice.group(1) if invalid_choice else None
    if value is None and unknown_option:
        value = unknown_option.group(1)
    if value is None:
        return None
    matches = difflib.get_close_matches(value, sorted(set(candidates)), n=1, cutoff=0.72)
    return f"Did you mean {matches[0]!r}?" if matches else None


def _command_path(args: argparse.Namespace) -> str:
    parts = [str(args.command)] if getattr(args, "command", None) else []
    for attribute in (
        "profile_command",
        "tool_mcp_command",
        "catalog_command",
        "gateway_command",
        "session_command",
        "jobs_command",
        "eval_command",
        "auth_command",
    ):
        value = getattr(args, attribute, None)
        if value:
            parts.append(str(value))
    return ".".join(parts)


def _validate_cli_args(args: argparse.Namespace) -> None:
    path = _command_path(args)
    if args.show_version and args.command is not None:
        raise CLIUsageError("--version cannot be combined with a command")
    if not args.show_version and args.command is None:
        raise CLIUsageError(
            "a command is required",
            hint="Run with --help to see the available commands.",
        )
    if args.global_json and path in {
        "run",
        "resume",
        "gateway.logs",
        "auth.status",
        "auth.login",
    }:
        raise CLIUsageError(
            f"--json is not supported by {path.replace('.', ' ')}",
            hint="This command attaches a terminal UI or forwards a raw external text stream.",
        )
    if path == "resume":
        if bool(args.identifier) == bool(args.last):
            raise CLIUsageError("provide exactly one SESSION_OR_THREAD_ID or --last")
        if args.all_cwds and not args.last:
            raise CLIUsageError("--all is valid only with --last")
    if path in {"doctor", "profile.doctor"} and args.probe and not args.live:
        raise CLIUsageError(
            "--probe requires --live",
            hint="A probe sends a real model request after live dependency checks pass.",
        )
    if path == "eval.compare" and len(args.run_ids) < 2:
        raise CLIUsageError("compare requires at least two RUN_ID arguments")
    if path == "catalog.refresh" and args.no_codex and args.install_codex_overlay:
        raise CLIUsageError("--no-codex cannot be combined with --install-codex-overlay")
    if path == "catalog.refresh" and args.no_remote:
        remote_overrides = [
            name
            for name, value in (
                ("--opencode-url", args.opencode_url),
                ("--opencode-zen-url", args.opencode_zen_url),
                ("--openrouter-url", args.openrouter_url),
                ("--zai-coding-url", args.zai_coding_url),
            )
            if value is not None
        ]
        if remote_overrides:
            raise CLIUsageError(
                f"--no-remote cannot be combined with {', '.join(remote_overrides)}"
            )
    if path == "catalog.refresh" and args.no_codex:
        codex_overrides = [
            name
            for name, value in (
                ("--codex-bin", args.codex_bin),
                ("--codex-home", args.codex_home),
            )
            if value is not None
        ]
        if codex_overrides:
            raise CLIUsageError(f"--no-codex cannot be combined with {', '.join(codex_overrides)}")
    if path == "catalog.discover":
        invalid: list[str] = []
        if args.source == "codex":
            invalid.extend(
                name
                for name, value in (
                    ("--url", args.url),
                    ("--opencode-url", args.opencode_url),
                    ("--opencode-zen-url", args.opencode_zen_url),
                    ("--timeout", args.timeout),
                )
                if value is not None
            )
        else:
            invalid.extend(
                name
                for name, value in (
                    ("--codex-bin", args.codex_bin),
                    ("--codex-home", args.codex_home),
                )
                if value is not None
            )
            if args.source != "opencode-go" and args.opencode_url is not None:
                invalid.append("--opencode-url")
            if args.source != "opencode-zen" and args.opencode_zen_url is not None:
                invalid.append("--opencode-zen-url")
            if (
                args.source == "opencode-go"
                and args.url is not None
                and args.opencode_url is not None
            ):
                raise CLIUsageError("use either --url or --opencode-url, not both")
            if (
                args.source == "opencode-zen"
                and args.url is not None
                and args.opencode_zen_url is not None
            ):
                raise CLIUsageError("use either --url or --opencode-zen-url, not both")
        if invalid:
            raise CLIUsageError(
                f"{', '.join(sorted(set(invalid)))} not valid for catalog source {args.source!r}"
            )


def _exec_prompt(args: argparse.Namespace) -> str:
    if args.prompt is not None:
        if not args.prompt.strip():
            raise CLIUsageError("PROMPT must contain non-whitespace text")
        return str(args.prompt)
    if bool(getattr(sys.stdin, "isatty", lambda: False)()):
        raise CLIUsageError(
            "exec requires PROMPT or piped stdin",
            hint="Supply a positional prompt or pipe non-empty text into codex-mmo exec.",
        )
    prompt = sys.stdin.read()
    if not prompt.strip():
        raise CLIUsageError("piped stdin for exec must contain non-whitespace text")
    return prompt


def _error_category(exc: Exception) -> str:
    if isinstance(exc, FileNotFoundError):
        return "not_found"
    if isinstance(exc, ValueError):
        return "invalid_input"
    if isinstance(exc, (RuntimeError, TimeoutError, ConnectionError, OSError)):
        return "runtime"
    return "internal"


def _error_hint(path: str, exc: Exception) -> str | None:
    if isinstance(exc, FileNotFoundError):
        if path.startswith("profile"):
            return "Run 'codex-mmo profile list' to inspect installed profiles."
        if path.startswith("session") or path == "resume":
            return "Run 'codex-mmo session list' to inspect retained and resumable sessions."
        if path.startswith("jobs"):
            return "Run 'codex-mmo jobs list' with the owning session or run filter."
    if path.startswith("catalog"):
        return "Run 'codex-mmo catalog --help' to inspect discovery and verification options."
    return None


def _broken_pipe_status() -> int:
    """Retire stdout so interpreter shutdown cannot print another pipe error."""

    try:
        descriptor = os.open(os.devnull, os.O_WRONLY)
        os.dup2(descriptor, sys.stdout.fileno())
        os.close(descriptor)
    except OSError:
        pass
    return 141


def _handle_catalog(args: argparse.Namespace) -> int:
    """Dispatch catalog commands outside the general control-plane router."""

    cmd = args.catalog_command
    if cmd == "summary":
        emit_structured(
            "catalog.summary", catalog_summary(args.profile), force_json=args.global_json
        )
        return 0
    if cmd == "models":
        compatible = True if args.agent_compatible else (False if args.catalog_only else None)
        emit_structured(
            "catalog.models",
            list_models(
                profile=args.profile,
                route=args.route,
                inventory=args.inventory,
                query=args.query,
                agent_compatible=compatible,
                availability=args.availability,
            ),
            force_json=args.global_json,
        )
        return 0
    if cmd == "model":
        emit_structured(
            "catalog.model",
            find_model(args.key, profile=args.profile),
            force_json=args.global_json,
        )
        return 0
    if cmd in {"routes", "resources"}:
        emit_structured(
            f"catalog.{cmd}", catalog_data(args.profile)[cmd], force_json=args.global_json
        )
        return 0
    if cmd == "inventory":
        report = local_inventory_report()
        emit_structured("catalog.inventory", report, force_json=args.global_json)
        return 0 if report["passed"] else 1
    if cmd == "verify":
        progress("Verifying catalog evidence...", quiet=args.quiet)
        codex_binary, codex_home = _configured_codex_context(args.codex_bin, args.codex_home)
        report = verify_catalog(
            remote=args.remote
            or any(
                value is not None
                for value in (
                    args.opencode_url,
                    args.opencode_zen_url,
                    args.openrouter_url,
                    args.zai_coding_url,
                )
            ),
            include_codex=args.codex or args.codex_bin is not None or args.codex_home is not None,
            opencode_url=args.opencode_url,
            opencode_zen_url=args.opencode_zen_url,
            openrouter_url=args.openrouter_url,
            zai_coding_url=args.zai_coding_url,
            codex_binary=codex_binary,
            codex_home=codex_home,
            timeout=args.timeout,
        )
        emit_structured("catalog.verify", report, force_json=args.global_json)
        return 0 if report["passed"] else 1
    if cmd == "discover":
        progress(f"Discovering models from {args.source}...", quiet=args.quiet)
        if args.source == "codex":
            codex_binary, codex_home = _configured_codex_context(args.codex_bin, args.codex_home)
            result = discover_codex(binary=codex_binary, home=codex_home)
        elif args.source == "opencode-go":
            result = discover_opencode_go(
                url=args.url or args.opencode_url, timeout=args.timeout or 10.0
            )
        elif args.source == "opencode-zen":
            result = discover_opencode_zen(
                url=args.url or args.opencode_zen_url, timeout=args.timeout or 10.0
            )
        elif args.source == "openrouter":
            result = discover_openrouter(url=args.url, timeout=args.timeout or 10.0)
        else:
            result = discover_zai(args.source, url=args.url, timeout=args.timeout or 10.0)
        emit_structured("catalog.discover", result, force_json=args.global_json)
        return 0 if result.get("passed") else 1
    if cmd == "refresh":
        progress("Refreshing retained catalog discovery evidence...", quiet=args.quiet)
        codex_binary, codex_home = _configured_codex_context(args.codex_bin, args.codex_home)
        result = refresh_discovery(
            remote=not args.no_remote,
            include_codex=not args.no_codex,
            install_codex_overlay=args.install_codex_overlay,
            opencode_url=args.opencode_url,
            opencode_zen_url=args.opencode_zen_url,
            openrouter_url=args.openrouter_url,
            zai_coding_url=args.zai_coding_url,
            codex_binary=codex_binary,
            codex_home=codex_home,
            timeout=args.timeout,
        )
        emit_structured("catalog.refresh", result, force_json=args.global_json)
        return 0 if result["report"]["passed"] else 1
    raise ValueError(f"unhandled catalog command: {cmd}")


def _handle_profile(args: argparse.Namespace) -> int:
    """Dispatch profile commands at their existing domain boundary."""

    cmd = args.profile_command
    if cmd == "list":
        items = discover_profiles()
        if stdout_is_tty() and not args.global_json:
            active = active_profile_id()
            items = {key: {**item, "active": key == active} for key, item in items.items()}
        emit_structured("profile.list", items, force_json=args.global_json)
        return 0
    if cmd == "current":
        profile_id = active_profile_id()
        emit_scalar(
            profile_id,
            json_value={"profile_id": profile_id},
            force_json=args.global_json,
        )
        return 0
    if cmd == "show":
        emit_structured(
            "profile.show",
            resolve_profile(args.profile) if args.resolved else profile_summary(args.profile),
            force_json=args.global_json,
        )
        return 0
    if cmd == "validate":
        result = profile_validation_report(
            args.profile or active_profile_id(), _bindings(args.bind)
        )
        emit_structured("profile.validate", result, force_json=args.global_json)
        return 0 if result["valid"] else 1
    if cmd == "compile":
        selected_profile = args.profile or active_profile_id()
        progress(f"Compiling profile {selected_profile}...", quiet=args.quiet)
        snap = compile_profile(
            selected_profile,
            bindings=_bindings(args.bind),
            force=args.force,
        )
        emit_structured("profile.compile", snap["manifest"], force_json=args.global_json)
        return 0
    if cmd == "use":
        selected = profile_summary(args.profile)
        set_active_profile(args.profile)
        if selected["maturity"] == "lab":
            print(
                f"warning: {args.profile} is an experimental lab profile",
                file=sys.stderr,
            )
        emit_scalar(
            args.profile,
            json_value={"profile_id": args.profile, "selected": True},
            force_json=args.global_json,
        )
        return 0
    if cmd == "install":
        profile_id = install_profile_pack(Path(args.source), replace=args.replace)
        emit_scalar(
            profile_id,
            json_value={"profile_id": profile_id, "installed": True},
            force_json=args.global_json,
        )
        return 0
    if cmd == "remove":
        remove_profile(args.profile)
        emit_tty_success(
            f"Removed profile {args.profile}.",
            json_value={"profile_id": args.profile, "removed": True},
            force_json=args.global_json,
        )
        return 0
    if cmd == "clone":
        destination_path = clone_profile(args.source, args.destination, replace=args.replace)
        emit_scalar(
            args.destination,
            json_value={
                "profile_id": args.destination,
                "path": str(destination_path),
                "source_profile_id": args.source,
                "cloned": True,
            },
            force_json=args.global_json,
        )
        return 0
    if cmd == "doctor":
        selected_profile = args.profile or active_profile_id()
        progress(f"Diagnosing profile {selected_profile}...", quiet=args.quiet)
        result = doctor(
            selected_profile,
            live=args.live,
            probe=args.probe,
            bindings=_bindings(args.bind),
            progress=lambda message: progress(message, quiet=args.quiet),
        )
        emit_structured("profile.doctor", result, force_json=args.global_json)
        return 0 if result["passed"] else 1
    if cmd == "smoke":
        selected_profile = args.profile or active_profile_id()
        progress(f"Smoke-testing profile {selected_profile}...", quiet=args.quiet)
        result = smoke_profile(
            selected_profile,
            cwd=args.cwd,
            bindings=_bindings(args.bind),
            root_only=args.root_only,
            workers_only=args.workers_only,
            progress=lambda message: progress(message, quiet=args.quiet),
        )
        emit_structured("profile.smoke", result, force_json=args.global_json)
        return 0 if result["passed"] else 1
    raise RuntimeError(f"unhandled profile command: {cmd}")


def _handle_session(args: argparse.Namespace) -> int:
    """Dispatch durable session inspection and lifecycle controls."""

    cmd = args.session_command
    if cmd == "list":
        sessions = sorted(
            iter_sessions(strict=False),
            key=lambda item: str(
                item.get("last_active_at")
                or item.get("finished_at")
                or item.get("created_at")
                or ""
            ),
            reverse=True,
        )
        emit_structured(
            "session.list",
            [public_session(item) for item in sessions[: args.limit]],
            force_json=args.global_json,
        )
        return 0
    if cmd == "show":
        emit_structured(
            "session.show",
            public_session(load_session(args.session_id), include_details=True),
            force_json=args.global_json,
        )
        return 0
    if cmd == "runs":
        emit_structured(
            "session.runs",
            [public_run(item) for item in iter_session_runs(args.session_id)[: args.limit]],
            force_json=args.global_json,
        )
        return 0
    if cmd == "cancel":
        progress(f"Cancelling session {args.session_id}...", quiet=args.quiet)
        emit_structured(
            "session.cancel",
            cancel_session(args.session_id),
            force_json=args.global_json,
        )
        return 0
    if cmd == "detach":
        emit_structured(
            "session.detach",
            detach_session(args.session_id),
            force_json=args.global_json,
        )
        return 0
    if cmd == "pause":
        progress(f"Pausing session {args.session_id}...", quiet=args.quiet)
        emit_structured(
            "session.pause",
            pause_session(args.session_id),
            force_json=args.global_json,
        )
        return 0
    if cmd == "compact":
        progress(f"Compacting paused session {args.session_id}...", quiet=args.quiet)
        emit_structured(
            "session.compact",
            compact_session(args.session_id),
            force_json=args.global_json,
        )
        return 0
    if cmd == "continue":
        progress(f"Continuing paused session {args.session_id}...", quiet=args.quiet)
        emit_structured(
            "session.continue",
            continue_session(
                args.session_id,
                input_text=args.input,
                goal_token_budget=args.goal_token_budget,
            ),
            force_json=args.global_json,
        )
        return 0
    if cmd == "stop":
        progress(
            f"Stopping session {args.session_id} with {args.grace}s finalization grace...",
            quiet=args.quiet,
        )
        emit_structured(
            "session.stop",
            stop_session(args.session_id, grace_seconds=args.grace),
            force_json=args.global_json,
        )
        return 0
    raise RuntimeError(f"unhandled session command: {cmd}")


def _handle_jobs(args: argparse.Namespace) -> int:
    """Dispatch worker inspection, waiting, evidence, and cancellation."""

    cmd = args.jobs_command
    if cmd == "list":
        emit_structured(
            "jobs.list",
            list_jobs(session_id=args.session, run_id=args.run, limit=args.limit),
            force_json=args.global_json,
        )
        return 0
    if cmd == "status":
        rows = list_jobs(job_ids=args.job_ids, limit=len(args.job_ids) + 5)
        by_id = {str(item["job_id"]): item for item in rows}
        missing = [item for item in args.job_ids if item not in by_id]
        if missing:
            raise FileNotFoundError("unknown jobs: " + ", ".join(missing))
        emit_structured(
            "jobs.status",
            [by_id[item] for item in args.job_ids],
            force_json=args.global_json,
        )
        return 0
    if cmd == "result":
        emit_structured(
            "jobs.result",
            read_result(args.job_id, max_chars=args.max_chars, cursor=args.cursor),
            force_json=args.global_json,
        )
        return 0
    if cmd == "wait":
        progress(
            f"Waiting up to {args.timeout}s for {len(args.job_ids)} job(s)...",
            quiet=args.quiet,
        )
        result = wait_for_jobs(args.job_ids, session_id=args.session, timeout_seconds=args.timeout)
        emit_structured("jobs.wait", result, force_json=args.global_json)
        return 1 if result["unfinished"] else 0
    if cmd == "cancel":
        progress(f"Cancelling job {args.job_id}...", quiet=args.quiet)
        emit_structured(
            "jobs.cancel",
            cancel_job(
                args.job_id,
                cascade=not args.no_cascade,
                reason=args.reason,
            ),
            force_json=args.global_json,
        )
        return 0
    raise RuntimeError(f"unhandled jobs command: {cmd}")


def _handle_eval(args: argparse.Namespace) -> int:
    """Dispatch evaluation suite and retained-run commands."""

    cmd = args.eval_command
    if cmd == "suites":
        emit_structured("eval.suites", discover_suites(), force_json=args.global_json)
        return 0
    if cmd == "validate":
        result = validate_suite(args.suite)
        emit_structured("eval.validate", result, force_json=args.global_json)
        return 0 if result["valid"] else 1
    if cmd == "run":
        progress(
            f"Running evaluation suite {args.suite} for profile {args.profile}...",
            quiet=args.quiet,
        )
        result = run_evaluation(
            profile=args.profile,
            suite=args.suite,
            bindings=_bindings(args.bind),
            wall_timeout_override=args.wall_timeout,
            dry_run=args.dry_run,
            trial_mode=args.trial_mode,
            progress=lambda message: progress(message, quiet=args.quiet),
        )
        emit_structured("eval.run", result, force_json=args.global_json)
        if result["status"] not in {"validated", "completed"}:
            return 1
        if args.trial_mode == "release" and result["status"] == "completed":
            promotion = result.get("summary", {}).get("promotion", {})
            if promotion.get("eligible") and promotion.get("passed") is not True:
                return 1
        return 0
    if cmd == "list":
        emit_structured("eval.list", list_runs(args.limit), force_json=args.global_json)
        return 0
    if cmd == "show":
        emit_structured("eval.show", load_run(args.run_id), force_json=args.global_json)
        return 0
    if cmd == "compare":
        emit_structured("eval.compare", compare_runs(args.run_ids), force_json=args.global_json)
        return 0
    raise RuntimeError(f"unhandled eval command: {cmd}")


def _handle_gateway(args: argparse.Namespace) -> int:
    """Dispatch Switchyard gateway inspection and lifecycle commands."""

    cmd = args.gateway_command
    if cmd == "list":
        emit_structured("gateway.list", list_gateways(), force_json=args.global_json)
        return 0
    if cmd == "stop-idle":
        progress("Stopping idle gateways...", quiet=args.quiet)
        emit_structured(
            "gateway.stop-idle",
            {"stopped": stop_idle_gateways()},
            force_json=args.global_json,
        )
        return 0
    profile = args.profile or active_profile_id()
    snap = compile_profile(profile)
    snapshot_hash = snap["manifest"]["snapshot_hash"]
    if cmd == "start":
        progress(f"Starting gateway for profile {profile}...", quiet=args.quiet)
        emit_structured(
            "gateway.start",
            ensure_gateway(snapshot_hash) or {"status": "not_required"},
            force_json=args.global_json,
        )
        return 0
    if cmd == "stop":
        progress(f"Stopping gateway for profile {profile}...", quiet=args.quiet)
        emit_structured("gateway.stop", stop_gateway(snapshot_hash), force_json=args.global_json)
        return 0
    if cmd == "status":
        emit_structured(
            "gateway.status", gateway_status(snapshot_hash), force_json=args.global_json
        )
        return 0
    if cmd == "models":
        emit_structured(
            "gateway.models", gateway_models(snapshot_hash), force_json=args.global_json
        )
        return 0
    if cmd == "logs":
        state = gateway_status(snapshot_hash)
        log_path = state.get("log_path")
        if not isinstance(log_path, str) or not Path(log_path).is_file():
            raise FileNotFoundError(f"no gateway log is available for profile {profile!r}")
        print(_tail(Path(log_path), args.lines))
        return 0
    raise RuntimeError(f"unhandled gateway command: {cmd}")


def main(argv: list[str] | None = None) -> int:
    raw_argv = list(sys.argv[1:] if argv is None else argv)
    entrypoint = os.environ.get("MMO_CLI_ENTRYPOINT", "codex-mmoctl")
    if entrypoint not in {"codex-mmo", "codex-mmoctl"}:
        entrypoint = "codex-mmoctl"
    parser = _parser(prog=entrypoint)
    routed_argv = _route_argv(parser, raw_argv, implicit_run=entrypoint == "codex-mmo")
    selected_parser = _selected_parser(parser, routed_argv)
    json_requested = "--json" in routed_argv
    try:
        args = parser.parse_args(routed_argv)
        _validate_cli_args(args)
        prompt = _exec_prompt(args) if args.command == "exec" else None
        if hasattr(args, "bind"):
            _bindings(args.bind)
    except CLIUsageError as exc:
        emit_usage_error(
            selected_parser.format_usage(),
            str(exc),
            hint=exc.hint or _usage_hint(str(exc), selected_parser, parser),
            as_json=json_requested,
        )
        return 2

    def structured(command: str, value: Any) -> None:
        emit_structured(command, value, force_json=args.global_json)

    try:
        if args.show_version or args.command == "version":
            version = package_version()
            emit_scalar(
                version,
                json_value={"version": version},
                force_json=args.global_json,
            )
            return 0
        if args.command == "run":
            codex_args = args.codex_args
            if codex_args and codex_args[0] == "--":
                codex_args = codex_args[1:]
            if _codex_subcommand(codex_args) == "resume":
                raise ValueError("use 'codex-mmo resume' instead of 'codex-mmo run resume'")
            return shell_exit_status(
                launch_interactive(
                    profile=args.profile,
                    cwd=args.cwd,
                    bindings=_bindings(args.bind),
                    codex_args=codex_args,
                )
            )
        if args.command == "resume":
            session_id = resolve_resume_session(
                args.identifier,
                last=args.last,
                all_cwds=args.all_cwds,
                cwd=os.getcwd(),
            )
            return shell_exit_status(
                resume_interactive(session_id, allow_tainted=args.allow_tainted)
            )
        if args.command == "exec":
            progress("Running noninteractive root task...", quiet=args.quiet)
            result = run_root_exec(
                profile=args.profile,
                cwd=args.cwd,
                prompt=str(prompt),
                bindings=_bindings(args.bind),
                images=args.image,
                wall_timeout_seconds=args.wall_timeout,
                sandbox_mode=args.sandbox,
            )
            if args.global_json:
                emit_json(result)
            else:
                print(result.get("result", ""))
            return shell_exit_status(int(result.get("exit_code", 1)))
        if args.command == "profile":
            return _handle_profile(args)
        if args.command == "tool-mcp":
            status = tool_mcp_status()
            cmd = args.tool_mcp_command
            if cmd == "list":
                structured("tool-mcp.list", status)
                return 0
            server_id = args.server
            if server_id is not None and server_id not in status["servers"]:
                raise FileNotFoundError(f"unknown Tool MCP server {server_id!r}")
            if cmd == "show":
                if server_id is None:
                    raise RuntimeError("tool-mcp show requires a server ID")
                registry, _sources = load_tool_mcp_registry_with_sources()
                structured(
                    "tool-mcp.show",
                    {
                        "definition": registry[server_id],
                        "readiness": status["servers"][server_id],
                    },
                )
                return 0
            selected = (
                status["servers"]
                if server_id is None
                else {server_id: status["servers"][server_id]}
            )
            result = {
                "registry_root": status["registry_root"],
                "servers": selected,
                "passed": all(bool(item["ready"]) for item in selected.values()),
            }
            structured("tool-mcp.validate", result)
            return 0 if result["passed"] else 1
        if args.command == "catalog":
            return _handle_catalog(args)
        if args.command == "gateway":
            return _handle_gateway(args)
        if args.command == "session":
            return _handle_session(args)
        if args.command == "jobs":
            return _handle_jobs(args)
        if args.command == "eval":
            return _handle_eval(args)
        if args.command == "doctor":
            selected_profile = args.profile or active_profile_id()
            progress(f"Diagnosing profile {selected_profile}...", quiet=args.quiet)
            result = doctor(
                selected_profile,
                live=args.live,
                probe=args.probe,
                bindings=_bindings(args.bind),
                progress=lambda message: progress(message, quiet=args.quiet),
            )
            structured("doctor", result)
            return 0 if result["passed"] else 1
        if args.command == "validate":
            progress("Validating Codex MMO configuration...", quiet=args.quiet)
            profiles = discover_profiles()
            selected_profiles = sorted(profiles) if args.all_profiles else [active_profile_id()]
            validation_result: dict[str, Any] = {
                item: profile_validation_report(item, {}) for item in selected_profiles
            }
            validation_result["catalog"] = {
                "summary": catalog_summary(),
                "inventory": local_inventory_report(),
            }
            validation_result["evaluation_suites"] = {
                item: validate_suite(item) for item in discover_suites()
            }
            validation_result["passed"] = (
                all(
                    item["valid"]
                    for key, item in validation_result.items()
                    if key not in {"catalog", "evaluation_suites", "passed"}
                )
                and validation_result["catalog"]["inventory"]["passed"]
                and all(item["valid"] for item in validation_result["evaluation_suites"].values())
            )
            structured("validate", validation_result)
            return 0 if validation_result["passed"] else 1
        if args.command == "clean":
            settings = load_settings()
            job_days = (
                args.job_days if args.job_days is not None else int(settings["job_retention_days"])
            )
            session_days = (
                args.session_days
                if args.session_days is not None
                else int(settings["session_retention_days"])
            )
            counts = clean_state(
                job_days=job_days,
                session_days=session_days,
                dry_run=args.dry_run,
            )
            structured(
                "clean",
                {
                    "dry_run": args.dry_run,
                    "job_days": job_days,
                    "session_days": session_days,
                    **counts,
                },
            )
            return 0
        if args.command == "prompt":
            prompt_text = textwrap.dedent("""\
                Analyze this repository using the active composition profile.

                Keep the immediate critical path in the root. Spawn independent participants only where they improve latency, specialization, or confidence. Continue useful non-overlapping root work while they run. Reconcile all material results from primary evidence, review changes, and run integrated validation before finalizing.
            """)
            emit_scalar(
                prompt_text.rstrip("\n"),
                json_value={"prompt": prompt_text.rstrip("\n")},
                force_json=args.global_json,
            )
            return 0
        if args.command == "auth":
            settings = load_settings()
            home = Path(str(settings.get("base_codex_home", "~/.codex"))).expanduser()
            env = filtered_environment(extra={"CODEX_HOME": str(home)})
            codex = os.environ.get("MMO_CODEX_BIN") or str(settings.get("codex_bin", "codex"))
            auth_command = (
                [codex, "login", "status"] if args.auth_command == "status" else [codex, "login"]
            )
            return shell_exit_status(subprocess.call(auth_command, env=env))
        raise RuntimeError(f"unhandled command path: {_command_path(args)}")
    except CLIUsageError as exc:
        emit_usage_error(
            selected_parser.format_usage(),
            str(exc),
            hint=exc.hint,
            as_json=args.global_json,
        )
        return 2
    except KeyboardInterrupt:
        return 130
    except BrokenPipeError:
        return _broken_pipe_status()
    except Exception as exc:
        category = _error_category(exc)
        message = str(exc) or type(exc).__name__
        hint = _error_hint(_command_path(args), exc)
        if category == "internal":
            message = f"unexpected internal failure: {message}"
            hint = hint or "Rerun with --debug to include a traceback."
        emit_error(
            category=category,
            message=message,
            hint=hint,
            as_json=args.global_json,
            exception_type=type(exc).__name__ if args.debug else None,
            traceback_text=traceback.format_exc() if args.debug else None,
        )
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
