Entrada CLI y dispatch de subcomandos
El binario del comando codex es un único ejecutable: toda la funcionalidad pasa por aquí. Sin subcomando entra en modo interactivo TUI; con subcomando se dispatcha a exec, login, mcp, app-server y otras decenas de subrutas. Esta capa solo se encarga de parsear argumentos, ensamblar config y ceder el control al crate correspondiente; la lógica de negocio real vive en los crates de aguas abajo llamados por los subcomandos.
Responsabilidades
- Parsear la CLI de nivel superior:
MultitoolCliusa clap para fundir flags globales (--config,--enable,--remote, opciones TUI) y un subcomando opcional en un único árbol;subcommand_negates_reqs = truehace que, al aparecer un subcomando, los campos obligatorios del TUI dejen de exigirse.codex-rs/cli/src/main.rs:106-121 - Definir el enum
Subcommand: lista todos los subcomandos visibles (exec,login,mcp,app-server,sandbox,debug,apply, etc.) para su uso en el dispatch.codex-rs/cli/src/main.rs:123-212 - Dispatch
match subcommand: cada rama pliegacli_config_overridesdentro del subcomando y luego llama alrun_*correspondiente; sin subcomando se va arun_interactive_tui.codex-rs/cli/src/main.rs:987-1001 - Dispatch por
arg0: distintas plataformas o canales de instalación pueden invocar codex con distintos nombres de binario (codex-x86_64-...);arg0_dispatch_or_elseunifica la entrada y corre sobre un hilo con tamaño de pila dedicado.codex-rs/arg0/src/lib.rs:208-232
Motivación de diseño
Al principio codex era simplemente un TUI interactivo; después se fueron añadiendo exec (ejecución no interactiva), login, gestión mcp, proceso en segundo plano app-server, herramienta debug. Meter todo en el mismo binario es más práctico que partir en múltiples ejecutables codex-tui, codex-exec, etc.: el usuario solo instala un codex, y la documentación y el shell completion se mantienen en un único sitio.
subcommand_negates_reqs = true en clap hace que los flags obligatorios exclusivos del TUI interactivo (como prompt) dejen de exigirse cuando hay subcomando, evitando que codex login falle por no llevar prompt. override_usage muestra lado a lado en la ayuda codex [OPTIONS] [PROMPT] y codex [OPTIONS] <COMMAND>, expresando de forma直观 «a secas entra al TUI, con subcomando va a ese flujo».
arg0_dispatch_or_else existe porque en algunas instalaciones de Linux el binario se renombra según plataforma (por ejemplo codex-x86_64-unknown-linux-musl); el dispatch interno permite que esas llamadas también enruten al flujo principal, y al mismo tiempo corre el runtime de Tokio sobre un hilo con tamaño de pila dedicado, evitando que el block_on del nivel superior reviente la pila del hilo principal.
Archivos clave
codex-rs/cli/src/main.rs:106-121 — struct MultitoolCli, que aplana config override, feature toggle, opciones TUI y subcomando en un único árbol clap.codex-rs/cli/src/main.rs:123-212 — enum Subcommand, con más de 30 variantes para todos los subcomandos.codex-rs/cli/src/main.rs:956-974 — fn main + entrada cli_main: tras plegar feature toggles en config override, dispatch.codex-rs/cli/src/main.rs:1339-1382 — rama Subcommand::Login, muestra cómo un subcomando pliega el root config override y elige distintas rutas de login según --with-api-key / --device-auth etc.codex-rs/cli/src/main.rs:2052-2075 — prepend_config_flags + reject_remote_mode_for_subcommand, guardianes de borde compartidos por todos los subcomandos.codex-rs/cli/src/main.rs:2236-2263 — run_interactive_tui, la ruta por defecto cuando no hay subcomando; gestiona compatibilidad de terminal como TERM=dumb.codex-rs/cli/src/app_cmd.rs:1-25 — subcomando App (macOS/Windows); run_app solo hace dispatch de plataforma.MultitoolCli reúne todos los flags de entrada en un único struct, y subcommand: Option<Subcommand> es el corazón: None va al TUI, Some(...) va al subcomando. subcommand_negates_reqs hace que los campos obligatorios del TUI dejen de exigirse cuando hay subcomando.
// cli/src/main.rs:106-121 — estructura CLI de nivel superior
struct MultitoolCli {
#[clap(flatten)]
pub config_overrides: CliConfigOverrides,
#[clap(flatten)]
pub feature_toggles: FeatureToggles,
#[clap(flatten)]
remote: InteractiveRemoteOptions,
#[clap(flatten)]
interactive: TuiCli,
#[clap(subcommand)]
subcommand: Option<Subcommand>,
}El núcleo del dispatch es match subcommand; cada rama gestiona por separado el plegado de config override y la validación de modo remote. Abajo está la versión simplificada de la rama login, mostrando los tres pasos estándar: «plegar root override → validar remote → elegir ruta por flag».
// cli/src/main.rs:1339-1379 — dispatch de la rama login
Some(Subcommand::Login(mut login_cli)) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
root_remote_auth_token_env.as_deref(),
"login",
)?;
prepend_config_flags(
&mut login_cli.config_overrides,
root_config_overrides.clone(),
);
match login_cli.action {
Some(LoginSubcommand::Status) => {
run_login_status(login_cli.config_overrides).await;
}
None => {
if login_cli.with_api_key && login_cli.with_access_token { /* error */ }
else if login_cli.use_device_code {
run_login_with_device_code(/* ... */).await;
} else if login_cli.with_api_key {
let api_key = read_api_key_from_stdin();
run_login_with_api_key(/* ... */).await;
} else {
run_login_with_chatgpt(login_cli.config_overrides).await;
}
}
}
}fn main se deja deliberadamente fino: solo toma el env remote_control_disabled y se lo pasa a arg0_dispatch_or_else; el parseo y dispatch reales viven en cli_main. Así el módulo arg0 se ocupa de las preocupaciones a nivel de proceso (hilo/pila/runtime), y cli_main solo de clap y subcomandos.
// cli/src/main.rs:956-974 — entrada intencionadamente fina, el trabajo real se delega
fn main() -> anyhow::Result<()> {
let remote_control_disabled = codex_app_server::take_remote_control_disabled_env();
arg0_dispatch_or_else(move |arg0_paths: Arg0DispatchPaths| async move {
cli_main(arg0_paths, remote_control_disabled).await?;
Ok(())
})
}
async fn cli_main(arg0_paths: Arg0DispatchPaths, remote_control_disabled: bool) -> anyhow::Result<()> {
let MultitoolCli { /* ... */ } = MultitoolCli::parse();
// los feature toggles se pliegan en config overrides y fluyen a todos los subcomandos
let toggle_overrides = feature_toggles.to_overrides()?;
root_config_overrides.raw_overrides.extend(toggle_overrides);
// ...
match subcommand { /* dispatch */ }
}Flujo de datos
Bordes y fallos
--strict-configno es universal:reject_root_strict_config_for_subcommandrechaza ese flag en subcomandos comologin/logout/updateque no leen config.toml, para evitar pensar que la validación surte efecto.codex-rs/cli/src/main.rs:2077-2091--api-keyestá deprecated:codex login --api-key <KEY>ya no se soporta; el dispatch imprime a stderr y sale con exit 1, sugiriendoprintenv OPENAI_API_KEY | codex login --with-api-keypara meter la clave por stdin y evitar que entre en el shell history.codex-rs/cli/src/main.rs:1366-1370- Subcomandos de plataforma: el subcomando
Appsolo se compila en macOS/Windows (#[cfg(any(target_os = "macos", target_os = "windows"))]); los usuarios de Linux no lo ven.codex-rs/cli/src/main.rs:154-155 - TERM=dumb rechaza el TUI: en terminal dumb y cuando stdin/stderr no son TTY,
run_interactive_tuidevuelve fatal directamente sin intentar arrancar ratatui, evitando media pantalla de basura.codex-rs/cli/src/main.rs:2247-2263
Resumen
El crate cli es la entrada general del binario codex; su responsabilidad es «parse + dispatch»: parsea los argumentos de línea de comandos en MultitoolCli y luego, según el subcomando, cede el control a los crates de aguas abajo como tui / exec / login / app-server. Esta capa se mantiene deliberadamente fina; la lógica de negocio está toda aguas abajo. Pero el plegado de config override y el guardián de strict-config/remote mode que aplican a todos los subcomandos viven aquí, para asegurar un comportamiento consistente. Los detalles del flujo de login en Login de ChatGPT y autenticación; el bucle principal del Agent que corre el modelo en Bucle principal del Agent.