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
3 changes: 2 additions & 1 deletion .github/workflows/rust-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -494,9 +494,10 @@ jobs:
--package codex-responses-api-proxy \
--package codex-sdk

- name: Stage macOS and Linux installer script
- name: Stage installer scripts
run: |
cp scripts/install/install.sh dist/install.sh
cp scripts/install/install.ps1 dist/install.ps1

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
Expand Down
196 changes: 196 additions & 0 deletions scripts/install/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
param(
[Parameter(Position=0)]
[string]$Version = "latest"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"

function Write-Step {
param(
[string]$Message
)

Write-Host "==> $Message"
}

function Normalize-Version {
param(
[string]$RawVersion
)

if ([string]::IsNullOrWhiteSpace($RawVersion) -or $RawVersion -eq "latest") {
return "latest"
}

if ($RawVersion.StartsWith("rust-v")) {
return $RawVersion.Substring(6)
}

if ($RawVersion.StartsWith("v")) {
return $RawVersion.Substring(1)
}

return $RawVersion
}

function Get-ReleaseUrl {
param(
[string]$AssetName,
[string]$ResolvedVersion
)

return "https://github.com/openai/codex/releases/download/rust-v$ResolvedVersion/$AssetName"
}

function Path-Contains {
param(
[string]$PathValue,
[string]$Entry
)

if ([string]::IsNullOrWhiteSpace($PathValue)) {
return $false
}

$needle = $Entry.TrimEnd("\")
foreach ($segment in $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)) {
if ($segment.TrimEnd("\") -ieq $needle) {
return $true
}
}

return $false
}

function Resolve-Version {
$normalizedVersion = Normalize-Version -RawVersion $Version
if ($normalizedVersion -ne "latest") {
return $normalizedVersion
}

$release = Invoke-RestMethod -Uri "https://api.github.com/repos/openai/codex/releases/latest"
if (-not $release.tag_name) {
Write-Error "Failed to resolve the latest Codex release version."
exit 1
}

return (Normalize-Version -RawVersion $release.tag_name)
}

if ($env:OS -ne "Windows_NT") {
Write-Error "install.ps1 supports Windows only. Use install.sh on macOS or Linux."
exit 1
}

if (-not [Environment]::Is64BitOperatingSystem) {
Write-Error "Codex requires a 64-bit version of Windows."
exit 1
}

$architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
$target = $null
$platformLabel = $null
$npmTag = $null
switch ($architecture) {
"Arm64" {
$target = "aarch64-pc-windows-msvc"
$platformLabel = "Windows (ARM64)"
$npmTag = "win32-arm64"
}
"X64" {
$target = "x86_64-pc-windows-msvc"
$platformLabel = "Windows (x64)"
$npmTag = "win32-x64"
}
default {
Write-Error "Unsupported architecture: $architecture"
exit 1
}
}

if ([string]::IsNullOrWhiteSpace($env:CODEX_INSTALL_DIR)) {
$installDir = Join-Path $env:LOCALAPPDATA "Programs\OpenAI\Codex\bin"
} else {
$installDir = $env:CODEX_INSTALL_DIR
}

$codexPath = Join-Path $installDir "codex.exe"
$installMode = if (Test-Path $codexPath) { "Updating" } else { "Installing" }

Write-Step "$installMode Codex CLI"
Write-Step "Detected platform: $platformLabel"

New-Item -ItemType Directory -Force -Path $installDir | Out-Null

$resolvedVersion = Resolve-Version
Write-Step "Resolved version: $resolvedVersion"
$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz"

$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-install-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null

try {
$archivePath = Join-Path $tempDir $packageAsset
$extractDir = Join-Path $tempDir "extract"
$url = Get-ReleaseUrl -AssetName $packageAsset -ResolvedVersion $resolvedVersion

Write-Step "Downloading Codex CLI"
Invoke-WebRequest -Uri $url -OutFile $archivePath

New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
tar -xzf $archivePath -C $extractDir

$vendorRoot = Join-Path $extractDir "package/vendor/$target"
Write-Step "Installing to $installDir"
$copyMap = @{
"codex/codex.exe" = "codex.exe"
"codex/codex-command-runner.exe" = "codex-command-runner.exe"
"codex/codex-windows-sandbox-setup.exe" = "codex-windows-sandbox-setup.exe"
"path/rg.exe" = "rg.exe"
}

foreach ($relativeSource in $copyMap.Keys) {
$sourcePath = Join-Path $vendorRoot $relativeSource
$destinationPath = Join-Path $installDir $copyMap[$relativeSource]
Move-Item -Force $sourcePath $destinationPath
}
} finally {
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
}

$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
$pathNeedsNewShell = $false
if (-not (Path-Contains -PathValue $userPath -Entry $installDir)) {
if ([string]::IsNullOrWhiteSpace($userPath)) {
$newUserPath = $installDir
} else {
$newUserPath = "$installDir;$userPath"
}

[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
if (-not (Path-Contains -PathValue $env:Path -Entry $installDir)) {
if ([string]::IsNullOrWhiteSpace($env:Path)) {
$env:Path = $installDir
} else {
$env:Path = "$installDir;$env:Path"
}
}
Write-Step "PATH updated for future PowerShell sessions."
$pathNeedsNewShell = $true
} elseif (Path-Contains -PathValue $env:Path -Entry $installDir) {
Write-Step "$installDir is already on PATH."
} else {
Write-Step "PATH is already configured for future PowerShell sessions."
$pathNeedsNewShell = $true
}

if ($pathNeedsNewShell) {
Write-Step ('Run now: $env:Path = "{0};$env:Path"; codex' -f $installDir)
Write-Step "Or open a new PowerShell window and run: codex"
} else {
Write-Step "Run: codex"
}

Write-Host "Codex CLI $resolvedVersion installed successfully."
Loading