Skip to content
Open
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install --cask codex</code></p>
<p align="center"><code>curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/install.sh | bash</code><br />or <code>brew install --cask codex</code><br />or <code>npm i -g @openai/codex</code></p>
<p align="center"><strong>Codex CLI</strong> is a coding agent from OpenAI that runs locally on your computer.
<p align="center">
<img src="./.github/codex-cli-splash.png" alt="Codex CLI splash" width="80%" />
Expand All @@ -15,6 +15,13 @@ If you want Codex in your code editor (VS Code, Cursor, Windsurf), <a href="http

Install globally with your preferred package manager:

```shell
# Install using curl
curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/install.sh | bash
```

If you want to install somewhere other than `~/.codex`, set `CODEX_HOME` first.

```shell
# Install using npm
npm install -g @openai/codex
Expand Down
36 changes: 31 additions & 5 deletions codex-rs/tui/src/update_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub enum UpdateAction {
BunGlobalLatest,
/// Update via `brew upgrade codex`.
BrewUpgrade,
/// Update via the curl installer.
CurlInstallerUpdate,
}

impl UpdateAction {
Expand All @@ -16,6 +18,13 @@ impl UpdateAction {
UpdateAction::NpmGlobalLatest => ("npm", &["install", "-g", "@openai/codex"]),
UpdateAction::BunGlobalLatest => ("bun", &["install", "-g", "@openai/codex"]),
UpdateAction::BrewUpgrade => ("brew", &["upgrade", "codex"]),
UpdateAction::CurlInstallerUpdate => (
"bash",
&[
"-c",
"set -euo pipefail; curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/update.sh | bash",
],
),
}
}

Expand All @@ -32,12 +41,14 @@ pub(crate) fn get_update_action() -> Option<UpdateAction> {
let exe = std::env::current_exe().unwrap_or_default();
let managed_by_npm = std::env::var_os("CODEX_MANAGED_BY_NPM").is_some();
let managed_by_bun = std::env::var_os("CODEX_MANAGED_BY_BUN").is_some();
let managed_by_curl = std::env::var_os("CODEX_MANAGED_BY_CURL").is_some();

detect_update_action(
cfg!(target_os = "macos"),
&exe,
managed_by_npm,
managed_by_bun,
managed_by_curl,
)
}

Expand All @@ -47,11 +58,14 @@ fn detect_update_action(
current_exe: &std::path::Path,
managed_by_npm: bool,
managed_by_bun: bool,
managed_by_curl: bool,
) -> Option<UpdateAction> {
if managed_by_npm {
Some(UpdateAction::NpmGlobalLatest)
} else if managed_by_bun {
Some(UpdateAction::BunGlobalLatest)
} else if managed_by_curl {
Some(UpdateAction::CurlInstallerUpdate)
} else if is_macos
&& (current_exe.starts_with("/opt/homebrew") || current_exe.starts_with("/usr/local"))
{
Expand All @@ -68,23 +82,30 @@ mod tests {
#[test]
fn detects_update_action_without_env_mutation() {
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), false, false),
detect_update_action(
false,
std::path::Path::new("/any/path"),
false,
false,
false
),
None
);
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), true, false),
detect_update_action(false, std::path::Path::new("/any/path"), true, false, false),
Some(UpdateAction::NpmGlobalLatest)
);
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), false, true),
detect_update_action(false, std::path::Path::new("/any/path"), false, true, false),
Some(UpdateAction::BunGlobalLatest)
);
assert_eq!(
detect_update_action(
true,
std::path::Path::new("/opt/homebrew/bin/codex"),
false,
false
false,
false,
),
Some(UpdateAction::BrewUpgrade)
);
Expand All @@ -93,9 +114,14 @@ mod tests {
true,
std::path::Path::new("/usr/local/bin/codex"),
false,
false
false,
false,
),
Some(UpdateAction::BrewUpgrade)
);
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), false, false, true),
Some(UpdateAction::CurlInstallerUpdate)
);
}
}
9 changes: 9 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
| Git (optional, recommended) | 2.23+ for built-in PR helpers |
| RAM | 4-GB minimum (8-GB recommended) |

### Install via curl

```bash
curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/install.sh | bash
```

The curl installer writes under `CODEX_HOME` (default: `~/.codex`). See
`installer/README.md` for the detailed layout and mechanics.

### DotSlash

The GitHub Release also contains a [DotSlash](https://dotslash-cli.com/) file for the Codex CLI named `codex`. Using a DotSlash file makes it possible to make a lightweight commit to source control to ensure all contributors use the same version of an executable, regardless of what platform they use for development.
Expand Down
104 changes: 104 additions & 0 deletions installer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Codex Curl Installer

This folder contains the non-Rust assets for the curl-based Codex installer
and update flow. Rust code lives under `codex-rs/`.

## Goals

- Download only the binaries needed for the current platform.
- Keep the install isolated under `CODEX_HOME` (default: `~/.codex`).
- Avoid breaking npm/brew installs; shadowing is acceptable.
- Support additional helper CLIs without globally polluting `PATH`.

## Install Root (`CODEX_HOME`)

The installer treats `CODEX_HOME` as the install root:

- If `CODEX_HOME` is set, it is used as-is.
- Otherwise, the default is `~/.codex`.

All curl-managed artifacts should live under this root.

## On-Disk Layout

The layout is designed to support multiple versions, helper binaries, and
atomic updates:

- `CODEX_HOME/bin/`
- `CODEX_HOME/versions/<version>/`
- `CODEX_HOME/versions/<version>/bin/`
- `CODEX_HOME/tools/<tool>/<version>/`
- `CODEX_HOME/tools/bin/`

Key conventions:

- The user-facing entrypoint is `CODEX_HOME/bin/codex`.
- `CODEX_HOME/versions/current` is a symlink to the active version directory.
- Helper CLIs that ship with Codex (for example, Windows sandbox helpers) live
in `CODEX_HOME/versions/<version>/bin/`.
- Third-party tools we fetch (for example, `rg`) live under
`CODEX_HOME/tools/...`, with optional shims in `CODEX_HOME/tools/bin/`.

## PATH Strategy

We separate the user's global `PATH` from Codex's runtime `PATH`:

1. The installer ensures `CODEX_HOME/bin` is on the user's `PATH`.
2. The `codex` wrapper augments `PATH` at runtime to include:
- `CODEX_HOME/bin`
- `CODEX_HOME/tools/bin`
- `CODEX_HOME/versions/current/bin`

This keeps helper CLIs available to Codex without exposing them as global
commands in every shell session.

## Versioning And Atomic Updates

Curl-managed installs should be versioned:

1. Download into a versioned directory:
- `CODEX_HOME/versions/<version>/`
2. Link `CODEX_HOME/versions/current` to the new version atomically.
3. Keep a small number of prior versions for rollback.

Because the wrapper resolves through `versions/current`, repointing the
symlink updates the effective version without editing shell rc files again.

## Helper CLI Placement

Any additional CLIs that Codex needs at runtime should follow these rules:

- Bundled CLIs that are version-coupled to Codex:
- Place in `CODEX_HOME/versions/<version>/bin/`
- Third-party tools that may be shared across versions:
- Place in `CODEX_HOME/tools/<tool>/<version>/`
- Optionally add a stable shim in `CODEX_HOME/tools/bin/`

The wrapper then makes them available during execution.

## Ripgrep (`rg`)

The preferred approach is:

- Use a system `rg` when available.
- Otherwise, allow curl-managed installs to place `rg` under
`CODEX_HOME/tools/rg/<version>/` with a shim in `CODEX_HOME/tools/bin/rg`.

Codex CLI can optionally honor an explicit `CODEX_RG_PATH` to point directly
to a managed `rg`.

## Scripts

Planned/expected scripts in this folder:

- `installer/install.sh`
- `installer/lib.sh`
- `installer/update.sh`

The public one-liner should look like:

```sh
curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/install.sh | bash
```

All scripts must honor `CODEX_HOME` with a fallback to `~/.codex`.
73 changes: 73 additions & 0 deletions installer/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash

if [ -z "${BASH_VERSION:-}" ]; then
echo "This installer requires bash." >&2
echo "Re-run with: curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/install.sh | bash" >&2
exit 1
fi

set -euo pipefail

INSTALLER_BASE_URL="${CODEX_INSTALLER_BASE_URL:-https://raw.githubusercontent.com/openai/codex/main/installer}"

load_lib() {
local script_dir lib_path tmp_lib
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
lib_path="${script_dir}/lib.sh"

if [ -f "$lib_path" ]; then
# Running from a repo checkout.
# shellcheck disable=SC1090
source "$lib_path"
return
fi

tmp_lib="$(mktemp)"
curl -fsSL "${INSTALLER_BASE_URL}/lib.sh" -o "$tmp_lib"
# shellcheck disable=SC1090
source "$tmp_lib"
rm -f "$tmp_lib"
}

load_lib

main() {
ensure_dirs

local arch os tag version url tarball rc_file resolved_home
arch="$(detect_arch)"
os="$(detect_os)"

if [ -n "${CODEX_VERSION:-}" ]; then
version="$(normalize_version "$CODEX_VERSION")"
tag="$(release_tag_for_version "$version")"
else
tag="$(latest_tag)"
if [ -z "$tag" ]; then
echo "Failed to determine the latest Codex release tag." >&2
exit 1
fi
version="$(normalize_version "$tag")"
fi

url="$(release_url "$version" "$arch" "$os")"
tarball="$(mktemp)"
curl -fsSL "$url" -o "$tarball"

install_version_from_tarball "$version" "$tarball" "$arch" "$os"
activate_version "$version"
install_wrapper
cleanup_old_versions 2

rc_file="$(choose_rc_file)"
resolved_home="$(codex_home)"
ensure_path_block "$rc_file" "$resolved_home"

rm -f "$tarball"

echo "Codex ${version} installed to ${resolved_home}"
echo "Updated PATH in ${rc_file}"
echo "Open a new shell or run: source ${rc_file}"
}

main "$@"
Loading
Loading