# Sundy Handbook ยท Gemini CLI installer for Windows # # Recommended usage, paste into an already-open PowerShell window: # try { $env:GEMINI_SUNDY_NO_PAUSE='1'; $u='https://sundy.tumpai.site/install-gemini.ps1'; iex ([System.Text.Encoding]::UTF8.GetString((New-Object Net.WebClient).DownloadData($u))) } catch { Write-Host ('Installer failed to start: ' + $_.Exception.Message) -ForegroundColor Red } finally { if (-not [Console]::IsInputRedirected) { Read-Host 'Press Enter to exit' } } # # Unattended: # $env:SUNDY_API_KEY='sk-xxx'; $env:GEMINI_SUNDY_YES='1'; try { $env:GEMINI_SUNDY_NO_PAUSE='1'; $u='https://sundy.tumpai.site/install-gemini.ps1'; iex ([System.Text.Encoding]::UTF8.GetString((New-Object Net.WebClient).DownloadData($u))) } catch { Write-Host ('Installer failed to start: ' + $_.Exception.Message) -ForegroundColor Red } finally { if (-not [Console]::IsInputRedirected) { Read-Host 'Press Enter to exit' } } # # If Codex / Claude Code setup already saved SUNDY_API_KEY, this installer reuses it. $ErrorActionPreference = "Stop" try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) $OutputEncoding = [Console]::OutputEncoding if ($PSVersionTable.PSEdition -eq "Desktop") { chcp 65001 > $null } } catch {} $LogFile = Join-Path $env:TEMP "gemini-sundy-install-last.log" function Add-Log { param([string]$Message) try { Add-Content -Encoding UTF8 -Path $LogFile -Value $Message } catch {} } try { "Gemini CLI installer started at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" | Set-Content -Encoding UTF8 -Path $LogFile } catch {} $DefaultBaseUrl = "https://sundy-api.tumpai.site:2053/api/provider/antigravity" $LegacyBaseUrl = "https://sundy-api.tumpai.site:2053" $BaseUrl = if ($env:GOOGLE_GEMINI_BASE_URL) { $env:GOOGLE_GEMINI_BASE_URL.TrimEnd("/") } elseif ($env:GEMINI_BASE_URL) { $env:GEMINI_BASE_URL.TrimEnd("/") } else { $DefaultBaseUrl } if ($BaseUrl -eq $LegacyBaseUrl) { $BaseUrl = $DefaultBaseUrl } $ApiVersion = if ($env:GOOGLE_GENAI_API_VERSION) { $env:GOOGLE_GENAI_API_VERSION } elseif ($env:GEMINI_API_VERSION) { $env:GEMINI_API_VERSION } else { "v1beta" } $AuthMechanism = if ($env:GEMINI_API_KEY_AUTH_MECHANISM) { $env:GEMINI_API_KEY_AUTH_MECHANISM } else { "x-goog-api-key" } $ModelDefault = if ($env:GEMINI_MODEL) { $env:GEMINI_MODEL } else { "auto-gemini-3" } if ($ModelDefault -in @("gemini-2.5-flash", "gemini-3-flash")) { $ModelDefault = "auto-gemini-3" } $InvalidAuthMechanism = $AuthMechanism -notin @("x-goog-api-key", "bearer") $NonInteractive = $false try { if ([Console]::IsInputRedirected) { $NonInteractive = $true } } catch { $NonInteractive = $true } $AssumeYes = ($env:GEMINI_SUNDY_YES -eq "1") -or $NonInteractive function Write-Title { param([string]$Message) Write-Host "" Write-Host "==> $Message" -ForegroundColor Cyan Add-Log "==> $Message" } function Write-Ok { param([string]$Message) Write-Host "OK: $Message" -ForegroundColor Green Add-Log "OK: $Message" } function Write-Info { param([string]$Message) Write-Host "INFO: $Message" -ForegroundColor Cyan Add-Log "INFO: $Message" } function Write-Warn2 { param([string]$Message) Write-Host "WARN: $Message" -ForegroundColor Yellow Add-Log "WARN: $Message" } function Write-Fail { param([string]$Message) throw $Message } function Wait-BeforeExit { if ($env:GEMINI_SUNDY_NO_PAUSE -eq "1" -or $env:CI -eq "true") { return } try { Write-Host "" Read-Host "Press Enter to exit" | Out-Null } catch { try { cmd.exe /c pause | Out-Null } catch {} } } function Handle-InstallError { param([object]$Err) Write-Host "" Write-Host ("ERROR: Installation stopped: " + $Err.Exception.Message) -ForegroundColor Red Add-Log ("ERROR: " + $Err.Exception.Message) Write-Host ("Log file: " + $LogFile) -ForegroundColor Yellow Wait-BeforeExit } function Read-Line { param([string]$Prompt, [string]$Default = "") if ($NonInteractive) { return $Default } try { $value = Read-Host $Prompt if ([string]::IsNullOrWhiteSpace($value)) { return $Default } return $value } catch { return $Default } } function Get-NpmCommand { $cmd = Get-Command npm.cmd -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } $cmd = Get-Command npm -CommandType Application -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } return $null } function Write-Utf8NoBom { param([string]$Path, [string]$Content) $utf8NoBom = [System.Text.UTF8Encoding]::new($false) [System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom) } function Get-NodeMajor { try { $raw = node -p "Number(process.versions.node.split('.')[0])" return [int]$raw } catch { return 0 } } function Maybe-Setup-NpmMirror { $hasProxy = $env:HTTP_PROXY -or $env:HTTPS_PROXY -or $env:ALL_PROXY $npm = Get-NpmCommand if (-not $hasProxy -and $npm) { try { $currentRegistry = (& $npm config get registry).Trim() if ($currentRegistry -eq "https://registry.npmjs.org/") { Write-Info "No proxy detected. If npm is slow, run: npm.cmd config set registry https://registry.npmmirror.com" } } catch {} } } function Ensure-ObjectProperty { param([object]$Object, [string]$Name) if (-not $Object.PSObject.Properties[$Name] -or $null -eq $Object.$Name -or $Object.$Name.GetType().Name -ne "PSCustomObject") { $Object | Add-Member -MemberType NoteProperty -Name $Name -Value ([pscustomobject]@{}) -Force } return $Object.$Name } function ConvertTo-DotEnvValue { param([string]$Value) return '"' + ($Value.Replace('\', '\\').Replace('"', '\"')) + '"' } function Get-PowerShellProfilePath { try { if ($PROFILE -and $PROFILE.PSObject.Properties["CurrentUserAllHosts"] -and $PROFILE.CurrentUserAllHosts) { return [string]$PROFILE.CurrentUserAllHosts } if ($PROFILE) { return [string]$PROFILE } } catch {} $documents = [Environment]::GetFolderPath("MyDocuments") if ([string]::IsNullOrWhiteSpace($documents)) { $documents = $HOME } return (Join-Path $documents "WindowsPowerShell\profile.ps1") } function Write-GeminiYoloProfile { $profilePath = Get-PowerShellProfilePath if ([string]::IsNullOrWhiteSpace($profilePath)) { Write-Warn2 "Could not locate the PowerShell profile path; run gemini --approval-mode=yolo manually if needed." return } $profileDir = Split-Path -Parent $profilePath if (-not [string]::IsNullOrWhiteSpace($profileDir)) { New-Item -ItemType Directory -Force -Path $profileDir | Out-Null } $oldProfile = "" if (Test-Path $profilePath) { $backup = "$profilePath.bak-" + (Get-Date -Format "yyyyMMdd-HHmmss") Copy-Item $profilePath $backup Write-Ok "Backed up PowerShell profile to $backup" $oldProfile = Get-Content -Raw -Path $profilePath } $oldProfile = [regex]::Replace($oldProfile, "(?ms)\r?\n?# >>> gemini-sundy-yolo >>>.*?# <<< gemini-sundy-yolo <<<\r?\n?", "`n") $block = @' # >>> gemini-sundy-yolo >>> function gemini { $geminiArgs = @($args) $hasApproval = $false $hasSkipTrust = $false foreach ($arg in $geminiArgs) { $text = [string]$arg if ($text -eq "--approval-mode" -or $text -like "--approval-mode=*" -or $text -eq "-y" -or $text -eq "--yolo") { $hasApproval = $true } if ($text -eq "--skip-trust") { $hasSkipTrust = $true } } $cmd = Get-Command gemini.cmd -CommandType Application -ErrorAction SilentlyContinue if (-not $cmd) { $cmd = Get-Command gemini.exe -CommandType Application -ErrorAction SilentlyContinue } if (-not $cmd) { $cmd = Get-Command gemini -CommandType Application -ErrorAction SilentlyContinue } if (-not $cmd) { throw "gemini executable was not found in PATH" } if (-not $hasApproval) { $geminiArgs += "--approval-mode=yolo" if (-not $hasSkipTrust) { $geminiArgs += "--skip-trust" } } & $cmd.Source @geminiArgs } # <<< gemini-sundy-yolo <<< '@ $newProfile = $oldProfile.TrimEnd() + "`n`n" + $block + "`n" Write-Utf8NoBom -Path $profilePath -Content $newProfile.TrimStart() Write-Ok "Wrote Gemini YOLO wrapper to $profilePath" } function Resolve-GeminiModel { param( [string]$CurrentModel, [string]$BaseUrl, [string]$ApiVersion, [string]$AuthMechanism, [string]$ApiKey ) $modelsUrl = "$BaseUrl/$ApiVersion/models" $headers = if ($AuthMechanism -eq "bearer") { @{ "Authorization" = "Bearer $ApiKey" } } else { @{ "x-goog-api-key" = $ApiKey } } $autoRequirements = @{ "auto" = @("gemini-pro-agent", "gemini-3-flash", "gemini-3.1-flash-lite") "auto-gemini-3" = @("gemini-pro-agent", "gemini-3-flash", "gemini-3.1-flash-lite") "auto-gemini-2.5" = @("gemini-3.1-pro-low", "gemini-3.1-flash-lite") } $preferred = @( "auto-gemini-3", "gemini-3-flash", "gemini-3-pro-low", "gemini-3-pro-high", "gemini-3.1-flash-lite", "gemini-3.1-pro-low", "gemini-pro-agent" ) try { $response = Invoke-WebRequest -Uri $modelsUrl -Headers $headers -UseBasicParsing -TimeoutSec 15 if ($response.StatusCode -ne 200) { Write-Warn2 ("Could not read model list (HTTP " + $response.StatusCode + "); keeping $CurrentModel") return $CurrentModel } $json = $response.Content | ConvertFrom-Json $names = @() foreach ($model in @($json.models)) { if ($model.name) { $names += ($model.name -replace "^models/", "") } } if ($autoRequirements.ContainsKey($CurrentModel)) { $missing = @($autoRequirements[$CurrentModel] | Where-Object { $names -notcontains $_ }) if ($missing.Count -eq 0) { Write-Ok "Default model is available: $CurrentModel" return $CurrentModel } } if ($names -contains $CurrentModel) { Write-Ok "Default model is available: $CurrentModel" return $CurrentModel } foreach ($candidate in $preferred) { if ($autoRequirements.ContainsKey($candidate)) { $missing = @($autoRequirements[$candidate] | Where-Object { $names -notcontains $_ }) if ($missing.Count -eq 0) { Write-Warn2 "Current GEMINI_MODEL=$CurrentModel is not in the server model list; using $candidate" return $candidate } } if ($names -contains $candidate) { Write-Warn2 "Current GEMINI_MODEL=$CurrentModel is not in the server model list; using $candidate" return $candidate } } $fallback = $names | Where-Object { $_ -match "^gemini" } | Select-Object -First 1 if ($fallback) { Write-Warn2 "Current GEMINI_MODEL=$CurrentModel is not in the server model list; using $fallback" return $fallback } Write-Warn2 "Model list is reachable, but no Gemini model could be selected; keeping $CurrentModel" return $CurrentModel } catch { Write-Warn2 "Could not read model list: $($_.Exception.Message). Keeping $CurrentModel" return $CurrentModel } } function Resolve-AntigravityOwnedGeminiModel { param( [string]$CurrentModel, [string]$BaseUrl, [string]$ApiKey ) $apiRoot = $BaseUrl if ($apiRoot.EndsWith("/api/provider/antigravity")) { $apiRoot = $apiRoot.Substring(0, $apiRoot.Length - "/api/provider/antigravity".Length) } $modelsUrl = "$apiRoot/v1/models" $autoRequirements = @{ "auto" = @("gemini-pro-agent", "gemini-3-flash", "gemini-3.1-flash-lite") "auto-gemini-3" = @("gemini-pro-agent", "gemini-3-flash", "gemini-3.1-flash-lite") "auto-gemini-2.5" = @("gemini-3.1-pro-low", "gemini-3.1-flash-lite") } $preferred = @( "auto-gemini-3", "gemini-3-flash", "gemini-3.1-flash-lite", "gemini-3.1-pro-low", "gemini-pro-agent", "gemini-3-pro-low", "gemini-3-pro-high" ) try { $response = Invoke-WebRequest -Uri $modelsUrl -Headers @{ "Authorization" = "Bearer $ApiKey" } -UseBasicParsing -TimeoutSec 15 if ($response.StatusCode -ne 200) { Write-Warn2 ("Could not read $modelsUrl (HTTP " + $response.StatusCode + "); cannot prove model ownership") return $CurrentModel } $json = $response.Content | ConvertFrom-Json $rawModels = if ($json.data) { @($json.data) } elseif ($json.models) { @($json.models) } else { @() } $owned = @() foreach ($model in $rawModels) { $owner = [string]$model.owned_by $name = if ($model.id) { [string]$model.id } else { [string]$model.name } $name = $name -replace "^models/", "" if ($owner.ToLowerInvariant() -eq "antigravity" -and $name -match "^gemini") { $owned += $name } } if ($autoRequirements.ContainsKey($CurrentModel)) { $missing = @($autoRequirements[$CurrentModel] | Where-Object { $owned -notcontains $_ }) if ($missing.Count -eq 0) { Write-Ok "Confirmed $CurrentModel resolves only to owned_by=antigravity Gemini models in /v1/models" return $CurrentModel } } if ($owned -contains $CurrentModel) { Write-Ok "Confirmed $CurrentModel is owned_by=antigravity in /v1/models" return $CurrentModel } foreach ($candidate in $preferred) { if ($autoRequirements.ContainsKey($candidate)) { $missing = @($autoRequirements[$candidate] | Where-Object { $owned -notcontains $_ }) if ($missing.Count -eq 0) { Write-Warn2 "Current GEMINI_MODEL=$CurrentModel is not an Antigravity-owned Gemini model; using $candidate" return $candidate } } if ($owned -contains $candidate) { Write-Warn2 "Current GEMINI_MODEL=$CurrentModel is not an Antigravity-owned Gemini model; using $candidate" return $candidate } } if ($owned.Count -gt 0) { Write-Warn2 "Current GEMINI_MODEL=$CurrentModel is not an Antigravity-owned Gemini model; using $($owned[0])" return $owned[0] } Write-Warn2 "No owned_by=antigravity Gemini model was found in $modelsUrl. Ask the service provider to configure an Antigravity-specific Gemini alias." return $CurrentModel } catch { Write-Warn2 "Could not read $modelsUrl: $($_.Exception.Message). Cannot prove model ownership." return $CurrentModel } } function Get-ExistingApiKey { $names = @( "SUNDY_API_KEY", "GEMINI_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_API_KEY", "CODEX_API_KEY" ) foreach ($name in $names) { $value = [System.Environment]::GetEnvironmentVariable($name, "Process") if (-not [string]::IsNullOrWhiteSpace($value)) { return [pscustomobject]@{ Value = $value; Source = "process environment: $name" } } } foreach ($name in $names) { $value = [System.Environment]::GetEnvironmentVariable($name, "User") if (-not [string]::IsNullOrWhiteSpace($value)) { return [pscustomobject]@{ Value = $value; Source = "user environment: $name" } } } $envFiles = @( (Join-Path $HOME ".gemini\.env"), (Join-Path $HOME ".env") ) foreach ($file in $envFiles) { if (-not (Test-Path $file)) { continue } try { $lines = Get-Content -Path $file -ErrorAction Stop foreach ($name in $names) { $match = $lines | Where-Object { $_ -match "^\s*(?:export\s+)?$([regex]::Escape($name))\s*=" } | Select-Object -Last 1 if ($match) { $value = ($match -replace "^\s*(?:export\s+)?$([regex]::Escape($name))\s*=\s*", "").Trim() $value = ($value -replace "\s+#.*$", "").Trim() $value = $value.Trim('"').Trim("'") if (-not [string]::IsNullOrWhiteSpace($value)) { return [pscustomobject]@{ Value = $value; Source = "$file`: $name" } } } } } catch {} } return $null } try { Write-Host "" Write-Host "================================================" -ForegroundColor Cyan Write-Host " Sundy Gemini CLI setup - Windows installer" -ForegroundColor Cyan Write-Host "================================================" -ForegroundColor Cyan Write-Host "" Write-Host "This script will:" Write-Host " 1. Check and install Node.js 20+ with winget if needed" Write-Host " 2. Install Gemini CLI globally with npm" Write-Host " 3. Reuse an existing Sundy API key; ask only if none is found" Write-Host " 4. Confirm the default model exists and is owned_by=antigravity" Write-Host " 5. Write %USERPROFILE%\.gemini\settings.json, .env, user environment variables, and a YOLO launcher wrapper" Write-Host " 6. Test the Gemini API proxy endpoint" Write-Host "" Write-Host "Gemini base URL: $BaseUrl" Write-Host "Gemini API version: $ApiVersion" Write-Host "Default model: $ModelDefault" Write-Host "Default approval: YOLO (gemini appends --approval-mode=yolo --skip-trust)" Write-Host "" if ($InvalidAuthMechanism) { Write-Fail "GEMINI_API_KEY_AUTH_MECHANISM must be x-goog-api-key or bearer. Current value: $AuthMechanism" } if ($AssumeYes) { Write-Info "Auto-confirm mode is enabled. Continuing..." } else { $go = Read-Line -Prompt "Continue? [Y/n]" -Default "Y" if ($go -and $go -notmatch '^[Yy]') { Write-Info "Cancelled." Wait-BeforeExit return } } Write-Title "1/6 Checking Node.js" $node = Get-Command node -ErrorAction SilentlyContinue if ($node -and (Get-NodeMajor) -ge 20) { Write-Ok ("Node is installed: " + (node -v)) } else { if ($node) { Write-Warn2 ("Current Node " + (node -v) + " is lower than Gemini CLI's required 20+. Installing Node.js LTS with winget...") } else { Write-Warn2 "Node.js was not found. Installing Node.js LTS with winget..." } if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { Write-Fail "winget was not found. Install Node.js 20+ manually from https://nodejs.org/ and run this installer again." } winget install -e --id OpenJS.NodeJS.LTS --silent --accept-package-agreements --accept-source-agreements $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") if (-not (Get-Command node -ErrorAction SilentlyContinue) -or (Get-NodeMajor) -lt 20) { Write-Fail "Node.js 20+ was installed but node is still not available in this PowerShell session. Close PowerShell, open a new one, and run this installer again." } Write-Ok ("Node installed: " + (node -v)) } Maybe-Setup-NpmMirror Write-Title "2/6 Installing Gemini CLI" $NpmCmd = Get-NpmCommand if (-not $NpmCmd) { Write-Fail "npm was not found. Close PowerShell, open a new one, and run this installer again." } & $NpmCmd install -g "@google/gemini-cli" if ($LASTEXITCODE -ne 0) { Write-Fail "Gemini CLI install failed. If npm is slow, run: npm.cmd config set registry https://registry.npmmirror.com" } Write-Ok "Gemini CLI installed" Write-Title "3/6 API key" $foundKey = Get-ExistingApiKey $ApiKey = if ($foundKey) { $foundKey.Value } else { $null } if ($ApiKey) { Write-Ok ("Reused existing API key from " + $foundKey.Source + " (" + $ApiKey.Length + " chars)") } elseif ($NonInteractive) { Write-Fail "Non-interactive mode could not find SUNDY_API_KEY or GEMINI_API_KEY." } else { Write-Host "No existing Sundy API key was found. Paste the Sundy API key issued by the service provider below. Input will be visible on screen." Write-Host "It will be saved as GEMINI_API_KEY for Gemini CLI." Write-Host "" while (-not $ApiKey) { $ApiKey = Read-Host "API Key" if (-not $ApiKey) { Write-Warn2 "API key is empty. Try again." } } Write-Ok ("Received API key (" + $ApiKey.Length + " chars)") } if ($env:GOOGLE_API_KEY -and $env:GOOGLE_API_KEY -ne $ApiKey) { Write-Warn2 "GOOGLE_API_KEY is already set in this PowerShell session. Gemini SDK prefers GOOGLE_API_KEY over GEMINI_API_KEY; remove it if Gemini CLI still uses the old key." } Write-Title "4/6 Confirming default model" $ModelDefault = Resolve-GeminiModel -CurrentModel $ModelDefault -BaseUrl $BaseUrl -ApiVersion $ApiVersion -AuthMechanism $AuthMechanism -ApiKey $ApiKey $ModelDefault = Resolve-AntigravityOwnedGeminiModel -CurrentModel $ModelDefault -BaseUrl $BaseUrl -ApiKey $ApiKey Write-Title "5/6 Writing Gemini config" [System.Environment]::SetEnvironmentVariable("SUNDY_API_KEY", $ApiKey, "User") [System.Environment]::SetEnvironmentVariable("GEMINI_API_KEY", $ApiKey, "User") [System.Environment]::SetEnvironmentVariable("GOOGLE_GEMINI_BASE_URL", $BaseUrl, "User") [System.Environment]::SetEnvironmentVariable("GOOGLE_GENAI_API_VERSION", $ApiVersion, "User") [System.Environment]::SetEnvironmentVariable("GEMINI_API_KEY_AUTH_MECHANISM", $AuthMechanism, "User") [System.Environment]::SetEnvironmentVariable("GEMINI_MODEL", $ModelDefault, "User") $env:SUNDY_API_KEY = $ApiKey $env:GEMINI_API_KEY = $ApiKey $env:GOOGLE_GEMINI_BASE_URL = $BaseUrl $env:GOOGLE_GENAI_API_VERSION = $ApiVersion $env:GEMINI_API_KEY_AUTH_MECHANISM = $AuthMechanism $env:GEMINI_MODEL = $ModelDefault $geminiDir = Join-Path $HOME ".gemini" $settingsFile = Join-Path $geminiDir "settings.json" $envFile = Join-Path $geminiDir ".env" New-Item -ItemType Directory -Force -Path $geminiDir | Out-Null if (Test-Path $settingsFile) { $backup = "$settingsFile.bak-" + (Get-Date -Format "yyyyMMdd-HHmmss") Copy-Item $settingsFile $backup Write-Ok "Backed up old settings to $backup" } if (Test-Path $settingsFile) { $rawSettings = Get-Content -Raw -Path $settingsFile if ([string]::IsNullOrWhiteSpace($rawSettings)) { $settings = [pscustomobject]@{} } else { $settings = $rawSettings | ConvertFrom-Json } } else { $settings = [pscustomobject]@{} } if (-not $settings.PSObject.Properties['$schema']) { $settings | Add-Member -MemberType NoteProperty -Name '$schema' -Value "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json" } $settings | Add-Member -MemberType NoteProperty -Name "selectedAuthType" -Value "gemini-api-key" -Force $settings | Add-Member -MemberType NoteProperty -Name "enforcedAuthType" -Value "gemini-api-key" -Force $security = Ensure-ObjectProperty -Object $settings -Name "security" $auth = Ensure-ObjectProperty -Object $security -Name "auth" $auth | Add-Member -MemberType NoteProperty -Name "selectedType" -Value "gemini-api-key" -Force $auth | Add-Member -MemberType NoteProperty -Name "enforcedType" -Value "gemini-api-key" -Force $modelConfigs = Ensure-ObjectProperty -Object $settings -Name "modelConfigs" $customAliases = Ensure-ObjectProperty -Object $modelConfigs -Name "customAliases" $customAliasesJson = @' { "gemini-3.1-pro-preview": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-pro-agent" } }, "gemini-3.1-pro-preview-customtools": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-pro-agent" } }, "gemini-3-pro-preview": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-3-pro-high" } }, "gemini-3-flash-preview": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-3-flash" } }, "gemini-2.5-pro": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-3.1-pro-low" } }, "gemini-2.5-flash": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-3.1-flash-lite" } }, "gemini-2.5-flash-lite": { "extends": "chat-base-3", "modelConfig": { "model": "gemini-3.1-flash-lite" } }, "classifier": { "extends": "base", "modelConfig": { "model": "gemini-3.1-flash-lite", "generateContentConfig": { "maxOutputTokens": 1024, "thinkingConfig": { "thinkingLevel": "LOW" } } } }, "prompt-completion": { "extends": "base", "modelConfig": { "model": "gemini-3.1-flash-lite", "generateContentConfig": { "temperature": 0.3, "maxOutputTokens": 16000 } } }, "fast-ack-helper": { "extends": "base", "modelConfig": { "model": "gemini-3.1-flash-lite", "generateContentConfig": { "temperature": 0.2, "maxOutputTokens": 120 } } }, "edit-corrector": { "extends": "base", "modelConfig": { "model": "gemini-3.1-flash-lite" } }, "summarizer-default": { "extends": "base", "modelConfig": { "model": "gemini-3.1-flash-lite", "generateContentConfig": { "maxOutputTokens": 2000 } } }, "summarizer-shell": { "extends": "base", "modelConfig": { "model": "gemini-3.1-flash-lite", "generateContentConfig": { "maxOutputTokens": 2000 } } } } '@ | ConvertFrom-Json foreach ($property in $customAliasesJson.PSObject.Properties) { $customAliases | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value -Force } $settingsJson = $settings | ConvertTo-Json -Depth 20 Write-Utf8NoBom -Path $settingsFile -Content ($settingsJson + "`n") Write-Ok "Wrote $settingsFile" $oldEnv = "" if (Test-Path $envFile) { $oldEnv = Get-Content -Raw -Path $envFile } $oldEnv = [regex]::Replace($oldEnv, "(?ms)\r?\n?# >>> gemini-sundy >>>.*?# <<< gemini-sundy <<<\r?\n?", "`n") $envBlockLines = @( "# >>> gemini-sundy >>>", ("SUNDY_API_KEY=" + (ConvertTo-DotEnvValue $ApiKey)), ("GEMINI_API_KEY=" + (ConvertTo-DotEnvValue $ApiKey)), ("GOOGLE_GEMINI_BASE_URL=" + (ConvertTo-DotEnvValue $BaseUrl)), ("GOOGLE_GENAI_API_VERSION=" + (ConvertTo-DotEnvValue $ApiVersion)), ("GEMINI_API_KEY_AUTH_MECHANISM=" + (ConvertTo-DotEnvValue $AuthMechanism)), ("GEMINI_MODEL=" + (ConvertTo-DotEnvValue $ModelDefault)), "# <<< gemini-sundy <<<" ) $newEnv = $oldEnv.TrimEnd() + "`n`n" + (($envBlockLines -join "`n") + "`n") Write-Utf8NoBom -Path $envFile -Content $newEnv.TrimStart() Write-Ok "Wrote $envFile" Write-GeminiYoloProfile Write-Ok "Wrote user environment variables for Gemini CLI" Write-Title "6/6 Testing Gemini API proxy" $checkUrl = "$BaseUrl/$ApiVersion/models" try { if ($AuthMechanism -eq "bearer") { $headers = @{ "Authorization" = "Bearer $ApiKey" } } else { $headers = @{ "x-goog-api-key" = $ApiKey } } $response = Invoke-WebRequest -Uri $checkUrl -Headers $headers -UseBasicParsing -TimeoutSec 15 if ($response.StatusCode -eq 200) { Write-Ok "Gemini API proxy is reachable and the key looks valid" } else { Write-Warn2 ("HTTP " + $response.StatusCode + ". You can continue and test Gemini CLI manually.") } } catch { $code = $null try { $code = $_.Exception.Response.StatusCode.value__ } catch {} if ($code -eq 404) { Write-Fail "The server has not enabled the Gemini API route ($checkUrl returned 404). Ask the service provider to enable Gemini gateway in CLIProxyAPI/Sundy, then run this installer again." } elseif ($code -eq 401 -or $code -eq 403) { Write-Warn2 "The key was rejected (HTTP $code). Check your API key." } else { Write-Warn2 "Connectivity test failed: $($_.Exception.Message). You can try again later." } } Write-Host "" Write-Host "================================================" -ForegroundColor Green Write-Host " Gemini CLI install complete" -ForegroundColor Green Write-Host "================================================" -ForegroundColor Green Write-Host "" Write-Host "Important: open a new PowerShell window before running gemini." -ForegroundColor Yellow Write-Host "If Gemini CLI still uses an old Google key, remove GOOGLE_API_KEY from that session." -ForegroundColor Yellow Write-Host "Default model: $ModelDefault" -ForegroundColor Yellow Write-Host "Default approval: YOLO; the PowerShell profile wraps gemini with --approval-mode=yolo --skip-trust." -ForegroundColor Yellow Write-Host "The installer verified this model as owned_by=antigravity when /v1/models was reachable." -ForegroundColor Yellow Write-Host "" Write-Host "Next:" Write-Host " 1. Close this PowerShell window." Write-Host " 2. Open a new PowerShell window." Write-Host " 3. Run: gemini" Write-Host "" Write-Host "Quick test:" Write-Host " gemini `"explain this project's directory structure`"" -ForegroundColor Cyan Write-Host "Temporarily disable YOLO:" Write-Host " gemini --approval-mode=default" -ForegroundColor Cyan Write-Host "" Write-Host "Log file: $LogFile" Write-Host "" Wait-BeforeExit } catch { Handle-InstallError $_ return }