Skip to content

Entrada CLI y dispatch de subcomandos

源码版本rust-v0.145.0

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

  1. Parsear la CLI de nivel superior: MultitoolCli usa clap para fundir flags globales (--config, --enable, --remote, opciones TUI) y un subcomando opcional en un único árbol; subcommand_negates_reqs = true hace que, al aparecer un subcomando, los campos obligatorios del TUI dejen de exigirse. codex-rs/cli/src/main.rs:106-121
  2. 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
  3. Dispatch match subcommand: cada rama pliega cli_config_overrides dentro del subcomando y luego llama al run_* correspondiente; sin subcomando se va a run_interactive_tui. codex-rs/cli/src/main.rs:987-1001
  4. Dispatch por arg0: distintas plataformas o canales de instalación pueden invocar codex con distintos nombres de binario (codex-x86_64-...); arg0_dispatch_or_else unifica 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-974fn 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-2075prepend_config_flags + reject_remote_mode_for_subcommand, guardianes de borde compartidos por todos los subcomandos.codex-rs/cli/src/main.rs:2236-2263run_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.

rust
// 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».

rust
// 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.

rust
// 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-config no es universal: reject_root_strict_config_for_subcommand rechaza ese flag en subcomandos como login / logout / update que no leen config.toml, para evitar pensar que la validación surte efecto. codex-rs/cli/src/main.rs:2077-2091
  • --api-key está deprecated: codex login --api-key <KEY> ya no se soporta; el dispatch imprime a stderr y sale con exit 1, sugiriendo printenv OPENAI_API_KEY | codex login --with-api-key para 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 App solo 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_tui devuelve 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.