# cheapllm.shop setup for Windows: installs and configures the tools from https://cheapllm.shop/integrations # that you pick in a list. macOS / Linux: curl -fsSL https://cheapllm.shop/setup.sh | sh # # irm https://cheapllm.shop/setup.ps1 | iex # # Options, as environment variables set before the command: # $env:CHEAPLLM_API_KEY = "sk-cheap-..." skip the key prompt # $env:CHEAPLLM_DRY_RUN = "1" show what would change, write and install nothing # $env:CHEAPLLM_ONLY = "claude-code,codex" no list: only these # $env:CHEAPLLM_ALL = "1" no list: everything # $env:CHEAPLLM_YES = "1" no list: what is found on this PC # $env:CHEAPLLM_INSTALL = "yes" | "no" install missing tools without asking / never # Every file it changes is copied to .cheapllm-bak first. Windows PowerShell 5.1 or PowerShell 7. & { $ErrorActionPreference = 'Stop' # Windows PowerShell 5.1 serializes nested arrays as {value, Count} unless this type data is dropped if ($PSVersionTable.PSVersion.Major -lt 6) { Remove-TypeData System.Array -ErrorAction SilentlyContinue } try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch {} $Origin = 'https://cheapllm.shop' $Dry = $env:CHEAPLLM_DRY_RUN -eq '1' $HomeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME } $Utf8 = New-Object System.Text.UTF8Encoding $false # no BOM: Node and Rust JSON parsers reject it Write-Host '' Write-Host ' cheapllm.shop setup' Write-Host ' ------------------' $Key = $env:CHEAPLLM_API_KEY if (-not $Key) { $secure = Read-Host " Your API key (sk-cheap-..., get one free at $Origin/signup)" -AsSecureString $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) try { $Key = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } } $Key = ("$Key").Trim() if ($Key -notmatch '^sk-cheap-[A-Za-z0-9_-]+$') { Write-Host " No valid key (it starts with sk-cheap-). Get one free at $Origin/signup"; return } try { $body = '{"model":"x","messages":[{"role":"user","content":"hi"}]}' $null = Invoke-RestMethod -Method Post -Uri "$Origin/v1/messages/count_tokens" -Body $body -ContentType 'application/json' ` -Headers @{ 'x-api-key' = $Key; 'anthropic-version' = '2023-06-01' } -TimeoutSec 20 -UseBasicParsing Write-Host ' Key accepted.' } catch { $code = 0 try { $code = [int]$_.Exception.Response.StatusCode } catch {} if ($code -eq 401 -or $code -eq 403) { Write-Host " That key was refused. Check it on $Origin/dashboard"; return } Write-Host " Could not check the key ($code), continuing." } # The catalog is the same for every OS; the key is filled in here, never sent anywhere. $catalogJson = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('W3siaWQiOiJjbGF1ZGUtY29kZSIsIm5hbWUiOiJDbGF1ZGUgQ29kZSIsImdyb3VwIjoiVGVybWluYWwgYWdlbnRzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiY2xhdWRlIl0sInBhdGhzIjp7InVuaXgiOlsifi8uY2xhdWRlIl0sIndpbmRvd3MiOlsiJVVTRVJQUk9GSUxFJS8uY2xhdWRlIl19fSwiaW5zdGFsbCI6eyJ1bml4IjoiY3VybCAtZnNTTCBodHRwczovL2NsYXVkZS5haS9pbnN0YWxsLnNoIHwgYmFzaCIsIndpbmRvd3MiOiJpcm0gaHR0cHM6Ly9jbGF1ZGUuYWkvaW5zdGFsbC5wczEgfCBpZXgifSwiYWN0aW9ucyI6W3sidHlwZSI6Impzb24iLCJwYXRoIjp7InVuaXgiOiJ+Ly5jbGF1ZGUvc2V0dGluZ3MuanNvbiIsIndpbmRvd3MiOiIlVVNFUlBST0ZJTEUlLy5jbGF1ZGUvc2V0dGluZ3MuanNvbiJ9LCJwcml2YXRlIjp0cnVlLCJtZXJnZSI6eyJlbnYiOnsiQU5USFJPUElDX0JBU0VfVVJMIjoiaHR0cHM6Ly9jaGVhcGxsbS5zaG9wIiwiQU5USFJPUElDX0FVVEhfVE9LRU4iOiJ7S0VZfSIsIkFOVEhST1BJQ19NT0RFTCI6ImRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyIsIkFOVEhST1BJQ19ERUZBVUxUX09QVVNfTU9ERUwiOiJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciLCJBTlRIUk9QSUNfREVGQVVMVF9TT05ORVRfTU9ERUwiOiJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciLCJBTlRIUk9QSUNfREVGQVVMVF9IQUlLVV9NT0RFTCI6ImRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyIsIkNMQVVERV9DT0RFX0RJU0FCTEVfRVhQRVJJTUVOVEFMX0JFVEFTIjoiMSJ9fSwicmVtb3ZlIjpbImVudi5BTlRIUk9QSUNfQVBJX0tFWSJdfV0sImFmdGVyIjoiUnVuIGNsYXVkZSwgdGhlbiAvc3RhdHVzIHRvIGNoZWNrIHRoZSBiYXNlIFVSTC4ifSx7ImlkIjoiY29kZXgiLCJuYW1lIjoiQ29kZXggQ0xJIiwiZ3JvdXAiOiJUZXJtaW5hbCBhZ2VudHMiLCJkZXRlY3QiOnsiYmlucyI6WyJjb2RleCJdLCJwYXRocyI6eyJ1bml4IjpbIn4vLmNvZGV4Il0sIndpbmRvd3MiOlsiJVVTRVJQUk9GSUxFJS8uY29kZXgiXX19LCJpbnN0YWxsIjp7InVuaXgiOiJucG0gaW5zdGFsbCAtZyBAb3BlbmFpL2NvZGV4Iiwid2luZG93cyI6Im5wbSBpbnN0YWxsIC1nIEBvcGVuYWkvY29kZXgiLCJuZWVkcyI6WyJucG0iXX0sImFjdGlvbnMiOlt7InR5cGUiOiJlbnYiLCJ2YXJzIjp7IkNIRUFQTExNX0FQSV9LRVkiOiJ7S0VZfSJ9fSx7InR5cGUiOiJ0b21sIiwicGF0aCI6eyJ1bml4Ijoifi8uY29kZXgvY29uZmlnLnRvbWwiLCJ3aW5kb3dzIjoiJVVTRVJQUk9GSUxFJS8uY29kZXgvY29uZmlnLnRvbWwifSwidG9wIjp7Im1vZGVsIjoiZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3IiwibW9kZWxfcHJvdmlkZXIiOiJjaGVhcGxsbSJ9LCJ0YWJsZXMiOnsibW9kZWxfcHJvdmlkZXJzLmNoZWFwbGxtIjp7Im5hbWUiOiJDaGVhcExMTSIsImJhc2VfdXJsIjoiaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxIiwiZW52X2tleSI6IkNIRUFQTExNX0FQSV9LRVkiLCJ3aXJlX2FwaSI6InJlc3BvbnNlcyJ9fX1dLCJhZnRlciI6Ik9wZW4gYSBuZXcgdGVybWluYWwgYW5kIHJ1biBjb2RleC4ifSx7ImlkIjoib3BlbmNvZGUiLCJuYW1lIjoiT3BlbkNvZGUiLCJncm91cCI6IlRlcm1pbmFsIGFnZW50cyIsImRldGVjdCI6eyJiaW5zIjpbIm9wZW5jb2RlIl0sInBhdGhzIjp7InVuaXgiOlsifi8uY29uZmlnL29wZW5jb2RlIl0sIndpbmRvd3MiOlsiJVVTRVJQUk9GSUxFJS8uY29uZmlnL29wZW5jb2RlIl19fSwiaW5zdGFsbCI6eyJ1bml4IjoiY3VybCAtZnNTTCBodHRwczovL29wZW5jb2RlLmFpL2luc3RhbGwgfCBiYXNoIiwid2luZG93cyI6Im5wbSBpbnN0YWxsIC1nIG9wZW5jb2RlLWFpIiwibmVlZHMiOnsid2luZG93cyI6WyJucG0iXX19LCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJDSEVBUExMTV9BUElfS0VZIjoie0tFWX0ifX0seyJ0eXBlIjoianNvbiIsInBhdGgiOnsidW5peCI6In4vLmNvbmZpZy9vcGVuY29kZS9vcGVuY29kZS5qc29uIiwid2luZG93cyI6IiVVU0VSUFJPRklMRSUvLmNvbmZpZy9vcGVuY29kZS9vcGVuY29kZS5qc29uIn0sIm1lcmdlIjp7IiRzY2hlbWEiOiJodHRwczovL29wZW5jb2RlLmFpL2NvbmZpZy5qc29uIiwicHJvdmlkZXIiOnsiY2hlYXBsbG0iOnsibnBtIjoiQGFpLXNkay9vcGVuYWktY29tcGF0aWJsZSIsIm5hbWUiOiJDaGVhcExMTSIsIm9wdGlvbnMiOnsiYmFzZVVSTCI6Imh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MSIsImFwaUtleSI6IntlbnY6Q0hFQVBMTE1fQVBJX0tFWX0ifSwibW9kZWxzIjp7ImRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyI6eyJuYW1lIjoiRGVlcFNlZWsgVjQuMSBGbGFzaCIsImxpbWl0Ijp7ImNvbnRleHQiOjEyODAwMCwib3V0cHV0IjoxNjAwMH19fX19LCJtb2RlbCI6ImNoZWFwbGxtL2RlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyJ9fV0sImFmdGVyIjoiT3BlbiBhIG5ldyB0ZXJtaW5hbCBhbmQgcnVuIG9wZW5jb2RlLiJ9LHsiaWQiOiJkc2giLCJuYW1lIjoiRGVlcFNlZWsgSGFybmVzcyAoZHNoKSIsImdyb3VwIjoiVGVybWluYWwgYWdlbnRzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiZHNoIl0sInBhdGhzIjp7InVuaXgiOlsifi8uZHNoIl0sIndpbmRvd3MiOlsiJVVTRVJQUk9GSUxFJS8uZHNoIl19fSwiaW5zdGFsbCI6eyJ1bml4IjoibnBtIGluc3RhbGwgLWcgQGRlZXBzZWVrLWFpL2RzaCIsIndpbmRvd3MiOiJucG0gaW5zdGFsbCAtZyBAZGVlcHNlZWstYWkvZHNoIiwibmVlZHMiOlsibnBtIl19LCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJDSEVBUExMTV9BUElfS0VZIjoie0tFWX0ifX0seyJ0eXBlIjoiZmlsZSIsInBhdGgiOnsidW5peCI6In4vLmRzaC9wcm9maWxlcy9jaGVhcGxsbS9jb3JkaXMucGF0Y2gueW1sIiwid2luZG93cyI6IiVVU0VSUFJPRklMRSUvLmRzaC9wcm9maWxlcy9jaGVhcGxsbS9jb3JkaXMucGF0Y2gueW1sIn0sImNvbnRlbnQiOiItIGlkOiBhZ2VudC1kZWZhdWx0LW1vZGVsXG4gIG5hbWU6IFwiQGRlZXBzZWVrLWFpL2RzaC1hZ2VudC1kZWZhdWx0LW1vZGVsXCJcbiAgY29uZmlnOiB7IHByb3ZpZGVyOiBjaGVhcGxsbSwgbW9kZWw6IGRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyB9XG4tIGlkOiBsbG0tcGktYWlcbiAgbmFtZTogXCJAZGVlcHNlZWstYWkvZHNoLWxsbS1waS1haVwiXG4gIGNvbmZpZzpcbiAgICBwcm92aWRlcnM6XG4gICAgICBjaGVhcGxsbTpcbiAgICAgICAgZGlzcGxheU5hbWU6IENoZWFwTExNXG4gICAgICAgIGFwaUtleUVudjogQ0hFQVBMTE1fQVBJX0tFWVxuICAgICAgICBhcGk6IG9wZW5haS1jb21wbGV0aW9uc1xuICAgICAgICBiYXNlVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjFcbiAgICAgICAgbW9kZWxzOlxuICAgICAgICAgIC0gaWQ6IGRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxN1xuICAgICAgICAgICAgbmFtZTogRGVlcFNlZWsgVjQuMSBGbGFzaFxuIn1dLCJhZnRlciI6Ik9wZW4gYSBuZXcgdGVybWluYWwgYW5kIHJ1biBkc2ggLS1wcm9maWxlIGNoZWFwbGxtLiJ9LHsiaWQiOiJjcnVzaCIsIm5hbWUiOiJDcnVzaCIsImdyb3VwIjoiVGVybWluYWwgYWdlbnRzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiY3J1c2giXSwicGF0aHMiOnsidW5peCI6WyJ+Ly5jb25maWcvY3J1c2giXSwid2luZG93cyI6WyIlTE9DQUxBUFBEQVRBJS9jcnVzaCJdfX0sImluc3RhbGwiOnsidW5peCI6Im5wbSBpbnN0YWxsIC1nIEBjaGFybWxhbmQvY3J1c2giLCJ3aW5kb3dzIjoibnBtIGluc3RhbGwgLWcgQGNoYXJtbGFuZC9jcnVzaCIsIm5lZWRzIjpbIm5wbSJdfSwiYWN0aW9ucyI6W3sidHlwZSI6ImVudiIsInZhcnMiOnsiQ0hFQVBMTE1fQVBJX0tFWSI6IntLRVl9In19LHsidHlwZSI6Impzb24iLCJwYXRoIjp7InVuaXgiOiJ+Ly5jb25maWcvY3J1c2gvY3J1c2guanNvbiIsIndpbmRvd3MiOiIlTE9DQUxBUFBEQVRBJS9jcnVzaC9jcnVzaC5qc29uIn0sIm1lcmdlIjp7InByb3ZpZGVycyI6eyJjaGVhcGxsbSI6eyJuYW1lIjoiQ2hlYXBMTE0iLCJ0eXBlIjoib3BlbmFpLWNvbXBhdCIsImJhc2VfdXJsIjoiaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxIiwiYXBpX2tleSI6IiRDSEVBUExMTV9BUElfS0VZIiwibW9kZWxzIjpbeyJpZCI6ImRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyIsIm5hbWUiOiJEZWVwU2VlayBWNC4xIEZsYXNoIiwiY29udGV4dF93aW5kb3ciOjEyODAwMCwiZGVmYXVsdF9tYXhfdG9rZW5zIjo4MTkyfV19fX19XSwiYWZ0ZXIiOiJPcGVuIGEgbmV3IHRlcm1pbmFsLCBydW4gY3J1c2ggYW5kIHBpY2sgdGhlIG1vZGVsIHdpdGggQ3RybCtMLiJ9LHsiaWQiOiJnb29zZSIsIm5hbWUiOiJHb29zZSIsImdyb3VwIjoiVGVybWluYWwgYWdlbnRzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiZ29vc2UiXSwicGF0aHMiOnsidW5peCI6WyJ+Ly5jb25maWcvZ29vc2UiXSwid2luZG93cyI6WyIlQVBQREFUQSUvQmxvY2svZ29vc2UiXX19LCJpbnN0YWxsIjp7InVuaXgiOiJjdXJsIC1mc1NMIGh0dHBzOi8vZ2l0aHViLmNvbS9ibG9jay9nb29zZS9yZWxlYXNlcy9kb3dubG9hZC9zdGFibGUvZG93bmxvYWRfY2xpLnNoIHwgQ09ORklHVVJFPWZhbHNlIGJhc2gifSwiYWN0aW9ucyI6W3sidHlwZSI6ImVudiIsInZhcnMiOnsiQ0hFQVBMTE1fQVBJX0tFWSI6IntLRVl9In19LHsidHlwZSI6ImZpbGUiLCJwYXRoIjp7InVuaXgiOiJ+Ly5jb25maWcvZ29vc2UvY3VzdG9tX3Byb3ZpZGVycy9jaGVhcGxsbS5qc29uIiwid2luZG93cyI6IiVBUFBEQVRBJS9CbG9jay9nb29zZS9jb25maWcvY3VzdG9tX3Byb3ZpZGVycy9jaGVhcGxsbS5qc29uIn0sImpzb24iOnsibmFtZSI6ImNoZWFwbGxtIiwiZW5naW5lIjoib3BlbmFpIiwiZGlzcGxheV9uYW1lIjoiQ2hlYXBMTE0iLCJhcGlfa2V5X2VudiI6IkNIRUFQTExNX0FQSV9LRVkiLCJiYXNlX3VybCI6Imh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MS9jaGF0L2NvbXBsZXRpb25zIiwic3VwcG9ydHNfc3RyZWFtaW5nIjp0cnVlLCJtb2RlbHMiOlt7Im5hbWUiOiJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciLCJjb250ZXh0X2xpbWl0IjoxMjgwMDB9XX19XSwiYWZ0ZXIiOiJSdW4gZ29vc2UgY29uZmlndXJlIGFuZCBwaWNrIHRoZSBDaGVhcExMTSBwcm92aWRlci4ifSx7ImlkIjoicXdlbi1jb2RlIiwibmFtZSI6IlF3ZW4gQ29kZSIsImdyb3VwIjoiVGVybWluYWwgYWdlbnRzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsicXdlbiJdLCJwYXRocyI6eyJ1bml4IjpbIn4vLnF3ZW4iXSwid2luZG93cyI6WyIlVVNFUlBST0ZJTEUlLy5xd2VuIl19fSwiaW5zdGFsbCI6eyJ1bml4IjoibnBtIGluc3RhbGwgLWcgQHF3ZW4tY29kZS9xd2VuLWNvZGUiLCJ3aW5kb3dzIjoibnBtIGluc3RhbGwgLWcgQHF3ZW4tY29kZS9xd2VuLWNvZGUiLCJuZWVkcyI6WyJucG0iXX0sImFjdGlvbnMiOlt7InR5cGUiOiJibG9jayIsImNvbW1lbnQiOiIjIiwicGF0aCI6eyJ1bml4Ijoifi8ucXdlbi8uZW52Iiwid2luZG93cyI6IiVVU0VSUFJPRklMRSUvLnF3ZW4vLmVudiJ9LCJwcml2YXRlIjp0cnVlLCJkcm9wIjpbIl5cXHMqKGV4cG9ydFxccyspP09QRU5BSV8oQkFTRV9VUkx8QVBJX0tFWXxNT0RFTClcXHMqPSJdLCJ0ZXh0IjoiT1BFTkFJX0JBU0VfVVJMPWh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MVxuT1BFTkFJX0FQSV9LRVk9e0tFWX1cbk9QRU5BSV9NT0RFTD1kZWVwc2Vlay12NC4xLWZsYXNoLTEwMTcifV0sImFmdGVyIjoiUnVuIHF3ZW4uIElmIGl0IHN0aWxsIGFza3MgaG93IHRvIHNpZ24gaW4sIHBpY2sgXCJPcGVuQUktY29tcGF0aWJsZVwiLiJ9LHsiaWQiOiJhaWRlciIsIm5hbWUiOiJBaWRlciIsImdyb3VwIjoiVGVybWluYWwgYWdlbnRzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiYWlkZXIiXX0sImluc3RhbGwiOnsidW5peCI6ImN1cmwgLUxzU2YgaHR0cHM6Ly9haWRlci5jaGF0L2luc3RhbGwuc2ggfCBzaCIsIndpbmRvd3MiOiJwb3dlcnNoZWxsIC1FeGVjdXRpb25Qb2xpY3kgQnlQYXNzIC1jIFwiaXJtIGh0dHBzOi8vYWlkZXIuY2hhdC9pbnN0YWxsLnBzMSB8IGlleFwiIn0sImFjdGlvbnMiOlt7InR5cGUiOiJibG9jayIsImNvbW1lbnQiOiIjIiwicGF0aCI6eyJ1bml4Ijoifi8uYWlkZXIuY29uZi55bWwiLCJ3aW5kb3dzIjoiJVVTRVJQUk9GSUxFJS8uYWlkZXIuY29uZi55bWwifSwicHJpdmF0ZSI6dHJ1ZSwiZHJvcCI6WyJeKG1vZGVsfG9wZW5haS1hcGktYmFzZXxvcGVuYWktYXBpLWtleSlcXHMqOiJdLCJ0ZXh0IjoibW9kZWw6IG9wZW5haS9kZWVwc2Vlay12NC4xLWZsYXNoLTEwMTdcbm9wZW5haS1hcGktYmFzZTogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxXG5vcGVuYWktYXBpLWtleToge0tFWX0ifV0sImFmdGVyIjoiUnVuIGFpZGVyIGluIGEgcHJvamVjdCBmb2xkZXIuIn0seyJpZCI6Im9wZW5oYW5kcyIsIm5hbWUiOiJPcGVuSGFuZHMiLCJncm91cCI6IlRlcm1pbmFsIGFnZW50cyIsImRldGVjdCI6eyJiaW5zIjpbIm9wZW5oYW5kcyJdLCJwYXRocyI6eyJ1bml4IjpbIn4vLm9wZW5oYW5kcyJdLCJ3aW5kb3dzIjpbIiVVU0VSUFJPRklMRSUvLm9wZW5oYW5kcyJdfX0sImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJTZXR0aW5ncyA+IExMTSA+IEFkdmFuY2VkIChpbiB0aGUgQ0xJOiAvc2V0dGluZ3MpLiIsIkN1c3RvbSBtb2RlbDogb3BlbmFpL2RlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyIsIkJhc2UgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSJdfV19LHsiaWQiOiJ3YXJwIiwibmFtZSI6IldhcnAiLCJncm91cCI6IlRlcm1pbmFsIGFnZW50cyIsImRldGVjdCI6eyJiaW5zIjpbIndhcnAtdGVybWluYWwiXSwicGF0aHMiOnsibWFjIjpbIi9BcHBsaWNhdGlvbnMvV2FycC5hcHAiXSwid2luZG93cyI6WyIlTE9DQUxBUFBEQVRBJS9Qcm9ncmFtcy9XYXJwIl19fSwiYWN0aW9ucyI6W3sidHlwZSI6Im1hbnVhbCIsInN0ZXBzIjpbIlNldHRpbmdzID4gc2VhcmNoIFwiaW5mZXJlbmNlIGVuZHBvaW50XCIgPiBhZGQgYW4gZW5kcG9pbnQuIiwiVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSIsIk1vZGVsIElEOiBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTcsIHRoZW4gcGljayBpdCBpbiB0aGUgYWdlbnQgbW9kZWwgbWVudSAobm90IFwiQXV0b1wiKS4iXX1dfSx7ImlkIjoiZ2VtaW5pLWNsaSIsIm5hbWUiOiJHZW1pbmkgQ0xJIiwiZ3JvdXAiOiJUZXJtaW5hbCBhZ2VudHMiLCJ1bnN1cHBvcnRlZCI6dHJ1ZSwiZGV0ZWN0Ijp7ImJpbnMiOlsiZ2VtaW5pIl19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiR2VtaW5pIENMSSBvbmx5IHNwZWFrcyBHb29nbGUncyBmb3JtYXQgYW5kIGNhbid0IGJlIHBvaW50ZWQgYXQgdXMuIFVzZSBDbGF1ZGUgQ29kZSwgQ29kZXgsIE9wZW5Db2RlIG9yIFF3ZW4gQ29kZSBpbnN0ZWFkIChRd2VuIENvZGUgaXMgYSBHZW1pbmkgQ0xJIGZvcmspLiJdfV19LHsiaWQiOiJjb3BpbG90IiwibmFtZSI6IlZTIENvZGU6IEdpdEh1YiBDb3BpbG90IiwiZ3JvdXAiOiJFZGl0b3JzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiY29kZSJdLCJwYXRocyI6eyJtYWMiOlsifi9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvQ29kZSJdLCJsaW51eCI6WyJ+Ly5jb25maWcvQ29kZSJdLCJ3aW5kb3dzIjpbIiVBUFBEQVRBJS9Db2RlIl19fSwiYWN0aW9ucyI6W3sidHlwZSI6Impzb24tYXJyYXkiLCJwYXRoIjp7Im1hYyI6In4vTGlicmFyeS9BcHBsaWNhdGlvbiBTdXBwb3J0L0NvZGUvVXNlci9jaGF0TGFuZ3VhZ2VNb2RlbHMuanNvbiIsImxpbnV4Ijoifi8uY29uZmlnL0NvZGUvVXNlci9jaGF0TGFuZ3VhZ2VNb2RlbHMuanNvbiIsIndpbmRvd3MiOiIlQVBQREFUQSUvQ29kZS9Vc2VyL2NoYXRMYW5ndWFnZU1vZGVscy5qc29uIn0sIm1hdGNoIjp7Im5hbWUiOiJDaGVhcExMTSJ9LCJwcml2YXRlIjp0cnVlLCJpdGVtIjp7Im5hbWUiOiJDaGVhcExMTSIsInZlbmRvciI6ImN1c3RvbWVuZHBvaW50IiwiYXBpS2V5Ijoie0tFWX0iLCJhcGlUeXBlIjoiY2hhdC1jb21wbGV0aW9ucyIsIm1vZGVscyI6W3siaWQiOiJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciLCJuYW1lIjoiRGVlcFNlZWsgVjQuMSBGbGFzaCIsInVybCI6Imh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MS9jaGF0L2NvbXBsZXRpb25zIiwidG9vbENhbGxpbmciOnRydWUsInZpc2lvbiI6dHJ1ZSwibWF4SW5wdXRUb2tlbnMiOjEyODAwMCwibWF4T3V0cHV0VG9rZW5zIjoxNjAwMH1dfX0seyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiUmVsb2FkIFZTIENvZGUgKERldmVsb3BlcjogUmVsb2FkIFdpbmRvdyksIHRoZW4gcGljayBcIkRlZXBTZWVrIFY0LjEgRmxhc2hcIiBpbiB0aGUgY2hhdCBtb2RlbCBwaWNrZXIuIiwiQ29waWxvdCBCdXNpbmVzcy9FbnRlcnByaXNlOiBhbiBhZG1pbiBtdXN0IGFsbG93IFwiQnJpbmcgWW91ciBPd24gTGFuZ3VhZ2UgTW9kZWwgS2V5XCIuIl19XX0seyJpZCI6ImNsaW5lIiwibmFtZSI6IkNsaW5lIiwiZ3JvdXAiOiJFZGl0b3JzIiwiZGV0ZWN0Ijp7ImV4dCI6InNhb3Vkcml6d2FuLmNsYXVkZS1kZXYifSwiaW5zdGFsbCI6eyJ1bml4IjoiY29kZSAtLWluc3RhbGwtZXh0ZW5zaW9uIHNhb3Vkcml6d2FuLmNsYXVkZS1kZXYiLCJ3aW5kb3dzIjoiY29kZSAtLWluc3RhbGwtZXh0ZW5zaW9uIHNhb3Vkcml6d2FuLmNsYXVkZS1kZXYiLCJuZWVkcyI6WyJjb2RlIl19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiQ2xpbmUgPiBzZXR0aW5ncyAoZ2VhcikgPiBBUEkgUHJvdmlkZXI6IE9wZW5BSSBDb21wYXRpYmxlLiIsIkJhc2UgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSIsIk1vZGVsIElEOiBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTc7IHR1cm4gaW1hZ2Ugc3VwcG9ydCBvbiwgY29udGV4dCB3aW5kb3cgMTI4MDAwLiJdfV19LHsiaWQiOiJyb28iLCJuYW1lIjoiUm9vIENvZGUiLCJncm91cCI6IkVkaXRvcnMiLCJkZXRlY3QiOnsiZXh0Ijoicm9vdmV0ZXJpbmFyeWluYy5yb28tY2xpbmUifSwiaW5zdGFsbCI6eyJ1bml4IjoiY29kZSAtLWluc3RhbGwtZXh0ZW5zaW9uIFJvb1ZldGVyaW5hcnlJbmMucm9vLWNsaW5lIiwid2luZG93cyI6ImNvZGUgLS1pbnN0YWxsLWV4dGVuc2lvbiBSb29WZXRlcmluYXJ5SW5jLnJvby1jbGluZSIsIm5lZWRzIjpbImNvZGUiXX0sImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJSb28gQ29kZSA+IFNldHRpbmdzID4gQVBJIFByb3ZpZGVyOiBPcGVuQUkgQ29tcGF0aWJsZS4iLCJCYXNlIFVSTDogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxIiwiQVBJIGtleToge0tFWX0iLCJNb2RlbDogZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3OyB0dXJuIGltYWdlcyBvbi4iXX1dfSx7ImlkIjoia2lsbyIsIm5hbWUiOiJLaWxvIENvZGUiLCJncm91cCI6IkVkaXRvcnMiLCJkZXRlY3QiOnsiZXh0Ijoia2lsb2NvZGUua2lsby1jb2RlIn0sImluc3RhbGwiOnsidW5peCI6ImNvZGUgLS1pbnN0YWxsLWV4dGVuc2lvbiBraWxvY29kZS5LaWxvLUNvZGUiLCJ3aW5kb3dzIjoiY29kZSAtLWluc3RhbGwtZXh0ZW5zaW9uIGtpbG9jb2RlLktpbG8tQ29kZSIsIm5lZWRzIjpbImNvZGUiXX0sImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJLaWxvIENvZGUgPiBTZXR0aW5ncyA+IFByb3ZpZGVycyA+IEN1c3RvbSBwcm92aWRlciwgUHJvdmlkZXIgQVBJIFwiT3BlbkFJIENvbXBhdGlibGVcIi4iLCJQcm92aWRlciBJRDogY2hlYXBsbG0iLCJCYXNlIFVSTDogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxIiwiQVBJIGtleToge0tFWX0iLCJNb2RlbHM6IGFkZCBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTcuIl19XX0seyJpZCI6ImNvbnRpbnVlIiwibmFtZSI6IkNvbnRpbnVlIiwiZ3JvdXAiOiJFZGl0b3JzIiwiZGV0ZWN0Ijp7ImV4dCI6ImNvbnRpbnVlLmNvbnRpbnVlIiwicGF0aHMiOnsidW5peCI6WyJ+Ly5jb250aW51ZSJdLCJ3aW5kb3dzIjpbIiVVU0VSUFJPRklMRSUvLmNvbnRpbnVlIl19fSwiaW5zdGFsbCI6eyJ1bml4IjoiY29kZSAtLWluc3RhbGwtZXh0ZW5zaW9uIENvbnRpbnVlLmNvbnRpbnVlIiwid2luZG93cyI6ImNvZGUgLS1pbnN0YWxsLWV4dGVuc2lvbiBDb250aW51ZS5jb250aW51ZSIsIm5lZWRzIjpbImNvZGUiXX0sImFjdGlvbnMiOlt7InR5cGUiOiJmaWxlIiwiaWZNaXNzaW5nIjp0cnVlLCJwcml2YXRlIjp0cnVlLCJwYXRoIjp7InVuaXgiOiJ+Ly5jb250aW51ZS9jb25maWcueWFtbCIsIndpbmRvd3MiOiIlVVNFUlBST0ZJTEUlLy5jb250aW51ZS9jb25maWcueWFtbCJ9LCJjb250ZW50IjoibmFtZTogQ2hlYXBMTE1cbnZlcnNpb246IDAuMC4xXG5zY2hlbWE6IHYxXG5tb2RlbHM6XG4gIC0gbmFtZTogRGVlcFNlZWsgVjQuMSBGbGFzaFxuICAgIHByb3ZpZGVyOiBvcGVuYWlcbiAgICBtb2RlbDogZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3XG4gICAgYXBpQmFzZTogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxXG4gICAgYXBpS2V5OiB7S0VZfVxuICAgIHJvbGVzOiBbY2hhdCwgZWRpdCwgYXBwbHldXG4iLCJlbHNlU3RlcHMiOlsiWW91ciB+Ly5jb250aW51ZS9jb25maWcueWFtbCBhbHJlYWR5IGV4aXN0cywgc28gaXQgd2FzIGxlZnQgYWxvbmUuIEFkZCB0aGlzIHVuZGVyIG1vZGVsczoiLCIgIC0gbmFtZTogRGVlcFNlZWsgVjQuMSBGbGFzaFxuICAgIHByb3ZpZGVyOiBvcGVuYWlcbiAgICBtb2RlbDogZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3XG4gICAgYXBpQmFzZTogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxXG4gICAgYXBpS2V5OiB7S0VZfVxuICAgIHJvbGVzOiBbY2hhdCwgZWRpdCwgYXBwbHldIl19XX0seyJpZCI6ImN1cnNvciIsIm5hbWUiOiJDdXJzb3IiLCJncm91cCI6IkVkaXRvcnMiLCJkZXRlY3QiOnsiYmlucyI6WyJjdXJzb3IiXSwicGF0aHMiOnsibWFjIjpbIi9BcHBsaWNhdGlvbnMvQ3Vyc29yLmFwcCJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL1Byb2dyYW1zL2N1cnNvciJdfX0sImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJDdXJzb3IgU2V0dGluZ3MgPiBNb2RlbHMgPiBPcGVuQUkgQVBJIEtleToge0tFWX0iLCJUdXJuIG9uIFwiT3ZlcnJpZGUgT3BlbkFJIEJhc2UgVVJMXCI6IGh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MSIsIkFkZCBhIGN1c3RvbSBtb2RlbCBuYW1lZCBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTcgYW5kIGVuYWJsZSBpdC4iLCJGb3IgYWdlbnQgd29yaywgQ2xpbmUsIFJvbyBDb2RlIG9yIEtpbG8gQ29kZSBpbnNpZGUgQ3Vyc29yIHdvcmsgYmV0dGVyLiJdfV19LHsiaWQiOiJ6ZWQiLCJuYW1lIjoiWmVkIiwiZ3JvdXAiOiJFZGl0b3JzIiwiZGV0ZWN0Ijp7ImJpbnMiOlsiemVkIiwiemVkaXRvciJdLCJwYXRocyI6eyJtYWMiOlsiL0FwcGxpY2F0aW9ucy9aZWQuYXBwIiwifi8uY29uZmlnL3plZCJdLCJsaW51eCI6WyJ+Ly5jb25maWcvemVkIl0sIndpbmRvd3MiOlsiJUFQUERBVEElL1plZCJdfX0sImFjdGlvbnMiOlt7InR5cGUiOiJlbnYiLCJ2YXJzIjp7IkNIRUFQTExNX0FQSV9LRVkiOiJ7S0VZfSJ9fSx7InR5cGUiOiJqc29uIiwicGF0aCI6eyJ1bml4Ijoifi8uY29uZmlnL3plZC9zZXR0aW5ncy5qc29uIiwid2luZG93cyI6IiVBUFBEQVRBJS9aZWQvc2V0dGluZ3MuanNvbiJ9LCJtZXJnZSI6eyJsYW5ndWFnZV9tb2RlbHMiOnsib3BlbmFpX2NvbXBhdGlibGUiOnsiY2hlYXBsbG0iOnsiYXBpX3VybCI6Imh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MSIsImF2YWlsYWJsZV9tb2RlbHMiOlt7Im5hbWUiOiJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciLCJkaXNwbGF5X25hbWUiOiJEZWVwU2VlayBWNC4xIEZsYXNoIiwibWF4X3Rva2VucyI6MTI4MDAwLCJjYXBhYmlsaXRpZXMiOnsidG9vbHMiOnRydWUsImltYWdlcyI6dHJ1ZSwicGFyYWxsZWxfdG9vbF9jYWxscyI6ZmFsc2UsInByb21wdF9jYWNoZV9rZXkiOmZhbHNlfX1dfX19fX1dLCJhZnRlciI6IlJlc3RhcnQgWmVkIGFuZCBwaWNrIERlZXBTZWVrIFY0LjEgRmxhc2ggaW4gdGhlIEFnZW50IHBhbmVsLiJ9LHsiaWQiOiJqZXRicmFpbnMiLCJuYW1lIjoiSmV0QnJhaW5zIEFJIEFzc2lzdGFudCIsImdyb3VwIjoiRWRpdG9ycyIsImRldGVjdCI6eyJwYXRocyI6eyJtYWMiOlsifi9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvSmV0QnJhaW5zIl0sImxpbnV4IjpbIn4vLmNvbmZpZy9KZXRCcmFpbnMiXSwid2luZG93cyI6WyIlQVBQREFUQSUvSmV0QnJhaW5zIl19fSwiYWN0aW9ucyI6W3sidHlwZSI6Im1hbnVhbCIsInN0ZXBzIjpbIlNldHRpbmdzID4gVG9vbHMgPiBBSSBBc3Npc3RhbnQgPiBQcm92aWRlcnMgJiBBUEkga2V5cyA+IE9wZW5BSS1jb21wYXRpYmxlLiIsIkJhc2UgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSIsIlBpY2sgZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3LiJdfV19LHsiaWQiOiJ3aW5kc3VyZiIsIm5hbWUiOiJXaW5kc3VyZiIsImdyb3VwIjoiRWRpdG9ycyIsImRldGVjdCI6eyJiaW5zIjpbIndpbmRzdXJmIl0sInBhdGhzIjp7Im1hYyI6WyIvQXBwbGljYXRpb25zL1dpbmRzdXJmLmFwcCJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL1Byb2dyYW1zL1dpbmRzdXJmIl19fSwiaW5zdGFsbCI6eyJ1bml4Ijoid2luZHN1cmYgLS1pbnN0YWxsLWV4dGVuc2lvbiBzYW91ZHJpendhbi5jbGF1ZGUtZGV2Iiwid2luZG93cyI6IndpbmRzdXJmIC0taW5zdGFsbC1leHRlbnNpb24gc2FvdWRyaXp3YW4uY2xhdWRlLWRldiIsIm5lZWRzIjpbIndpbmRzdXJmIl19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiV2luZHN1cmYgaGFzIG5vIGN1c3RvbSBiYXNlIFVSTDsgdGhlIGluc3RhbGxlciBhZGRzIENsaW5lIHRvIGl0LiBJbiBDbGluZTogQVBJIFByb3ZpZGVyIE9wZW5BSSBDb21wYXRpYmxlLCBCYXNlIFVSTCBodHRwczovL2NoZWFwbGxtLnNob3AvdjEsIGtleSB7S0VZfSwgbW9kZWwgZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3LiJdfV19LHsiaWQiOiJhdmFudGUiLCJuYW1lIjoiTmVvdmltOiBhdmFudGUubnZpbSIsImdyb3VwIjoiRWRpdG9ycyIsImRldGVjdCI6eyJwYXRocyI6eyJ1bml4IjpbIn4vLmxvY2FsL3NoYXJlL252aW0vbGF6eS9hdmFudGUubnZpbSJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL252aW0tZGF0YS9sYXp5L2F2YW50ZS5udmltIl19fSwiYWN0aW9ucyI6W3sidHlwZSI6ImVudiIsInZhcnMiOnsiQ0hFQVBMTE1fQVBJX0tFWSI6IntLRVl9In19LHsidHlwZSI6Im1hbnVhbCIsInN0ZXBzIjpbIkluIHlvdXIgYXZhbnRlIHNldHVwOiIsInJlcXVpcmUoXCJhdmFudGVcIikuc2V0dXAoe1xuICBwcm92aWRlciA9IFwiY2hlYXBsbG1cIixcbiAgcHJvdmlkZXJzID0geyBjaGVhcGxsbSA9IHsgX19pbmhlcml0ZWRfZnJvbSA9IFwib3BlbmFpXCIsIGVuZHBvaW50ID0gXCJodHRwczovL2NoZWFwbGxtLnNob3AvdjFcIiwgYXBpX2tleV9uYW1lID0gXCJDSEVBUExMTV9BUElfS0VZXCIsIG1vZGVsID0gXCJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTdcIiB9IH0sXG59KSJdfV19LHsiaWQiOiJncHRlbCIsIm5hbWUiOiJFbWFjczogZ3B0ZWwiLCJncm91cCI6IkVkaXRvcnMiLCJwcmVzZWxlY3QiOiJuZXZlciIsImRldGVjdCI6eyJiaW5zIjpbImVtYWNzIl19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiSW4geW91ciBpbml0IGZpbGU6IiwiKHNldHEgZ3B0ZWwtbW9kZWwgJ2RlZXBzZWVrLXY0LjEtZmxhc2gtMTAxN1xuICAgICAgZ3B0ZWwtYmFja2VuZCAoZ3B0ZWwtbWFrZS1vcGVuYWkgXCJDaGVhcExMTVwiIDpob3N0IFwiY2hlYXBsbG0uc2hvcFwiIDplbmRwb2ludCBcIi92MS9jaGF0L2NvbXBsZXRpb25zXCIgOnN0cmVhbSB0IDprZXkgXCJ7S0VZfVwiIDptb2RlbHMgJyhkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTcpKSkiXX1dfSx7ImlkIjoib3BlbndlYnVpIiwibmFtZSI6Ik9wZW4gV2ViVUkiLCJncm91cCI6IkNoYXQgYXBwcyIsImRldGVjdCI6eyJiaW5zIjpbIm9wZW4td2VidWkiXX0sImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJBZG1pbiBTZXR0aW5ncyA+IENvbm5lY3Rpb25zID4gT3BlbkFJIEFQSSA+ICsiLCJVUkw6IGh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MSIsIktleToge0tFWX0iLCJTZWxmLWhvc3RlZDogT1BFTkFJX0FQSV9CQVNFX1VSTD1odHRwczovL2NoZWFwbGxtLnNob3AvdjEgYW5kIE9QRU5BSV9BUElfS0VZIGFsc28gd29yay4iXX1dfSx7ImlkIjoibGlicmVjaGF0IiwibmFtZSI6IkxpYnJlQ2hhdCIsImdyb3VwIjoiQ2hhdCBhcHBzIiwicHJlc2VsZWN0IjoibmV2ZXIiLCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiSW4gbGlicmVjaGF0LnlhbWwsIHRoZW4gc2V0IENIRUFQTExNX0FQSV9LRVkgaW4gTGlicmVDaGF0J3MgLmVudjoiLCJlbmRwb2ludHM6XG4gIGN1c3RvbTpcbiAgICAtIG5hbWU6IFwiQ2hlYXBMTE1cIlxuICAgICAgYXBpS2V5OiBcIiR7Q0hFQVBMTE1fQVBJX0tFWX1cIlxuICAgICAgYmFzZVVSTDogXCJodHRwczovL2NoZWFwbGxtLnNob3AvdjFcIlxuICAgICAgbW9kZWxzOlxuICAgICAgICBkZWZhdWx0OiBbXCJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTdcIl1cbiAgICAgICAgZmV0Y2g6IHRydWUiXX1dfSx7ImlkIjoibG9iZWNoYXQiLCJuYW1lIjoiTG9iZUNoYXQiLCJncm91cCI6IkNoYXQgYXBwcyIsInByZXNlbGVjdCI6Im5ldmVyIiwiYWN0aW9ucyI6W3sidHlwZSI6Im1hbnVhbCIsInN0ZXBzIjpbIlNldHRpbmdzID4gQUkgU2VydmljZSBQcm92aWRlciA+ICsgY3VzdG9tIHByb3ZpZGVyLCByZXF1ZXN0IGZvcm1hdCBPcGVuQUkuIiwiUHJveHkgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSIsIkZldGNoIHRoZSBtb2RlbCBsaXN0LiJdfV19LHsiaWQiOiJqYW4iLCJuYW1lIjoiSmFuIiwiZ3JvdXAiOiJDaGF0IGFwcHMiLCJkZXRlY3QiOnsicGF0aHMiOnsibWFjIjpbIi9BcHBsaWNhdGlvbnMvSmFuLmFwcCJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL1Byb2dyYW1zL2phbiJdfX0sImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJTZXR0aW5ncyA+IE1vZGVsIFByb3ZpZGVycyA+IEFkZCBQcm92aWRlciAoT3BlbkFJLWNvbXBhdGlibGUpLiIsIkJhc2UgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSJdfV19LHsiaWQiOiJjaGF0Ym94IiwibmFtZSI6IkNoYXRib3giLCJncm91cCI6IkNoYXQgYXBwcyIsImRldGVjdCI6eyJwYXRocyI6eyJtYWMiOlsiL0FwcGxpY2F0aW9ucy9DaGF0Ym94LmFwcCJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL1Byb2dyYW1zL2NoYXRib3giXX19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiU2V0dGluZ3MgPiBNb2RlbCBQcm92aWRlciA+IEFkZCA+IEN1c3RvbSBQcm92aWRlciwgQVBJIG1vZGUgXCJPcGVuQUkgQVBJIENvbXBhdGlibGVcIi4iLCJBUEkgSG9zdDogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxIChrZWVwIHRoZSBwYXRoIC9jaGF0L2NvbXBsZXRpb25zKSIsIkFQSSBrZXk6IHtLRVl9IiwiTW9kZWw6IGRlZXBzZWVrLXY0LjEtZmxhc2gtMTAxNyJdfV19LHsiaWQiOiJhbnl0aGluZ2xsbSIsIm5hbWUiOiJBbnl0aGluZ0xMTSIsImdyb3VwIjoiQ2hhdCBhcHBzIiwiZGV0ZWN0Ijp7InBhdGhzIjp7Im1hYyI6WyIvQXBwbGljYXRpb25zL0FueXRoaW5nTExNLmFwcCJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL1Byb2dyYW1zL2FueXRoaW5nbGxtLWRlc2t0b3AiXX19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiU2V0dGluZ3MgPiBMTE0gUHJlZmVyZW5jZSA+IEdlbmVyaWMgT3BlbkFJLiIsIkJhc2UgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSIsIk1vZGVsOiBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTcsIGNvbnRleHQgd2luZG93IDEyODAwMCJdfV19LHsiaWQiOiJtc3R5IiwibmFtZSI6Ik1zdHkiLCJncm91cCI6IkNoYXQgYXBwcyIsImRldGVjdCI6eyJwYXRocyI6eyJtYWMiOlsiL0FwcGxpY2F0aW9ucy9Nc3R5LmFwcCJdLCJ3aW5kb3dzIjpbIiVMT0NBTEFQUERBVEElL1Byb2dyYW1zL01zdHkiXX19LCJhY3Rpb25zIjpbeyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiTW9kZWwgUHJvdmlkZXJzID4gQWRkIFJlbW90ZSBNb2RlbCBQcm92aWRlciA+IE9wZW5BSS1jb21wYXRpYmxlLiIsIkVuZHBvaW50OiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJBUEkga2V5OiB7S0VZfSIsIk1vZGVsOiBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciXX1dfSx7ImlkIjoic2lsbHl0YXZlcm4iLCJuYW1lIjoiU2lsbHlUYXZlcm4iLCJncm91cCI6IkNoYXQgYXBwcyIsInByZXNlbGVjdCI6Im5ldmVyIiwiYWN0aW9ucyI6W3sidHlwZSI6Im1hbnVhbCIsInN0ZXBzIjpbIkFQSTogQ2hhdCBDb21wbGV0aW9uID4gU291cmNlOiBDdXN0b20gKE9wZW5BSS1jb21wYXRpYmxlKS4iLCJFbmRwb2ludDogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxIChubyB0cmFpbGluZyBzbGFzaCkiLCJBUEkga2V5OiB7S0VZfSIsIk1vZGVsOiBkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTciXX1dfSx7ImlkIjoiY3VybCIsIm5hbWUiOiJjdXJsIiwiZ3JvdXAiOiJDb2RlIGFuZCBmcmFtZXdvcmtzIiwicHJlc2VsZWN0IjoibmV2ZXIiLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJDSEVBUExMTV9BUElfS0VZIjoie0tFWX0ifX0seyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiVHJ5IGl0IChtYWNPUy9MaW51eCk6IiwiY3VybCBodHRwczovL2NoZWFwbGxtLnNob3AvdjEvY2hhdC9jb21wbGV0aW9ucyAtSCBcIkF1dGhvcml6YXRpb246IEJlYXJlciAkQ0hFQVBMTE1fQVBJX0tFWVwiIC1IIFwiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uXCIgLWQgJ3tcIm1vZGVsXCI6IFwiZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3XCIsIFwibWVzc2FnZXNcIjogW3tcInJvbGVcIjogXCJ1c2VyXCIsIFwiY29udGVudFwiOiBcIkhlbGxvIVwifV19JyJdfV19LHsiaWQiOiJvcGVuYWktcHkiLCJuYW1lIjoiT3BlbkFJIFNESyAoUHl0aG9uKSIsImdyb3VwIjoiQ29kZSBhbmQgZnJhbWV3b3JrcyIsInByZXNlbGVjdCI6Im5ldmVyIiwiZ2xvYmFsIjp0cnVlLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJPUEVOQUlfQkFTRV9VUkwiOiJodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJPUEVOQUlfQVBJX0JBU0UiOiJodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJPUEVOQUlfQVBJX0tFWSI6IntLRVl9In19XSwiYWZ0ZXIiOiJPcGVuQUkoKSBub3cgdGFsa3MgdG8gdXMgYnkgZGVmYXVsdDsgdXNlIG1vZGVsPVwiZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3XCIuIn0seyJpZCI6Im9wZW5haS1qcyIsIm5hbWUiOiJPcGVuQUkgU0RLIChOb2RlKSIsImdyb3VwIjoiQ29kZSBhbmQgZnJhbWV3b3JrcyIsInByZXNlbGVjdCI6Im5ldmVyIiwiZ2xvYmFsIjp0cnVlLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJPUEVOQUlfQkFTRV9VUkwiOiJodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJPUEVOQUlfQVBJX0tFWSI6IntLRVl9In19XSwiYWZ0ZXIiOiJuZXcgT3BlbkFJKCkgbm93IHRhbGtzIHRvIHVzIGJ5IGRlZmF1bHQ7IHVzZSBtb2RlbDogXCJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTdcIi4ifSx7ImlkIjoiYW50aHJvcGljLXNkayIsIm5hbWUiOiJBbnRocm9waWMgU0RLIiwiZ3JvdXAiOiJDb2RlIGFuZCBmcmFtZXdvcmtzIiwicHJlc2VsZWN0IjoibmV2ZXIiLCJnbG9iYWwiOnRydWUsImFjdGlvbnMiOlt7InR5cGUiOiJlbnYiLCJ2YXJzIjp7IkFOVEhST1BJQ19CQVNFX1VSTCI6Imh0dHBzOi8vY2hlYXBsbG0uc2hvcCIsIkFOVEhST1BJQ19BUElfS0VZIjoie0tFWX0ifX1dLCJhZnRlciI6IkFudGhyb3BpYygpIG5vdyB0YWxrcyB0byB1cyBieSBkZWZhdWx0OyBhbnkgbW9kZWwgbmFtZSBpcyBhY2NlcHRlZC4ifSx7ImlkIjoibGFuZ2NoYWluIiwibmFtZSI6IkxhbmdDaGFpbiIsImdyb3VwIjoiQ29kZSBhbmQgZnJhbWV3b3JrcyIsInByZXNlbGVjdCI6Im5ldmVyIiwiZ2xvYmFsIjp0cnVlLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJPUEVOQUlfQkFTRV9VUkwiOiJodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJPUEVOQUlfQVBJX0tFWSI6IntLRVl9In19XSwiYWZ0ZXIiOiJDaGF0T3BlbkFJKG1vZGVsPVwiZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3XCIpIG5vdyB0YWxrcyB0byB1cy4ifSx7ImlkIjoibGxhbWFpbmRleCIsIm5hbWUiOiJMbGFtYUluZGV4IiwiZ3JvdXAiOiJDb2RlIGFuZCBmcmFtZXdvcmtzIiwicHJlc2VsZWN0IjoibmV2ZXIiLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJDSEVBUExMTV9BUElfS0VZIjoie0tFWX0ifX0seyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsicGlwIGluc3RhbGwgbGxhbWEtaW5kZXgtbGxtcy1vcGVuYWktbGlrZSwgdGhlbjoiLCJPcGVuQUlMaWtlKG1vZGVsPVwiZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3XCIsIGFwaV9iYXNlPVwiaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxXCIsIGFwaV9rZXk9b3MuZW52aXJvbltcIkNIRUFQTExNX0FQSV9LRVlcIl0sIGlzX2NoYXRfbW9kZWw9VHJ1ZSwgaXNfZnVuY3Rpb25fY2FsbGluZ19tb2RlbD1UcnVlLCBjb250ZXh0X3dpbmRvdz0xMjgwMDApIl19XX0seyJpZCI6ImFpLXNkayIsIm5hbWUiOiJWZXJjZWwgQUkgU0RLIiwiZ3JvdXAiOiJDb2RlIGFuZCBmcmFtZXdvcmtzIiwicHJlc2VsZWN0IjoibmV2ZXIiLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJDSEVBUExMTV9BUElfS0VZIjoie0tFWX0ifX0seyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsiY3JlYXRlT3BlbkFJQ29tcGF0aWJsZSh7IG5hbWU6IFwiY2hlYXBsbG1cIiwgYmFzZVVSTDogXCJodHRwczovL2NoZWFwbGxtLnNob3AvdjFcIiwgYXBpS2V5OiBwcm9jZXNzLmVudi5DSEVBUExMTV9BUElfS0VZIH0pIiwidGhlbiBtb2RlbDogY2hlYXBsbG0oXCJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTdcIikiXX1dfSx7ImlkIjoibGl0ZWxsbSIsIm5hbWUiOiJMaXRlTExNIiwiZ3JvdXAiOiJDb2RlIGFuZCBmcmFtZXdvcmtzIiwicHJlc2VsZWN0IjoibmV2ZXIiLCJhY3Rpb25zIjpbeyJ0eXBlIjoiZW52IiwidmFycyI6eyJDSEVBUExMTV9BUElfS0VZIjoie0tFWX0ifX0seyJ0eXBlIjoibWFudWFsIiwic3RlcHMiOlsibGl0ZWxsbS5jb21wbGV0aW9uKG1vZGVsPVwib3BlbmFpL2RlZXBzZWVrLXY0LjEtZmxhc2gtMTAxN1wiLCBhcGlfYmFzZT1cImh0dHBzOi8vY2hlYXBsbG0uc2hvcC92MVwiLCBhcGlfa2V5PW9zLmVudmlyb25bXCJDSEVBUExMTV9BUElfS0VZXCJdLCBtZXNzYWdlcz1bLi4uXSkiLCJQcm94eSBjb25maWc6IG1vZGVsOiBvcGVuYWkvZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3LCBhcGlfYmFzZTogaHR0cHM6Ly9jaGVhcGxsbS5zaG9wL3YxLCBhcGlfa2V5OiBvcy5lbnZpcm9uL0NIRUFQTExNX0FQSV9LRVkiXX1dfSx7ImlkIjoicHlkYW50aWMtYWkiLCJuYW1lIjoiUHlkYW50aWMgQUkiLCJncm91cCI6IkNvZGUgYW5kIGZyYW1ld29ya3MiLCJwcmVzZWxlY3QiOiJuZXZlciIsImFjdGlvbnMiOlt7InR5cGUiOiJlbnYiLCJ2YXJzIjp7IkNIRUFQTExNX0FQSV9LRVkiOiJ7S0VZfSJ9fSx7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJPcGVuQUlDaGF0TW9kZWwoXCJkZWVwc2Vlay12NC4xLWZsYXNoLTEwMTdcIiwgcHJvdmlkZXI9T3BlbkFJQ29tcGF0aWJsZVByb3ZpZGVyKFwiY2hlYXBsbG1cIiwgYmFzZV91cmw9XCJodHRwczovL2NoZWFwbGxtLnNob3AvdjFcIiwgYXBpX2tleT1vcy5lbnZpcm9uW1wiQ0hFQVBMTE1fQVBJX0tFWVwiXSkpIl19XX0seyJpZCI6Im44biIsIm5hbWUiOiJuOG4iLCJncm91cCI6IkNvZGUgYW5kIGZyYW1ld29ya3MiLCJwcmVzZWxlY3QiOiJuZXZlciIsImFjdGlvbnMiOlt7InR5cGUiOiJtYW51YWwiLCJzdGVwcyI6WyJDcmVkZW50aWFscyA+IE5ldyA+IE9wZW5BSS4iLCJBUEkga2V5OiB7S0VZfSIsIkJhc2UgVVJMOiBodHRwczovL2NoZWFwbGxtLnNob3AvdjEiLCJJbiBhbiBcIk9wZW5BSSBDaGF0IE1vZGVsXCIgbm9kZSwgdHlwZSB0aGUgbW9kZWwgSUQgZGVlcHNlZWstdjQuMS1mbGFzaC0xMDE3LiJdfV19XQ==')).Replace('{KEY}', $Key) # foreach, not @(... | ConvertFrom-Json): Windows PowerShell 5.1 emits a JSON array as one object $Catalog = @(); foreach ($x in (ConvertFrom-Json -InputObject $catalogJson)) { $Catalog += $x } # ---------------------------------------------------------------- helpers function Resolve-CLPath($p) { if (-not $p) { return $null } $s = $p.windows if (-not $s) { return $null } $s = $s -replace '^~(?=/|$)', $HomeDir $s = [Environment]::ExpandEnvironmentVariables($s.Replace('%USERPROFILE%', $HomeDir)) return $s.Replace('/', '\') } function Short($p) { return $p.Replace($HomeDir, '~') } function ReadText($p) { if (Test-Path -LiteralPath $p) { return [IO.File]::ReadAllText($p) } return $null } function WriteText($p, $text) { if ($Dry) { return } $dir = Split-Path -Parent $p if (-not (Test-Path -LiteralPath $dir)) { $null = New-Item -ItemType Directory -Force -Path $dir } if (Test-Path -LiteralPath $p) { Copy-Item -LiteralPath $p -Destination "$p.cheapllm-bak" -Force } [IO.File]::WriteAllText($p, $text, $Utf8) } # PSCustomObject (from ConvertFrom-Json) -> ordered hashtables and arrays, so values can be merged function ToPlain($o) { if ($null -eq $o) { return $null } if ($o -is [System.Management.Automation.PSCustomObject]) { $h = [ordered]@{} foreach ($pr in $o.PSObject.Properties) { $h[$pr.Name] = ToPlain $pr.Value } return $h } if ($o -is [System.Collections.IDictionary]) { $h = [ordered]@{}; foreach ($k in $o.Keys) { $h[$k] = ToPlain $o[$k] }; return $h } if (($o -is [System.Collections.IEnumerable]) -and -not ($o -is [string])) { $list = New-Object System.Collections.ArrayList foreach ($x in $o) { $null = $list.Add((ToPlain $x)) } return ,($list.ToArray()) } return $o } function MergeInto($t, $s) { foreach ($k in @($s.Keys)) { if (($s[$k] -is [System.Collections.IDictionary]) -and ($t.Contains($k)) -and ($t[$k] -is [System.Collections.IDictionary])) { MergeInto $t[$k] $s[$k] } else { $t[$k] = $s[$k] } } } function ReadJsonPlain($p, $fallback) { $t = ReadText $p if (-not $t -or -not $t.Trim()) { return ,$fallback } try { $v = ConvertFrom-Json -InputObject $t } catch { throw 'NOT_JSON' } if ($t.TrimStart().StartsWith('[')) { # 5.1 hands back the array as one object, 7 unwraps a one-element array: rebuild it either way $list = New-Object System.Collections.ArrayList foreach ($x in $v) { if ($null -ne $x) { $null = $list.Add((ToPlain $x)) } } return ,($list.ToArray()) } return ,(ToPlain $v) } function ToJson($v) { return (ConvertTo-Json -InputObject $v -Depth 32) + "`n" } function OnPath($bin) { return [bool](Get-Command $bin -ErrorAction SilentlyContinue) } function ExtFound($prefix) { $d = Join-Path $HomeDir '.vscode\extensions' if (-not (Test-Path $d)) { return $false } return [bool](Get-ChildItem -LiteralPath $d -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name.ToLower().StartsWith($prefix.ToLower() + '-') }) } function Detect($t) { $d = $t.detect if (-not $d) { return $false } foreach ($b in @($d.bins)) { if ($b -and (OnPath $b)) { return $true } } if ($d.paths -and $d.paths.windows) { foreach ($p in @($d.paths.windows)) { if (Test-Path (Resolve-CLPath ([pscustomobject]@{ windows = $p }))) { return $true } } } if ($d.ext -and (ExtFound $d.ext)) { return $true } return $false } function InstallFor($t) { if (-not $t.install -or -not $t.install.windows) { return $null } $n = $t.install.needs $needs = @() if ($n -is [array]) { $needs = $n } elseif ($n -and $n.windows) { $needs = @($n.windows) } return [pscustomobject]@{ cmd = $t.install.windows; needs = $needs } } # ---------------------------------------------------------------- choose $items = @() foreach ($t in $Catalog) { $items += [pscustomobject]@{ t = $t; found = (Detect $t); inst = (InstallFor $t) } } function IsAuto($it) { return [bool](@($it.t.actions) | Where-Object { $_.type -ne 'manual' }) } function Status($it) { if ($it.t.unsupported) { return 'not supported, see notes' } if ($it.t.global) { return 'sets variables for every program' } $kind = if (IsAuto $it) { '' } else { ', steps to follow' } if (-not $it.t.detect) { if (IsAuto $it) { return 'key in your environment + snippet' } else { return 'steps to follow' } } if ($it.found) { return 'found' + $kind } if ($it.inst) { return 'not found, can install' + $kind } return 'not found' + $kind } $chosen = New-Object 'System.Collections.Generic.HashSet[string]' $only = @() if ($env:CHEAPLLM_ONLY) { $only = @($env:CHEAPLLM_ONLY.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) } if ($only.Count -gt 0) { foreach ($id in $only) { if ($items | Where-Object { $_.t.id -eq $id }) { $null = $chosen.Add($id) } } } elseif ($env:CHEAPLLM_ALL -eq '1') { foreach ($it in $items) { $null = $chosen.Add($it.t.id) } } else { foreach ($it in $items) { if ($it.found -and $it.t.preselect -ne 'never' -and -not $it.t.unsupported) { $null = $chosen.Add($it.t.id) } } } if ($only.Count -eq 0 -and $env:CHEAPLLM_ALL -ne '1' -and $env:CHEAPLLM_YES -ne '1') { while ($true) { Write-Host '' Write-Host ' What should be set up? Ticked = found on this PC.' $group = '' for ($i = 0; $i -lt $items.Count; $i++) { $it = $items[$i] if ($it.t.group -ne $group) { $group = $it.t.group; Write-Host ''; Write-Host " $group" } $mark = if ($chosen.Contains($it.t.id)) { '[x]' } else { '[ ]' } Write-Host (' {0,3} {1} {2,-26} {3}' -f ($i + 1), $mark, $it.t.name, (Status $it)) } Write-Host '' $a = ("" + (Read-Host ' Numbers to tick or untick (e.g. 2 5 12), a = all, n = none, Enter = go')).Trim().ToLower() if (-not $a) { break } if ($a -eq 'a') { foreach ($it in $items) { $null = $chosen.Add($it.t.id) }; continue } if ($a -eq 'n') { $chosen.Clear(); continue } foreach ($tok in ($a -split '[\s,]+')) { if ($tok -match '^(\d+)(?:-(\d+))?$') { $lo = [int]$Matches[1]; $hi = if ($Matches[2]) { [int]$Matches[2] } else { $lo } for ($n = $lo; $n -le $hi; $n++) { if ($n -ge 1 -and $n -le $items.Count) { $id = $items[$n - 1].t.id; if ($chosen.Contains($id)) { $null = $chosen.Remove($id) } else { $null = $chosen.Add($id) } } } } } } } $selected = @($items | Where-Object { $chosen.Contains($_.t.id) }) if ($selected.Count -eq 0) { Write-Host ''; Write-Host ' Nothing selected. Run it again any time.'; return } # ---------------------------------------------------------------- install $done = New-Object System.Collections.ArrayList $manual = New-Object System.Collections.ArrayList $after = New-Object System.Collections.ArrayList $envVars = [ordered]@{} $missing = @($selected | Where-Object { -not $_.found -and $_.inst }) if ($missing.Count -gt 0) { $go = $env:CHEAPLLM_INSTALL -eq 'yes' if ($env:CHEAPLLM_INSTALL -ne 'yes' -and $env:CHEAPLLM_INSTALL -ne 'no' -and -not $Dry) { $ans = ("" + (Read-Host (' Install ' + (($missing | ForEach-Object { $_.t.name }) -join ', ') + '? [Y/n]'))).Trim().ToLower() $go = ($ans -eq '' -or $ans -eq 'y' -or $ans -eq 'yes') } foreach ($it in $missing) { $lack = @($it.inst.needs | Where-Object { $_ -and -not (OnPath $_) }) if ($Dry) { $null = $done.Add("$($it.t.name): would install with: $($it.inst.cmd)"); continue } if (-not $go) { $null = $manual.Add(@($it.t.name, @("Not installed. To install: $($it.inst.cmd)"))); continue } if ($lack.Count -gt 0) { $null = $manual.Add(@($it.t.name, @("Needs $($lack -join ', ') first (Node.js for npm, VS Code's 'code' command for extensions), then: $($it.inst.cmd)"))); continue } Write-Host '' Write-Host " Installing $($it.t.name): $($it.inst.cmd)" & powershell.exe -NoProfile -ExecutionPolicy Bypass -Command $it.inst.cmd if ($LASTEXITCODE -eq 0) { $it.found = $true; $null = $done.Add("$($it.t.name): installed") } else { $null = $manual.Add(@($it.t.name, @("Install failed (exit $LASTEXITCODE). Try by hand: $($it.inst.cmd)"))) } } } # ---------------------------------------------------------------- configure function Apply($it, $a) { $name = $it.t.name $p = Resolve-CLPath $a.path switch ($a.type) { 'env' { foreach ($pr in $a.vars.PSObject.Properties) { $envVars[$pr.Name] = $pr.Value }; return $null } 'manual' { $null = $manual.Add(@($name, @($a.steps))); return $null } 'json' { try { $j = ReadJsonPlain $p ([ordered]@{}) } catch { $null = $manual.Add(@($name, @("$(Short $p) has comments, so it was left alone. Merge this into it:", (ToJson (ToPlain $a.merge))))); return $null } if (-not ($j -is [System.Collections.IDictionary])) { $j = [ordered]@{} } MergeInto $j (ToPlain $a.merge) foreach ($r in @($a.remove)) { if (-not $r) { continue } $ks = $r.Split('.'); $o = $j for ($i = 0; $i -lt $ks.Count - 1; $i++) { if ($o -is [System.Collections.IDictionary] -and $o.Contains($ks[$i])) { $o = $o[$ks[$i]] } else { $o = $null } } if ($o -is [System.Collections.IDictionary] -and $o.Contains($ks[-1])) { $o.Remove($ks[-1]) } } WriteText $p (ToJson $j) return (Short $p) } 'json-array' { try { $arr = ReadJsonPlain $p @() } catch { $null = $manual.Add(@($name, @("$(Short $p) has comments, so it was left alone. Add this entry:", (ToJson (ToPlain $a.item))))); return $null } $list = New-Object System.Collections.ArrayList if ($arr -is [array]) { foreach ($x in $arr) { $null = $list.Add($x) } } $item = ToPlain $a.item $idx = -1 for ($i = 0; $i -lt $list.Count; $i++) { $x = $list[$i]; $ok = $x -is [System.Collections.IDictionary] if ($ok) { foreach ($pr in $a.match.PSObject.Properties) { if ($x[$pr.Name] -ne $pr.Value) { $ok = $false } } } if ($ok) { $idx = $i; break } } if ($idx -ge 0) { $list[$idx] = $item } else { $null = $list.Add($item) } WriteText $p (ToJson ($list.ToArray())) return (Short $p) } 'toml' { $fmt = { param($v) if ($v -is [bool]) { "$v".ToLower() } elseif ($v -is [int] -or $v -is [long] -or $v -is [double] -or $v -is [decimal]) { "$v" } else { '"' + ("$v".Replace('\', '\\').Replace('"', '\"')) + '"' } } $topKeys = @(); if ($a.top) { $topKeys = @($a.top.PSObject.Properties | ForEach-Object { $_.Name }) } $tableNames = @(); if ($a.tables) { $tableNames = @($a.tables.PSObject.Properties | ForEach-Object { $_.Name }) } $t = ReadText $p $lines = if ($t) { $t -split "`r?`n" } else { @() } $out = New-Object System.Collections.ArrayList $top = $true; $skip = $false foreach ($l in $lines) { if ($l -match '^\s*\[([^\]]+)\]\s*$') { $top = $false; $skip = $tableNames -contains $Matches[1].Trim() } if ($skip) { continue } $drop = $false if ($top) { foreach ($k in $topKeys) { if ($l -match ('^\s*' + [regex]::Escape($k) + '\s*=')) { $drop = $true } } } if (-not $drop) { $null = $out.Add($l) } } while ($out.Count -gt 0 -and -not ("" + $out[$out.Count - 1]).Trim()) { $out.RemoveAt($out.Count - 1) } $head = (($a.top.PSObject.Properties | ForEach-Object { "$($_.Name) = $(& $fmt $_.Value)" }) -join "`n") + "`n" $tail = '' foreach ($tn in $tableNames) { $kv = $a.tables.$tn $tail += "`n[$tn]`n" + (($kv.PSObject.Properties | ForEach-Object { "$($_.Name) = $(& $fmt $_.Value)" }) -join "`n") + "`n" } $middle = if ($out.Count -gt 0) { ($out -join "`n") + "`n" } else { '' } WriteText $p ($head + $middle + $tail) return (Short $p) } 'block' { $c = if ($a.comment) { $a.comment } else { '#' } $begin = "$c >>> cheapllm.shop >>>"; $end = "$c <<< cheapllm.shop <<<" $t = ReadText $p $lines = if ($t) { $t -split "`r?`n" } else { @() } $out = New-Object System.Collections.ArrayList $inside = $false foreach ($l in $lines) { if ($l -eq $begin) { $inside = $true; continue } if ($l -eq $end) { $inside = $false; continue } if ($inside) { continue } $drop = $false foreach ($r in @($a.drop)) { if ($r -and $l -match $r) { $drop = $true } } if (-not $drop) { $null = $out.Add($l) } } while ($out.Count -gt 0 -and -not ("" + $out[$out.Count - 1]).Trim()) { $out.RemoveAt($out.Count - 1) } $prefix = if ($out.Count -gt 0) { ($out -join "`n") + "`n`n" } else { '' } WriteText $p ($prefix + "$begin`n$($a.text)`n$end`n") return (Short $p) } 'file' { if ($a.ifMissing -and (Test-Path -LiteralPath $p)) { $steps = if ($a.elseSteps) { @($a.elseSteps) } else { @("$(Short $p) exists and was left alone.") } $null = $manual.Add(@($name, $steps)); return $null } $text = if ($a.json) { ToJson (ToPlain $a.json) } else { $a.content } WriteText $p $text return (Short $p) } } return $null } foreach ($it in $selected) { $files = @() foreach ($a in @($it.t.actions)) { try { $f = Apply $it $a; if ($f) { $files += $f } } catch { $null = $manual.Add(@($it.t.name, @("Not changed: $($_.Exception.Message)"))) } } if ($files.Count -gt 0) { $null = $done.Add("$($it.t.name): " + ($files -join ', ')) } if ($it.t.after) { $null = $after.Add("$($it.t.name): $($it.t.after)") } } if ($envVars.Count -gt 0) { if (-not $Dry) { foreach ($k in $envVars.Keys) { [Environment]::SetEnvironmentVariable($k, $envVars[$k], 'User') Set-Item -Path "Env:$k" -Value $envVars[$k] } } $null = $done.Add('Environment: ' + (@($envVars.Keys) -join ', ') + ' set for your Windows user') } # ---------------------------------------------------------------- report Write-Host '' foreach ($d in $done) { if ($Dry) { Write-Host " would $d" } else { Write-Host " done $d" } } if ($manual.Count -gt 0) { Write-Host '' Write-Host " By hand (these apps keep their settings where a script can't write):" foreach ($m in $manual) { Write-Host '' Write-Host " $($m[0])" foreach ($s in @($m[1])) { Write-Host (' ' + ("$s" -replace "`n", "`n ")) } } } if ($after.Count -gt 0) { Write-Host ''; Write-Host ' Next'; foreach ($x in $after) { Write-Host " $x" } } Write-Host '' if ($Dry) { Write-Host ' Dry run: nothing was written or installed.' } else { Write-Host ' Old versions of changed files are saved as *.cheapllm-bak.' } Write-Host ' Open a new terminal so it sees the new variables, then start your tool.' Write-Host " Everything else: $Origin/integrations" Write-Host '' }