Compare commits

...

15 Commits

Author SHA1 Message Date
Edward Frazer
3de8981df3 refactor!: remove installer release inputs 2026-05-26 17:19:19 -07:00
Edward Frazer
be346634f9 fix: wrap standalone update notice commands 2026-05-26 17:18:46 -07:00
Edward Frazer
b69722885c test: cover standalone Unix update notice 2026-05-26 17:18:45 -07:00
Edward Frazer
1706ee2d01 test: cover standalone update notice in standard tests 2026-05-26 17:18:45 -07:00
Edward Frazer
fd8c8791e1 test: cover standalone Windows update prompt 2026-05-26 17:18:45 -07:00
Edward Frazer
9e7e1119e4 fix: keep Windows standalone update command pasteable 2026-05-26 17:18:45 -07:00
Edward Frazer
a6347cccdb fix: run standalone updates noninteractively 2026-05-26 17:18:45 -07:00
Edward Frazer
592acb4f89 fix: preserve PowerShell latest release casing 2026-05-26 17:17:54 -07:00
Edward Frazer
2579a92421 fix: prioritize standalone installer on PATH 2026-05-26 17:14:41 -07:00
Edward Frazer
6255863d2d fix: validate installer release selectors 2026-05-26 17:09:53 -07:00
Edward Frazer
116fff2926 refactor: keep noninteractive install configuration environment-only 2026-05-26 13:57:19 -07:00
Edward Frazer
6587c570dd fix: support installer environment controls 2026-05-26 13:35:40 -07:00
efrazer-oai
fa5f8fe66f fix: expose noninteractive installer flag 2026-05-26 12:14:54 -07:00
efrazer-oai
2cb39a8d6a fix: expose noninteractive installer flag 2026-05-26 12:14:54 -07:00
Edward Frazer
b064b1022d fix: add noninteractive install script mode 2026-05-26 12:14:53 -07:00
7 changed files with 143 additions and 24 deletions

View File

@@ -52,7 +52,8 @@ impl HistoryCell for UpdateAvailableHistoryCell {
.width()
.min(usize::from(width.saturating_sub(4)))
.max(1);
with_border_with_inner_width(content.lines, inner_width)
let lines = adaptive_wrap_lines(content.lines, RtOptions::new(inner_width));
with_border_with_inner_width(lines, inner_width)
}
fn raw_lines(&self) -> Vec<Line<'static>> {

View File

@@ -0,0 +1,11 @@
---
source: tui/src/history_cell/tests.rs
expression: rendered
---
╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✨Update available! 0.0.0 -> 9.9.9 │
│ Run sh -c 'curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh' to update. │
│ │
│ See full release notes: │
│ https://github.com/openai/codex/releases/latest │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯

View File

@@ -0,0 +1,12 @@
---
source: tui/src/history_cell/tests.rs
expression: rendered
---
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✨Update available! 0.0.0 -> 9.9.9 │
│ Run powershell -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm │
│ https://chatgpt.com/codex/install.ps1 | iex' to update. │
│ │
│ See full release notes: │
│ https://github.com/openai/codex/releases/latest │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

View File

@@ -1027,6 +1027,24 @@ fn web_search_history_cell_snapshot() {
insta::assert_snapshot!(rendered);
}
#[test]
fn standalone_unix_update_available_history_cell_snapshot() {
let cell =
UpdateAvailableHistoryCell::new("9.9.9".to_string(), Some(UpdateAction::StandaloneUnix));
let rendered = render_lines(&cell.display_lines(/*width*/ 110)).join("\n");
insta::assert_snapshot!(rendered);
}
#[test]
fn standalone_windows_update_available_history_cell_snapshot() {
let cell =
UpdateAvailableHistoryCell::new("9.9.9".to_string(), Some(UpdateAction::StandaloneWindows));
let rendered = render_lines(&cell.display_lines(/*width*/ 110)).join("\n");
insta::assert_snapshot!(rendered);
}
#[test]
fn web_search_history_cell_wraps_with_indented_continuation() {
let query = "example search query with several generic words to exercise wrapping".to_string();

View File

@@ -14,9 +14,9 @@ pub enum UpdateAction {
BunGlobalLatest,
/// Update via `brew upgrade codex`.
BrewUpgrade,
/// Update via `curl -fsSL https://chatgpt.com/codex/install.sh | sh`.
/// Update via `curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh`.
StandaloneUnix,
/// Update via `irm https://chatgpt.com/codex/install.ps1|iex`.
/// Update via `$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex`.
StandaloneWindows,
}
@@ -43,7 +43,10 @@ impl UpdateAction {
UpdateAction::BrewUpgrade => ("brew", &["upgrade", "--cask", "codex"]),
UpdateAction::StandaloneUnix => (
"sh",
&["-c", "curl -fsSL https://chatgpt.com/codex/install.sh | sh"],
&[
"-c",
"curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh",
],
),
UpdateAction::StandaloneWindows => (
"powershell",
@@ -51,7 +54,7 @@ impl UpdateAction {
"-ExecutionPolicy",
"Bypass",
"-c",
"irm https://chatgpt.com/codex/install.ps1 | iex",
"$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex",
],
),
}
@@ -140,7 +143,10 @@ mod tests {
UpdateAction::StandaloneUnix.command_args(),
(
"sh",
&["-c", "curl -fsSL https://chatgpt.com/codex/install.sh | sh"][..],
&[
"-c",
"curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh"
][..],
)
);
assert_eq!(
@@ -151,7 +157,7 @@ mod tests {
"-ExecutionPolicy",
"Bypass",
"-c",
"irm https://chatgpt.com/codex/install.ps1 | iex"
"$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex"
][..],
)
);

