Files
codex-mmo/libexec/mmoctl.py
T
2026-08-24 08:11:59 -07:00

1821 lines
68 KiB
Python
Executable File

#!/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())