# ============================================================== # aiproxynode setup script (standalone, generated by build.py) # Do not edit by hand. Edit source files in scripts/ and re-run build.py. # ============================================================== # _common.ps1 — aiproxynode setup scripts shared library (Windows) # Will be inlined into each setup-*.ps1 at build time. $ErrorActionPreference = 'Stop' $AIPROXY_BASE = 'https://aiproxynode.com' $BACKUP_SUFFIX = (Get-Date -Format 'yyyyMMdd') function Write-Header { param([string]$Scenario, [string]$Platform = 'Windows (PowerShell)') Write-Host '' Write-Host '========================================' -ForegroundColor Cyan Write-Host ' aiproxynode 客户端配置脚本' -ForegroundColor Cyan Write-Host " 场景: $Scenario" -ForegroundColor Cyan Write-Host " 平台: $Platform" -ForegroundColor Cyan Write-Host '========================================' -ForegroundColor Cyan Write-Host '' } function Read-Token { Write-Host '[1/4] 请输入您的 API Token (sk-* 格式)' -ForegroundColor Yellow $sec = Read-Host -Prompt '> ' -AsSecureString $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec) try { $tok = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr).Trim() } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } if (-not $tok) { Write-Host '错误: Token 不能为空' -ForegroundColor Red exit 2 } if (-not $tok.StartsWith('sk-')) { Write-Host "错误: Token 必须以 sk- 开头,当前: $($tok.Substring(0, [Math]::Min(8,$tok.Length)))..." -ForegroundColor Red exit 2 } return $tok } function Mask-Token { param([string]$Token) if ($Token.Length -le 10) { return 'sk-***' } return $Token.Substring(0,6) + '***' + $Token.Substring($Token.Length - 4) } function Backup-File { param([string]$Path) if (Test-Path $Path) { $bak = "$Path.bak.$BACKUP_SUFFIX" Copy-Item -Path $Path -Destination $bak -Force Write-Host " 已备份: $bak" -ForegroundColor DarkGray } } function Merge-Json { param( [string]$Path, [hashtable]$Updates ) # 读已有 JSON(不存在则空对象),merge updates 进去,写回 # 兼容 PS 5.1(无 -AsHashtable)和 PS 7+ $existing = @{} if (Test-Path $Path) { try { $raw = Get-Content -Path $Path -Raw -Encoding UTF8 if ($raw.Trim()) { $parsed = $null try { # PS 7+ 快路径 $parsed = $raw | ConvertFrom-Json -AsHashtable -ErrorAction Stop } catch [System.Management.Automation.ParameterBindingException] { # PS 5.1: 没有 -AsHashtable 参数 $obj = $raw | ConvertFrom-Json -ErrorAction Stop $parsed = @{} foreach ($p in $obj.PSObject.Properties) { $parsed[$p.Name] = $p.Value } } if ($parsed -is [hashtable]) { $existing = $parsed } elseif ($null -ne $parsed) { # 某些版本返回 PSCustomObject 即使没参数错误,兜底 $existing = @{} foreach ($p in $parsed.PSObject.Properties) { $existing[$p.Name] = $p.Value } } } } catch { Write-Host "错误: 现有 JSON 文件解析失败,未写入: $Path" -ForegroundColor Red Write-Host "原文件未动。手工写入以下字段:" -ForegroundColor Yellow $Updates.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key) = $($_.Value)" } exit 3 } } if (-not $existing) { $existing = @{} } foreach ($k in $Updates.Keys) { $existing[$k] = $Updates[$k] } $dir = Split-Path -Parent $Path if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } try { ($existing | ConvertTo-Json -Depth 20) | Set-Content -Path $Path -Encoding UTF8 -ErrorAction Stop } catch { Write-Host "错误: 写入 JSON 文件失败: $Path" -ForegroundColor Red Write-Host "原因: $($_.Exception.Message)" -ForegroundColor Red Write-Host "请检查文件权限/磁盘空间后重试。" -ForegroundColor Yellow exit 3 } } function Set-UserEnvFenced { # 把 3 个环境变量写到用户级 env(Windows 没有 "围栏"概念,直接 setx 即可) param([hashtable]$Vars) foreach ($k in $Vars.Keys) { [Environment]::SetEnvironmentVariable($k, $Vars[$k], 'User') Write-Host " 用户环境变量: $k = $($Vars[$k])" -ForegroundColor DarkGray } } function Test-Endpoint { param([string]$Token, [string]$Model) Write-Host '[3/4] 验证连通性 (POST /v1/chat/completions)...' -ForegroundColor Yellow $body = @{ model = $Model messages = @(@{ role = 'user'; content = 'hi' }) max_tokens = 16 } | ConvertTo-Json -Depth 5 try { $resp = Invoke-RestMethod ` -Uri "$AIPROXY_BASE/v1/chat/completions" ` -Method POST ` -Headers @{ 'Authorization' = "Bearer $Token"; 'Content-Type' = 'application/json' } ` -Body $body ` -ErrorAction Stop $content = $resp.choices[0].message.content if (-not $content) { Write-Host ' HTTP 200 ✓' -ForegroundColor Green Write-Host ' content 为空 ✗' -ForegroundColor Red return $false } Write-Host ' HTTP 200 ✓' -ForegroundColor Green $preview = if ($content.Length -gt 40) { $content.Substring(0,40) + '...' } else { $content } Write-Host " content 非空 ✓ (返回: `"$preview`")" -ForegroundColor Green return $true } catch { $statusCode = 0 try { $statusCode = $_.Exception.Response.StatusCode.value__ } catch {} if ($statusCode -gt 0) { Write-Host " HTTP $statusCode ✗" -ForegroundColor Red } else { Write-Host ' 连接失败 (无响应) ✗' -ForegroundColor Red } try { $stream = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($stream) $errBody = $reader.ReadToEnd() Write-Host " 响应: $errBody" -ForegroundColor Red } catch { Write-Host " 错误: $($_.Exception.Message)" -ForegroundColor Red } return $false } } function Print-FailureHints { Write-Host '' Write-Host '验证失败,可能的原因:' -ForegroundColor Red Write-Host ' □ Token 输入错误 → 重新运行脚本输入' -ForegroundColor Yellow Write-Host ' □ Token 已在 aiproxynode 后台删除 → 重新创建' -ForegroundColor Yellow Write-Host ' □ Token 绑的分组不对 → Claude 场景需要 kiro 分组,GPT 场景需要 gptpuls 分组' -ForegroundColor Yellow Write-Host '' Write-Host '配置已写入,但未生效。请处理后重新运行脚本。' -ForegroundColor Yellow } function Print-Success { param([string[]]$NextSteps) Write-Host '' Write-Host '[4/4] 完成 ✓' -ForegroundColor Green Write-Host '' Write-Host '下一步:' -ForegroundColor Cyan for ($i=0; $i -lt $NextSteps.Count; $i++) { Write-Host " $($i+1)) $($NextSteps[$i])" } Write-Host '' Write-Host '如果遇到问题:' -ForegroundColor Cyan Write-Host ' - "no available channels" → 检查 token 绑的分组(Claude 用 kiro,GPT 用 gptpuls)' Write-Host ' - "invalid api key" → token 可能填错或已删除' Write-Host ' - 详见 https://docs.aiproxynode.com/api.html' } Write-Header -Scenario 'Claude Desktop + Claude Code CLI' $token = Read-Token Write-Host '[2/4] 写入配置...' -ForegroundColor Yellow $model = 'claude-opus-4-7' Set-UserEnvFenced -Vars @{ 'ANTHROPIC_BASE_URL' = $AIPROXY_BASE 'ANTHROPIC_AUTH_TOKEN' = $token 'ANTHROPIC_MODEL' = $model } $claudeConfig = Join-Path $env:USERPROFILE '.claude\config.json' Backup-File -Path $claudeConfig Merge-Json -Path $claudeConfig -Updates @{ 'anthropic_base_url' = $AIPROXY_BASE 'anthropic_auth_token' = $token 'anthropic_model' = $model } Write-Host " CLI 配置: $claudeConfig" -ForegroundColor DarkGray $desktopConfig = Join-Path $env:APPDATA 'Claude\claude_desktop_config.json' Backup-File -Path $desktopConfig Merge-Json -Path $desktopConfig -Updates @{ 'anthropic_base_url' = $AIPROXY_BASE 'anthropic_auth_token' = $token 'anthropic_model' = $model } Write-Host " Desktop 配置: $desktopConfig" -ForegroundColor DarkGray Write-Host '[3/4] 跳过在线连通性验证(按用户配置)' -ForegroundColor DarkGray Print-Success -NextSteps @( '完全退出并重启 Claude Desktop app', '重启 VSCode 或新开终端,运行 claude 命令验证 CLI', "如需切换模型,环境变量 ANTHROPIC_MODEL(当前: $model)" )