param( [ValidateSet('menu','codex','opencode','aider','claude','all')][string]$App = 'menu', [switch]$Install ) $ErrorActionPreference = 'Stop' $baseUrl = 'https://codgeass.ru/v1' function Save-CodgeassFile([string]$Path, [string]$Text) { $directory = Split-Path -Parent $Path [IO.Directory]::CreateDirectory($directory) | Out-Null if (Test-Path -LiteralPath $Path) { $backup = $Path + '.backup-' + [DateTime]::UtcNow.ToString('yyyyMMdd-HHmmss-fffffff') Copy-Item -LiteralPath $Path -Destination $backup Write-Host "Backup: $backup" } [IO.File]::WriteAllText($Path, $Text, (New-Object Text.UTF8Encoding($false))) Write-Host "Saved: $Path" -ForegroundColor Green } function Update-CodgeassCodex([string]$Path, [string]$Model) { $text = if (Test-Path -LiteralPath $Path) { [IO.File]::ReadAllText($Path) } else { '' } # A multiline TOML string can contain text resembling a table. Leave it for manual setup. if ($text.Contains('"""') -or $text.Contains("'''")) { throw 'Multiline TOML detected. Use the manual setup guide; no config was changed.' } if ($text -match '(?m)^\s*\[\s*model_providers\s*\]') { throw 'An inline model_providers table needs manual merging. No config was changed.' } $firstTable = [regex]::Match($text, '(?m)^\s*\[') $top = if ($firstTable.Success) { $text.Substring(0, $firstTable.Index) } else { $text } $tail = if ($firstTable.Success) { $text.Substring($firstTable.Index) } else { '' } $top = [regex]::Replace($top, '(?m)^\s*(model|model_provider)\s*=.*(?:\r?\n|$)', '') $tail = [regex]::Replace($tail, '(?ms)^\s*\[model_providers\.codgeass\]\s*\r?\n.*?(?=^\s*\[|\z)', '') $encodedModel = ConvertTo-Json -InputObject $Model -Compress $result = "model_provider = `"codgeass`"`nmodel = $encodedModel`n" + $top.TrimStart() + "`n" + $tail.TrimEnd() + @' [model_providers.codgeass] name = "Codgeass" base_url = "https://codgeass.ru/v1" env_key = "CODGEASS_API_KEY" wire_api = "responses" requires_openai_auth = false supports_websockets = false '@ Save-CodgeassFile $Path ($result + "`n") } function Update-CodgeassOpenCode([string]$Path, [string]$Model) { $config = if (Test-Path -LiteralPath $Path) { Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json } else { [pscustomobject]@{} } if ($null -eq $config -or $config -is [array] -or $config -is [string]) { throw 'OpenCode config must be a JSON object.' } if (-not $config.PSObject.Properties['provider']) { $config | Add-Member provider ([pscustomobject]@{}) } if ($null -eq $config.provider) { throw 'OpenCode provider must be an object.' } $models = @{}; $models[$Model] = @{ name = $Model } $provider = @{ npm = '@ai-sdk/openai-compatible'; name = 'Codgeass'; options = @{ baseURL = $baseUrl; apiKey = '{env:CODGEASS_API_KEY}' }; models = $models } $config.provider | Add-Member codgeass $provider -Force $config | Add-Member model "codgeass/$Model" -Force Save-CodgeassFile $Path ($config | ConvertTo-Json -Depth 100) } function Install-CodgeassCli([string]$SelectedApp) { if ($SelectedApp -eq 'aider') { Write-Host 'Install aider using the manual guide: https://codgeass.ru/setup?app=aider'; return } $npm = Get-Command npm.cmd -ErrorAction SilentlyContinue if (-not $npm) { throw 'Install Node.js LTS from https://nodejs.org first, then open a new PowerShell window.' } $package = if ($SelectedApp -eq 'codex') { '@openai/codex' } else { 'opencode-ai' } & $npm.Source install -g $package if ($LASTEXITCODE -ne 0) { throw "npm installation failed: $package" } } Write-Host 'Codgeass automatic setup - Windows PowerShell 5.1 / PowerShell 7' if ($App -eq 'menu') { Write-Host '1: Codex 2: OpenCode 3: aider 4: All three 5: Claude Code compatibility' $choice = Read-Host 'Choose 1-5' $App = switch ($choice) { '1' {'codex'} '2' {'opencode'} '3' {'aider'} '4' {'all'} '5' {'claude'} default { throw 'Invalid choice.' } } } if ($App -eq 'claude') { Write-Host 'Claude Code requires Anthropic Messages API. Codgeass compatibility is not confirmed; no settings were changed.' -ForegroundColor Yellow Write-Host 'Use Claude models through OpenCode or Cline with the OpenAI-compatible API instead.' return } $selectedApps = if ($App -eq 'all') { @('codex','opencode','aider') } else { @($App) } $secret = Read-Host 'Paste your Codgeass API key (hidden)' -AsSecureString $credential = New-Object System.Management.Automation.PSCredential('codgeass', $secret) $apiKey = $credential.GetNetworkCredential().Password.Trim() if ([string]::IsNullOrWhiteSpace($apiKey) -or $apiKey -match '[\r\n]') { throw 'Invalid API key.' } if ($PSVersionTable.PSVersion.Major -lt 6) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } try { $response = Invoke-RestMethod -Uri "$baseUrl/models" -Headers @{ Authorization = "Bearer $apiKey" } -TimeoutSec 30 } catch { throw 'Cannot load Codgeass models. Check your key, network and account. No settings were changed.' } $modelIds = @($response.data | ForEach-Object { $_.id } | Where-Object { $_ -is [string] -and $_.Length -gt 0 }) if ($modelIds.Count -eq 0) { throw 'No models available for this key. No settings were changed.' } for ($i=0; $i -lt $modelIds.Count; $i++) { Write-Host ("{0}: {1}" -f ($i+1), $modelIds[$i]) } Write-Host 'Choose a model compatible with your app. Codex requires Responses; OpenCode/aider require Chat Completions.' $number = 0 if (-not [int]::TryParse((Read-Host 'Model number'), [ref]$number) -or $number -lt 1 -or $number -gt $modelIds.Count) { throw 'Invalid model number.' } $modelId = $modelIds[$number-1] foreach ($selectedApp in $selectedApps) { try { if ($Install) { Install-CodgeassCli $selectedApp } switch ($selectedApp) { 'codex' { $codexDirectory = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $env:USERPROFILE '.codex' } Update-CodgeassCodex (Join-Path $codexDirectory 'config.toml') $modelId } 'opencode' { $configDirectory = if ($env:XDG_CONFIG_HOME) { $env:XDG_CONFIG_HOME } else { Join-Path $env:USERPROFILE '.config' } $openCodeDirectory = Join-Path $configDirectory 'opencode' if (Test-Path -LiteralPath (Join-Path $openCodeDirectory 'opencode.jsonc')) { throw 'Existing opencode.jsonc detected. Use manual setup to preserve its comments and settings.' } Update-CodgeassOpenCode (Join-Path $openCodeDirectory 'opencode.json') $modelId } 'aider' { $aiderConfig = Join-Path $env:USERPROFILE '.aider.conf.yml' $existing = if (Test-Path -LiteralPath $aiderConfig) { [IO.File]::ReadAllText($aiderConfig) } else { '' } if ($existing -match '(?m)^\s*(model|openai-api-base)\s*:') { throw 'Existing aider model settings detected. Use manual setup to merge them.' } $aiderModel = ConvertTo-Json -InputObject "openai/$modelId" -Compress Save-CodgeassFile $aiderConfig ($existing.TrimEnd() + "`nmodel: $aiderModel`nopenai-api-base: $baseUrl`n") [Environment]::SetEnvironmentVariable('OPENAI_API_KEY', $apiKey, 'User') $env:OPENAI_API_KEY = $apiKey Write-Host 'aider uses the user OPENAI_API_KEY environment variable; this also affects other tools using that variable.' } } [Environment]::SetEnvironmentVariable('CODGEASS_API_KEY', $apiKey, 'User') $env:CODGEASS_API_KEY = $apiKey Write-Host "Ready: $selectedApp" -ForegroundColor Green } catch { Write-Warning ("{0}: {1}" -f $selectedApp, $_.Exception.Message) } } $apiKey = $null; $credential = $null; $secret = $null Write-Host 'Restart the app to load its settings. A project config or selected profile may override the global config.' Write-Host 'No generation request was made. Check with a short prompt and view usage at https://codgeass.ru/account'