[CmdletBinding()] param( [string]$ApiKey, [switch]$UpdateOnly ) $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 function Write-Step([string]$Message) { Write-Host "[DAL]" $Message -ForegroundColor Cyan } function Ensure-Directory([string]$Path) { if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } } function Prepend-ProcessPath([string[]]$Entries) { $current = ($env:Path -split ';' | Where-Object { $_ -and $_.Trim() }) | Select-Object -Unique $merged = @() foreach ($entry in $Entries) { if ($entry -and -not ($merged -contains $entry)) { $merged += $entry } } foreach ($part in $current) { if ($part -and -not ($merged -contains $part)) { $merged += $part } } $env:Path = $merged -join ';' } function Prepend-UserPath([string[]]$Entries) { $userPath = [Environment]::GetEnvironmentVariable("Path", "User") $parts = @() if ($userPath) { $parts = $userPath -split ';' | Where-Object { $_ -and $_.Trim() } } $merged = @() foreach ($entry in $Entries) { if ($entry -and -not ($merged -contains $entry)) { $merged += $entry } } foreach ($part in $parts) { if ($part -and -not ($merged -contains $part)) { $merged += $part } } [Environment]::SetEnvironmentVariable("Path", ($merged -join ';'), "User") } function Disable-NpmPowerShellShim([string]$NpmGlobalRoot, [string]$CommandName) { $powerShellShim = Join-Path $NpmGlobalRoot "$CommandName.ps1" if (-not (Test-Path -LiteralPath $powerShellShim)) { return $false } $disabledShim = "$powerShellShim.disabled" Remove-Item -LiteralPath $disabledShim -Force -ErrorAction SilentlyContinue Move-Item -LiteralPath $powerShellShim -Destination $disabledShim -Force return $true } function Write-CodexLauncher([string]$LauncherRoot) { Ensure-Directory $LauncherRoot $launcherPath = Join-Path $LauncherRoot "codex.cmd" $content = "@echo off`r`ncall `"%~dp0..\npm-global\codex.cmd`" %*`r`n" [IO.File]::WriteAllText($launcherPath, $content, [Text.Encoding]::ASCII) return $launcherPath } function Read-ApiKeySecurely { $secureValue = Read-Host "Paste your API Key (ask admin if you don't have one)" -AsSecureString $pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureValue) try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) } } function Get-PortableNodePackage { $arch = if ([Environment]::Is64BitOperatingSystem) { "win-x64" } else { "win-x86" } $releases = Invoke-RestMethod -Uri "https://nodejs.org/dist/index.json" $lts = $releases | Where-Object { $_.lts -and -not $_.version.Contains("-") } | Select-Object -First 1 if (-not $lts) { throw "Unable to resolve the latest Node.js LTS release." } $fileBase = "node-$($lts.version)-$arch" [PSCustomObject]@{ Version = $lts.version ZipUrl = "https://nodejs.org/dist/$($lts.version)/$fileBase.zip" FileBase = $fileBase } } function Install-PortableNode([string]$NodeRoot) { $package = Get-PortableNodePackage $zipPath = Join-Path $env:TEMP "$($package.FileBase).zip" $extractRoot = Join-Path $env:TEMP "$($package.FileBase)-extract" Write-Step "Node.js not found. Downloading official LTS portable package $($package.Version)" Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue Invoke-WebRequest -Uri $package.ZipUrl -OutFile $zipPath Expand-Archive -Path $zipPath -DestinationPath $extractRoot -Force $expandedDir = Join-Path $extractRoot $package.FileBase if (-not (Test-Path -LiteralPath $expandedDir)) { throw "Expanded Node.js directory not found: $expandedDir" } Remove-Item -LiteralPath $NodeRoot -Recurse -Force -ErrorAction SilentlyContinue Move-Item -LiteralPath $expandedDir -Destination $NodeRoot return (Join-Path $NodeRoot "npm.cmd") } function Resolve-NpmCommand([string]$NodeRoot) { $existing = Get-Command npm.cmd -ErrorAction SilentlyContinue if ($existing) { return $existing.Source } $portableNpm = Join-Path $NodeRoot "npm.cmd" if (Test-Path -LiteralPath $portableNpm) { return $portableNpm } return (Install-PortableNode -NodeRoot $NodeRoot) } if ($env:OS -ne "Windows_NT") { throw "This one-click installer currently supports Windows only." } $bootstrapRoot = Join-Path $env:LOCALAPPDATA "Programs\DALCodexBootstrap" $nodeRoot = Join-Path $bootstrapRoot "node" $npmGlobalRoot = Join-Path $bootstrapRoot "npm-global" $launcherRoot = Join-Path $bootstrapRoot "bin" $codexCmd = Join-Path $npmGlobalRoot "codex.cmd" Ensure-Directory $bootstrapRoot Ensure-Directory $npmGlobalRoot Ensure-Directory $launcherRoot $npmCmd = Resolve-NpmCommand -NodeRoot $nodeRoot $nodeBinDir = Split-Path -Parent $npmCmd Prepend-ProcessPath @($launcherRoot, $nodeBinDir, $npmGlobalRoot) Prepend-UserPath @($launcherRoot, $nodeBinDir, $npmGlobalRoot) Write-Step "Installing the latest stable Codex CLI" & $npmCmd install -g @openai/codex@latest --registry=https://registry.npmjs.org --prefix $npmGlobalRoot if ($LASTEXITCODE -ne 0) { throw "Codex CLI installation failed with exit code $LASTEXITCODE." } if (Disable-NpmPowerShellShim -NpmGlobalRoot $npmGlobalRoot -CommandName "codex") { Write-Step "Disabled the npm codex.ps1 shim so PowerShell uses codex.cmd" } if (-not (Test-Path -LiteralPath $codexCmd)) { throw "Codex command shim not found after installation: $codexCmd" } $codexLauncher = Write-CodexLauncher -LauncherRoot $launcherRoot $resolvedCodex = Get-Command codex -CommandType Application -All -ErrorAction Stop | Select-Object -First 1 if ([IO.Path]::GetFullPath($resolvedCodex.Source) -ne [IO.Path]::GetFullPath($codexLauncher)) { throw "Stable Codex launcher did not win command resolution: $($resolvedCodex.Source)" } $codexVersion = & $codexLauncher --version if ($LASTEXITCODE -ne 0 -or -not $codexVersion) { throw "Installed Codex CLI version check failed." } Write-Step "Installed version: $codexVersion" if ($UpdateOnly) { Write-Host "Update complete. Existing Codex account and configuration were preserved." -ForegroundColor Green Write-Host "Open a new terminal and run: codex --version" -ForegroundColor Green return } Write-Step "Running DAL Codex activator" $activatorArgs = @( "exec", "--yes", "--package=https://cpa.dongailin.cn/downloads/dal-codex-activator-1.0.12.tgz", "--", "dal-codex-activate" ) if (-not $ApiKey) { $ApiKey = Read-ApiKeySecurely } if (-not $ApiKey -or -not $ApiKey.Trim()) { throw "API Key is required." } $hadPreviousApiKey = Test-Path Env:DAL_CODEX_API_KEY $previousApiKey = $env:DAL_CODEX_API_KEY try { $env:DAL_CODEX_API_KEY = $ApiKey.Trim() & $npmCmd @activatorArgs if ($LASTEXITCODE -ne 0) { throw "DAL Codex activator failed with exit code $LASTEXITCODE." } } finally { $ApiKey = $null if ($hadPreviousApiKey) { $env:DAL_CODEX_API_KEY = $previousApiKey } else { Remove-Item Env:DAL_CODEX_API_KEY -ErrorAction SilentlyContinue } } Write-Host "" Write-Host "Install complete." -ForegroundColor Green Write-Host "Open a new terminal and run: codex" -ForegroundColor Green Write-Host "You can also run:" -ForegroundColor Green Write-Host $codexLauncher -ForegroundColor Yellow