Codex Handbook
exec/src/cli.rs 311 lines
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] <COMMAND> [ARGS]")]pub struct Cli {    /// Action to perform. If omitted, runs a new non-interactive session.    #[command(subcommand)]    pub command: Option<Command>,    /// Error out when config.toml contains fields that are not recognized by this version of Codex.    #[arg(long = "strict-config", global = true, default_value_t = false)]    pub strict_config: bool,    #[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", global = true)]    pub output_schema: Option<PathBuf>,    #[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<PathBuf>,    /// 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 `<stdin>` block.    #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]    pub prompt: Option<String>,}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<Self, clap::Error> {        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)        })        .mut_arg("bypass_hook_trust", |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 <prompt>" 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<String>,    /// 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<PathBuf>,    /// Prompt to send after resuming the session. If `-` is used, read from stdin.    #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]    prompt: Option<String>,}#[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<String>,    /// 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<PathBuf>,    /// Prompt to send after resuming the session. If `-` is used, read from stdin.    pub prompt: Option<String>,}impl From<ResumeArgsRaw> 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<Self, clap::Error> {        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(Args, 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<String>,    /// Review the changes introduced by a commit.    #[arg(        long = "commit",        value_name = "SHA",        conflicts_with_all = ["uncommitted", "base", "prompt"]    )]    pub commit: Option<String>,    /// Optional commit title to display in the review summary.    #[arg(long = "title", value_name = "TITLE", requires = "commit")]    pub commit_title: Option<String>,    /// Custom review instructions. If `-` is used, read from stdin.    #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]    pub prompt: Option<String>,}#[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;