Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions codex-rs/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ enum Subcommand {
/// Generate shell completion scripts.
Completion(CompletionCommand),

/// Update Codex to the latest version.
Update,

/// Run commands within a Codex-provided sandbox.
Sandbox(SandboxArgs),

Expand Down Expand Up @@ -615,6 +618,25 @@ fn run_update_action(action: UpdateAction) -> anyhow::Result<()> {
Ok(())
}

fn run_update_command() -> anyhow::Result<()> {
#[cfg(debug_assertions)]
{
anyhow::bail!(
"`codex update` is not available in debug builds. Install a release build of Codex to use this command."
);
}

#[cfg(not(debug_assertions))]
{
let Some(action) = codex_tui::get_update_action() else {
anyhow::bail!(
"Could not detect the Codex installation method. Please update manually: https://developers.openai.com/codex/cli/"
);
};
run_update_action(action)
}
}

fn run_execpolicycheck(cmd: ExecPolicyCheckCommand) -> anyhow::Result<()> {
cmd.run()
}
Expand Down Expand Up @@ -998,6 +1020,14 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
)?;
print_completion(completion_cli);
}
Some(Subcommand::Update) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
root_remote_auth_token_env.as_deref(),
"update",
)?;
run_update_command()?;
}
Some(Subcommand::Cloud(mut cloud_cli)) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
Expand Down Expand Up @@ -1890,6 +1920,12 @@ mod tests {
assert!(matches!(cli.subcommand, Some(Subcommand::Plugin(_))));
}

#[test]
fn update_parses_as_update_subcommand() {
let cli = MultitoolCli::try_parse_from(["codex", "update"]).expect("parse");
assert!(matches!(cli.subcommand, Some(Subcommand::Update)));
}

#[test]
fn plugin_marketplace_remove_parses_under_plugin() {
let cli =
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/cli/tests/marketplace_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async fn marketplace_upgrade_no_longer_runs_at_top_level() -> Result<()> {
.args(["marketplace", "upgrade"])
.assert()
.failure()
.stderr(contains("unexpected argument 'upgrade' found"));
.stderr(contains("unrecognized subcommand 'upgrade'"));

Ok(())
}
24 changes: 24 additions & 0 deletions codex-rs/cli/tests/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use anyhow::Result;
use predicates::str::contains;
use std::path::Path;
use tempfile::TempDir;

fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
let mut cmd = assert_cmd::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
cmd.env("CODEX_HOME", codex_home);
Ok(cmd)
}

#[cfg(debug_assertions)]
#[tokio::test]
async fn update_does_not_start_interactive_prompt() -> Result<()> {
let codex_home = TempDir::new()?;

codex_command(codex_home.path())?
.arg("update")
.assert()
.failure()
.stderr(contains("`codex update` is not available in debug builds"));

Ok(())
}
2 changes: 2 additions & 0 deletions codex-rs/tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ mod tui;
mod ui_consts;
pub(crate) mod update_action;
pub use update_action::UpdateAction;
#[cfg(not(debug_assertions))]
pub use update_action::get_update_action;
mod update_prompt;
#[cfg(any(not(debug_assertions), test))]
mod update_versions;
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/tui/src/update_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl UpdateAction {
}

#[cfg(not(debug_assertions))]
pub(crate) fn get_update_action() -> Option<UpdateAction> {
pub fn get_update_action() -> Option<UpdateAction> {
UpdateAction::from_install_context(InstallContext::current())
}

Expand Down
Loading