$ErrorActionPreference = "Stop" $Registry = "https://install.cziyo.club/npm/" $ApiBaseUrl = "https://rawchat.cn/codex" $ApiModel = "gpt-5.6-sol" $ApiProvider = "rawchat" function Refresh-SessionPath { $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine") $userPath = [Environment]::GetEnvironmentVariable("Path", "User") $env:Path = "$machinePath;$userPath;$env:Path" } function Find-CommandPath { param([string]$Name) $command = Get-Command $Name -ErrorAction SilentlyContinue if ($null -eq $command) { return $null } return $command.Source } function Find-NpmPath { $npmPath = Find-CommandPath "npm.cmd" if ($null -ne $npmPath) { return $npmPath } $candidates = @( (Join-Path $env:ProgramFiles "nodejs\npm.cmd"), $(if (${env:ProgramFiles(x86)}) { Join-Path ${env:ProgramFiles(x86)} "nodejs\npm.cmd" }), $(if ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA "Programs\nodejs\npm.cmd" }) ) | Where-Object { $_ } foreach ($candidate in $candidates) { if (Test-Path -LiteralPath $candidate) { $nodeDirectory = Split-Path -Parent $candidate if (($env:Path -split ";") -notcontains $nodeDirectory) { $env:Path = "$nodeDirectory;$env:Path" } return $candidate } } return $null } function Test-CodexCommand { param([string]$CodexPath) try { & $CodexPath --version *> $null return ($LASTEXITCODE -eq 0) } catch { return $false } } function Assert-SupportedNodeVersion { $nodePath = Find-CommandPath "node.exe" if ($null -eq $nodePath) { $nodePath = Find-CommandPath "node" } if ($null -eq $nodePath) { throw "npm is available, but Node.js could not be found. Repair or update Node.js LTS, then run this installer again." } $nodeVersionOutput = & $nodePath --version if ($LASTEXITCODE -ne 0 -or $null -eq $nodeVersionOutput) { throw "Node.js could not report its version. Repair or update Node.js LTS, then run this installer again." } $nodeVersion = ($nodeVersionOutput | Select-Object -Last 1).ToString().Trim() if ($nodeVersion -notmatch '^v?([0-9]+)') { throw "The Node.js version '$nodeVersion' could not be parsed. Repair or update Node.js LTS, then run this installer again." } $nodeMajor = [int]$Matches[1] if ($nodeMajor -lt 16) { throw "Codex requires Node.js 16 or newer; detected $nodeVersion. Update Node.js LTS, then run this installer again." } } function Add-UserPathEntry { param([string]$Path) if ([string]::IsNullOrWhiteSpace($Path)) { return } $normalizedPath = $Path.TrimEnd("\") $userPath = [Environment]::GetEnvironmentVariable("Path", "User") $userPathEntries = @($userPath -split ";" | Where-Object { $_ }) $alreadySaved = $userPathEntries | Where-Object { $_.TrimEnd("\") -ieq $normalizedPath } if ($null -eq $alreadySaved) { $newUserPath = if ([string]::IsNullOrWhiteSpace($userPath)) { $Path } else { "$userPath;$Path" } [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") } $sessionPathEntries = @($env:Path -split ";" | Where-Object { $_ }) $alreadyInSession = $sessionPathEntries | Where-Object { $_.TrimEnd("\") -ieq $normalizedPath } if ($null -eq $alreadyInSession) { $env:Path = "$Path;$env:Path" } } function Get-NpmGlobalPath { param([string]$NpmPath) $prefixOutput = & $NpmPath prefix --global if ($LASTEXITCODE -ne 0 -or $null -eq $prefixOutput) { return $null } return ($prefixOutput | Select-Object -Last 1).ToString().Trim() } function Get-NpmGlobalRoot { param([string]$NpmPath) $rootOutput = & $NpmPath root --global if ($LASTEXITCODE -ne 0 -or $null -eq $rootOutput) { return $null } return ($rootOutput | Select-Object -Last 1).ToString().Trim() } function Backup-ExistingCodexConfig { param([string]$ConfigPath) $codexHome = Split-Path -Parent $ConfigPath $backupDirectory = Join-Path $codexHome "backups" New-Item -ItemType Directory -Force -Path $backupDirectory | Out-Null $timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMddTHHmmssfffZ") $backupPath = Join-Path $backupDirectory "config.toml.before-cziyo-$timestamp.bak" Copy-Item -LiteralPath $ConfigPath -Destination $backupPath -ErrorAction Stop Write-Host "Existing configuration backed up to: $backupPath" return $backupPath } function Initialize-CziyoCodexConfig { param([string]$ExpectedExistingHash) $codexHome = Join-Path $HOME ".codex" $configPath = Join-Path $codexHome "config.toml" if (Test-Path -LiteralPath $configPath) { if ([string]::IsNullOrWhiteSpace($ExpectedExistingHash)) { return $false } $currentHash = (Get-FileHash -LiteralPath $configPath -Algorithm SHA256).Hash if ($currentHash -ne $ExpectedExistingHash) { return $false } } New-Item -ItemType Directory -Force -Path $codexHome | Out-Null $configContent = @" model_provider = "$ApiProvider" model = "$ApiModel" [model_providers.$ApiProvider] name = "Cziyo API" base_url = "$ApiBaseUrl" wire_api = "responses" requires_openai_auth = true "@ $temporaryConfigPath = Join-Path $codexHome ("config.toml.tmp.{0}" -f [Guid]::NewGuid().ToString("N")) try { [System.IO.File]::WriteAllText( $temporaryConfigPath, $configContent, [System.Text.UTF8Encoding]::new($false) ) if (Test-Path -LiteralPath $configPath) { [System.IO.File]::Replace($temporaryConfigPath, $configPath, $null) } else { Move-Item -LiteralPath $temporaryConfigPath -Destination $configPath -ErrorAction Stop } } finally { if (Test-Path -LiteralPath $temporaryConfigPath) { Remove-Item -LiteralPath $temporaryConfigPath -Force } } return $true } function Configure-CziyoApiKey { param([string]$CodexPath) $configPath = Join-Path (Join-Path $HOME ".codex") "config.toml" $expectedExistingHash = $null $backupPath = $null $secureApiKey = Read-Host "Enter your API key (input hidden)" -AsSecureString $secureApiKeyConfirmation = Read-Host "Confirm your API key (input hidden)" -AsSecureString $apiKeyPointer = [IntPtr]::Zero $apiKeyConfirmationPointer = [IntPtr]::Zero $apiKey = $null $apiKeyConfirmation = $null try { $apiKeyPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureApiKey) $apiKey = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($apiKeyPointer) if ([string]::IsNullOrWhiteSpace($apiKey)) { throw "API key cannot be empty." } $apiKeyConfirmationPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureApiKeyConfirmation) $apiKeyConfirmation = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($apiKeyConfirmationPointer) if ($apiKey -cne $apiKeyConfirmation) { throw "API key entries do not match." } if (Test-Path -LiteralPath $configPath) { $expectedExistingHash = (Get-FileHash -LiteralPath $configPath -Algorithm SHA256).Hash $backupPath = Backup-ExistingCodexConfig $configPath if ((Get-FileHash -LiteralPath $configPath -Algorithm SHA256).Hash -ne $expectedExistingHash) { throw "Codex configuration changed while the backup was created. API credentials were not changed." } } if (-not (Initialize-CziyoCodexConfig -ExpectedExistingHash $expectedExistingHash)) { throw "Codex configuration was changed by another process. API credentials were not changed." } $apiKey | & $CodexPath login --with-api-key if ($LASTEXITCODE -ne 0) { if ($null -ne $backupPath -and (Test-Path -LiteralPath $backupPath)) { Copy-Item -LiteralPath $backupPath -Destination $configPath -Force -ErrorAction Stop Write-Host "API login failed. The previous Codex configuration was restored." } elseif (Test-Path -LiteralPath $configPath) { Remove-Item -LiteralPath $configPath -Force Write-Host "API login failed. The new Codex configuration was removed." } throw "API key configuration failed." } } finally { $apiKey = $null $apiKeyConfirmation = $null if ($apiKeyPointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($apiKeyPointer) } if ($apiKeyConfirmationPointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($apiKeyConfirmationPointer) } if ($null -ne $secureApiKey) { $secureApiKey.Dispose() } if ($null -ne $secureApiKeyConfirmation) { $secureApiKeyConfirmation.Dispose() } } Write-Host "API configuration complete for $ApiBaseUrl." } Write-Host "" Write-Host "Codex installer" Write-Host "Registry: $Registry" Write-Host "" Refresh-SessionPath $repairCodex = $false $codexPath = Find-CommandPath "codex.cmd" if ($null -eq $codexPath) { $codexPath = Find-CommandPath "codex" } if ($null -ne $codexPath) { if (Test-CodexCommand $codexPath) { Write-Host "Healthy Codex installation found: $codexPath" Write-Host "Package installation skipped. The existing Codex version was not changed." } else { Write-Host "The existing Codex command is damaged and cannot report its version: $codexPath" Write-Host "The installer will repair the Codex package." $repairCodex = $true $codexPath = $null } } if ($null -eq $codexPath) { $npmPath = Find-NpmPath if ($null -eq $npmPath) { $existingNodePath = Find-CommandPath "node.exe" if ($null -eq $existingNodePath) { $existingNodePath = Find-CommandPath "node" } if ($null -ne $existingNodePath) { throw "Node.js is installed at $existingNodePath, but npm is missing. Repair or update Node.js LTS so npm is included, then run this installer again." } Write-Host "Node.js/npm was not found. Trying to install Node.js LTS with winget..." $wingetPath = Find-CommandPath "winget.exe" if ($null -eq $wingetPath) { throw "Node.js LTS is required. Install Node.js LTS first, then run this command again." } & $wingetPath install --id OpenJS.NodeJS.LTS -e --source winget --accept-package-agreements --accept-source-agreements $wingetExitCode = $LASTEXITCODE Refresh-SessionPath $npmPath = Find-NpmPath if ($null -eq $npmPath) { if ($wingetExitCode -ne 0) { throw "winget could not make Node.js/npm available (exit code $wingetExitCode). Repair or reinstall Node.js LTS, then run this command again." } throw "Node.js was installed, but npm is not available. Close PowerShell, open a new one, and run this command again." } if ($wingetExitCode -ne 0) { Write-Host "winget reported no installation change, but the existing Node.js/npm installation was found. Continuing..." } } Assert-SupportedNodeVersion $npmGlobalPath = Get-NpmGlobalPath $npmPath if (-not [string]::IsNullOrWhiteSpace($npmGlobalPath)) { $existingCodexCandidate = Join-Path $npmGlobalPath "codex.cmd" if (Test-Path -LiteralPath $existingCodexCandidate) { Add-UserPathEntry $npmGlobalPath Refresh-SessionPath if (Test-CodexCommand $existingCodexCandidate) { $codexPath = $existingCodexCandidate Write-Host "Healthy Codex installation found: $codexPath" Write-Host "Package installation skipped. The existing Codex version was not changed." } else { Write-Host "The existing global Codex command is damaged: $existingCodexCandidate" $repairCodex = $true } } } if ($null -eq $codexPath) { $npmGlobalRoot = Get-NpmGlobalRoot $npmPath if (-not [string]::IsNullOrWhiteSpace($npmGlobalRoot)) { $existingCodexPackage = Join-Path (Join-Path $npmGlobalRoot "@openai") "codex" if (Test-Path -LiteralPath $existingCodexPackage) { Write-Host "An existing global @openai/codex package is incomplete or its command is unavailable." $repairCodex = $true } } } if ($null -eq $codexPath) { if ($repairCodex) { Write-Host "Repairing @openai/codex..." } else { Write-Host "Installing @openai/codex with the existing Node.js/npm environment..." } Write-Host "Downloading packages can take several minutes. npm will print registry requests while it works." $installStartedAt = Get-Date & $npmPath install --global @openai/codex --registry=$Registry --no-audit --fund=false --loglevel=http $npmExitCode = $LASTEXITCODE if ($npmExitCode -ne 0) { Write-Host "npm printed the detailed cause above." Write-Host "EBADENGINE: update Node.js LTS. EACCES/EPERM: fix npm permissions or close processes using Codex." Write-Host "ENOTFOUND/ETIMEDOUT/ECONNRESET: check DNS, proxy, firewall, and network connectivity." throw "Codex installation failed (npm exit code $npmExitCode)." } $installElapsed = (Get-Date) - $installStartedAt $installElapsedText = "{0:00}:{1:00}" -f [math]::Floor($installElapsed.TotalMinutes), $installElapsed.Seconds Write-Host "Package installation completed in $installElapsedText." $npmGlobalPath = Get-NpmGlobalPath $npmPath if ([string]::IsNullOrWhiteSpace($npmGlobalPath)) { throw "Codex was installed, but npm's global command directory could not be determined." } Add-UserPathEntry $npmGlobalPath Refresh-SessionPath $codexPath = Find-CommandPath "codex.cmd" if ($null -eq $codexPath) { $codexCandidate = Join-Path $npmGlobalPath "codex.cmd" if (Test-Path -LiteralPath $codexCandidate) { $codexPath = $codexCandidate } } if ($null -eq $codexPath -or -not (Test-CodexCommand $codexPath)) { throw "Codex package installation completed, but the command is still unavailable or damaged. Repair the npm global PATH or update Node.js LTS, then run this installer again." } } } if ($null -eq $codexPath) { throw "Codex could not be installed or repaired. Review the detailed error above." } $codexCommandDirectory = Split-Path -Parent $codexPath Write-Host "" Write-Host "Installation complete:" & $codexPath --version Write-Host "" Write-Host "Codex command directory: $codexCommandDirectory" Write-Host "" Configure-CziyoApiKey $codexPath Write-Host "Next step: run codex"