View File

@@ -1,11 +1,18 @@
param(
[string]$Release = "latest"
)
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$Release = $env:CODEX_RELEASE
if ([string]::IsNullOrWhiteSpace($Release)) {
$Release = "latest"
}
$NonInteractive = $env:CODEX_NON_INTERACTIVE -match "^(?i:1|true|yes)$"
function Write-Step {
param(
[string]$Message
@@ -27,6 +34,10 @@ function Prompt-YesNo {
[string]$Prompt
)
if ($NonInteractive) {
return $false
}
if ([Console]::IsInputRedirected -or [Console]::IsOutputRedirected) {
return $false
}
@@ -55,6 +66,16 @@ function Normalize-Version {
return $RawVersion
}
function Assert-ValidReleaseVersion {
param(
[string]$Version
)
if ($Version -cne "latest" -and $Version -cnotmatch "^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:alpha|beta)(?:\.[0-9]+)?)?$") {
throw "Invalid Codex release version: $Version. Expected latest or x.y.z[-alpha[.N]|-beta[.N]]."
}
}
function Find-ReleaseAssetMetadata {
param(
[string]$AssetName,
@@ -141,6 +162,22 @@ function Path-Contains {
return $false
}
function Prepend-PathEntry {
param(
[string]$PathValue,
[string]$Entry
)
$needle = $Entry.TrimEnd("\")
$segments = @($Entry)
if (-not [string]::IsNullOrWhiteSpace($PathValue)) {
$segments += $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries) |
Where-Object { $_.TrimEnd("\") -ine $needle }
}
return ($segments -join ";")
}
function Invoke-WithInstallLock {
param(
[string]$LockPath,
@@ -181,6 +218,7 @@ function Remove-StaleInstallArtifacts {
function Resolve-Version {
$normalizedVersion = Normalize-Version -RawVersion $Release
Assert-ValidReleaseVersion -Version $normalizedVersion
if ($normalizedVersion -ne "latest") {
return $normalizedVersion
}
@@ -191,7 +229,9 @@ function Resolve-Version {
exit 1
}
return (Normalize-Version -RawVersion $release.tag_name)
$resolvedVersion = Normalize-Version -RawVersion $release.tag_name
Assert-ValidReleaseVersion -Version $resolvedVersion
return $resolvedVersion
}
function Get-VersionFromBinary {
@@ -839,7 +879,16 @@ try {
Maybe-HandleConflictingInstall -Conflict $conflictingInstall
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if (-not (Path-Contains -PathValue $userPath -Entry $visibleBinDir)) {
$prioritizeVisibleBin = $null -ne $conflictingInstall
if ($prioritizeVisibleBin) {
$newUserPath = Prepend-PathEntry -PathValue $userPath -Entry $visibleBinDir
if ($newUserPath -cne $userPath) {
[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
Write-Step "PATH updated for future PowerShell sessions."
} else {
Write-Step "$visibleBinDir is already first on PATH."
}
} elseif (-not (Path-Contains -PathValue $userPath -Entry $visibleBinDir)) {
if ([string]::IsNullOrWhiteSpace($userPath)) {
$newUserPath = $visibleBinDir
} else {
@@ -854,7 +903,9 @@ if (-not (Path-Contains -PathValue $userPath -Entry $visibleBinDir)) {
Write-Step "PATH is already configured for future PowerShell sessions."
}
if (-not (Path-Contains -PathValue $env:Path -Entry $visibleBinDir)) {
if ($prioritizeVisibleBin) {
$env:Path = Prepend-PathEntry -PathValue $env:Path -Entry $visibleBinDir
} elseif (-not (Path-Contains -PathValue $env:Path -Entry $visibleBinDir)) {
if ([string]::IsNullOrWhiteSpace($env:Path)) {
$env:Path = $visibleBinDir
} else {

View File

@@ -2,7 +2,8 @@
set -eu
RELEASE="latest"
RELEASE="${CODEX_RELEASE:-latest}"
NON_INTERACTIVE="${CODEX_NON_INTERACTIVE:-false}"
BIN_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}"
BIN_PATH="$BIN_DIR/codex"
@@ -46,20 +47,29 @@ normalize_version() {
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]
Usage: install.sh
Environment:
CODEX_RELEASE Version to install, or latest.
CODEX_NON_INTERACTIVE Set to 1, true, or yes to skip prompts.
EOF
exit 0
;;
@@ -259,6 +269,7 @@ require_command() {
resolve_version() {
normalized_version="$(normalize_version "$RELEASE")"
validate_version "$normalized_version"
if [ "$normalized_version" != "latest" ]; then
printf '%s\n' "$normalized_version"
@@ -273,6 +284,7 @@ resolve_version() {
exit 1
fi
validate_version "$resolved"
printf '%s\n' "$resolved"
}
@@ -304,7 +316,9 @@ add_to_path() {
case ":$PATH:" in
*":$BIN_DIR:"*)
return
if [ -z "$conflict_manager" ]; then
return
fi
;;
esac
@@ -544,6 +558,12 @@ classify_existing_codex() {
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