285 lines
8.5 KiB
Rust
285 lines
8.5 KiB
Rust
|
|
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>,
|
|||
|
|
|
|||
|
|
#[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,
|
|||
|
|
|
|||
|
|
/// Path to a JSON Schema file describing the model's final response shape.
|
|||
|
|
#[arg(long = "output-schema", value_name = "FILE")]
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[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("full_auto", |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 <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(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<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;
|