Files
codex/scripts/install/install.sh
Channing Conger ef500f4d23 Move code mode behind an IPC host
Split the code-mode protocol and client from the V8-backed runtime so core and
app-server no longer link codex-code-mode in production. ThreadManager now
provisions durable code-mode sessions through a shared external host process,
while tests can still inject the in-process provider.

The IPC protocol uses a persistent stdin/stdout transport. Each frame is a
4-byte big-endian length followed by JSON, with a 16 MiB frame limit. Client
requests carry u64 request IDs so create, execute, wait, terminate, and shutdown
operations can be multiplexed over one process. Session IDs isolate durable
stored values. Execute returns an ExecutionStarted response immediately and an
asynchronous InitialResponse when the initial yield or completion is available.

Nested tool calls and notifications travel from the host back to the client as
delegate requests with their own IDs. Delegate responses, cancellation, and
cell-closed lifecycle messages use the same framed channel. Wire operations
encode errors as Result values. A dead connection fails pending operations,
cancels outstanding delegates, and lets the provider spawn a new host for later
sessions.

Build codex-code-mode-host with V8 pointer-compression sandbox support and add
it to canonical primary and app-server packages, legacy Linux and Windows
bundles, signing verification, installers, Python runtime packages, and release
CI for macOS, Linux, and Windows. The host is discovered next to the current
executable, through CODEX_CODE_MODE_HOST_PATH, or on PATH. OS-level seccomp or
seatbelt restrictions remain a follow-up to this cross-platform process split.

Benchmarks were run from release builds on Linux x86_64 with the V8 sandbox
profile and a text('ok') workload. Cold measurements used 30 samples, warm
session provisioning used 200, and warm command execution used 500. Values are
mean/p50/p95 in milliseconds:

- session startup: in-process 0.002/0.002/0.005, IPC 2.623/2.599/2.894
- fresh-session command: in-process 1.831/1.758/1.915, IPC 7.428/7.252/8.306
- warm session provisioning: in-process 0.002/0.002/0.003,
  IPC 0.471/0.463/0.581
- warm command: in-process 1.759/1.757/1.940, IPC 2.005/2.001/2.166

The steady-state median command overhead is approximately 0.244 ms. The median
fresh host plus first command cost is 7.252 ms.

Validation:

- 62/62 core code-mode integration tests passed against the external host
- focused protocol, client, runtime, host, tools, and trace tests passed
- Cargo and Bazel real-process host IPC tests passed
- 11/11 package builder tests passed
- Bazel lock verification, scoped Clippy fixes, and repository formatting passed
2026-06-10 14:38:41 -07:00

942 lines
21 KiB
Bash
Executable File

