# Instalador Avello para Windows: configura o Claude Code e/ou o Codex para usar a sua chave Avello. # # irm https://avello.online/instalar/windows | iex # # O que ele faz, nesta ordem: # 1. pergunta o que configurar (Claude Code, Codex, os dois) ou se e para desfazer; # 2. instala a ferramenta que faltar, pelo instalador oficial dela; # 3. pede a sua chave (a digitacao nao aparece) e testa em /v1/models, que nao gasta # credito nem comeca o relogio do teste gratis; # 4. guarda uma copia de cada arquivo antes de mudar (anotada em %USERPROFILE%\.avello); # 5. grava a configuracao. "Desfazer" devolve as copias. # # Opcoes, para suporte e automacao: -Ferramenta claude|codex|ambos -ChaveEm -Sim -Desfazer param( [string]$Ferramenta = "", [string]$ChaveEm = "", [switch]$Sim, [switch]$Desfazer ) $AvelloInstalador = @{ Codigo = 0 } & { $ErrorActionPreference = "Stop" $Origem = "https://avello.online" $ModeloClaude = "claude-sonnet-5" $ModeloClaudeOpus = "claude-opus-5.5" $ModeloClaudeHaiku = "claude-haiku-4.5" $ModeloCodex = "gpt-5.6-sol-medium" $Casa = if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME } $PastaAvello = Join-Path $Casa ".avello" $Manifesto = Join-Path $PastaAvello "instalacao.txt" $SettingsClaude = Join-Path (Join-Path $Casa ".claude") "settings.json" $ConfigCodex = Join-Path (Join-Path $Casa ".codex") "config.toml" $Utf8SemBom = New-Object System.Text.UTF8Encoding($false) $VariaveisAnthropic = @("ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL") $estado = @{ Chave = ""; UltimoCodigo = 0 } # TLS 1.2 por numero (3072): o nome Tls12 nao existe no .NET antigo. Vale so para este processo; os instaladores # oficiais rodam num powershell novo e recebem a mesma linha ($LigarTls12). [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072 $LigarTls12 = '[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072' function Diga([string]$t) { Write-Host $t } function Ok([string]$t) { Write-Host " OK $t" -ForegroundColor Green } function Aviso([string]$t) { Write-Host " ! $t" -ForegroundColor Yellow } function Falha([string]$t, [int]$codigo = 1) { Write-Host ""; Write-Host " ERRO $t" -ForegroundColor Red; Write-Host "" $AvelloInstalador.Codigo = $codigo throw "AVELLO_FALHA" } function Tem-Comando([string]$nome) { [bool](Get-Command $nome -ErrorAction SilentlyContinue) } function Escrever-Texto([string]$arq, [string]$texto) { [IO.File]::WriteAllText($arq, $texto, $Utf8SemBom) } function Anotar-Linha([string]$linha) { New-Item -ItemType Directory -Force -Path $PastaAvello | Out-Null $linhas = @(); if (Test-Path -LiteralPath $Manifesto) { $linhas = @([IO.File]::ReadAllLines($Manifesto)) } if ($linhas -contains $linha) { return } [IO.File]::AppendAllText($Manifesto, "$linha`n", $Utf8SemBom) } function Ja-Anotado([string]$arq) { if (-not (Test-Path -LiteralPath $Manifesto)) { return $false } $linhas = @([IO.File]::ReadAllLines($Manifesto)) return ($linhas -contains "copia|$arq") -or ($linhas -contains "novo|$arq") } function Anotar-E-Copiar([string]$arq) { if (Ja-Anotado $arq) { return } if (Test-Path -LiteralPath $arq) { Copy-Item -LiteralPath $arq -Destination "$arq.avello-bak" -Force; Anotar-Linha "copia|$arq" } else { Anotar-Linha "novo|$arq" } } function Guardar-Variavel([string]$nome) { $prefixo = "variavel|$nome|" if ((Test-Path -LiteralPath $Manifesto) -and (@([IO.File]::ReadAllLines($Manifesto)) | Where-Object { $_.StartsWith($prefixo) })) { return } $valor = [Environment]::GetEnvironmentVariable($nome, "User"); if ($null -eq $valor) { $valor = "" } Anotar-Linha ($prefixo + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($valor))) } function Perguntar([string]$texto) { return (Read-Host $texto) } function Hash-De([string]$arq) { return (Get-FileHash -LiteralPath $arq -Algorithm SHA256).Hash } # Depois de gravar um arquivo: anota o que a Avello deixou nele, para o Desfazer saber se alguem mexeu depois. function Anotar-Escrito([string]$arq) { $prefixo = "escrito|$arq|" $linhas = @([IO.File]::ReadAllLines($Manifesto) | Where-Object { -not $_.StartsWith($prefixo) }) $linhas += $prefixo + (Hash-De $arq) [IO.File]::WriteAllText($Manifesto, (($linhas -join "`n") + "`n"), $Utf8SemBom) } # O arquivo no disco nao e mais o que a Avello gravou (o cliente ou outro programa mexeu depois). function Mudou-Depois([string]$arq) { if ($arq.StartsWith($PastaAvello) -or -not (Test-Path -LiteralPath $arq)) { return $false } $prefixo = "escrito|$arq|" $gravado = @([IO.File]::ReadAllLines($Manifesto) | Where-Object { $_.StartsWith($prefixo) } | Select-Object -Last 1) if ($gravado.Count -eq 0) { return $false } return ((Hash-De $arq) -ne $gravado[0].Substring($prefixo.Length)) } function Escolher { if ($Ferramenta -or $Desfazer) { return } Diga "O que voce quer fazer?" Diga " 1) Configurar o Claude Code" Diga " 2) Configurar o Codex" Diga " 3) Configurar os dois" Diga " 4) Desfazer a instalacao anterior da Avello" switch (Perguntar "Escolha [1-4]") { "1" { $script:Ferramenta = "claude" } "2" { $script:Ferramenta = "codex" } "3" { $script:Ferramenta = "ambos" } "4" { $script:Desfazer = $true } default { Falha "opcao invalida" } } } function Limpar-Chave([string]$c) { return ($c -replace '[\s]', '') } function Chave-Valida([string]$c) { return $c -match '^sk-[A-Za-z0-9-]{8,}$' } function Testar-Chave([string]$chave) { try { $null = Invoke-WebRequest -Uri "$Origem/v1/models" -Headers @{ Authorization = "Bearer $chave" } -Method Get -UseBasicParsing -TimeoutSec 20 return 0 } catch { $resposta = $_.Exception.Response if ($null -ne $resposta) { $codigo = [int]$resposta.StatusCode if ($codigo -eq 401 -or $codigo -eq 403) { return 2 } $estado.UltimoCodigo = $codigo; return 1 } $estado.UltimoCodigo = 0; return 1 } } function Obter-Chave { if ($ChaveEm) { if (-not (Test-Path -LiteralPath $ChaveEm)) { Falha "nao consegui ler $ChaveEm" } $chave = Limpar-Chave ([IO.File]::ReadAllText($ChaveEm)) if (-not (Chave-Valida $chave)) { Falha "isso nao parece uma chave Avello (ela comeca com sk-). Nada foi alterado." 2 } switch (Testar-Chave $chave) { 0 { Ok "chave aceita pela Avello"; $estado.Chave = $chave; return } 2 { Falha "a Avello recusou essa chave. Nada foi alterado." 2 } default { Falha "nao consegui falar com a Avello (codigo $($estado.UltimoCodigo)). Confira a internet. Nada foi alterado." } } } foreach ($tentativa in 1..3) { $segura = Read-Host "Cole a sua chave Avello (a digitacao nao aparece) e tecle Enter" -AsSecureString $chave = Limpar-Chave ([Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($segura))) if (-not (Chave-Valida $chave)) { Aviso "isso nao parece uma chave Avello (ela comeca com sk-). Tentativa $tentativa de 3."; continue } switch (Testar-Chave $chave) { 0 { Ok "chave aceita pela Avello"; $estado.Chave = $chave; return } 1 { Falha "nao consegui falar com a Avello (codigo $($estado.UltimoCodigo)). Confira a internet. Nada foi alterado." } default { Aviso "a Avello recusou essa chave: confira se copiou inteira ou se o teste ja venceu. Tentativa $tentativa de 3." } } } Falha "chave recusada tres vezes. Nada foi alterado." 2 } # Devolve cada arquivo como era antes da Avello. O que mudou nele depois da instalacao nao some sem copia: # o estado de agora fica em .antes-do-desfazer. function Desfazer-Instalacao { if (-not (Test-Path -LiteralPath $Manifesto)) { Diga "Nao ha instalacao da Avello para desfazer."; return } $guardados = @() foreach ($linha in [IO.File]::ReadAllLines($Manifesto)) { $partes = $linha.Split("|", 3) if (($partes[0] -eq "copia" -or $partes[0] -eq "novo") -and (Mudou-Depois $partes[1])) { Copy-Item -LiteralPath $partes[1] -Destination "$($partes[1]).antes-do-desfazer" -Force $guardados += "$($partes[1]).antes-do-desfazer" } switch ($partes[0]) { "copia" { $bak = "$($partes[1]).avello-bak"; if (Test-Path -LiteralPath $bak) { Move-Item -LiteralPath $bak -Destination $partes[1] -Force; Ok "voltou como era: $($partes[1])" } } "novo" { if (Test-Path -LiteralPath $partes[1]) { Remove-Item -LiteralPath $partes[1] -Force; Ok "removido: $($partes[1])" } } "variavel" { $valor = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($partes[2])) if ($valor) { [Environment]::SetEnvironmentVariable($partes[1], $valor, "User") } else { [Environment]::SetEnvironmentVariable($partes[1], $null, "User") } Ok "variavel $($partes[1]) como era" } } } Remove-Item -LiteralPath $PastaAvello -Recurse -Force Ok "pronto: a configuracao de antes da Avello voltou." if ($guardados.Count -gt 0) { Aviso "estes arquivos mudaram depois da instalacao; o que eles tinham agora ficou guardado em:" foreach ($g in $guardados) { Diga " $g" } } } # Instalador oficial num powershell novo (quando ele termina, esta janela continua), com TLS 1.2 la dentro. Falha # dele, ou o programa que nao aparece, para tudo antes de gravar qualquer configuracao. function Instalar-Oficial([string]$nome, [string]$fabricante, [string]$url, [string]$comando, [string]$binario) { Diga " Instalando o $nome pelo instalador oficial da $fabricante..." & powershell -NoProfile -ExecutionPolicy Bypass -Command "$LigarTls12; irm $url | iex" | Out-Host if ($LASTEXITCODE -ne 0) { Falha "o instalador oficial do $nome falhou (codigo $LASTEXITCODE). Nenhuma configuracao foi alterada." } $pasta = Split-Path -Parent $binario $env:Path = "$pasta;$env:Path" if (-not (Test-Path -LiteralPath $binario) -and -not (Tem-Comando $comando)) { Falha "o instalador oficial do $nome terminou, mas o $comando nao apareceu em $pasta. Nenhuma configuracao foi alterada." } Ok "$nome instalado" } function Garantir-Claude { if (Tem-Comando "claude") { Ok "Claude Code ja instalado" } else { Instalar-Oficial "Claude Code" "Anthropic" "https://claude.ai/install.ps1" "claude" (Join-Path (Join-Path (Join-Path $Casa ".local") "bin") "claude.exe") } if (-not (Tem-Comando "git")) { if (Tem-Comando "winget") { Diga " Instalando o Git for Windows (recomendado pelo Claude Code)..." & winget install --id Git.Git -e --source winget --accept-source-agreements --accept-package-agreements | Out-Host } else { Aviso "recomendado: instale o Git for Windows (https://git-scm.com/download/win)" } } } function Garantir-Codex { if (Tem-Comando "codex") { Ok "Codex ja instalado"; return } $binario = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $env:LOCALAPPDATA "Programs") "OpenAI") "Codex") "bin") "codex.exe" Instalar-Oficial "Codex" "OpenAI" "https://chatgpt.com/codex/install.ps1" "codex" $binario } function Limpar-AnthropicDoUsuario { $achou = @($VariaveisAnthropic | Where-Object { [Environment]::GetEnvironmentVariable($_, "User") }) if ($achou.Count -eq 0) { return } Aviso "estas variaveis de usuario passariam por cima da configuracao da Avello: $($achou -join ', ')" if (-not $Sim) { $r = Perguntar " Remover? O valor antigo fica guardado para o Desfazer. [s/N]" if ($r -notmatch '^(s|sim)$') { Aviso "mantive as variaveis"; return } } foreach ($nome in $achou) { Guardar-Variavel $nome; [Environment]::SetEnvironmentVariable($nome, $null, "User"); Ok "variavel $nome removida" } } function Gravar-Claude { New-Item -ItemType Directory -Force -Path (Split-Path -Parent $SettingsClaude) | Out-Null $obj = [pscustomobject]@{} if (Test-Path -LiteralPath $SettingsClaude) { $texto = [IO.File]::ReadAllText($SettingsClaude) if ($texto.Trim()) { try { $obj = $texto | ConvertFrom-Json } catch { Falha "o $SettingsClaude esta com JSON invalido; corrija ou apague e rode de novo. Ele nao foi alterado." } if ($obj -isnot [pscustomobject]) { Falha "o $SettingsClaude nao e um objeto JSON. Ele nao foi alterado." } } } if (-not ($obj.PSObject.Properties.Name -contains "env") -or $obj.env -isnot [pscustomobject]) { $obj | Add-Member -NotePropertyName env -NotePropertyValue ([pscustomobject]@{}) -Force } $obj.env.PSObject.Properties.Remove("ANTHROPIC_API_KEY") $valores = [ordered]@{ ANTHROPIC_BASE_URL = $Origem; ANTHROPIC_AUTH_TOKEN = $estado.Chave; ANTHROPIC_MODEL = $ModeloClaude ANTHROPIC_DEFAULT_SONNET_MODEL = $ModeloClaude; ANTHROPIC_DEFAULT_OPUS_MODEL = $ModeloClaudeOpus ANTHROPIC_DEFAULT_HAIKU_MODEL = $ModeloClaudeHaiku } foreach ($par in $valores.GetEnumerator()) { $obj.env | Add-Member -NotePropertyName $par.Key -NotePropertyValue $par.Value -Force } Anotar-E-Copiar $SettingsClaude Escrever-Texto $SettingsClaude (($obj | ConvertTo-Json -Depth 50) + "`n") Anotar-Escrito $SettingsClaude Ok "Claude Code apontado para a Avello ($SettingsClaude)" } function Sem-AvelloNoToml([string[]]$linhas) { $saida = New-Object System.Collections.Generic.List[string] $bloco = $false; $tabela = $false; $pular = $false foreach ($l in $linhas) { if ($l -match '^# >>> avello') { $bloco = $true; continue } if ($l -match '^# <<< avello') { $bloco = $false; continue } if ($bloco) { continue } if ($l -match '^\s*\[') { $tabela = $true $pular = $l -match '^\s*\[model_providers\.avello\]\s*(#.*)?$' } if ($pular) { continue } if (-not $tabela -and $l -match '^\s*(model|model_provider)\s*=') { continue } $saida.Add($l) } return $saida.ToArray() } function Gravar-Codex { New-Item -ItemType Directory -Force -Path (Split-Path -Parent $ConfigCodex) | Out-Null $antigas = @(); if (Test-Path -LiteralPath $ConfigCodex) { $antigas = [IO.File]::ReadAllLines($ConfigCodex) } $topo = @("# >>> avello (instalador Avello; para desfazer, rode o instalador e escolha Desfazer)", "model = `"$ModeloCodex`"", 'model_provider = "avello"', "# <<< avello") $fim = @("# >>> avello", "[model_providers.avello]", 'name = "Avello"', "base_url = `"$Origem/v1`"", 'env_key = "AVELLO_API_KEY"', 'requires_openai_auth = false', 'wire_api = "responses"', "# <<< avello") Anotar-E-Copiar $ConfigCodex Escrever-Texto $ConfigCodex ((@($topo) + @(Sem-AvelloNoToml $antigas) + @($fim)) -join "`n") [IO.File]::AppendAllText($ConfigCodex, "`n", $Utf8SemBom) Anotar-Escrito $ConfigCodex Ok "Codex apontado para a Avello ($ConfigCodex)" Guardar-Variavel "AVELLO_API_KEY" [Environment]::SetEnvironmentVariable("AVELLO_API_KEY", $estado.Chave, "User") $env:AVELLO_API_KEY = $estado.Chave Ok "chave do Codex guardada na variavel de usuario AVELLO_API_KEY" } function Principal { Diga ""; Diga "Instalador Avello: Claude Code e Codex"; Diga "" Escolher if ($Desfazer) { Desfazer-Instalacao; return } if (@("claude", "codex", "ambos") -notcontains $Ferramenta) { Falha "ferramenta invalida: $Ferramenta (use claude, codex ou ambos)" } if ($Ferramenta -ne "codex") { Garantir-Claude } if ($Ferramenta -ne "claude") { Garantir-Codex } Obter-Chave if ($Ferramenta -ne "codex") { Limpar-AnthropicDoUsuario; Gravar-Claude } if ($Ferramenta -ne "claude") { Gravar-Codex } Diga ""; Diga "Pronto. Abra um PowerShell NOVO e rode:" if ($Ferramenta -ne "codex") { Diga " claude (modelo padrao: $ModeloClaude)" } if ($Ferramenta -ne "claude") { Diga " codex (modelo padrao: $ModeloCodex)" } Diga ""; Diga "Para desfazer: rode o mesmo comando e escolha 4." Diga "Ids dos modelos e precos: $Origem/comecar"; Diga "" } try { Principal } catch { if ($_.Exception.Message -ne "AVELLO_FALHA") { Write-Host " ERRO inesperado: $($_.Exception.Message)" -ForegroundColor Red $AvelloInstalador.Codigo = 1 } } } if ($PSCommandPath) { exit $AvelloInstalador.Codigo }