use clap::Args; use clap::FromArgMatches; use clap::Parser; use clap::ValueEnum; use codex_utils_cli::CliConfigOverrides; use codex_utils_cli::SharedCliOptions; use std::path::PathBuf; #[derive(Parser, Debug)] #[command( version, override_usage = "codex exec [OPTIONS] [PROMPT]\n codex exec [OPTIONS] [ARGS]" )] pub struct Cli { /// Action to perform. If omitted, runs a new non-interactive session. #[command(subcommand)] pub command: Option, #[clap(flatten)] pub shared: ExecSharedCliOptions, /// Allow running Codex outside a Git repository. #[arg(long = "skip-git-repo-check", global = true, default_value_t = false)] pub skip_git_repo_check: bool, /// Run without persisting session files to disk. #[arg(long = "ephemeral", global = true, default_value_t = false)] pub ephemeral: bool, /// Do not load `$CODEX_HOME/config.toml`; auth still uses `CODEX_HOME`. #[arg(long = "ignore-user-config", global = true, default_value_t = false)] pub ignore_user_config: bool, /// Do not load user or project execpolicy `.rules` files. #[arg(long = "ignore-rules", global = true, default_value_t = false)] pub ignore_rules: bool, /// Legacy compatibility trap for the removed `--full-auto` flag. #[arg( long = "full-auto", hide = true, global = true, default_value_t = false, conflicts_with = "dangerously_bypass_approvals_and_sandbox" )] pub removed_full_auto: bool, /// Path to a JSON Schema file describing the model's final response shape. #[arg(long = "output-schema", value_name = "FILE")] pub output_schema: Option, #[clap(skip)] pub config_overrides: CliConfigOverrides, /// Specifies color settings for use in the output. #[arg(long = "color", value_enum, default_value_t = Color::Auto)] pub color: Color, /// Print events to stdout as JSONL. #[arg( long = "json", alias = "experimental-json", default_value_t = false, global = true )] pub json: bool, /// Specifies file where the last message from the agent should be written. #[arg( long = "output-last-message", short = 'o', value_name = "FILE", global = true )] pub last_message_file: Option, /// Initial instructions for the agent. If not provided as an argument (or /// if `-` is used), instructions are read from stdin. If stdin is piped and /// a prompt is also provided, stdin is appended as a `` block. #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)] pub prompt: Option, } impl std::ops::Deref for Cli { type Target = SharedCliOptions; fn deref(&self) -> &Self::Target { &self.shared.0 } } impl std::ops::DerefMut for Cli { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.shared.0 } } impl Cli { pub fn removed_full_auto_warning(&self) -> Option<&'static str> { if self.removed_full_auto { return Some( "warning: `--full-auto` is deprecated; use `--sandbox workspace-write` instead.", ); } None } } #[derive(Debug, Default)] pub struct ExecSharedCliOptions(SharedCliOptions); impl ExecSharedCliOptions { pub fn into_inner(self) -> SharedCliOptions { self.0 } } impl std::ops::Deref for ExecSharedCliOptions { type Target = SharedCliOptions; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for ExecSharedCliOptions { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Args for ExecSharedCliOptions { fn augment_args(cmd: clap::Command) -> clap::Command { mark_exec_global_args(SharedCliOptions::augment_args(cmd)) } fn augment_args_for_update(cmd: clap::Command) -> clap::Command { mark_exec_global_args(SharedCliOptions::augment_args_for_update(cmd)) } } impl FromArgMatches for ExecSharedCliOptions { fn from_arg_matches(matches: &clap::ArgMatches) -> Result { SharedCliOptions::from_arg_matches(matches).map(Self) } fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { self.0.update_from_arg_matches(matches) } } fn mark_exec_global_args(cmd: clap::Command) -> clap::Command { cmd.mut_arg("model", |arg| arg.global(true)) .mut_arg("dangerously_bypass_approvals_and_sandbox", |arg| { arg.global(true) }) } #[derive(Debug, clap::Subcommand)] pub enum Command { /// Resume a previous session by id or pick the most recent with --last. Resume(ResumeArgs), /// Run a code review against the current repository. Review(ReviewArgs), } #[derive(Args, Debug)] struct ResumeArgsRaw { // Note: This is the direct clap shape. We reinterpret the positional when --last is set // so "codex resume --last " treats the positional as a prompt, not a session id. /// Conversation/session id (UUID) or thread name. UUIDs take precedence if it parses. /// If omitted, use --last to pick the most recent recorded session. #[arg(value_name = "SESSION_ID")] session_id: Option, /// Resume the most recent recorded session (newest) without specifying an id. #[arg(long = "last", default_value_t = false)] last: bool, /// Show all sessions (disables cwd filtering). #[arg(long = "all", default_value_t = false)] all: bool, /// Optional image(s) to attach to the prompt sent after resuming. #[arg( long = "image", short = 'i', value_name = "FILE", value_delimiter = ',', num_args = 1 )] images: Vec, /// Prompt to send after resuming the session. If `-` is used, read from stdin. #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)] prompt: Option, } #[derive(Debug)] pub struct ResumeArgs { /// Conversation/session id (UUID) or thread name. UUIDs take precedence if it parses. /// If omitted, use --last to pick the most recent recorded session. pub session_id: Option, /// Resume the most recent recorded session (newest) without specifying an id. pub last: bool, /// Show all sessions (disables cwd filtering). pub all: bool, /// Optional image(s) to attach to the prompt sent after resuming. pub images: Vec, /// Prompt to send after resuming the session. If `-` is used, read from stdin. pub prompt: Option, } impl From for ResumeArgs { fn from(raw: ResumeArgsRaw) -> Self { // When --last is used without an explicit prompt, treat the positional as the prompt // (clap can’t express this conditional positional meaning cleanly). let (session_id, prompt) = if raw.last && raw.prompt.is_none() { (None, raw.session_id) } else { (raw.session_id, raw.prompt) }; Self { session_id, last: raw.last, all: raw.all, images: raw.images, prompt, } } } impl Args for ResumeArgs { fn augment_args(cmd: clap::Command) -> clap::Command { ResumeArgsRaw::augment_args(cmd) } fn augment_args_for_update(cmd: clap::Command) -> clap::Command { ResumeArgsRaw::augment_args_for_update(cmd) } } impl FromArgMatches for ResumeArgs { fn from_arg_matches(matches: &clap::ArgMatches) -> Result { ResumeArgsRaw::from_arg_matches(matches).map(Self::from) } fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { *self = ResumeArgsRaw::from_arg_matches(matches).map(Self::from)?; Ok(()) } } #[derive(Parser, Debug)] pub struct ReviewArgs { /// Review staged, unstaged, and untracked changes. #[arg( long = "uncommitted", default_value_t = false, conflicts_with_all = ["base", "commit", "prompt"] )] pub uncommitted: bool, /// Review changes against the given base branch. #[arg( long = "base", value_name = "BRANCH", conflicts_with_all = ["uncommitted", "commit", "prompt"] )] pub base: Option, /// Review the changes introduced by a commit. #[arg( long = "commit", value_name = "SHA", conflicts_with_all = ["uncommitted", "base", "prompt"] )] pub commit: Option, /// Optional commit title to display in the review summary. #[arg(long = "title", value_name = "TITLE", requires = "commit")] pub commit_title: Option, /// Custom review instructions. If `-` is used, read from stdin. #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)] pub prompt: Option, } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)] #[value(rename_all = "kebab-case")] pub enum Color { Always, Never, #[default] Auto, } #[cfg(test)] #[path = "cli_tests.rs"] mod tests;