Entrée CLI et répartition des sous-commandes
L'exécutable de la ligne de commande codex est un binaire unique, toutes les fonctionnalités passent par lui. Sans sous-commande, il entre en mode interactif TUI ; avec une sous-commande, il répartit vers exec, login, mcp, app-server et des dizaines d'autres sous-chemins. Cette couche ne fait qu'analyser les arguments, monter la config et passer la main au crate concerné ; la vraie logique métier vit dans les crate en aval appelés par les sous-commandes.
Responsabilités
- Analyser la CLI de haut niveau :
MultitoolCliutilise clap pour fusionner flags globaux (--config,--enable,--remote, options TUI) et sous-commande optionnelle en un seul arbre ;subcommand_negates_reqs = truefait que les champs requis par la TUI sautent en présence d'une sous-commande.codex-rs/cli/src/main.rs:106-121 - Définir l'énum
Subcommand: liste toutes les sous-commandes visibles (exec,login,mcp,app-server,sandbox,debug,apply, etc.) pour le dispatch.codex-rs/cli/src/main.rs:123-212 - Répartir via
match subcommand: chaque branche repliecli_config_overridesdans la sous-commande puis appelle lerun_*correspondant ; sans sous-commande, brancherun_interactive_tui.codex-rs/cli/src/main.rs:987-1001 - Gérer le dispatch par
arg0: différentes plateformes/canaux d'installation peuvent appeler codex sous différents noms binaires (codex-x86_64-...) ;arg0_dispatch_or_elseunifie l'entrée et s'exécute sur un thread à taille de stack dédiée.codex-rs/arg0/src/lib.rs:208-232
Motivations de conception
À l'origine codex était une TUI interactive, puis se sont ajoutés exec (exécution non interactive), login, gestion mcp, processus app-server, outils debug. Tout regrouper dans un même binaire rather que d'éclater en codex-tui, codex-exec et autres facilite la vie : l'utilisateur n'installe qu'un seul codex, la doc et les completions shell ne sont maintenues qu'une fois.
Le subcommand_negates_reqs = true de clap fait que les flags requis spécifiques à la TUI interactive (par ex. prompt) ne sont plus exigés lorsqu'une sous-commande est présente, évitant que codex login ne plante pour prompt manquant. override_usage présente en parallèle dans l'aide codex [OPTIONS] [PROMPT] et codex [OPTIONS] <COMMAND>, exprimant clairement « lancer nu → TUI ; avec sous-commande → sous-flux ».
arg0_dispatch_or_else existe parce que certaines installations Linux renomment le binaire selon la plateforme (codex-x86_64-unknown-linux-musl) ; le dispatch interne permet à ces appels de router correctement vers le flux principal, tout en exécutant le runtime Tokio sur un thread à taille de stack dédiée pour éviter un stack overflow du block_on au top-niveau sur la stack du thread principal.
Fichiers clés
codex-rs/cli/src/main.rs:106-121 — la structure MultitoolCli, qui flatten config override, feature toggle, options TUI et sous-commande en un seul arbre clap.codex-rs/cli/src/main.rs:123-212 — l'énum Subcommand, plus de 30 variantes couvrant toutes les sous-commandes.codex-rs/cli/src/main.rs:956-974 — fn main + entrée cli_main, le feature toggle est replié dans config override puis dispatch.codex-rs/cli/src/main.rs:1339-1382 — branche Subcommand::Login, montre comment une sous-commande replie le root config override et choisit entre --with-api-key / --device-auth et autres chemins de login.codex-rs/cli/src/main.rs:2052-2075 — prepend_config_flags + reject_remote_mode_for_subcommand, gardiens de frontière partagés par toutes les sous-commandes.codex-rs/cli/src/main.rs:2236-2263 — run_interactive_tui, chemin par défaut sans sous-commande, gère les questions de compat terminal comme TERM=dumb.codex-rs/cli/src/app_cmd.rs:1-25 — sous-commande App (macOS/Windows), run_app ne fait que le dispatch plateforme.MultitoolCli rassemble tous les flags d'entrée sur une seule structure ; subcommand: Option<Subcommand> est le cœur — None → TUI, Some(...) → sous-commande. subcommand_negates_reqs désactive les champs requis de la TUI en présence d'une sous-commande.
// cli/src/main.rs:106-121 — 顶层 CLI 结构
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>,
}Le cœur du dispatch est match subcommand ; chaque branche gère le repli des config override et la validation du mode remote. Ci-dessous la version simplifiée de la branche login, qui montre la séquence standard « replier root override → valider remote → choisir le chemin selon les flags ».
// cli/src/main.rs:1339-1379 — login 分支的 dispatch
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 { /* 报错 */ }
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 est délibérément fine : elle ne lit que l'env remote_control_disabled puis délègue à arg0_dispatch_or_else ; le vrai parsing et dispatch sont dans cli_main. Ainsi, le module arg0 gère les préoccupations processus (thread/stack/runtime), et cli_main ne s'occupe que de clap et des sous-commandes.
// cli/src/main.rs:956-974 — 入口故意薄,真正工作下推
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();
// feature toggle 折叠进 config overrides,再流向所有子命令
let toggle_overrides = feature_toggles.to_overrides()?;
root_config_overrides.raw_overrides.extend(toggle_overrides);
// ...
match subcommand { /* dispatch */ }
}Flux de données
Limites et échecs
--strict-confign'est pas universel :reject_root_strict_config_for_subcommandrefuse ce flag sur les sous-commandes qui ne lisent pas config.toml (login/logout/update), pour éviter de croire que la validation s'applique.codex-rs/cli/src/main.rs:2077-2091--api-keydéprécié :codex login --api-key <KEY>n'est plus pris en charge ; le dispatch renvoie directement sur stderr + exit 1 et suggèreprintenv OPENAI_API_KEY | codex login --with-api-keypour injecter la clé via stdin, évitant qu'elle atterrisse dans l'historique shell.codex-rs/cli/src/main.rs:1366-1370- Sous-commandes plateforme : la sous-commande
Appn'est compilée que sur macOS/Windows (#[cfg(any(target_os = "macos", target_os = "windows"))]), invisible sous Linux.codex-rs/cli/src/main.rs:154-155 - TERM=dumb refuse la TUI : sur un terminal dumb avec stdin/stderr non TTY,
run_interactive_tuirenvoie une erreur fatale sans tenter de lancer ratatui, évitant un écran de garbage.codex-rs/cli/src/main.rs:2247-2263
Récapitulatif
Le crate cli est l'entrée unique du binaire codex ; sa responsabilité est « parse + dispatch » : analyser la ligne de commande en MultitoolCli puis, selon la sous-commande, passer la main aux crate en aval (tui / exec / login / app-server...). Cette couche est délibérément fine, toute la logique métier est en aval ; mais les logiques transversales comme le repli des config override et la garde strict-config/remote sont ici pour garantir un comportement cohérent entre toutes les sous-commandes. Pour le détail du flux de login, voir Login ChatGPT et authentification ; pour la boucle principale de l'Agent qui fait tourner le modèle, voir Boucle principale de l'Agent.