#!/bin/sh
set -eu
RELEASE="${CODEX_RELEASE:-latest}"
NON_INTERACTIVE="${CODEX_NON_INTERACTIVE:-false}"
BIN_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}"
BIN_PATH="$BIN_DIR/codex"
CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}"
STANDALONE_ROOT="$CODEX_HOME_DIR/packages/standalone"
RELEASES_DIR="$STANDALONE_ROOT/releases"
CURRENT_LINK="$STANDALONE_ROOT/current"
LOCK_FILE="$STANDALONE_ROOT/install.lock"
LOCK_DIR="$STANDALONE_ROOT/install.lock.d"
LOCK_STALE_AFTER_SECS=600
path_action="already"
path_profile=""
conflict_manager=""
conflict_path=""
lock_kind=""
tmp_dir=""
step() {
printf '==> %s\n' "$1"
}
warn() {
printf 'WARNING: %s\n' "$1" >&2
}
normalize_version() {
case "$1" in
"" | latest)
printf 'latest\n'
;;
rust-v*)
printf '%s\n' "${1#rust-v}"
;;
v*)
printf '%s\n' "${1#v}"
;;
*)
printf '%s\n' "$1"
;;
esac
}
validate_version() {
version="$1"
if [ "$version" = "latest" ]; then
return
fi
if ! printf '%s\n' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)(\.[0-9]+)?)?$'; then
echo "Invalid Codex release version: $version. Expected latest or x.y.z[-alpha[.N]|-beta[.N]]." >&2
exit 1
fi
}
parse_args() {
while [ "$#" -gt 0 ]; do
case "$1" in
--release)
if [ "$#" -lt 2 ]; then
echo "--release requires a value." >&2
exit 1
fi
RELEASE="$2"
shift
;;
--help | -h)
cat <<EOF
Usage: install.sh [--release VERSION]
Environment:
CODEX_RELEASE Version to install; overridden by --release.
CODEX_NON_INTERACTIVE Set to 1, true, or yes to skip prompts.
EOF
exit 0
;;
*)
echo "Unknown argument: $1" >&2
exit 1
;;
esac
shift
done
}
download_file() {
url="$1"
output="$2"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "$output"
return
fi
if command -v wget >/dev/null 2>&1; then
wget -q -O "$output" "$url"
return
fi
echo "curl or wget is required to install Codex." >&2
exit 1
}
download_text() {
url="$1"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url"
return
fi
if command -v wget >/dev/null 2>&1; then
wget -q -O - "$url"
return
fi
echo "curl or wget is required to install Codex." >&2
exit 1
}
release_url_for_asset() {
asset="$1"
resolved_version="$2"
printf 'https://github.com/openai/codex/releases/download/rust-v%s/%s\n' "$resolved_version" "$asset"
}
release_metadata_url() {
resolved_version="$1"
printf 'https://api.github.com/repos/openai/codex/releases/tags/rust-v%s\n' "$resolved_version"
}
release_asset_digest_or_empty() {
asset="$1"
resolved_version="$2"
release_json="$(download_text "$(release_metadata_url "$resolved_version")")"
digest="$(printf '%s\n' "$release_json" | awk -v asset="$asset" '
/"name":[[:space:]]*"[^"]+"/ {
name = $0
sub(/^.*"name":[[:space:]]*"/, "", name)
sub(/".*$/, "", name)
if (name == asset) {
in_asset = 1
asset_depth = depth
}
}
in_asset && /"digest":[[:space:]]*"[^"]+"/ {
digest = $0
sub(/^.*"digest":[[:space:]]*"/, "", digest)
sub(/".*$/, "", digest)
}
{
line = $0
opens = gsub(/\{/, "{", line)
closes = gsub(/\}/, "}", line)
depth += opens - closes
if (in_asset && depth < asset_depth) {
in_asset = 0
}
}
END {
if (digest != "") {
print digest
}
}
')"
case "$digest" in
sha256:????????????????????????????????????????????????????????????????)
printf '%s\n' "${digest#sha256:}"
;;
*)
return 1
;;
esac
}
release_asset_exists() {
asset="$1"
resolved_version="$2"
release_asset_digest_or_empty "$asset" "$resolved_version" >/dev/null 2>&1
}
release_asset_digest() {
asset="$1"
resolved_version="$2"
digest="$(release_asset_digest_or_empty "$asset" "$resolved_version" || true)"
if [ -z "$digest" ]; then
echo "Could not find SHA-256 digest for release asset $asset." >&2
exit 1
fi
printf '%s\n' "$digest"
}
package_archive_digest() {
asset="$1"
manifest_path="$2"
digest="$(awk -v asset="$asset" '
$2 == asset && $1 ~ /^[0-9a-fA-F]{64}$/ {
print tolower($1)
found = 1
exit
}
END {
if (!found) {
exit 1
}
}
' "$manifest_path" 2>/dev/null || true)"
if [ -z "$digest" ]; then
echo "Could not find SHA-256 digest for $asset in codex-package_SHA256SUMS." >&2
exit 1
fi
printf '%s\n' "$digest"
}
file_sha256() {
path="$1"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$path" | awk '{print $1}'
return
fi
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$path" | awk '{print $1}'
return
fi
if command -v openssl >/dev/null 2>&1; then
openssl dgst -sha256 "$path" | sed 's/^.*= //'
return
fi
echo "sha256sum, shasum, or openssl is required to verify the Codex download." >&2
exit 1
}
verify_archive_digest() {
archive_path="$1"
expected_digest="$2"
actual_digest="$(file_sha256 "$archive_path")"
if [ "$actual_digest" != "$expected_digest" ]; then
echo "Downloaded Codex archive checksum did not match expected digest." >&2
echo "expected: $expected_digest" >&2
echo "actual: $actual_digest" >&2
exit 1
fi
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "$1 is required to install Codex." >&2
exit 1
fi
}
resolve_version() {
normalized_version="$(normalize_version "$RELEASE")"
validate_version "$normalized_version"
if [ "$normalized_version" != "latest" ]; then
printf '%s\n' "$normalized_version"
return
fi
release_json="$(download_text "https://api.github.com/repos/openai/codex/releases/latest")"
resolved="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"rust-v\([^"]*\)".*/\1/p' | head -n 1)"
if [ -z "$resolved" ]; then
echo "Failed to resolve the latest Codex release version." >&2
exit 1
fi
validate_version "$resolved"
printf '%s\n' "$resolved"
}
pick_profile() {
# Use the same shell-specific split Homebrew documents because there is no
# universal startup file across macOS/Linux login and interactive shells.
case "$os:${SHELL:-}" in
darwin:*/zsh)
printf '%s\n' "$HOME/.zprofile"
;;
darwin:*/bash)
printf '%s\n' "$HOME/.bash_profile"
;;
linux:*/zsh)
printf '%s\n' "$HOME/.zshrc"
;;
linux:*/bash)
printf '%s\n' "$HOME/.bashrc"
;;
*)
printf '%s\n' "$HOME/.profile"
;;
esac
}
add_to_path() {
path_action="already"
path_profile=""
case ":$PATH:" in
*":$BIN_DIR:"*)
if [ -z "$conflict_manager" ]; then
return
fi
;;
esac
profile="$(pick_profile)"
path_profile="$profile"
begin_marker="# >>> Codex installer >>>"
end_marker="# <<< Codex installer <<<"
path_line="export PATH=\"$BIN_DIR:\$PATH\""
if [ -f "$profile" ] && grep -F "$begin_marker" "$profile" >/dev/null 2>&1; then
if grep -F "$path_line" "$profile" >/dev/null 2>&1; then
path_action="configured"
return
fi
if grep -F "$end_marker" "$profile" >/dev/null 2>&1; then
rewrite_path_block "$profile" "$begin_marker" "$end_marker" "$path_line"
path_action="updated"
return
fi
fi
append_path_block "$profile" "$begin_marker" "$end_marker" "$path_line"
path_action="added"
}
append_path_block() {
profile="$1"
begin_marker="$2"
end_marker="$3"
path_line="$4"
{
printf '\n%s\n' "$begin_marker"
printf '%s\n' "$path_line"
printf '%s\n' "$end_marker"
} >>"$profile"
}
rewrite_path_block() {
profile="$1"
begin_marker="$2"
end_marker="$3"
path_line="$4"
tmp_profile="$tmp_dir/profile.$$.tmp"
awk -v begin="$begin_marker" -v end="$end_marker" -v line="$path_line" '
BEGIN {
in_block = 0
replaced = 0
}
$0 == begin {
if (!replaced) {
print begin
print line
print end
replaced = 1
}
in_block = 1
next
}
in_block {
if ($0 == end) {
in_block = 0
}
next
}
{
print
}
END {
if (in_block != 0) {
exit 1
}
}
' "$profile" >"$tmp_profile"
mv "$tmp_profile" "$profile"
}
mkdir_lock_is_stale() {
[ -d "$LOCK_DIR" ] || return 1
pid="$(cat "$LOCK_DIR/pid" 2>/dev/null || true)"
started_at="$(cat "$LOCK_DIR/started_at" 2>/dev/null || true)"
now="$(date +%s 2>/dev/null || printf '0')"
case "$started_at" in
''|*[!0-9]*)
started_at=0
;;
esac
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
return 1
fi
if [ "$started_at" -eq 0 ] || [ "$now" -eq 0 ]; then
return 0
fi
[ $((now - started_at)) -ge "$LOCK_STALE_AFTER_SECS" ]
}
acquire_install_lock() {
mkdir -p "$STANDALONE_ROOT"
if [ "$os" = "darwin" ] && command -v lockf >/dev/null 2>&1; then
: >>"$LOCK_FILE"
exec 9<>"$LOCK_FILE"
lockf 9
lock_kind="lockf"
return
fi
if command -v flock >/dev/null 2>&1; then
exec 9>"$LOCK_FILE"
flock 9
lock_kind="flock"
return
fi
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
if mkdir_lock_is_stale; then
warn "Removing stale installer lock at $LOCK_DIR"
rm -rf "$LOCK_DIR"
continue
fi
sleep 1
done
printf '%s\n' "$$" >"$LOCK_DIR/pid"
date +%s >"$LOCK_DIR/started_at" 2>/dev/null || true
lock_kind="mkdir"
}
release_install_lock() {
if [ "$lock_kind" = "mkdir" ]; then
rm -rf "$LOCK_DIR" 2>/dev/null || true
elif [ "$lock_kind" = "flock" ] || [ "$lock_kind" = "lockf" ]; then
exec 9>&- 2>/dev/null || true
fi
lock_kind=""
}
cleanup_stale_install_artifacts() {
mkdir -p "$RELEASES_DIR" "$STANDALONE_ROOT"
find "$RELEASES_DIR" -mindepth 1 -maxdepth 1 -name '.staging.*' -exec rm -rf {} +
find "$STANDALONE_ROOT" -mindepth 1 -maxdepth 1 -name '.current.*' -exec rm -f {} +
if [ -d "$BIN_DIR" ]; then
find "$BIN_DIR" -mindepth 1 -maxdepth 1 -name '.codex.*' -exec rm -f {} +
fi
}
replace_path_with_symlink() {
link_path="$1"
link_target="$2"
tmp_link="$3"
rm -f "$tmp_link"
ln -s "$link_target" "$tmp_link"
if mv -Tf "$tmp_link" "$link_path" 2>/dev/null; then
return
fi
if mv -hf "$tmp_link" "$link_path" 2>/dev/null; then
return
fi
rm -f "$link_path"
mv -f "$tmp_link" "$link_path"
}
version_from_binary() {
codex_path="$1"
if [ ! -x "$codex_path" ]; then
return 1
fi
"$codex_path" --version 2>/dev/null | sed -n 's/.* \([0-9][0-9A-Za-z.+-]*\)$/\1/p' | head -n 1
}
current_installed_version() {
version="$(version_from_binary "$CURRENT_LINK/bin/codex" || true)"
if [ -n "$version" ]; then
printf '%s\n' "$version"
return 0
fi
version="$(version_from_binary "$CURRENT_LINK/codex" || true)"
if [ -n "$version" ]; then
printf '%s\n' "$version"
return 0
fi
return 0
}
resolve_existing_codex() {
command -v codex 2>/dev/null || true
}
classify_existing_codex() {
existing_path="$1"
if [ -z "$existing_path" ] || [ "$existing_path" = "$BIN_PATH" ]; then
return 1
fi
case "$existing_path" in
/opt/homebrew/* | /usr/local/*)
if [ "$os" = "darwin" ]; then
printf 'brew\n'
return 0
fi
;;
esac
if [ -f "$existing_path" ] && grep -F "#!/usr/bin/env node" "$existing_path" >/dev/null 2>&1; then
case "$existing_path" in
*".bun"*)
printf 'bun\n'
;;
*)
printf 'npm\n'
;;
esac
return 0
fi
return 1
}
prompt_yes_no() {
prompt="$1"
case "$NON_INTERACTIVE" in
1 | [Tt][Rr][Uu][Ee] | [Yy][Ee][Ss])
return 1
;;
esac
if ( : </dev/tty ) 2>/dev/null; then
printf '%s [y/N] ' "$prompt" >/dev/tty
if ! IFS= read -r answer </dev/tty; then
return 1
fi
elif [ -t 0 ]; then
printf '%s [y/N] ' "$prompt"
if ! IFS= read -r answer; then
return 1
fi
else
return 1
fi
case "$answer" in
y | Y | yes | YES)
return 0
;;
*)
return 1
;;
esac
}
print_launch_instructions() {
case "$path_action" in
added)
step "Current terminal: export PATH=\"$BIN_DIR:\$PATH\" && codex"
step "Future terminals: open a new terminal and run: codex"
step "PATH was added to $path_profile"
;;
updated)
step "Current terminal: export PATH=\"$BIN_DIR:\$PATH\" && codex"
step "Future terminals: open a new terminal and run: codex"
step "PATH was updated in $path_profile"
;;
configured)
step "Current terminal: export PATH=\"$BIN_DIR:\$PATH\" && codex"
step "Future terminals: open a new terminal and run: codex"
step "PATH is already configured in $path_profile"
;;
*)
step "Current terminal: codex"
step "Future terminals: open a new terminal and run: codex"
;;
esac
}
maybe_launch_codex_now() {
if prompt_yes_no "Start Codex now?"; then
step "Launching Codex"
"$BIN_PATH"
fi
}
detect_conflicting_install() {
existing_path="$(resolve_existing_codex)"
manager="$(classify_existing_codex "$existing_path" || true)"
if [ -z "$manager" ]; then
return
fi
conflict_manager="$manager"
conflict_path="$existing_path"
step "Detected existing $manager-managed Codex at $existing_path"
warn "Multiple managed Codex installs can be ambiguous because PATH order decides which one runs."
}
handle_conflicting_install() {
if [ -z "$conflict_manager" ]; then
return
fi
case "$conflict_manager" in
brew)
uninstall_cmd="brew uninstall --cask codex"
;;
bun)
uninstall_cmd="bun remove -g @openai/codex"
;;
*)
uninstall_cmd="npm uninstall -g @openai/codex"
;;
esac
if prompt_yes_no "Uninstall the existing $conflict_manager-managed Codex now?"; then
step "Running: $uninstall_cmd"
if ! sh -c "$uninstall_cmd"; then
warn "Failed to uninstall the existing $conflict_manager-managed Codex. Continuing with the standalone install."
fi
else
warn "Leaving the existing $conflict_manager-managed Codex installed. PATH order will determine which codex runs."
fi
}
install_package_release() {
release_dir="$1"
archive_path="$2"
stage_release="$RELEASES_DIR/.staging.$(basename "$release_dir").$$"
mkdir -p "$RELEASES_DIR"
rm -rf "$stage_release"
mkdir -p "$stage_release"
tar -xzf "$archive_path" -C "$stage_release"
chmod 0755 \
"$stage_release/bin/codex" \
"$stage_release/bin/codex-code-mode-host" \
"$stage_release/codex-path/rg"
if [ -f "$stage_release/codex-resources/bwrap" ]; then
chmod 0755 "$stage_release/codex-resources/bwrap"
fi
ln -sf "bin/codex" "$stage_release/codex"
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
rm -rf "$release_dir"
fi
mv "$stage_release" "$release_dir"
}
install_legacy_platform_npm_release() {
release_dir="$1"
archive_path="$2"
target="$3"
stage_release="$RELEASES_DIR/.staging.$(basename "$release_dir").$$"
extract_dir="$tmp_dir/extract"
vendor_root="$extract_dir/package/vendor/$target"
mkdir -p "$RELEASES_DIR"
rm -rf "$stage_release" "$extract_dir"
mkdir -p "$stage_release/codex-resources" "$extract_dir"
tar -xzf "$archive_path" -C "$extract_dir"
cp "$vendor_root/codex/codex" "$stage_release/codex"
cp "$vendor_root/path/rg" "$stage_release/codex-resources/rg"
chmod 0755 "$stage_release/codex" "$stage_release/codex-resources/rg"
if [ -f "$vendor_root/codex-resources/bwrap" ]; then
cp "$vendor_root/codex-resources/bwrap" "$stage_release/codex-resources/bwrap"
chmod 0755 "$stage_release/codex-resources/bwrap"
fi
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
rm -rf "$release_dir"
fi
mv "$stage_release" "$release_dir"
}
release_dir_is_complete() {
release_dir="$1"
expected_version="$2"
expected_target="$3"
layout="$4"
[ -d "$release_dir" ] &&
[ "$(basename "$release_dir")" = "$expected_version-$expected_target" ] ||
return 1
case "$layout" in
package)
[ -f "$release_dir/codex-package.json" ] &&
[ -x "$release_dir/bin/codex" ] &&
[ -x "$release_dir/bin/codex-code-mode-host" ] &&
[ -x "$release_dir/codex" ] &&
[ -x "$release_dir/codex-path/rg" ] ||
return 1
;;
legacy-platform-npm)
[ -x "$release_dir/codex" ] &&
[ -x "$release_dir/codex-resources/rg" ] ||
return 1
;;
*)
return 1
;;
esac
case "$layout:$expected_target" in
package:*linux* | legacy-platform-npm:*linux*) [ -x "$release_dir/codex-resources/bwrap" ] ;;
*) true ;;
esac
}
update_current_link() {
release_dir="$1"
tmp_link="$STANDALONE_ROOT/.current.$$"
replace_path_with_symlink "$CURRENT_LINK" "$release_dir" "$tmp_link"
}
release_codex_relative_path() {
release_dir="$1"
if [ -x "$release_dir/bin/codex" ]; then
printf 'bin/codex\n'
else
printf 'codex\n'
fi
}
update_visible_command() {
release_dir="$1"
mkdir -p "$BIN_DIR"
tmp_link="$BIN_DIR/.codex.$$"
codex_relative_path="$(release_codex_relative_path "$release_dir")"
replace_path_with_symlink "$BIN_PATH" "$CURRENT_LINK/$codex_relative_path" "$tmp_link"
}
verify_visible_command() {
"$BIN_PATH" --version >/dev/null
}
parse_args "$@"
require_command mktemp
require_command tar
case "$(uname -s)" in
Darwin)
os="darwin"
;;
Linux)
os="linux"
;;
*)
echo "install.sh supports macOS and Linux. Use install.ps1 on Windows." >&2
exit 1
;;
esac
case "$(uname -m)" in
x86_64 | amd64)
arch="x86_64"
;;
arm64 | aarch64)
arch="aarch64"
;;
*)
echo "Unsupported architecture: $(uname -m)" >&2
exit 1
;;
esac
if [ "$os" = "darwin" ] && [ "$arch" = "x86_64" ]; then
if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then
arch="aarch64"
fi
fi
if [ "$os" = "darwin" ]; then
if [ "$arch" = "aarch64" ]; then
npm_tag="darwin-arm64"
vendor_target="aarch64-apple-darwin"
platform_label="macOS (Apple Silicon)"
else
npm_tag="darwin-x64"
vendor_target="x86_64-apple-darwin"
platform_label="macOS (Intel)"
fi
else
if [ "$arch" = "aarch64" ]; then
npm_tag="linux-arm64"
vendor_target="aarch64-unknown-linux-musl"
platform_label="Linux (ARM64)"
else
npm_tag="linux-x64"
vendor_target="x86_64-unknown-linux-musl"
platform_label="Linux (x64)"
fi
fi
resolved_version="$(resolve_version)"
package_asset="codex-package-$vendor_target.tar.gz"
checksum_asset="codex-package_SHA256SUMS"
if release_asset_exists "$package_asset" "$resolved_version" &&
release_asset_exists "$checksum_asset" "$resolved_version"; then
install_layout="package"
asset="$package_asset"
elif release_asset_exists "codex-npm-$npm_tag-$resolved_version.tgz" "$resolved_version"; then
install_layout="legacy-platform-npm"
asset="codex-npm-$npm_tag-$resolved_version.tgz"
else
echo "Could not find Codex package or platform npm release assets for Codex $resolved_version." >&2
exit 1
fi
download_url="$(release_url_for_asset "$asset" "$resolved_version")"
checksum_url="$(release_url_for_asset "$checksum_asset" "$resolved_version")"
release_name="$resolved_version-$vendor_target"
release_dir="$RELEASES_DIR/$release_name"
current_version="$(current_installed_version)"
if [ -n "$current_version" ] && [ "$current_version" != "$resolved_version" ]; then
step "Updating Codex CLI from $current_version to $resolved_version"
elif [ -n "$current_version" ]; then
step "Updating Codex CLI"
else
step "Installing Codex CLI"
fi
step "Detected platform: $platform_label"
step "Resolved version: $resolved_version"
detect_conflicting_install
tmp_dir="$(mktemp -d)"
cleanup() {
release_install_lock
if [ -n "$tmp_dir" ]; then
rm -rf "$tmp_dir"
fi
}
trap cleanup EXIT INT TERM
acquire_install_lock
cleanup_stale_install_artifacts
if ! release_dir_is_complete "$release_dir" "$resolved_version" "$vendor_target" "$install_layout"; then
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
warn "Found incomplete existing release at $release_dir; reinstalling."
fi
archive_path="$tmp_dir/$asset"
checksum_path="$tmp_dir/$checksum_asset"
step "Downloading Codex CLI"
if [ "$install_layout" = "package" ]; then
checksum_digest="$(release_asset_digest "$checksum_asset" "$resolved_version")"
download_file "$checksum_url" "$checksum_path"
verify_archive_digest "$checksum_path" "$checksum_digest"
expected_digest="$(package_archive_digest "$asset" "$checksum_path")"
else
expected_digest="$(release_asset_digest "$asset" "$resolved_version")"
fi
download_file "$download_url" "$archive_path"
verify_archive_digest "$archive_path" "$expected_digest"
step "Installing standalone package to $release_dir"
if [ "$install_layout" = "package" ]; then
install_package_release "$release_dir" "$archive_path"
else
install_legacy_platform_npm_release "$release_dir" "$archive_path" "$vendor_target"
fi
fi
update_current_link "$release_dir"
update_visible_command "$release_dir"
add_to_path
verify_visible_command
release_install_lock
handle_conflicting_install
case "$path_action" in
added)
print_launch_instructions
;;
updated)
print_launch_instructions
;;
configured)
print_launch_instructions
;;
*)
step "$BIN_DIR is already on PATH"
print_launch_instructions
;;
esac
printf 'Codex CLI %s installed successfully.\n' "$resolved_version"
maybe_launch_codex_now