src/Configuration.ps1
|
$script:QuietTasks = [Collections.Generic.Stack[object]]::new() $script:QuietCommands = @{} $script:QuietMaskers = [Collections.Generic.List[object]]::new() $script:QuietSources = [Runtime.CompilerServices.ConditionalWeakTable[object,object]]::new() $script:QuietReplay = $false $script:QuietBuildingContract = $false $script:QuietReplayContext = $null $script:LastGainError = $null $script:QuietDefaults = @{ MaxItems=20; MaxOutputBytes=4096; TimeoutSeconds=600; ExcerptLines=20; MaxCaptureBytes=10485760L Recall=$true; RecallMaxEntries=200; RecallRetentionDays=30; OutputFormat='json'; Envelope='compact' Tracking=$true; Tokenizer='o200k_base'; Modules=@(); HookAwareness='warn' } function Get-QuietConfiguration { [CmdletBinding()]param() $settings=$script:QuietDefaults.Clone() $path=if($env:QUIETCLI_CONFIG_PATH){$env:QUIETCLI_CONFIG_PATH}else{Join-Path $HOME '.quietcli/config.json'} if(Test-Path -LiteralPath $path){$document=Get-Content -LiteralPath $path -Raw|ConvertFrom-Json -AsHashtable;foreach($key in $document.Keys){if(!$settings.ContainsKey($key)){throw "Unknown QuietCli setting: $key"};$settings[$key]=$document[$key]}} if($env:QUIETCLI_TRACKING -in @('0','off','false')){$settings.Tracking=$false} if($env:QUIETCLI_RECALL -in @('0','off','false')){$settings.Recall=$false} if($env:QUIETCLI_TOKENIZER){$settings.Tokenizer=$env:QUIETCLI_TOKENIZER} if($settings.MaxItems -lt 0 -or $settings.MaxOutputBytes -lt 256 -or $settings.TimeoutSeconds -lt 1 -or $settings.MaxCaptureBytes -lt 0){throw 'Invalid QuietCli limits.'} return $settings } function Get-QuietDatabasePath { param([string]$Path) if(!$Path){$Path=$env:QUIETCLI_DB_PATH};if(!$Path){$Path=Join-Path $HOME '.quietcli/gain.sqlite3'} if($Path.StartsWith('~/')){$Path=Join-Path $HOME $Path.Substring(2)} $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) } function Invoke-QuietSql { param([Parameter(Mandatory)][string]$Sql,[object[]]$Values=@(),[switch]$Read,[string]$DatabasePath) $path=Get-QuietDatabasePath $DatabasePath if($Read -and !(Test-Path -LiteralPath $path)){return} $database=[QuietCliStack.GainV2.Database]::new($path,!$Read) try{if($Read){$database.Query($Sql,$Values)}else{$database.Execute($Sql,$Values)}}finally{$database.Dispose()} } function Register-QuietObfuscator { [CmdletBinding()]param([Parameter(Mandatory)][string]$Id,[Parameter(Mandatory)][scriptblock]$Transform) if(@($script:QuietMaskers|Where-Object Id -eq $Id).Count){throw "Duplicate obfuscator: $Id"} $script:QuietMaskers.Add(@{Id=$Id;Transform=$Transform}) } function Protect-QuietValue { param($Value,[hashtable]$State=@{Limited=$false;Redacted=$false},[int]$Depth=0) if($null -eq $Value){return $null} if($Depth -gt 48){$State.Limited=$true;return '[OMITTED:DEPTH]'} if($Value -is [string]){ $text=$Value -replace '\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)','' $before=$text if(!$State.ContainsKey('Secrets')){$State.Secrets=@(Get-ChildItem Env:|Where-Object {$_.Name -match '(?i)password|secret|token|api_?key|connectionstring' -and $_.Value.Length -ge 8}|ForEach-Object Value)} foreach($secret in $State.Secrets){$text=$text.Replace($secret,'[REDACTED]',[StringComparison]::Ordinal)} $text=$text -replace '(?is)-----BEGIN [^-]*PRIVATE KEY-----.*?(?:-----END [^-]*PRIVATE KEY-----|$)','[REDACTED]' $text=$text -replace '(?i)\b(?:Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+','[REDACTED]' $text=$text -replace '(?i)\b(?:github_pat_[A-Za-z0-9_]+|gh[pousr]_[A-Za-z0-9_]+|sk-[A-Za-z0-9_-]{12,}|AKIA[A-Z0-9]{16})\b','[REDACTED]' $text=$text -replace '(?i)"(password|passwd|secret|token|api[_-]?key|authorization)"\s*:\s*"(?:\\.|[^"\\])*"','"$1":"[REDACTED]"' $text=$text -replace '(?i)((?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*)(?:"[^"]*"|''[^'']*''|[^\s,;&"'']+)','$1[REDACTED]' $text=$text -replace '(?i)([a-z][a-z0-9+.-]*://)[^/\s@?#]+@','$1[REDACTED]@' foreach($masker in $script:QuietMaskers){ try{$items=@(& $masker.Transform $text);if($items.Count -ne 1 -or $items[0] -isnot [string]){throw 'Obfuscator must return one string.'};$text=$items[0]}catch{$State.Limited=$true;$text='[OBFUSCATION FAILED]'} } if($text -cne $before){$State.Redacted=$true} return $text } if($Value -is [datetime] -or $Value -is [datetimeoffset]){return $Value.ToString('o')} if($Value -is [ValueType]){return $Value} if($Value -is [Collections.IDictionary] -or ($Value -is [pscustomobject] -and $Value -isnot [Collections.IEnumerable])){ $map=[ordered]@{};$keys=if($Value -is [Collections.IDictionary]){@($Value.Keys)}else{@($Value.PSObject.Properties.Name)} foreach($key in $keys){ $safeKey=Protect-QuietValue ([string]$key) $State ($Depth+1) if([string]$key -match '(?i)^(password|passwd|secret|token|api[_-]?key|authorization|credential|connectionstring|environment)$'){$map[$safeKey]='[REDACTED]';$State.Redacted=$true} else{$map[$safeKey]=Protect-QuietValue $Value.$key $State ($Depth+1)} } return [pscustomobject]$map } if($Value -is [Collections.IEnumerable]){ $items=[Collections.Generic.List[object]]::new();foreach($item in $Value){$items.Add((Protect-QuietValue $item $State ($Depth+1)))} return ,$items.ToArray() } $State.Limited=$true;return '[OMITTED:TYPE]' } function Invoke-QuietCallback { param([scriptblock]$Callback,[object[]]$Arguments) & $Callback @Arguments *>&1|ForEach-Object{ if($_ -is [Management.Automation.ErrorRecord] -or $_ -is [Management.Automation.InformationalRecord] -or $_ -is [Management.Automation.InformationRecord]){throw 'Callbacks must return domain values, not diagnostics.'} ,$_ } } function Get-QuietExecutableName { param([string]$Path) ([IO.Path]::GetFileName($Path)) -replace '(?i)\.(exe|cmd|bat|com)$','' } function Invoke-QuietFactory { param([scriptblock]$Factory,[string]$Path,[hashtable]$Invocation) $previous=$script:QuietBuildingContract;$script:QuietBuildingContract=$true try{if($Path){& $Path $Invocation}else{Invoke-QuietCallback $Factory @($Invocation)}} finally{$script:QuietBuildingContract=$previous} } |