mirror of
https://github.com/openai/codex.git
synced 2026-02-02 15:03:38 +00:00
Compare commits
2 Commits
rakesh/Sup
...
dev/fouad/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
504b6e238e | ||
|
|
7d27e0e7e7 |
@@ -32,6 +32,14 @@ Then simply run `codex` to get started:
|
||||
codex
|
||||
```
|
||||
|
||||
To update Codex after installing it, run:
|
||||
|
||||
```shell
|
||||
codex update
|
||||
```
|
||||
|
||||
You can also run `codex upgrade`. The CLI will reuse the installation method you originally used (npm or Homebrew).
|
||||
|
||||
<details>
|
||||
<summary>You can also go to the <a href="https://github.com/openai/codex/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
|
||||
|
||||
|
||||
@@ -132,6 +132,14 @@ Run interactively:
|
||||
codex
|
||||
```
|
||||
|
||||
To upgrade Codex after installing it, run:
|
||||
|
||||
```shell
|
||||
codex update
|
||||
```
|
||||
|
||||
(`codex upgrade` works too.) The CLI reuses the installation method you originally used (npm or Homebrew).
|
||||
|
||||
Or, run with a prompt as input (and optionally in `Full Auto` mode):
|
||||
|
||||
```shell
|
||||
|
||||
@@ -38,3 +38,6 @@ tokio = { version = "1", features = [
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
codex-protocol-ts = { path = "../protocol-ts" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod debug_sandbox;
|
||||
mod exit_status;
|
||||
pub mod login;
|
||||
pub mod proto;
|
||||
pub mod update;
|
||||
|
||||
use clap::Parser;
|
||||
use codex_common::CliConfigOverrides;
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_cli::login::run_login_with_api_key;
|
||||
use codex_cli::login::run_login_with_chatgpt;
|
||||
use codex_cli::login::run_logout;
|
||||
use codex_cli::proto;
|
||||
use codex_cli::update::run_update_command;
|
||||
use codex_common::CliConfigOverrides;
|
||||
use codex_exec::Cli as ExecCli;
|
||||
use codex_tui::Cli as TuiCli;
|
||||
@@ -73,6 +74,10 @@ enum Subcommand {
|
||||
#[clap(visible_alias = "a")]
|
||||
Apply(ApplyCommand),
|
||||
|
||||
/// Update Codex using the original installation method.
|
||||
#[clap(visible_alias = "upgrade")]
|
||||
Update,
|
||||
|
||||
/// Internal: generate TypeScript protocol bindings.
|
||||
#[clap(hide = true)]
|
||||
GenerateTs(GenerateTsCommand),
|
||||
@@ -212,6 +217,9 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
||||
Some(Subcommand::GenerateTs(gen_cli)) => {
|
||||
codex_protocol_ts::generate_ts(&gen_cli.out_dir, gen_cli.prettier.as_deref())?;
|
||||
}
|
||||
Some(Subcommand::Update) => {
|
||||
run_update_command().await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
157
codex-rs/cli/src/update.rs
Normal file
157
codex-rs/cli/src/update.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::exit_status::handle_exit_status;
|
||||
|
||||
const RELEASE_URL: &str = "https://github.com/openai/codex/releases/latest";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum InstallMethod {
|
||||
Npm,
|
||||
Brew,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InstallEnvironment {
|
||||
managed_by_npm: bool,
|
||||
current_exe: Option<PathBuf>,
|
||||
is_macos: bool,
|
||||
}
|
||||
|
||||
impl InstallEnvironment {
|
||||
fn from_system() -> Self {
|
||||
Self {
|
||||
managed_by_npm: std::env::var_os("CODEX_MANAGED_BY_NPM").is_some(),
|
||||
current_exe: std::env::current_exe().ok(),
|
||||
is_macos: cfg!(target_os = "macos"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_update_command() -> ! {
|
||||
let env = InstallEnvironment::from_system();
|
||||
let Some(method) = detect_install_method(&env) else {
|
||||
eprintln!("Unable to determine how Codex was installed.");
|
||||
eprintln!("If you installed Codex with npm, run `npm install -g @openai/codex@latest`.",);
|
||||
eprintln!("If you installed Codex with Homebrew, run `brew upgrade codex`.");
|
||||
eprintln!("For other installation methods, see {RELEASE_URL}.");
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
let (program, args) = match method {
|
||||
InstallMethod::Npm => ("npm", ["install", "-g", "@openai/codex@latest"]),
|
||||
InstallMethod::Brew => ("brew", ["upgrade", "codex"]),
|
||||
};
|
||||
|
||||
run_external_command(program, &args).await;
|
||||
}
|
||||
|
||||
fn detect_install_method(env: &InstallEnvironment) -> Option<InstallMethod> {
|
||||
if env.managed_by_npm {
|
||||
return Some(InstallMethod::Npm);
|
||||
}
|
||||
|
||||
if env.is_macos
|
||||
&& env
|
||||
.current_exe
|
||||
.as_deref()
|
||||
.is_some_and(is_homebrew_executable)
|
||||
{
|
||||
return Some(InstallMethod::Brew);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_homebrew_executable(exe: &Path) -> bool {
|
||||
const HOMEBREW_PREFIXES: &[&str] = &["/opt/homebrew", "/usr/local"];
|
||||
HOMEBREW_PREFIXES
|
||||
.iter()
|
||||
.any(|prefix| exe.starts_with(prefix))
|
||||
}
|
||||
|
||||
async fn run_external_command(program: &str, args: &[&str]) -> ! {
|
||||
let command_display = format_command(program, args);
|
||||
eprintln!("Running `{command_display}` to update Codex...");
|
||||
|
||||
let status = Command::new(program)
|
||||
.args(args)
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.await;
|
||||
|
||||
match status {
|
||||
Ok(status) => handle_exit_status(status),
|
||||
Err(err) => {
|
||||
eprintln!("Failed to execute `{command_display}`: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_command(program: &str, args: &[&str]) -> String {
|
||||
let mut command = String::from(program);
|
||||
for arg in args {
|
||||
command.push(' ');
|
||||
command.push_str(arg);
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn detects_npm_when_env_var_is_present() {
|
||||
let env = InstallEnvironment {
|
||||
managed_by_npm: true,
|
||||
current_exe: Some(PathBuf::from("/opt/homebrew/bin/codex")),
|
||||
is_macos: true,
|
||||
};
|
||||
assert_eq!(detect_install_method(&env), Some(InstallMethod::Npm));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_homebrew_install_on_macos() {
|
||||
let env = InstallEnvironment {
|
||||
managed_by_npm: false,
|
||||
current_exe: Some(PathBuf::from("/opt/homebrew/bin/codex")),
|
||||
is_macos: true,
|
||||
};
|
||||
assert_eq!(detect_install_method(&env), Some(InstallMethod::Brew));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_when_install_method_is_unknown() {
|
||||
let env = InstallEnvironment {
|
||||
managed_by_npm: false,
|
||||
current_exe: Some(PathBuf::from("/tmp/codex")),
|
||||
is_macos: false,
|
||||
};
|
||||
assert_eq!(detect_install_method(&env), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn homebrew_prefixes_are_detected() {
|
||||
assert!(is_homebrew_executable(Path::new("/opt/homebrew/bin/codex")));
|
||||
assert!(is_homebrew_executable(Path::new("/usr/local/bin/codex")));
|
||||
assert!(!is_homebrew_executable(Path::new(
|
||||
"/home/user/.local/bin/codex"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_formatting_is_readable() {
|
||||
assert_eq!(
|
||||
format_command("npm", &["install", "-g", "@openai/codex@latest"]),
|
||||
"npm install -g @openai/codex@latest"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -269,21 +269,17 @@ async fn run_ratatui_app(
|
||||
format!("{current_version} -> {latest_version}.").into(),
|
||||
]));
|
||||
|
||||
if managed_by_npm {
|
||||
let npm_cmd = "npm install -g @openai/codex@latest";
|
||||
let knows_update_command = managed_by_npm
|
||||
|| (cfg!(target_os = "macos")
|
||||
&& (exe.starts_with("/opt/homebrew") || exe.starts_with("/usr/local")));
|
||||
|
||||
if knows_update_command {
|
||||
lines.push(Line::from(vec![
|
||||
"Run ".into(),
|
||||
npm_cmd.cyan(),
|
||||
" to update.".into(),
|
||||
]));
|
||||
} else if cfg!(target_os = "macos")
|
||||
&& (exe.starts_with("/opt/homebrew") || exe.starts_with("/usr/local"))
|
||||
{
|
||||
let brew_cmd = "brew upgrade codex";
|
||||
lines.push(Line::from(vec![
|
||||
"Run ".into(),
|
||||
brew_cmd.cyan(),
|
||||
" to update.".into(),
|
||||
"codex update".cyan(),
|
||||
" (or ".into(),
|
||||
"codex upgrade".cyan(),
|
||||
") to update automatically.".into(),
|
||||
]));
|
||||
} else {
|
||||
lines.push(Line::from(vec![
|
||||
|
||||
@@ -109,6 +109,7 @@ fn parse_version(v: &str) -> Option<(u64, u64, u64)> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn prerelease_version_is_not_considered_newer() {
|
||||
|
||||
Reference in New Issue
Block a user