Private/SettingsIo.ps1
|
# JSON read/write helpers. Everything returns new objects; nothing mutates its input. # Env vars that make Claude Code bypass account OAuth entirely. A profile launch # is an explicit request for that profile's identity, so these are stripped from # the child environment with a warning rather than silently honoured. $script:AuthOverrideEnvVars = @( 'ANTHROPIC_API_KEY' 'ANTHROPIC_AUTH_TOKEN' 'CLAUDE_CODE_OAUTH_TOKEN' 'CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR' 'CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR' ) # OTEL keys the company package requires. CCWB's newer credential process runs # telemetry_enforcement_mode=block, so a profile missing any of these can fail # to receive AWS credentials at all. $script:RequiredTelemetryKeys = @( 'CLAUDE_CODE_ENABLE_TELEMETRY' 'OTEL_METRICS_EXPORTER' 'OTEL_LOGS_EXPORTER' 'OTEL_EXPORTER_OTLP_PROTOCOL' 'OTEL_EXPORTER_OTLP_ENDPOINT' 'OTEL_RESOURCE_ATTRIBUTES' ) function Read-CcsJson { <# .SYNOPSIS Read a JSON file, returning $null when absent and throwing on malformed content. #> param([Parameter(Mandatory)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { return $null } $raw = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop if ([string]::IsNullOrWhiteSpace($raw)) { return $null } try { return $raw | ConvertFrom-Json -ErrorAction Stop } catch { throw "Malformed JSON in $Path : $($_.Exception.Message)" } } function Write-CcsJson { <# .SYNOPSIS Write an object as UTF-8 JSON, creating parent directories as needed. #> param( [Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)]$Value ) $dir = Split-Path -Parent $Path if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $json = $Value | ConvertTo-Json -Depth 20 Set-Content -LiteralPath $Path -Value $json -Encoding utf8NoBOM } function Backup-CcsFile { <# .SYNOPSIS Copy a file next to itself with a timestamp suffix. Returns the backup path, or $null when the source does not exist. #> param([Parameter(Mandatory)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { return $null } $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $backup = "$Path.bak.$stamp" Copy-Item -LiteralPath $Path -Destination $backup -Force $backup } function Get-CcsSettingsEnv { <# .SYNOPSIS The 'env' block of a settings object as a hashtable (empty when absent). #> param($Settings) $result = @{} if (-not $Settings) { return $result } $envProp = $Settings.PSObject.Properties['env'] if (-not $envProp -or -not $envProp.Value) { return $result } foreach ($p in $envProp.Value.PSObject.Properties) { $result[$p.Name] = $p.Value } $result } function Test-CcsBedrockSettings { <# .SYNOPSIS Whether a settings object routes Claude Code to Amazon Bedrock. #> param($Settings) $envMap = Get-CcsSettingsEnv -Settings $Settings $flag = $envMap['CLAUDE_CODE_USE_BEDROCK'] if ($null -eq $flag) { return $false } "$flag" -in @('1', 'true', 'True') } function Remove-CcsProviderSettings { <# .SYNOPSIS Return a NEW settings object with every provider-specific key removed. .DESCRIPTION Used when deriving a subscription profile from a Bedrock one: the AWS, telemetry and model-pin keys would otherwise keep routing the new profile at Bedrock, and Bedrock model IDs are not valid against the Claude API. Personal preferences (permissions, plugins, statusline, ...) are kept. #> param($Settings) if (-not $Settings) { return [pscustomobject]@{} } $dropTopLevel = @('otelHeadersHelper', 'awsAuthRefresh', 'awsCredentialExport', 'modelOverrides', 'availableModels') $dropEnvPrefixes = @('AWS_', 'OTEL_', 'ANTHROPIC_DEFAULT_', 'ANTHROPIC_BEDROCK_') $dropEnvExact = @( 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_MANTLE', 'CLAUDE_CODE_SKIP_BEDROCK_AUTH', 'CLAUDE_CODE_SKIP_MANTLE_AUTH', 'CLAUDE_CODE_ENABLE_TELEMETRY', 'ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL' ) $result = [pscustomobject]@{} foreach ($prop in $Settings.PSObject.Properties) { if ($prop.Name -in $dropTopLevel) { continue } if ($prop.Name -eq 'env') { $keptEnv = [pscustomobject]@{} foreach ($e in $prop.Value.PSObject.Properties) { if ($e.Name -in $dropEnvExact) { continue } if ($dropEnvPrefixes | Where-Object { $e.Name.StartsWith($_) }) { continue } Add-Member -InputObject $keptEnv -NotePropertyName $e.Name -NotePropertyValue $e.Value } if (@($keptEnv.PSObject.Properties).Count -gt 0) { Add-Member -InputObject $result -NotePropertyName 'env' -NotePropertyValue $keptEnv } continue } Add-Member -InputObject $result -NotePropertyName $prop.Name -NotePropertyValue $prop.Value } $result } function Get-CcsAwsProfileNames { <# .SYNOPSIS Profile names declared in ~/.aws/config ("default" plus "profile <name>" sections). #> param([string]$Path = (Get-CcsAwsConfigPath)) if (-not (Test-Path -LiteralPath $Path)) { return @() } $names = foreach ($line in Get-Content -LiteralPath $Path) { if ($line -match '^\s*\[\s*profile\s+(?<n>[^\]]+?)\s*\]\s*$') { $Matches['n'] } elseif ($line -match '^\s*\[\s*default\s*\]\s*$') { 'default' } } @($names) } |