litellm / scripts /sync-local-secrets.ps1
alchoholpad's picture
full repo sync
5b54ed8 verified
Raw
History Blame Contribute Delete
8.43 kB
param(
[Parameter(Mandatory = $true)]
[string]$Email,
[Parameter(Mandatory = $true)]
[string]$SpreadsheetId,
[string]$SheetName = 'emails',
[string]$GwsAccountHelper = (Join-Path $env:USERPROFILE 'Downloads\mainframe\gws-account.ps1'),
[string]$OutputPath = (Join-Path (Split-Path -Parent $PSScriptRoot) 'config\local.secrets.json')
)
$ErrorActionPreference = 'Stop'
function ConvertTo-ColumnName {
param([int]$Index)
$name = ''
while ($Index -gt 0) {
$Index--
$name = [char](65 + ($Index % 26)) + $name
$Index = [math]::Floor($Index / 26)
}
return $name
}
function Get-JsonObjectFromOutput {
param([string[]]$Output)
$text = ($Output -join "`n")
$start = $text.IndexOf('{')
$end = $text.LastIndexOf('}')
if ($start -lt 0 -or $end -le $start) {
throw 'Could not find JSON in command output.'
}
return $text.Substring($start, $end - $start + 1) | ConvertFrom-Json
}
function Quote-ProcessArgument {
param([string]$Value)
if ($Value -match '^[A-Za-z0-9_./:+@=-]+$') {
return $Value
}
return '"' + ($Value -replace '"', '\"') + '"'
}
function Get-AiApiEntries {
param([string]$CellText)
$entries = New-Object System.Collections.Generic.List[string]
$inBlock = $false
$lines = $CellText -split "`r?`n"
foreach ($line in $lines) {
$trimmed = $line.Trim()
if ($trimmed -match '^\[(.+)\]$') {
$section = $Matches[1].Trim()
$inBlock = ($section -ieq 'ai apis')
continue
}
if ($inBlock -and $trimmed.Length -gt 0) {
$entries.Add($trimmed) | Out-Null
}
}
return $entries
}
function Set-PrivateFileAcl {
param([string]$Path)
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$acl = New-Object System.Security.AccessControl.FileSecurity
$acl.SetAccessRuleProtection($true, $false)
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($identity, 'FullControl', 'Allow')))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule('SYSTEM', 'FullControl', 'Allow')))
Set-Acl -LiteralPath $Path -AclObject $acl
attrib +h $Path | Out-Null
}
$providerSlots = @{
'admin.mistral.ai' = @('MISTRAL_API_KEY')
'aimlapi.com' = @('AIMLAPI_API_KEY')
'aistudio.google.com' = @('GEMINI_API_KEY')
'app.edenai.run' = @('EDENAI_API_KEY')
'app.genlabs.dev' = @('GENLABS_API_KEY')
'app.inference.sh' = @('INFERENCE_SH_API_KEY')
'app.tavily.com' = @('TAVILY_API_KEY')
'assemblyai.com' = @('ASSEMBLYAI_API_KEY')
'cloud.cerebras.ai' = @('CEREBRAS_API_KEY')
'cloud.sambanova.ai' = @('SAMBANOVA_API_KEY')
'cloudflare.com' = @('CLOUDFLARE_API_TOKEN')
'console.deepgram.com' = @('DEEPGRAM_API_KEY')
'console.groq.com' = @('GROQ_API_KEY')
'dashboard.cohere.com' = @('COHERE_API_KEY')
'dashboard.exa.ai' = @('EXA_API_KEY')
'discord.com' = @('DISCORD_TOKEN')
'elevenlabs.io' = @('ELEVENLABS_API_KEY')
'github.com' = @('GITHUB_API_KEY')
'huggingface.co' = @('HUGGINGFACE_API_KEY', 'HUGGINGFACE_API_KEY_1', 'HUGGINGFACE_API_KEY_2')
'jina.ai' = @('JINA_AI_API_KEY')
'mapleflow.io' = @('MAPLEFLOW_API_KEY')
'modal.com' = @('MODAL_API_KEY')
'openrouter.ai' = @(
'OPENROUTER_API_KEY',
'OPENROUTER_API_KEY_1',
'OPENROUTER_API_KEY_2',
'OPENROUTER_API_KEY_3',
'OPENROUTER_API_KEY_4'
)
'platform.openai.com' = @('OPENAI_API_KEY')
'platform.worldlabs.ai' = @('WORLDLABS_API_KEY')
'playground.electronhub.ai' = @('ELECTRONHUB_API_KEY')
'playground.twelvelabs.io' = @('TWELVELABS_API_KEY')
'pollinations.ai' = @('POLLINATIONS_API_KEY', 'POLLINATIONS_API_KEY_1')
'stablehorde.net' = @('STABLEHORDE_API_KEY')
'vercel.com' = @('VERCEL_AI_GATEWAY_API_KEY')
'voidai.app' = @('VOIDAI_API_KEY')
'xiaomimimo.com' = @('XIAOMI_MIMO_API_KEY', 'XIAOMI_MIMO_PLAN_TOKEN')
'you.com' = @('YOU_API_KEY')
}
if (-not (Test-Path -LiteralPath $GwsAccountHelper)) {
throw "GWS account helper not found: $GwsAccountHelper"
}
$arguments = @(
'-NoProfile',
'-ExecutionPolicy',
'Bypass',
'-File',
$GwsAccountHelper,
'run',
$Email,
'sheets',
'+read',
'--spreadsheet',
$SpreadsheetId,
'--range',
$SheetName,
'--format',
'json'
)
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = 'powershell.exe'
$processInfo.Arguments = (($arguments | ForEach-Object { Quote-ProcessArgument $_ }) -join ' ')
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
[void]$process.Start()
$stdout = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
if ($process.ExitCode -ne 0) {
$safeError = (($stderr -split "`r?`n") | Where-Object { $_ -and $_ -notmatch '\S{20,}' }) -join "`n"
throw "gws read failed with exit code $($process.ExitCode). Sanitized stderr:`n$safeError"
}
$valueRange = Get-JsonObjectFromOutput -Output @($stdout)
$headerRow = if ($valueRange.values.Count -ge 1) { @($valueRange.values[0]) } else { @() }
$inheritedHeaders = @{}
$lastHeader = ''
for ($colIndex = 0; $colIndex -lt $headerRow.Count; $colIndex++) {
$candidate = [string]$headerRow[$colIndex]
if ($candidate.Trim().Length -gt 0) {
$lastHeader = $candidate.Trim()
}
$inheritedHeaders[$colIndex] = $lastHeader
}
$providerEntries = @{}
$cellSummary = New-Object System.Collections.Generic.List[object]
for ($rowIndex = 0; $rowIndex -lt $valueRange.values.Count; $rowIndex++) {
$row = @($valueRange.values[$rowIndex])
for ($colIndex = 0; $colIndex -lt $row.Count; $colIndex++) {
$cell = [string]$row[$colIndex]
if ($cell -notmatch '(?im)^\s*\[ai apis\]\s*$') {
continue
}
$provider = if ($inheritedHeaders.ContainsKey($colIndex)) { [string]$inheritedHeaders[$colIndex] } else { '' }
if (-not $providerSlots.ContainsKey($provider)) {
throw "No wrapper env mapping for provider: $provider"
}
if (-not $providerEntries.ContainsKey($provider)) {
$providerEntries[$provider] = New-Object System.Collections.Generic.List[string]
}
$entries = @(Get-AiApiEntries -CellText $cell)
foreach ($entry in $entries) {
$providerEntries[$provider].Add([string]$entry) | Out-Null
}
$coordinate = '{0}!{1}{2}' -f $SheetName, (ConvertTo-ColumnName ($colIndex + 1)), ($rowIndex + 1)
$cellSummary.Add([pscustomobject]@{
cell = $coordinate
provider = $provider
count = $entries.Count
}) | Out-Null
}
}
$secrets = [ordered]@{}
$assignments = New-Object System.Collections.Generic.List[object]
foreach ($provider in ($providerEntries.Keys | Sort-Object)) {
$values = @($providerEntries[$provider])
$slots = @($providerSlots[$provider])
if ($values.Count -gt $slots.Count) {
throw "Provider $provider has $($values.Count) entries but only $($slots.Count) env slots."
}
for ($i = 0; $i -lt $values.Count; $i++) {
$envName = $slots[$i]
$secrets[$envName] = $values[$i]
$assignments.Add([pscustomobject]@{
provider = $provider
env = $envName
status = 'synced'
}) | Out-Null
}
}
$outputDir = Split-Path -Parent $OutputPath
if ($outputDir) {
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
}
($secrets | ConvertTo-Json -Depth 4) | Set-Content -LiteralPath $OutputPath -Encoding UTF8
Set-PrivateFileAcl -Path $OutputPath
[pscustomobject]@{
outputPath = $OutputPath
sheetBlocks = $cellSummary.Count
sheetEntries = ($cellSummary | Measure-Object -Property count -Sum).Sum
secretEnvCount = $secrets.Count
assignments = $assignments
} | ConvertTo-Json -Depth 6