# Duiliu Codex setup for Windows PowerShell 5.1+ and PowerShell 7. # Download and run, or: irm https://app.duiliu.uk/install.ps1 | iex & { $ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $stateDir = Join-Path $env:LOCALAPPDATA 'Duiliu' $nodeDir = Join-Path $stateDir 'node' $runtimeDir = Join-Path $stateDir 'codex' $configDir = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.codex-duiliu' $binDir = Join-Path $stateDir 'bin' $launcher = Join-Path $binDir 'duiliu.cmd' $launcherScript = Join-Path $binDir 'start-duiliu.ps1' $marker = '# managed by duiliu-codex-installer' $tempDir = $null $plainKey = $null $keyPointer = [IntPtr]::Zero $utf8 = New-Object Text.UTF8Encoding($false) function Write-Utf8([string]$Path, [string]$Text) { [IO.File]::WriteAllText($Path, $Text, $utf8) } function Get-Download([string]$Url, [string]$Path) { for ($attempt = 1; $attempt -le 3; $attempt++) { try { Invoke-WebRequest -UseBasicParsing -Uri $Url -OutFile $Path -TimeoutSec 600 return } catch { if ($attempt -eq 3) { throw 'Download failed. Check your network and rerun the installer.' } Start-Sleep -Seconds 2 } } } try { if ([Environment]::OSVersion.Platform -ne 'Win32NT') { throw 'This script requires Windows. Use install.sh on macOS / Linux.' } foreach ($path in @($stateDir, $nodeDir, $runtimeDir, $configDir, $binDir, $launcher, $launcherScript, (Join-Path $configDir 'api-key.dpapi'), (Join-Path $configDir 'config.toml'))) { if ((Test-Path -LiteralPath $path) -and ((Get-Item -LiteralPath $path -Force).Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw "Linked destination is not supported: $path" } } foreach ($path in @($launcher, $launcherScript)) { if ((Test-Path -LiteralPath $path) -and !(Select-String -LiteralPath $path -SimpleMatch 'managed by duiliu-codex-installer' -Quiet)) { throw "An unrelated launcher already exists: $path" } } Write-Host 'Duiliu Codex setup (Windows)' $secureKey = Read-Host 'Enter your Duiliu API Key (hidden)' -AsSecureString $keyPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey) $plainKey = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($keyPointer) if ([string]::IsNullOrWhiteSpace($plainKey) -or $plainKey -eq 'YOUR_KEY' -or $plainKey -match '\s|[\x00-\x1f\x7f]') { throw 'The API key is empty or contains whitespace.' } Write-Host 'Checking your API key and available models (no model call)...' try { $models = Invoke-RestMethod -Uri 'https://app.duiliu.uk/v1/models' -Headers @{ Authorization = "Bearer $plainKey" } -TimeoutSec 20 } catch { throw 'API key check failed. Confirm your Duiliu key, model permissions and network, then rerun.' } $available = @($models.data | ForEach-Object { $_.id }) $model = @('gpt-6-astra', 'gpt-6.1-sol', 'gpt-6-sol', 'gpt-6-luna') | Where-Object { $available -contains $_ } | Select-Object -First 1 if (!$model) { throw 'This key has no supported Codex model. Check its group and model permissions.' } foreach ($dir in @($stateDir, $configDir, $binDir)) { $null = New-Item -ItemType Directory -Force -Path $dir } # Restrict the isolated config (including backups) to the current user and SYSTEM. $acl = New-Object Security.AccessControl.DirectorySecurity $acl.SetAccessRuleProtection($true, $false) $identity = [Security.Principal.WindowsIdentity]::GetCurrent().User $inherit = [Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit' $propagation = [Security.AccessControl.PropagationFlags]::None foreach ($sid in @($identity, (New-Object Security.Principal.SecurityIdentifier('S-1-5-18')))) { $rule = New-Object Security.AccessControl.FileSystemAccessRule($sid, 'FullControl', $inherit, $propagation, 'Allow') $null = $acl.AddAccessRule($rule) } Set-Acl -LiteralPath $configDir -AclObject $acl $tempDir = Join-Path ([IO.Path]::GetTempPath()) ('duiliu-setup-' + [Guid]::NewGuid().ToString('N')) $null = New-Item -ItemType Directory -Path $tempDir $nodeExe = Join-Path $nodeDir 'node.exe' if (!(Test-Path -LiteralPath $nodeExe)) { Write-Host 'Installing Node.js LTS into your user directory...' $cpu = $env:PROCESSOR_ARCHITEW6432 if (!$cpu) { $cpu = $env:PROCESSOR_ARCHITECTURE } switch ($cpu.ToUpperInvariant()) { 'AMD64' { $arch = 'x64' } 'ARM64' { $arch = 'arm64' } default { throw 'This installer supports x64 and ARM64 Windows.' } } $sumsFile = Join-Path $tempDir 'SHASUMS256.txt' Get-Download 'https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt' $sumsFile $assetLine = Get-Content -LiteralPath $sumsFile | Where-Object { $_ -match ("\s+node-v24\.\d+\.\d+-win-" + $arch + '\.zip$') } | Select-Object -First 1 if (!$assetLine) { throw 'No matching Node.js LTS download was found.' } $expectedHash, $asset = $assetLine.Trim() -split '\s+' $archive = Join-Path $tempDir $asset Get-Download "https://nodejs.org/dist/latest-v24.x/$asset" $archive if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $expectedHash) { throw 'Node.js download checksum did not match. Please rerun.' } Expand-Archive -LiteralPath $archive -DestinationPath $tempDir $extracted = Join-Path $tempDir ([IO.Path]::GetFileNameWithoutExtension($asset)) $nodeStage = Join-Path $stateDir ('.node-' + [Guid]::NewGuid().ToString('N')) Copy-Item -LiteralPath $extracted -Destination $nodeStage -Recurse if (Test-Path -LiteralPath $nodeDir) { Move-Item -LiteralPath $nodeDir -Destination ($nodeDir + '.backup-' + [Guid]::NewGuid().ToString('N')) } Move-Item -LiteralPath $nodeStage -Destination $nodeDir } & $nodeExe --version if ($LASTEXITCODE -ne 0) { throw 'Node.js could not start.' } $oldPath = $env:PATH try { $env:PATH = "$nodeDir;$oldPath" Write-Host 'Installing / updating the official Codex CLI...' $npmEntry = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js' & $nodeExe $npmEntry install --prefix $runtimeDir --registry=https://registry.npmjs.org --no-audit --no-fund --progress=false '@openai/codex@latest' if ($LASTEXITCODE -ne 0) { throw 'Codex installation failed. Check your network and rerun.' } $codexEntry = Join-Path $runtimeDir 'node_modules\@openai\codex\bin\codex.js' & $nodeExe $codexEntry --version if ($LASTEXITCODE -ne 0) { throw 'Codex could not start. Rerun the installer.' } } finally { $env:PATH = $oldPath } $backupDir = Join-Path $configDir ('backups\' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [Guid]::NewGuid().ToString('N').Substring(0, 8)) foreach ($old in @((Join-Path $configDir 'config.toml'), (Join-Path $configDir 'api-key.dpapi'), $launcher, $launcherScript)) { if (Test-Path -LiteralPath $old) { $null = New-Item -ItemType Directory -Force -Path $backupDir Copy-Item -LiteralPath $old -Destination $backupDir } } $keyStage = Join-Path $configDir ('.key-' + [Guid]::NewGuid().ToString('N')) Write-Utf8 $keyStage (ConvertFrom-SecureString -SecureString $secureKey) Move-Item -LiteralPath $keyStage -Destination (Join-Path $configDir 'api-key.dpapi') -Force $config = @" model = "$model" model_provider = "duiliu" forced_login_method = "api" model_reasoning_effort = "high" [model_providers.duiliu] name = "Duiliu" base_url = "https://app.duiliu.uk/v1" env_key = "DUILIU_API_KEY" wire_api = "responses" supports_websockets = false "@ $configStage = Join-Path $configDir ('.config-' + [Guid]::NewGuid().ToString('N')) Write-Utf8 $configStage $config Move-Item -LiteralPath $configStage -Destination (Join-Path $configDir 'config.toml') -Force $launchContent = @' # managed by duiliu-codex-installer $ErrorActionPreference = 'Stop' $configDir = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.codex-duiliu' $stateDir = Join-Path $env:LOCALAPPDATA 'Duiliu' $nodeDir = Join-Path $stateDir 'node' $nodeExe = Join-Path $nodeDir 'node.exe' $codexEntry = Join-Path $stateDir 'codex\node_modules\@openai\codex\bin\codex.js' $keyFile = Join-Path $configDir 'api-key.dpapi' $savedCodexHome = [Environment]::GetEnvironmentVariable('CODEX_HOME', 'Process') $savedKey = [Environment]::GetEnvironmentVariable('DUILIU_API_KEY', 'Process') $savedPath = $env:PATH $pointer = [IntPtr]::Zero $exitCode = 1 try { if (!(Test-Path -LiteralPath $keyFile)) { throw 'API key is missing. Rerun the Duiliu installer.' } $secure = Get-Content -LiteralPath $keyFile -Raw | ConvertTo-SecureString $pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) $env:DUILIU_API_KEY = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) $env:CODEX_HOME = $configDir $env:PATH = "$nodeDir;$savedPath" # Opt in per launch. Use the native Codex Fast setting, also controlled by /fast. $launchArgs = @($args) if ($launchArgs.Count -gt 0 -and $launchArgs[0] -in @('fast', '--fast')) { $remainingArgs = @() if ($launchArgs.Count -gt 1) { $remainingArgs = @($launchArgs[1..($launchArgs.Count - 1)]) } $launchArgs = @('-c', 'service_tier="fast"') + $remainingArgs } & $nodeExe $codexEntry @launchArgs $exitCode = $LASTEXITCODE } catch { Write-Host $_.Exception.Message -ForegroundColor Red } finally { if ($pointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) } [Environment]::SetEnvironmentVariable('CODEX_HOME', $savedCodexHome, 'Process') [Environment]::SetEnvironmentVariable('DUILIU_API_KEY', $savedKey, 'Process') $env:PATH = $savedPath } exit $exitCode '@ Write-Utf8 $launcherScript $launchContent Write-Utf8 $launcher "@echo off`r`nrem managed by duiliu-codex-installer`r`npowershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"%~dp0start-duiliu.ps1`" %*`r`nexit /b %errorlevel%`r`n" $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if (@($userPath -split ';' | ForEach-Object { $_.TrimEnd('\') }) -notcontains $binDir.TrimEnd('\')) { [Environment]::SetEnvironmentVariable('Path', ($binDir + ';' + $userPath), 'User') } $env:PATH = "$binDir;$env:PATH" # Optional shell integrations must not turn a successful install into a failure. try { $shell = New-Object -ComObject WScript.Shell foreach ($folder in @([Environment]::GetFolderPath('Desktop'), [Environment]::GetFolderPath('Programs'))) { if ($folder) { $shortcut = $shell.CreateShortcut((Join-Path $folder 'Duiliu Codex.lnk')) $shortcut.TargetPath = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' $shortcut.Arguments = '-NoLogo -NoProfile -ExecutionPolicy Bypass -File "' + $launcherScript + '"' $shortcut.WorkingDirectory = [Environment]::GetFolderPath('UserProfile') $shortcut.Save() } } } catch { Write-Host 'Shortcut creation was unavailable. Use the duiliu command instead.' } Write-Host "`nSetup complete. Model: $model" -ForegroundColor Green Write-Host 'Start now: duiliu' Write-Host 'Optional Fast mode: duiliu fast' Write-Host 'You can also open the Duiliu Codex desktop shortcut.' Write-Host 'Change your API key: rerun this installer.' if (Test-Path -LiteralPath $backupDir) { Write-Host "Previous Duiliu configuration backed up: $backupDir" } } catch { Write-Host ("`nSetup stopped: " + $_.Exception.Message) -ForegroundColor Red throw 'Duiliu setup did not finish. Correct the issue above and rerun.' } finally { if ($keyPointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($keyPointer) } $plainKey = $null if ($secureKey) { $secureKey.Dispose() } if ($tempDir -and (Test-Path -LiteralPath $tempDir)) { Remove-Item -LiteralPath $tempDir -Recurse -Force } } }