#__NEXUS_PS1__ # NEXUS_AI - установщик в 1 клик для Windows (Windows PowerShell 5.1+). # # Что делает: # 1. проверяет ваш ключ и показывает баланс; # 2. находит Cursor / VS Code / Windsurf и терминальные агенты (Claude Code, Codex, Qwen Code, OpenCode, Kilo Code); # ставит NEXUS CLI (агент в терминале): ядро в ~/.nexus-ai/code/engine, настройки в ~/.nexus-ai/code, # команды nexus и nexus-code в ~/.nexus-ai/bin (папка добавляется в PATH); # 3. прописывает в их настройки адрес шлюза и ваш ключ (в Cursor/VS Code ставит агент Cline, уже подключённый к NEXUS AI). # Перед каждым изменением копия файла сохраняется в %USERPROFILE%\.nexus-ai\backups\<время>\. # Откат последней установки: запустите установщик ещё раз и введите R (или NEXUS_ACTION=restore). # # Переменные: NEXUS_KEY (ключ), NEXUS_MODEL (модель по умолчанию), NEXUS_AUTO=1 (без вопросов), # NEXUS_ACTION=restore (откат), NEXUS_SITE (адрес шлюза), NEXUS_TEST_ROOT (песочница для тестов), # NEXUS_ONLY=nexus-code (отметить только эти пункты, через запятую; кнопка NEXUS CLI на сайте), # NEXUS_SKIP_CHECK=1 (без проверочного запроса в конце; так работает `nexus update`). $ErrorActionPreference = 'Continue' try { [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false } catch {} try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072 } catch {} $global:NexusExit = 0 $script:NexusSetupVersion = '2026.11.03.1' $Site = if ($env:NEXUS_SITE) { $env:NEXUS_SITE.TrimEnd('/') } else { 'https://nexusallin.store' } $OpenAiBase = "$Site/v1" $FilesSite = $Site # installer files always come from here, even when the API goes through a faster server $Utf8NoBom = New-Object System.Text.UTF8Encoding $false $Stamp = Get-Date -Format 'yyyyMMdd_HHmmss' $Auto = ($env:NEXUS_AUTO -eq '1') # one-program installers from the site (NEXUS_CLI_Setup.bat, NEXUS_Roblox_Setup.bat): no menu, no questions $QuietMode = @{ cli = 'nexus-code'; roblox = 'roblox' }[[string]$env:NEXUS_MODE] $TestRoot = $env:NEXUS_TEST_ROOT if ($TestRoot) { $UserHome = Join-Path $TestRoot 'home' $AppData = Join-Path $TestRoot 'AppData\Roaming' $LocalApp = Join-Path $TestRoot 'AppData\Local' } else { $UserHome = [Environment]::GetFolderPath('UserProfile') $AppData = [Environment]::GetFolderPath('ApplicationData') $LocalApp = [Environment]::GetFolderPath('LocalApplicationData') } $NexusDir = Join-Path $UserHome '.nexus-ai' $BackupRoot = Join-Path $NexusDir 'backups' $RunDir = Join-Path $BackupRoot $Stamp $n = 2 while (Test-Path $RunDir) { $RunDir = Join-Path $BackupRoot ("{0}_{1}" -f $Stamp, $n); $n++ } # каждый запуск - своя папка $LogFile = Join-Path $NexusDir 'install.log' # ── вывод ───────────────────────────────────────────────────────────────────── function Say([string]$Text = '', [string]$Color = 'Gray') { Write-Host $Text -ForegroundColor $Color } function Ok([string]$Text) { Write-Host ' [OK] ' -ForegroundColor Green -NoNewline; Write-Host $Text } function Warn([string]$Text) { Write-Host ' [!] ' -ForegroundColor Yellow -NoNewline; Write-Host $Text } function Fail([string]$Text) { Write-Host ' [X] ' -ForegroundColor Red -NoNewline; Write-Host $Text } function Step([string]$Text) { Write-Host ' -> ' -ForegroundColor DarkCyan -NoNewline; Write-Host $Text } function Log([string]$Text) { try { if (-not (Test-Path $NexusDir)) { New-Item -ItemType Directory -Force -Path $NexusDir | Out-Null } Add-Content -Path $LogFile -Value ("{0} {1}" -f (Get-Date -Format 's'), $Text) -Encoding UTF8 } catch {} } function Ask([string]$Prompt, [string]$Default = '') { if ($Auto) { return $Default } $a = Read-Host $Prompt if ($null -eq $a) { return $Default } $a = $a.Trim() if ($a -eq '') { return $Default } return $a } function Mask([string]$k) { if ($k.Length -gt 12) { return $k.Substring(0, 6) + '...' + $k.Substring($k.Length - 4) } return $k } function Fmt([object]$n) { try { return ([decimal]$n).ToString('#,0', [Globalization.CultureInfo]::InvariantCulture).Replace(',', ' ') } catch { return [string]$n } } # ── резервные копии и откат ─────────────────────────────────────────────────── $script:BackedUp = @{} function Backup-Once([string]$Path) { if ($script:BackedUp.ContainsKey($Path)) { return } $script:BackedUp[$Path] = $true if (-not (Test-Path $RunDir)) { New-Item -ItemType Directory -Force -Path $RunDir | Out-Null } $manifest = Join-Path $RunDir 'manifest.tsv' if (Test-Path -LiteralPath $Path) { $name = ('{0:D2}_' -f $script:BackedUp.Count) + (Split-Path $Path -Leaf) Copy-Item -LiteralPath $Path -Destination (Join-Path $RunDir $name) -Force Add-Content -Path $manifest -Value ("FILE`t$Path`t$name`t1") -Encoding UTF8 } else { Add-Content -Path $manifest -Value ("FILE`t$Path`t`t0") -Encoding UTF8 } } function Save-Text([string]$Path, [string]$Text) { Backup-Once $Path $dir = Split-Path $Path -Parent if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } [IO.File]::WriteAllText($Path, $Text, $Utf8NoBom) } function Set-UserEnv([string]$Name, [string]$Value) { $old = Get-UserEnv $Name if (-not (Test-Path $RunDir)) { New-Item -ItemType Directory -Force -Path $RunDir | Out-Null } $had = if ($null -ne $old) { '1' } else { '0' } Add-Content -Path (Join-Path $RunDir 'manifest.tsv') -Value ("ENV`t$Name`t$old`t$had") -Encoding UTF8 if ($TestRoot) { Add-Content -Path (Join-Path $TestRoot 'user-env.txt') -Value "$Name=$Value" -Encoding UTF8 } else { [Environment]::SetEnvironmentVariable($Name, $Value, 'User') } Set-Item -Path "Env:$Name" -Value $Value } function Get-UserEnv([string]$Name) { if ($TestRoot) { $f = Join-Path $TestRoot 'user-env-initial.txt' if (Test-Path $f) { foreach ($l in Get-Content $f) { if ($l -like "$Name=*") { return $l.Substring($Name.Length + 1) } } } return $null } return [Environment]::GetEnvironmentVariable($Name, 'User') } function Invoke-Restore { $runs = @() if (Test-Path $BackupRoot) { $runs = @(Get-ChildItem $BackupRoot -Directory | Where-Object { $_.Name -match '^\d{8}_\d{6}(_\d+)?$' -and (Test-Path (Join-Path $_.FullName 'manifest.tsv')) } | Sort-Object { $_.CreationTimeUtc }, Name -Descending) } if ($runs.Count -eq 0) { Warn 'Нечего откатывать: резервных копий нет.'; return } $run = $runs[0] Say (" Откатываю установку от {0}..." -f $run.Name) White foreach ($line in Get-Content (Join-Path $run.FullName 'manifest.tsv') -Encoding UTF8) { $p = $line -split "`t" if ($p.Count -lt 4) { continue } if ($p[0] -eq 'FILE') { if ($p[3] -eq '1') { Copy-Item -LiteralPath (Join-Path $run.FullName $p[2]) -Destination $p[1] -Force; Ok "восстановлен $($p[1])" } elseif (Test-Path -LiteralPath $p[1]) { Remove-Item -LiteralPath $p[1] -Force; Ok "удалён созданный файл $($p[1])" } } elseif ($p[0] -eq 'PATHADD') { Remove-UserPath $p[1] Ok "папка $($p[1]) убрана из PATH" } elseif ($p[0] -eq 'ENV') { $value = if ($p[3] -eq '1') { $p[2] } else { $null } if (-not $TestRoot) { [Environment]::SetEnvironmentVariable($p[1], $value, 'User') } Ok "переменная $($p[1]) возвращена" } } Rename-Item -LiteralPath $run.FullName -NewName ($run.Name + '.restored') Say '' Ok 'Готово. Установленные расширения (Cline) не удалялись - их можно удалить в редакторе.' Log "restore $($run.Name)" } # ── JSON / JSONC: читаем с комментариями, меняем только нужные ключи, остальной файл не трогаем ── $script:TokRe = [regex]'"(?:[^"\\]|\\.)*"|//[^\r\n]*|/\*[\s\S]*?\*/|[{}\[\]:,]|[^\s{}\[\]:,"/]+' function Get-JsonTokens([string]$Text) { $list = New-Object System.Collections.Generic.List[object] foreach ($m in $script:TokRe.Matches($Text)) { if ($m.Value.StartsWith('//') -or $m.Value.StartsWith('/*')) { continue } $list.Add($m) } return , $list } function ConvertFrom-Jsonc([string]$Text) { $toks = Get-JsonTokens $Text if ($toks.Count -eq 0) { return $null } $parts = New-Object System.Collections.Generic.List[string] for ($i = 0; $i -lt $toks.Count; $i++) { $v = $toks[$i].Value if ($v -eq ',' -and ($i + 1) -lt $toks.Count -and ($toks[$i + 1].Value -eq '}' -or $toks[$i + 1].Value -eq ']')) { continue } $parts.Add($v) } return (($parts -join ' ') | ConvertFrom-Json) } function Read-JsonFile([string]$Path) { # $null - файла нет или он пуст; исключение - файл есть, но это не JSON-объект (такие файлы не трогаем) if (-not (Test-Path -LiteralPath $Path)) { return $null } $text = [IO.File]::ReadAllText($Path) if (-not $text.Trim()) { return $null } $obj = ConvertFrom-Jsonc $text if ($null -eq $obj -or $obj.GetType().FullName -ne 'System.Management.Automation.PSCustomObject') { throw "в $Path не JSON-объект" } return $obj } function ConvertTo-JsonStr([string]$s) { $s = $s.Replace('\', '\\').Replace('"', '\"').Replace("`r", '\r').Replace("`n", '\n').Replace("`t", '\t') $s = [regex]::Replace($s, '[\x00-\x1f]', { param($m) '\u{0:x4}' -f [int][char]$m.Value }) return '"' + $s + '"' } function ConvertTo-NexusJson($Value, [int]$Level, [string]$Unit, [string]$NL) { if ($null -eq $Value) { return 'null' } if ($Value -is [bool]) { if ($Value) { return 'true' } else { return 'false' } } if ($Value -is [string] -or $Value -is [char]) { return (ConvertTo-JsonStr ([string]$Value)) } if ($Value -is [double] -or $Value -is [single]) { return $Value.ToString('R', [Globalization.CultureInfo]::InvariantCulture) } if ($Value -is [int] -or $Value -is [long] -or $Value -is [decimal] -or $Value -is [int16] -or $Value -is [byte] -or $Value -is [uint32] -or $Value -is [uint64]) { return ([System.Convert]::ToString($Value, [Globalization.CultureInfo]::InvariantCulture)) } $pad = $Unit * ($Level + 1) $end = $Unit * $Level $isDict = $Value -is [System.Collections.IDictionary] $isObj = $Value.GetType().FullName -eq 'System.Management.Automation.PSCustomObject' if ($isDict -or $isObj) { $items = New-Object System.Collections.Generic.List[string] if ($isDict) { foreach ($k in $Value.Keys) { $items.Add($pad + (ConvertTo-JsonStr ([string]$k)) + ': ' + (ConvertTo-NexusJson $Value[$k] ($Level + 1) $Unit $NL)) } } else { foreach ($p in $Value.PSObject.Properties) { $items.Add($pad + (ConvertTo-JsonStr $p.Name) + ': ' + (ConvertTo-NexusJson $p.Value ($Level + 1) $Unit $NL)) } } if ($items.Count -eq 0) { return '{}' } return '{' + $NL + ($items -join (',' + $NL)) + $NL + $end + '}' } if ($Value -is [System.Collections.IEnumerable]) { $items = New-Object System.Collections.Generic.List[string] foreach ($x in $Value) { $items.Add($pad + (ConvertTo-NexusJson $x ($Level + 1) $Unit $NL)) } if ($items.Count -eq 0) { return '[]' } return '[' + $NL + ($items -join (',' + $NL)) + $NL + $end + ']' } return (ConvertTo-JsonStr ([string]$Value)) } function Set-JsonTopLevel([string]$Text, [string]$Key, [string]$ValueJson, [string]$NL, [string]$Unit) { $toks = Get-JsonTokens $Text if ($toks.Count -eq 0 -or $toks[0].Value -ne '{') { throw 'корень файла - не объект' } $depth = 0 $close = $null $quoted = ConvertTo-JsonStr $Key for ($i = 0; $i -lt $toks.Count; $i++) { $v = $toks[$i].Value if ($v -eq '{' -or $v -eq '[') { $depth++; continue } if ($v -eq '}' -or $v -eq ']') { $depth--; if ($depth -eq 0) { $close = $i; break }; continue } if ($depth -eq 1 -and $v -ceq $quoted -and ($i + 2) -lt $toks.Count -and $toks[$i + 1].Value -eq ':') { $j = $i + 2 $start = $toks[$j].Index $stop = $toks[$j].Index + $toks[$j].Length if ($toks[$j].Value -eq '{' -or $toks[$j].Value -eq '[') { $d = 0 for ($k = $j; $k -lt $toks.Count; $k++) { $t = $toks[$k].Value if ($t -eq '{' -or $t -eq '[') { $d++ } elseif ($t -eq '}' -or $t -eq ']') { $d--; if ($d -eq 0) { $stop = $toks[$k].Index + 1; break } } } } return $Text.Substring(0, $start) + $ValueJson + $Text.Substring($stop) } } if ($null -eq $close) { throw 'не найден конец объекта' } $prev = $toks[$close - 1] $prop = $quoted + ': ' + $ValueJson if ($prev.Value -eq '{') { return $Text.Substring(0, $prev.Index + 1) + $NL + $Unit + $prop + $NL + $Text.Substring($toks[$close].Index) } if ($prev.Value -eq ',') { $at = $prev.Index + 1; $sep = $NL + $Unit } else { $at = $prev.Index + $prev.Length; $sep = ',' + $NL + $Unit } return $Text.Substring(0, $at) + $sep + $prop + $Text.Substring($at) } function Update-JsonFile([string]$Path, $Values) { # Values: упорядоченный словарь "ключ верхнего уровня" -> значение. Остальные ключи и комментарии сохраняются. $text = '' if (Test-Path -LiteralPath $Path) { $text = [IO.File]::ReadAllText($Path) } if (-not $text.Trim()) { $text = "{`n}" } $NL = if ($text.Contains("`r`n")) { "`r`n" } else { "`n" } $m = [regex]::Match($text, '\n([ \t]+)"') $Unit = if ($m.Success) { $m.Groups[1].Value } else { ' ' } $null = ConvertFrom-Jsonc $text foreach ($k in $Values.Keys) { $json = ConvertTo-NexusJson $Values[$k] 1 $Unit $NL $text = Set-JsonTopLevel $text $k $json $NL $Unit } $null = ConvertFrom-Jsonc $text # результат обязан оставаться валидным, иначе ничего не пишем Save-Text $Path $text } function New-Obj { return New-Object PSObject } function Set-Prop($Obj, [string]$Name, $Value) { if ($Obj.PSObject.Properties[$Name]) { $Obj.$Name = $Value } else { $Obj | Add-Member -NotePropertyName $Name -NotePropertyValue $Value } } function Get-Prop($Obj, [string]$Name) { if ($null -ne $Obj -and $Obj.PSObject.Properties[$Name]) { return $Obj.$Name } return $null } function Get-ObjProp($Obj, [string]$Name) { $v = Get-Prop $Obj $Name if ($null -ne $v -and $v.GetType().FullName -eq 'System.Management.Automation.PSCustomObject') { return $v } return (New-Obj) } # ── меню в терминале: стрелки, Пробел, A, Enter ─────────────────────────────── function Test-Tui { # a real console only: tests, NEXUS_AUTO and piped input keep the numbered text menu if ($Auto -or $TestRoot) { return $false } try { return (-not [Console]::IsInputRedirected) -and [Console]::WindowHeight -ge 12 -and [Console]::WindowWidth -ge 50 } catch { return $false } } function Invoke-Menu { # Rows: objects with Label, On, Kind ('check' | 'radio'). Returns 'ok' | 'quit' | 'restore'. param([string]$Title, [object[]]$Rows, [string]$Hint, [string[]]$Extra = @()) $n = $Rows.Count $view = [Math]::Max(4, [Math]::Min($n, [Console]::WindowHeight - 8)) $height = $view + 2 $width = [Math]::Max(40, [Console]::WindowWidth - 1) for ($i = 0; $i -lt $height; $i++) { Write-Host '' } $top = [Math]::Max(0, [Console]::CursorTop - $height) $pos = 0; $first = 0 $fit = { param($t) $t = $t.PadRight($width); if ($t.Length -gt $width) { $t = $t.Substring(0, $width) }; $t } try { [Console]::CursorVisible = $false } catch {} try { while ($true) { if ($pos -lt $first) { $first = $pos } elseif ($pos -ge $first + $view) { $first = $pos - $view + 1 } [Console]::SetCursorPosition(0, $top) Write-Host (& $fit (' ' + $Title)) -ForegroundColor White $last = [Math]::Min($n, $first + $view) for ($i = $first; $i -lt $last; $i++) { $r = $Rows[$i] $box = if ($r.Kind -eq 'radio') { if ($r.On) { '(*)' } else { '( )' } } else { if ($r.On) { '[x]' } else { '[ ]' } } $line = & $fit (' {0} {1} {2}' -f $(if ($i -eq $pos) { '>' } else { ' ' }), $box, $r.Label) if ($i -eq $pos) { Write-Host $line -ForegroundColor Black -BackgroundColor DarkYellow } elseif ($r.On) { Write-Host $line -ForegroundColor White } else { Write-Host $line -ForegroundColor Gray } } for ($i = $last - $first; $i -lt $view; $i++) { Write-Host (& $fit '') } $more = if ($n -gt $view) { ' [{0}-{1} из {2}]' -f ($first + 1), $last, $n } else { '' } Write-Host (& $fit (' ' + $Hint + $more)) -ForegroundColor DarkGray $k = [Console]::ReadKey($true) $ch = [string][char]::ToLower($k.KeyChar) if ($k.Key -eq 'UpArrow') { $pos = ($pos - 1 + $n) % $n } elseif ($k.Key -eq 'DownArrow') { $pos = ($pos + 1) % $n } elseif ($k.Key -eq 'PageUp') { $pos = [Math]::Max(0, $pos - $view) } elseif ($k.Key -eq 'PageDown') { $pos = [Math]::Min($n - 1, $pos + $view) } elseif ($k.Key -eq 'Home') { $pos = 0 } elseif ($k.Key -eq 'End') { $pos = $n - 1 } elseif ($k.Key -eq 'Spacebar') { $r = $Rows[$pos] if ($r.Kind -eq 'radio') { foreach ($x in $Rows) { if ($x.Kind -eq 'radio') { $x.On = $false } }; $r.On = $true } else { $r.On = -not $r.On } } elseif ($k.Key -eq 'Enter') { $r = $Rows[$pos] if ($r.Kind -eq 'radio') { foreach ($x in $Rows) { if ($x.Kind -eq 'radio') { $x.On = $false } }; $r.On = $true } return 'ok' } elseif ($k.Key -eq 'Escape' -or (($ch -eq 'q' -or $ch -eq 'й') -and $Extra -contains 'quit')) { return 'quit' } elseif (($ch -eq 'a' -or $ch -eq 'ф') -and @($Rows | Where-Object { $_.Kind -eq 'check' }).Count -gt 1) { $checks = @($Rows | Where-Object { $_.Kind -eq 'check' }) $all = @($checks | Where-Object { -not $_.On }).Count -gt 0 foreach ($x in $checks) { $x.On = $all } } elseif (($ch -eq 'r' -or $ch -eq 'к') -and $Extra -contains 'restore') { return 'restore' } } } finally { try { [Console]::CursorVisible = $true } catch {} try { [Console]::SetCursorPosition(0, $top + $height) } catch {} } } # ── поиск программ ──────────────────────────────────────────────────────────── function Find-Cli([string[]]$Names) { if ($TestRoot) { foreach ($n in $Names) { $p = Join-Path $TestRoot "bin\$n"; if (Test-Path $p) { return $p } } return $null } foreach ($n in $Names) { $c = Get-Command $n -ErrorAction SilentlyContinue | Select-Object -First 1 if ($c) { return $c.Source } } return $null } function Refresh-Path { if ($TestRoot) { return } $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User') } $Editors = @( [pscustomobject]@{ Id = 'cursor'; Name = 'Cursor'; Cmd = 'cursor.cmd'; Paths = @("$LocalApp\Programs\cursor\resources\app\bin\cursor.cmd"); User = "$AppData\Cursor\User"; Ext = "$UserHome\.cursor\extensions"; Cli = $null; Found = $false }, [pscustomobject]@{ Id = 'vscode'; Name = 'VS Code'; Cmd = 'code.cmd'; Paths = @("$LocalApp\Programs\Microsoft VS Code\bin\code.cmd", "$env:ProgramFiles\Microsoft VS Code\bin\code.cmd"); User = "$AppData\Code\User"; Ext = "$UserHome\.vscode\extensions"; Cli = $null; Found = $false }, [pscustomobject]@{ Id = 'windsurf'; Name = 'Windsurf'; Cmd = 'windsurf.cmd'; Paths = @("$LocalApp\Programs\Windsurf\bin\windsurf.cmd"); User = "$AppData\Windsurf\User"; Ext = "$UserHome\.windsurf\extensions"; Cli = $null; Found = $false } ) function Detect-Editors { foreach ($e in $Editors) { $cli = $null if ($TestRoot) { $cli = Find-Cli @($e.Cmd) } else { foreach ($p in $e.Paths) { if ($p -and (Test-Path -LiteralPath $p)) { $cli = $p; break } } if (-not $cli) { $cli = Find-Cli @($e.Cmd) } } $e.Cli = $cli $e.Found = [bool]($cli -or (Test-Path -LiteralPath $e.User)) } } function Test-Extension($Editor, [string]$Prefix) { if (-not (Test-Path -LiteralPath $Editor.Ext)) { return $false } return [bool](Get-ChildItem -LiteralPath $Editor.Ext -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "$Prefix-*" } | Select-Object -First 1) } function Invoke-Tool([string]$File, [string[]]$Arguments, [int]$TimeoutSec = 300) { # запускает .cmd/.exe, ждёт с таймаутом, возвращает код выхода и вывод. # Пока ждём - печатаем точки, чтобы долгая установка (Cursor ставит расширение 1-2 минуты) не выглядела зависанием. $out = [IO.Path]::GetTempFileName(); $err = [IO.Path]::GetTempFileName() try { $p = Start-Process -FilePath $File -ArgumentList $Arguments -NoNewWindow -PassThru -RedirectStandardOutput $out -RedirectStandardError $err $null = $p.Handle # без этого PowerShell 5.1 теряет код выхода ($p.ExitCode = пусто), и успешная установка считалась ошибкой $sw = [Diagnostics.Stopwatch]::StartNew(); $dots = $false while (-not $p.WaitForExit(3000)) { if ($sw.Elapsed.TotalSeconds -ge $TimeoutSec) { try { $p.Kill() } catch {} if ($dots) { Write-Host '' } return @{ Code = -1; Text = 'таймаут' } } if (-not $dots) { Write-Host ' ' -NoNewline; $dots = $true } Write-Host '.' -NoNewline -ForegroundColor DarkGray } if ($dots) { Write-Host (' {0} c' -f [int]$sw.Elapsed.TotalSeconds) -ForegroundColor DarkGray } $p.WaitForExit() $text = ((Get-Content $out -Raw -ErrorAction SilentlyContinue) + "`n" + (Get-Content $err -Raw -ErrorAction SilentlyContinue)).Trim() return @{ Code = $p.ExitCode; Text = $text } } catch { return @{ Code = -1; Text = $_.Exception.Message } } finally { Remove-Item $out, $err -Force -ErrorAction SilentlyContinue } } function Install-Extension($Editor, [string]$Id) { if ($TestRoot) { New-Item -ItemType Directory -Force -Path (Join-Path $Editor.Ext ($Id.ToLower() + '-9.9.9')) | Out-Null return $true } if (-not $Editor.Cli) { return $false } $r = Invoke-Tool $Editor.Cli @('--install-extension', $Id, '--force') 240 # успех определяем не только по коду выхода: редактор пишет предупреждения в stderr, # а иногда возвращает не 0 при успешной установке if ($r.Code -eq 0 -or $r.Text -match 'successfully installed|already installed|уже установлен' -or (Test-Extension $Editor $Id.ToLower())) { return $true } Log "extension $Id in $($Editor.Name) failed (code $($r.Code)): $($r.Text)" return $false } # ── ключ и каталог моделей ──────────────────────────────────────────────────── function Invoke-Api([string]$Path, [string]$Key) { $h = @{} if ($Key) { $h['Authorization'] = "Bearer $Key" } return Invoke-RestMethod -Uri ($OpenAiBase + $Path) -Headers $h -TimeoutSec 25 -UseBasicParsing } function Test-Key([string]$Key) { # возвращает 'ok' | 'bad' | 'expired' | 'offline', печатает баланс try { $b = Invoke-Api '/balance' $Key if ($b.is_unlimited) { Ok ("Ключ {0} активен - безлимит" -f (Mask $Key)) } else { $extra = '' if ($b.expires_at) { try { $extra = ' · действует до ' + ([datetime]$b.expires_at).ToString('dd.MM.yyyy') } catch {} } Ok ("Ключ {0} активен - баланс {1} токенов{2}" -f (Mask $Key), (Fmt $b.remaining_credits), $extra) } if ($b.remaining_credits -le 0 -and -not $b.is_unlimited) { Warn 'Баланс пуст - программы подключатся, но ответы пойдут после пополнения на FunPay.' } return 'ok' } catch { $code = $null try { $code = [int]$_.Exception.Response.StatusCode } catch {} if ($code -eq 401 -or $code -eq 403) { if ("$($_.ErrorDetails.Message)" -match 'expired') { return 'expired' } return 'bad' } Warn ("Не удалось проверить ключ ({0}). Продолжаю - проверьте интернет позже." -f $_.Exception.Message) return 'offline' } } $Curated = @( @{ Id = 'deepseek-v4-flash'; Note = 'быстрая, для кода и агентов' }, @{ Id = 'claude-haiku-5.5'; Note = 'модель Anthropic: тексты и код' }, @{ Id = 'gpt-6-luna'; Note = 'модель OpenAI' }, @{ Id = 'minimax-m2.7'; Note = 'с рассуждениями' }, @{ Id = 'glm-5.3-flash'; Note = 'быстрая и дешёвая' }, @{ Id = 'gemini-2.5-flash'; Note = 'видит изображения' } ) $FastCandidates = @('claude-haiku-5.5', 'glm-5.3-flash', 'deepseek-v4-flash') function Get-Catalog { # the public catalogue: the real models with their names, multipliers and context windows. # Not /v1/models: that list also carries the extra alias ids made for Cursor (sonnet-5, 4.7-grok, ...). $script:CatalogNames = @{}; $script:CatalogCtx = @{} try { $r = Invoke-RestMethod -Uri "$Site/api/public/models" -TimeoutSec 25 -UseBasicParsing # a JSON array arrives as Object[] (and $r.models on it would enumerate nulls); an object may wrap it in .models $list = if ($r -is [array]) { $r } elseif ($null -ne $r.models) { $r.models } else { @($r) } $map = @{} foreach ($m in @($list)) { $id = [string]$m.id if (-not $id) { continue } $map[$id] = if ($null -ne $m.rate) { $m.rate } else { $m.multiplier } if ($m.name) { $script:CatalogNames[$id] = [string]$m.name } if ($m.context_window) { $script:CatalogCtx[$id] = [int]$m.context_window } } if ($map.Count -gt 0) { return $map } } catch {} return $null } function Get-ModelContext([string]$Id) { if ($script:CatalogCtx -and $script:CatalogCtx[$Id]) { return [int]$script:CatalogCtx[$Id] } return 128000 } # Public NEXUS addresses. The main one has the real domain; Moscow works from Russia without a VPN, the US one suits a VPN. $Servers = @( [pscustomobject]@{ Id = 'ru'; Name = 'Россия'; Url = 'https://178-236-242-96.nip.io' }, [pscustomobject]@{ Id = 'eu'; Name = 'Нидерланды'; Url = 'https://94-228-161-75.nip.io' }, [pscustomobject]@{ Id = 'us'; Name = 'США'; Url = 'https://34-227-208-238.nip.io' } ) function Measure-Server([string]$Url) { # best of three /health round trips, $null when the server does not answer $best = $null for ($i = 0; $i -lt 3; $i++) { $sw = [Diagnostics.Stopwatch]::StartNew() try { $null = Invoke-WebRequest -Uri "$Url/health" -UseBasicParsing -TimeoutSec 5 $ms = [int]$sw.Elapsed.TotalMilliseconds if ($null -eq $best -or $ms -lt $best) { $best = $ms } } catch {} } return $best } function Choose-Server { # only for the public addresses: tests and a custom NEXUS_SITE stay as given; NEXUS_SERVER=main|ru|us forces one if ($TestRoot -or ($Site -notmatch '^https://(nexusallin\.store|[0-9-]+\.nip\.io)$')) { return } Step 'Выбираю самый быстрый сервер...' $res = @(foreach ($s in $Servers) { [pscustomobject]@{ S = $s; Ms = (Measure-Server $s.Url) } }) if (-not $QuietMode) { Say (' ' + (($res | ForEach-Object { '{0}: {1}' -f $_.S.Name, $(if ($null -ne $_.Ms) { "$($_.Ms) мс" } else { 'нет связи' }) }) -join ' | ')) DarkGray } $pick = $res | Where-Object { $_.S.Id -eq $(if ($env:NEXUS_SERVER -eq 'main') { 'ru' } else { [string]$env:NEXUS_SERVER }) -and $null -ne $_.Ms } | Select-Object -First 1 if (-not $pick) { $alive = @($res | Where-Object { $null -ne $_.Ms } | Sort-Object Ms) if ($alive.Count -eq 0) { Warn "Ни один сервер не ответил - оставляю $Site. Проверьте интернет."; return } $pick = $alive[0] $main = $res | Where-Object { $_.S.Id -eq 'main' -and $null -ne $_.Ms } | Select-Object -First 1 # the main address stays unless another one is clearly faster if ($main -and ($main.Ms -le $pick.Ms * 1.3 -or ($main.Ms - $pick.Ms) -lt 40)) { $pick = $main } } $script:Site = $pick.S.Url $script:OpenAiBase = "$($script:Site)/v1" Ok "Сервер: $($pick.S.Name) - $($pick.Ms) мс" } function Test-Model { # one short request through the configured address, key and model: shows that answers really come Step "Проверяю связь: короткий запрос к $($script:Model)..." $body = @{ model = $script:Model; max_tokens = 16; stream = $false; messages = @(@{ role = 'user'; content = 'Reply with one word: ok' }) } | ConvertTo-Json -Depth 5 -Compress $sw = [Diagnostics.Stopwatch]::StartNew() try { $null = Invoke-RestMethod -Uri "$OpenAiBase/chat/completions" -Method Post -Headers @{ Authorization = "Bearer $($script:Key)" } ` -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($body)) -TimeoutSec 90 -UseBasicParsing Ok ("Связь есть: {0} ответила за {1} с" -f $script:Model, $sw.Elapsed.TotalSeconds.ToString('0.0', [Globalization.CultureInfo]::InvariantCulture)) } catch { $code = $null try { $code = [int]$_.Exception.Response.StatusCode } catch {} $why = if ($code) { "HTTP $code" } else { $_.Exception.Message } Warn "Проверочный запрос не прошёл ($why). Программы настроены; если ответов нет - проверьте баланс: $Site/stats.html" } } # ── действия ────────────────────────────────────────────────────────────────── # Cline 4 keeps its providers in files under ~/.cline/data/settings, shared by the VS Code / Cursor / Windsurf extension, # JetBrains and the Cline CLI: models.json declares the "NEXUS AI" provider with every model, providers.json holds its key # and makes it the current provider. Other providers the user has there are kept. function Get-ClineDataDir { if ($TestRoot) { return (Join-Path $UserHome '.cline\data') } if ($env:CLINE_DATA_DIR) { return $env:CLINE_DATA_DIR } if ($env:CLINE_DIR) { return (Join-Path $env:CLINE_DIR 'data') } return (Join-Path $UserHome '.cline\data') } function Apply-Cline { if ($script:ClineDone) { return } $dir = Join-Path (Get-ClineDataDir) 'settings' $models = [ordered]@{} foreach ($id in (Get-AllModelIds)) { $models[$id] = [ordered]@{ id = $id; name = (Get-ModelLabel $id); contextWindow = (Get-ModelContext $id); maxInputTokens = (Get-ModelContext $id); capabilities = @('streaming', 'tools', 'images') } } $mPath = Join-Path $dir 'models.json' $cur = Read-JsonFile $mPath $provs = Get-ObjProp $cur 'providers' Set-Prop $provs 'nexus' ([ordered]@{ provider = [ordered]@{ name = 'NEXUS AI'; baseUrl = $OpenAiBase; defaultModelId = $script:Model; protocol = 'openai-chat'; client = 'openai-compatible' } models = $models }) Update-JsonFile $mPath ([ordered]@{ version = 1; providers = $provs }) $pPath = Join-Path $dir 'providers.json' $cur = Read-JsonFile $pPath $provs = Get-ObjProp $cur 'providers' $settings = [ordered]@{ provider = 'nexus'; apiKey = $script:Key; model = $script:Model; baseUrl = $OpenAiBase } $stamp = [DateTime]::UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", [Globalization.CultureInfo]::InvariantCulture) $old = Get-Prop $provs 'nexus' $oldSet = Get-Prop $old 'settings' if ($oldSet -and (Get-Prop $oldSet 'apiKey') -eq $script:Key -and (Get-Prop $oldSet 'model') -eq $script:Model -and (Get-Prop $oldSet 'baseUrl') -eq $OpenAiBase) { $prev = Get-Prop $old 'updatedAt' # nothing changed: keep the old time so a repeated run leaves the file as it is if ($prev -is [datetime]) { $prev = $prev.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", [Globalization.CultureInfo]::InvariantCulture) } if ($prev) { $stamp = [string]$prev } } Set-Prop $provs 'nexus' ([ordered]@{ settings = $settings; updatedAt = $stamp; tokenSource = 'manual' }) Update-JsonFile $pPath ([ordered]@{ version = 1; lastUsedProvider = 'nexus'; modes = (Get-ObjProp $cur 'modes'); providers = $provs }) $script:ClineDone = $true Ok "Cline подключён к NEXUS AI (модель $($script:Model); остальные модели - в списке провайдера NEXUS AI)" } function Apply-EditorCline($Editor) { if (Test-Extension $Editor 'saoudrizwan.claude-dev') { Ok "$($Editor.Name): Cline уже установлен" } else { Step "$($Editor.Name): устанавливаю Cline (до 1-2 минут)..." if (Install-Extension $Editor 'saoudrizwan.claude-dev') { Ok "$($Editor.Name): Cline установлен" } else { Warn "$($Editor.Name): не удалось поставить Cline автоматически. Поставьте вручную: Ctrl+Shift+X -> Cline -> Install. Настройки уже готовы и подхватятся сами." $script:Notes.Add("$($Editor.Name): установите расширение Cline (Ctrl+Shift+X -> Cline) - NEXUS AI подключится сам.") } } Apply-Cline if ($Editor.Id -eq 'cursor') { # Cursor 3 opens in the Agents window, where third-party extensions are not shown $script:Next.Add("Cursor: перезапустите Cursor и откройте окно редактора (File -> Open Editor Window или папку проекта). " + "Cline - значок слева; провайдер NEXUS AI уже выбран. Нет значка: Ctrl+Shift+P -> Cline: Open In New Tab.") } else { $script:Next.Add("$($Editor.Name): перезапустите редактор -> слева значок Cline -> пишите задачу (провайдер NEXUS AI уже выбран).") } } function Apply-EditorClaudeExt($Editor) { $path = Join-Path $Editor.User 'settings.json' $cur = Read-JsonFile $path $list = New-Object System.Collections.Generic.List[object] $ours = @('ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_MODEL', 'ANTHROPIC_API_KEY', 'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY') $existing = Get-Prop $cur 'claudeCode.environmentVariables' if ($existing) { foreach ($e in @($existing)) { if ($ours -notcontains [string](Get-Prop $e 'name')) { $list.Add($e) } } } $list.Add([ordered]@{ name = 'ANTHROPIC_BASE_URL'; value = $Site }) $list.Add([ordered]@{ name = 'ANTHROPIC_AUTH_TOKEN'; value = $script:Key }) $list.Add([ordered]@{ name = 'ANTHROPIC_MODEL'; value = $script:Model }) $list.Add([ordered]@{ name = 'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY'; value = '1' }) Update-JsonFile $path ([ordered]@{ 'claudeCode.environmentVariables' = $list.ToArray(); 'claudeCode.disableLoginPrompt' = $true }) Ok "$($Editor.Name): расширение Claude Code подключено" } function Apply-ClaudeCode { $path = Join-Path $UserHome '.claude\settings.json' $cur = Read-JsonFile $path $envObj = Get-ObjProp $cur 'env' if ($envObj.PSObject.Properties['ANTHROPIC_API_KEY']) { $envObj.PSObject.Properties.Remove('ANTHROPIC_API_KEY') } Set-Prop $envObj 'ANTHROPIC_BASE_URL' $Site Set-Prop $envObj 'ANTHROPIC_AUTH_TOKEN' $script:Key Set-Prop $envObj 'ANTHROPIC_MODEL' $script:Model Set-Prop $envObj 'ANTHROPIC_DEFAULT_HAIKU_MODEL' $script:FastModel Set-Prop $envObj 'ANTHROPIC_SMALL_FAST_MODEL' $script:FastModel Set-Prop $envObj 'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY' '1' Update-JsonFile $path ([ordered]@{ env = $envObj }) # первый запуск без экрана входа в аккаунт Anthropic $state = Join-Path $UserHome '.claude.json' if (-not (Test-Path -LiteralPath $state)) { Save-Text $state "{`n `"hasCompletedOnboarding`": true`n}`n" } else { $t = [IO.File]::ReadAllText($state) if ($t -match '"hasCompletedOnboarding"\s*:\s*false') { Save-Text $state ([regex]::Replace($t, '"hasCompletedOnboarding"\s*:\s*false', '"hasCompletedOnboarding": true', 1)) } elseif ($t -notmatch '"hasCompletedOnboarding"') { $i = $t.IndexOf('{') if ($i -ge 0) { Save-Text $state ($t.Substring(0, $i + 1) + "`n `"hasCompletedOnboarding`": true," + $t.Substring($i + 1)) } } } # старые постоянные переменные Windows перекрывают settings.json - обновляем их foreach ($pair in @(@('ANTHROPIC_BASE_URL', $Site), @('ANTHROPIC_AUTH_TOKEN', $script:Key), @('ANTHROPIC_MODEL', $script:Model))) { if ($null -ne (Get-UserEnv $pair[0])) { Set-UserEnv $pair[0] $pair[1]; Ok "обновлена переменная Windows $($pair[0])" } } $apiKey = Get-UserEnv 'ANTHROPIC_API_KEY' if ($apiKey) { Warn 'В Windows задана переменная ANTHROPIC_API_KEY (другой ключ). Если Claude Code спросит, использовать ли её, ответьте No.' } Ok "Claude Code подключён (модель $($script:Model), фоновая $($script:FastModel))" $script:Next.Add('Claude Code: откройте НОВОЕ окно терминала в папке проекта -> claude') } function Apply-Codex { $dir = if ($env:CODEX_HOME -and -not $TestRoot) { $env:CODEX_HOME } else { Join-Path $UserHome '.codex' } $path = Join-Path $dir 'config.toml' $lines = @() if (Test-Path -LiteralPath $path) { $lines = @([IO.File]::ReadAllLines($path)) } $top = New-Object System.Collections.Generic.List[string] $rest = New-Object System.Collections.Generic.List[string] $inTables = $false; $skip = $false foreach ($l in $lines) { if ($l -match '^\s*\[') { $inTables = $true $skip = ($l -match '^\s*\[\s*model_providers\s*\.\s*"?tokine"?\s*(\]|\.)') } if (-not $inTables) { if ($l -match '^\s*(model|model_provider)\s*=') { continue } $top.Add($l) } elseif (-not $skip) { $rest.Add($l) } } while ($top.Count -gt 0 -and -not $top[0].Trim()) { $top.RemoveAt(0) } while ($top.Count -gt 0 -and -not $top[$top.Count - 1].Trim()) { $top.RemoveAt($top.Count - 1) } while ($rest.Count -gt 0 -and -not $rest[$rest.Count - 1].Trim()) { $rest.RemoveAt($rest.Count - 1) } $out = New-Object System.Collections.Generic.List[string] $out.Add("model = `"$($script:Model)`"") $out.Add('model_provider = "tokine"') foreach ($l in $top) { $out.Add($l) } if ($rest.Count -gt 0) { $out.Add(''); foreach ($l in $rest) { $out.Add($l) } } $out.Add('') $out.Add('[model_providers.tokine]') $out.Add('name = "NEXUS_AI Gateway"') $out.Add("base_url = `"$OpenAiBase`"") $out.Add('wire_api = "responses"') $out.Add("experimental_bearer_token = `"$($script:Key)`"") Save-Text $path (($out -join "`n") + "`n") Ok "Codex CLI подключён (модель $($script:Model))" $script:Next.Add('Codex: откройте НОВОЕ окно терминала в папке проекта -> codex') } $script:AllModels = ([string]$env:NEXUS_ALL_MODELS -ne '0') function Get-ModelEntries { # the main model first, then the recommended ones; with "all models" every catalogue model follows $ids = New-Object System.Collections.Generic.List[string] $ids.Add($script:Model) foreach ($c in $Curated) { if (-not $ids.Contains($c.Id) -and ($null -eq $script:Catalog -or $script:Catalog.ContainsKey($c.Id))) { $ids.Add($c.Id) } } if ($script:AllModels -and $script:Catalog) { foreach ($k in (Get-CatalogOrder)) { if (-not $ids.Contains($k)) { $ids.Add($k) } } } return , $ids } function Get-CatalogOrder { # catalogue ids, cheapest first if (-not $script:Catalog) { return @() } return @($script:Catalog.Keys | Sort-Object { [double]$(if ($null -ne $script:Catalog[$_]) { $script:Catalog[$_] } else { 1 }) }, { $_ }) } function Apply-Qwen { $path = Join-Path $UserHome '.qwen\settings.json' $cur = Read-JsonFile $path $providers = Get-ObjProp $cur 'modelProviders' $list = New-Object System.Collections.Generic.List[object] $existing = Get-Prop $providers 'openai' # entries of every NEXUS address are replaced (a server switch used to leave the old ones as duplicates) $ours = @($Servers | ForEach-Object { $_.Url }) + @($Site) if ($existing) { foreach ($e in @($existing)) { $u = [string](Get-Prop $e 'baseUrl') if (-not ($ours | Where-Object { $u -like "$_*" })) { $list.Add($e) } } } foreach ($id in (Get-ModelEntries)) { $list.Add([ordered]@{ id = $id; name = "$id (NEXUS_AI)"; baseUrl = $OpenAiBase; envKey = 'NEXUS_API_KEY' }) } Set-Prop $providers 'openai' $list.ToArray() $envObj = Get-ObjProp $cur 'env' Set-Prop $envObj 'NEXUS_API_KEY' $script:Key $security = Get-ObjProp $cur 'security' $auth = Get-ObjProp $security 'auth' Set-Prop $auth 'selectedType' 'openai' Set-Prop $security 'auth' $auth $modelObj = Get-ObjProp $cur 'model' Set-Prop $modelObj 'name' $script:Model Update-JsonFile $path ([ordered]@{ modelProviders = $providers; env = $envObj; security = $security; model = $modelObj }) Ok "Qwen Code подключён (модель $($script:Model))" $script:Next.Add('Qwen Code: откройте НОВОЕ окно терминала -> qwen') } # every catalogue model, cheapest first (the chosen and the recommended ones lead) function Get-AllModelIds { return (Get-ModelEntries) } # " · ×", as on the pricing page function Get-ModelLabel([string]$Id) { # "Claude Sonnet 5.5 · ×8": the catalogue name and multiplier, as on the pricing page $name = if ($script:CatalogNames -and $script:CatalogNames[$Id]) { $script:CatalogNames[$Id] } else { $Id } if ($script:Catalog -and $null -ne $script:Catalog[$Id]) { return ('{0} {1} {2}{3}' -f $name, [char]0x00B7, [char]0x00D7, ([double]$script:Catalog[$Id]).ToString([Globalization.CultureInfo]::InvariantCulture)) } return $name } function Get-OpenCodeProvider { $models = [ordered]@{} foreach ($id in (Get-AllModelIds)) { $models[$id] = [ordered]@{ name = (Get-ModelLabel $id) } } return [ordered]@{ npm = '@ai-sdk/openai-compatible'; name = 'NEXUS_AI'; options = [ordered]@{ baseURL = $OpenAiBase; apiKey = $script:Key }; models = $models } } function Apply-OpenCodeLike([string]$Path, [string]$Title) { $cur = Read-JsonFile $Path $values = [ordered]@{} if (-not (Get-Prop $cur '$schema') -and @('OpenCode', 'NEXUS CLI') -contains $Title) { $values['$schema'] = 'https://opencode.ai/config.json' } $prov = Get-ObjProp $cur 'provider' Set-Prop $prov 'nexus' (Get-OpenCodeProvider) $values['provider'] = $prov $values['model'] = "nexus/$($script:Model)" if (-not (Get-Prop $cur 'small_model')) { $values['small_model'] = "nexus/$($script:FastModel)" } Update-JsonFile $Path $values Ok "$Title подключён (модель nexus/$($script:Model))" } function Apply-OpenCode { Apply-OpenCodeLike (Join-Path $UserHome '.config\opencode\opencode.json') 'OpenCode' $script:Next.Add('OpenCode: в папке проекта -> opencode (модели в /models, провайдер NEXUS_AI)') } # NEXUS CLI (terminal agent) = a pinned release of its open-source engine + our setup in ~/.nexus-ai/code. # The engine is the signed binary from the npm registry (checked by sha512 and signature), kept as # ~/.nexus-ai/code/engine/nexus-cli.exe: no Node.js needed, no update prompts, other tools on the computer untouched. # Files come from $Site/install/nexus-code/ (theme, plugins, launcher); the launcher goes to ~/.nexus-ai/bin (on PATH). $NexusEngineVersion = '2.2.1' $NexusEngineSha = @{ 'windows-x64' = 'sha512-Xce6HGeY6SVRoojce5bcDARzXpT+Q5qwOUpXlcVRRBza3QmmZFzgMJw4sfBVZMytEA5IDxQRzgy4oDCUJsKYbA==' 'windows-x64-baseline' = 'sha512-xZGZvg6vOWrgHKVizpBQiXhsqIv6u/6NufwixOKaso6mqoe7/wHgNdLEpN08FhjzJLbzs8atZwCEfBVYZ2PwDw==' 'windows-arm64' = 'sha512-DQgq+6SQiBim65rhG7CHgziJBLLzm/Q8CE0pF9SjWX3sfwI7MhJ0G0V127cY8bVqEG7BitmUuOLE/39GGck+yw==' } $NexusEngineMirrors = @('https://registry.npmjs.org', 'https://registry.npmmirror.com') function Get-EnginePlatform { $arch = '' try { $arch = [string][Runtime.InteropServices.RuntimeInformation]::OSArchitecture } catch {} if ($arch -eq 'Arm64' -or $env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { return 'windows-arm64' } # CPUs without AVX2 need the "baseline" build (PF_AVX2_INSTRUCTIONS_AVAILABLE = 40) try { $k = Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int f);' -Name 'NexusCpu' -Namespace 'NexusSetup' -PassThru -ErrorAction Stop if (-not $k::IsProcessorFeaturePresent(40)) { return 'windows-x64-baseline' } } catch {} return 'windows-x64' } function Get-FileWithProgress([string]$Url, [string]$Dest, [string]$Label) { # streamed download with a progress bar in place: [█████░░░░░] 52% 62/119 МБ 8.4 МБ/с $req = [Net.HttpWebRequest]::Create($Url) $req.Timeout = 30000 $req.ReadWriteTimeout = 60000 $req.UserAgent = 'NEXUS-AI-Setup' $resp = $req.GetResponse() $total = [double]$resp.ContentLength $in = $resp.GetResponseStream() $out = [IO.File]::Create($Dest) try { $buf = New-Object byte[] 262144 $done = 0.0 $sw = [Diagnostics.Stopwatch]::StartNew() $lastDraw = -1.0 $width = 24 try { $width = [Math]::Max(10, [Math]::Min(30, [Console]::WindowWidth - 52)) } catch {} while (($n = $in.Read($buf, 0, $buf.Length)) -gt 0) { $out.Write($buf, 0, $n) $done += $n $t = $sw.Elapsed.TotalSeconds if ($t - $lastDraw -ge 0.15) { $lastDraw = $t $mb = $done / 1MB $speed = if ($t -gt 0) { $mb / $t } else { 0 } if ($total -gt 0) { $p = [Math]::Min(1.0, $done / $total) $fill = [int][Math]::Round($p * $width) $bar = ([string][char]0x2588) * $fill + ([string][char]0x2591) * ($width - $fill) $line = (' {0} [{1}] {2,3}% {3:0}/{4:0} МБ {5:0.0} МБ/с' -f $Label, $bar, [int]($p * 100), $mb, ($total / 1MB), $speed) } else { $line = (' {0} {1:0} МБ {2:0.0} МБ/с' -f $Label, $mb, $speed) } Write-Host ("`r" + $line + ' ') -NoNewline -ForegroundColor Cyan } } $bar = ([string][char]0x2588) * $width Write-Host ("`r" + (' {0} [{1}] 100% {2:0} МБ готово ' -f $Label, $bar, ($done / 1MB))) -ForegroundColor Green } finally { $out.Close(); $in.Close(); $resp.Close() } } function Install-NexusEngine { # returns the path of nexus-cli.exe or $null $dir = Join-Path $UserHome '.nexus-ai\code\engine' $exe = Join-Path $dir 'nexus-cli.exe' $plat = Get-EnginePlatform $want = "$NexusEngineVersion $plat" $stamp = Join-Path $dir 'version.txt' if ((Test-Path -LiteralPath $exe) -and (Test-Path -LiteralPath $stamp) -and ([IO.File]::ReadAllText($stamp).Trim() -eq $want)) { Ok "NEXUS CLI: ядро $NexusEngineVersion уже установлено" return $exe } if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } if ($TestRoot) { [IO.File]::WriteAllText($exe, 'test engine') [IO.File]::WriteAllText($stamp, $want) return $exe } $name = "cli-$plat" $tmp = Join-Path ([IO.Path]::GetTempPath()) ("nexus-engine-" + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Force -Path $tmp | Out-Null try { $tgz = Join-Path $tmp 'engine.tgz' $okDownload = $false $oldProgress = $ProgressPreference $ProgressPreference = 'SilentlyContinue' foreach ($m in $NexusEngineMirrors) { if ($m -ne $NexusEngineMirrors[0]) { Step "NEXUS CLI: пробую запасной адрес ($(([uri]$m).Host))..." } try { Get-FileWithProgress "$m/@opencode/$name/-/$name-$NexusEngineVersion.tgz" $tgz 'Загрузка NEXUS CLI' $fs = [IO.File]::OpenRead($tgz) try { $sha = [Convert]::ToBase64String((New-Object Security.Cryptography.SHA512Managed).ComputeHash($fs)) } finally { $fs.Close() } if ("sha512-$sha" -eq $NexusEngineSha[$plat]) { $okDownload = $true; break } Warn 'NEXUS CLI: файл повреждён при скачивании (контрольная сумма не сошлась), пробую другой адрес' } catch { Write-Host ''; Warn "NEXUS CLI: не скачалось с $(([uri]$m).Host): $($_.Exception.Message)" } } $ProgressPreference = $oldProgress if (-not $okDownload) { Fail 'NEXUS CLI: не удалось скачать ядро. Проверьте интернет и запустите установщик ещё раз.'; return $null } $tar = Join-Path $env:SystemRoot 'System32\tar.exe' if (-not (Test-Path $tar)) { $tar = 'tar.exe' } $r = Invoke-Tool $tar @('-xzf', $tgz, '-C', $tmp) 300 $src = Join-Path $tmp 'package\bin\opencode.exe' if ($r.Code -ne 0 -or -not (Test-Path -LiteralPath $src)) { Fail "NEXUS CLI: не удалось распаковать ядро. $($r.Text)"; return $null } $sig = Get-AuthenticodeSignature -LiteralPath $src if ($sig.Status -ne 'Valid') { Fail "NEXUS CLI: подпись ядра не прошла проверку ($($sig.Status)), установка остановлена."; return $null } try { Copy-Item -LiteralPath $src -Destination $exe -Force -ErrorAction Stop } catch { Fail 'NEXUS CLI сейчас запущен - закройте его окно (Ctrl+C) и запустите установщик ещё раз.'; return $null } [IO.File]::WriteAllText($stamp, $want) Ok "NEXUS CLI: ядро $NexusEngineVersion установлено (подпись проверена)" return $exe } finally { Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue } } function Add-UserPath([string]$Dir) { # adds a folder to the user PATH, keeping the registry value type (REG_EXPAND_SZ); undone by the rollback if ($TestRoot) { Add-Content -Path (Join-Path $TestRoot 'user-path.txt') -Value $Dir -Encoding UTF8; return } $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true) try { $raw = [string]$key.GetValue('Path', '', 'DoNotExpandEnvironmentNames') $parts = @($raw.Split(';') | Where-Object { $_ }) if ($parts | Where-Object { [Environment]::ExpandEnvironmentVariables($_).TrimEnd('\') -ieq $Dir.TrimEnd('\') }) { return } if (-not (Test-Path $RunDir)) { New-Item -ItemType Directory -Force -Path $RunDir | Out-Null } Add-Content -Path (Join-Path $RunDir 'manifest.tsv') -Value ("PATHADD`t$Dir`t`t1") -Encoding UTF8 $kind = [Microsoft.Win32.RegistryValueKind]::ExpandString try { $kind = $key.GetValueKind('Path') } catch {} $key.SetValue('Path', (($parts + $Dir) -join ';'), $kind) } finally { $key.Close() } # tell Explorer and new terminals about the change (setting any user variable broadcasts WM_SETTINGCHANGE) try { [Environment]::SetEnvironmentVariable('NEXUS_AI_PATH_UPDATED', $null, 'User') } catch {} if (($env:Path.Split(';') | Where-Object { $_.TrimEnd('\') -ieq $Dir.TrimEnd('\') }).Count -eq 0) { $env:Path = $env:Path.TrimEnd(';') + ';' + $Dir } } function Remove-UserPath([string]$Dir) { if ($TestRoot) { return } $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true) try { $raw = [string]$key.GetValue('Path', '', 'DoNotExpandEnvironmentNames') $parts = @($raw.Split(';') | Where-Object { $_ -and ([Environment]::ExpandEnvironmentVariables($_).TrimEnd('\') -ine $Dir.TrimEnd('\')) }) $kind = [Microsoft.Win32.RegistryValueKind]::ExpandString try { $kind = $key.GetValueKind('Path') } catch {} $key.SetValue('Path', ($parts -join ';'), $kind) } finally { $key.Close() } try { [Environment]::SetEnvironmentVariable('NEXUS_AI_PATH_UPDATED', $null, 'User') } catch {} } function Get-SiteText([string]$Rel) { $tmp = [IO.Path]::GetTempFileName() try { Invoke-WebRequest -Uri "$FilesSite/install/nexus-code/$Rel" -OutFile $tmp -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop $text = [IO.File]::ReadAllText($tmp, [Text.Encoding]::UTF8) if (-not $text.Trim() -or $text -match '(?is)^\s*( $cmd (баланс: $cmd balance, докупить: $cmd topup, обновить: $cmd update)") } # NEXUS AI for Roblox Studio: the plugin file (roblox-plugin/build.py) with the key and server filled in, # put into Studio's local plugins folder. Studio loads it on the next start. function Get-RobloxPluginsDir { return (Join-Path $LocalApp 'Roblox\Plugins') } function Test-RobloxStudio { $versions = Join-Path $LocalApp 'Roblox\Versions' if (Test-Path (Get-RobloxPluginsDir)) { return $true } if (-not (Test-Path $versions)) { return $false } return [bool](Get-ChildItem -LiteralPath $versions -Directory -ErrorAction SilentlyContinue | Where-Object { Test-Path (Join-Path $_.FullName 'RobloxStudioBeta.exe') } | Select-Object -First 1) } function Apply-Roblox { $tmp = [IO.Path]::GetTempFileName() try { Invoke-WebRequest -Uri "$FilesSite/install/roblox/NEXUS-AI.rbxmx" -OutFile $tmp -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop $xml = [IO.File]::ReadAllText($tmp, [Text.Encoding]::UTF8) } finally { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } if ($xml -notmatch '(?s)^\s*\s*$') { throw 'Сервер вернул неверный файл плагина Roblox. Повторите установку позже.' } $null = [xml]$xml $esc = { param($s) ([string]$s).Replace('&', '&').Replace('<', '<').Replace('>', '>').Replace('"', '"') } $xml = $xml.Replace('__NEXUS_KEY__', (& $esc $script:Key)).Replace('__NEXUS_SITE__', (& $esc $Site)) Save-Text (Join-Path (Get-RobloxPluginsDir) 'NEXUS-AI.rbxmx') $xml Ok 'Roblox Studio: плагин NEXUS AI установлен (ключ уже внутри)' $script:Next.Add('Roblox Studio: перезапустите Studio -> вкладка Plugins -> кнопка NEXUS AI. На первый запрос Studio спросит доступ к сайту и к скриптам - разрешите.') } # Aider: ~/.aider.conf.yml holds plain "key: value" lines; only our three keys are set, everything else stays. function Apply-Aider { $path = Join-Path $UserHome '.aider.conf.yml' $values = [ordered]@{ 'openai-api-base' = $OpenAiBase; 'openai-api-key' = $script:Key; 'model' = "openai/$($script:Model)" } $lines = New-Object System.Collections.Generic.List[string] if (Test-Path -LiteralPath $path) { foreach ($l in [IO.File]::ReadAllLines($path)) { $lines.Add($l) } } foreach ($k in $values.Keys) { $v = $k + ': "' + ([string]$values[$k]).Replace('"', '\"') + '"' $i = -1 for ($j = 0; $j -lt $lines.Count; $j++) { if ($lines[$j] -match ('^' + [regex]::Escape($k) + '\s*:')) { $i = $j; break } } if ($i -ge 0) { $lines[$i] = $v } else { $lines.Add($v) } } Save-Text $path (($lines -join "`n") + "`n") Ok "Aider подключён (модель openai/$($script:Model))" $script:Next.Add('Aider: в папке проекта -> aider (модель из NEXUS AI уже выбрана; другая: aider --model openai/)') } # Continue (VS Code / JetBrains): ~/.continue/config.yaml. A file the user wrote is never touched; ours carries a marker. $ContinueMark = '# NEXUS AI - written by the NEXUS AI installer; run it again to update. Delete this line to keep your own edits.' function Apply-Continue { $path = Join-Path $UserHome '.continue\config.yaml' if ((Test-Path -LiteralPath $path) -and -not ([IO.File]::ReadAllText($path).StartsWith($ContinueMark))) { Warn 'Continue: у вас свой config.yaml - его не трогаю. Добавьте модель вручную: provider openai, apiBase ' + $OpenAiBase $script:Notes.Add("Continue: свой config.yaml не изменён. Модель NEXUS AI: provider: openai, apiBase: $OpenAiBase, apiKey: ваш ключ") return } $q = { param($t) '"' + ([string]$t).Replace('\', '\\').Replace('"', '\"') + '"' } $out = New-Object System.Collections.Generic.List[string] $out.Add($ContinueMark); $out.Add('name: NEXUS AI'); $out.Add('version: 1.0.0'); $out.Add('schema: v1'); $out.Add('models:') foreach ($id in (Get-ModelEntries)) { $out.Add(' - name: ' + (& $q (Get-ModelLabel $id))) $out.Add(' provider: openai') $out.Add(' model: ' + (& $q $id)) $out.Add(' apiBase: ' + (& $q $OpenAiBase)) $out.Add(' apiKey: ' + (& $q $script:Key)) $out.Add(' roles: [chat, edit, apply]') $out.Add(' capabilities: [tool_use]') $out.Add(' defaultCompletionOptions:') $out.Add(' contextLength: ' + (Get-ModelContext $id)) } Save-Text $path (($out -join "`n") + "`n") Ok "Continue подключён (модели NEXUS AI, основная $($script:Model))" $script:Next.Add('Continue: перезапустите редактор -> панель Continue -> модели NEXUS AI уже в списке') } function Apply-Kilo { Apply-OpenCodeLike (Join-Path $UserHome '.config\kilo\kilo.json') 'Kilo Code' $script:Next.Add('Kilo Code: перезапустите редактор - в списке моделей появится провайдер NEXUS_AI') } function Install-NpmTool([string]$Package, [string]$Title) { $npm = Find-Cli @('npm.cmd') if (-not $npm) { Fail "${Title}: нужен Node.js - сначала установите его (пункт Node.js в меню)"; return $false } if ($TestRoot) { return $true } Step "${Title}: устанавливаю $Package (до 2 минут)..." $r = Invoke-Tool $npm @('install', '-g', $Package) 420 if ($r.Code -ne 0) { Fail "${Title}: npm не смог установить пакет. $($r.Text.Split("`n") | Select-Object -Last 3)"; return $false } Ok "$Title установлен" return $true } function Install-Winget([string]$Id, [string]$Title) { if ($TestRoot) { return $true } $wg = Find-Cli @('winget.exe') if (-not $wg) { Fail "${Title}: не найден winget. Скачайте вручную: $(if ($Id -like '*Node*') { 'https://nodejs.org' } else { 'https://git-scm.com/download/win' })"; return $false } Step "${Title}: устанавливаю через winget (Windows может попросить подтверждение)..." $r = Invoke-Tool $wg @('install', '-e', '--id', $Id, '--silent', '--accept-source-agreements', '--accept-package-agreements') 900 Refresh-Path if ($r.Code -ne 0 -and $r.Text -notmatch 'already installed|уже установ') { Fail "${Title}: winget вернул ошибку ($($r.Code))."; return $false } Ok "$Title установлен" return $true } function Get-EffectivePolicy { try { $list = Get-ExecutionPolicy -List foreach ($scope in @('MachinePolicy', 'UserPolicy', 'CurrentUser', 'LocalMachine')) { $p = ($list | Where-Object { [string]$_.Scope -eq $scope }).ExecutionPolicy if ($p -and [string]$p -ne 'Undefined') { return @{ Scope = $scope; Policy = [string]$p } } } } catch {} return @{ Scope = 'Default'; Policy = 'Restricted' } } function Apply-ExecutionPolicy { if ($TestRoot) { Ok 'PowerShell: запуск npm-команд разрешён (тест)'; return } try { Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force -ErrorAction Stop Ok 'PowerShell: разрешён запуск claude / codex и других npm-команд (RemoteSigned для вашего пользователя)' } catch { Warn "PowerShell: не удалось изменить политику запуска ($($_.Exception.Message))" } } function Clear-KeyTraces { if ($TestRoot) { return } try { $mru = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU' if (Test-Path $mru) { $props = Get-ItemProperty $mru $order = [string]$props.MRUList foreach ($p in $props.PSObject.Properties) { if ($p.Name.Length -eq 1 -and [string]$p.Value -like '*NEXUS_KEY*') { Remove-ItemProperty -Path $mru -Name $p.Name -ErrorAction SilentlyContinue $order = $order.Replace($p.Name, '') } } Set-ItemProperty -Path $mru -Name MRUList -Value $order -ErrorAction SilentlyContinue } } catch {} try { $hist = Join-Path $AppData 'Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt' if (Test-Path $hist) { # read and written as UTF-8 without BOM (PSReadLine's format): Get-Content alone reads ANSI and broke Cyrillic lines $all = [IO.File]::ReadAllLines($hist, [Text.Encoding]::UTF8) $keep = @($all | Where-Object { $_ -notmatch 'NEXUS_KEY' }) if ($keep.Count -ne $all.Count) { [IO.File]::WriteAllLines($hist, [string[]]$keep, $Utf8NoBom) } } } catch {} } # ── главный сценарий ────────────────────────────────────────────────────────── function Show-Banner { Say '' Say ' ==========================================================' DarkYellow Say ' NEXUS_AI - подключение программ в 1 клик' White Say ' ==========================================================' DarkYellow Say " Шлюз: $Site" DarkGray Say '' } function Read-ValidKey { $k = [string]$env:NEXUS_KEY $k = $k.Trim() for ($try = 0; $try -lt 4; $try++) { if (-not $k) { if ($Auto) { Fail 'Ключ не передан (NEXUS_KEY).'; return $null } $k = (Read-Host ' Вставьте ваш ключ sk-... (правый клик или Ctrl+V) и нажмите Enter').Trim() } if ($k -notmatch '^sk-[A-Za-z0-9_\-]{16,}$') { Fail 'Это не похоже на ключ NEXUS_AI (он начинается с sk-).'; $k = ''; continue } $state = Test-Key $k if ($state -eq 'expired') { Fail 'Срок действия ключа истёк. Купите пакет или подписку на FunPay с того же аккаунта - токены придут на этот же ключ.'; $k = ''; if ($Auto) { return $null }; continue } if ($state -eq 'bad') { Fail 'Ключ не найден или отключён. Проверьте, что скопировали его целиком.'; $k = ''; if ($Auto) { return $null }; continue } return $k } return $null } function Choose-Model { $script:Catalog = Get-Catalog $options = New-Object System.Collections.Generic.List[object] foreach ($c in $Curated) { if ($null -eq $script:Catalog -or $script:Catalog.ContainsKey($c.Id)) { $mult = if ($script:Catalog) { $script:Catalog[$c.Id] } else { $null } $options.Add([pscustomobject]@{ Id = $c.Id; Note = $c.Note; Mult = $mult }) } } $want = [string]$env:NEXUS_MODEL if ($want) { $script:Model = $want } elseif ((Test-Tui) -and $script:Catalog) { $notes = @{}; foreach ($c in $Curated) { $notes[$c.Id] = $c.Note } $main = if ($options.Count) { $options[0].Id } else { (Get-CatalogOrder)[0] } $rows = New-Object System.Collections.Generic.List[object] $rows.Add([pscustomobject]@{ Kind = 'check'; On = $script:AllModels; Id = ''; Label = ('Подключить все модели ({0}) - в программах будет весь список' -f $script:Catalog.Count) }) foreach ($id in (Get-CatalogOrder)) { $m = ('x{0}' -f ([double]$script:Catalog[$id]).ToString('0.0#', [Globalization.CultureInfo]::InvariantCulture)) $rows.Add([pscustomobject]@{ Kind = 'radio'; On = ($id -eq $main); Id = $id; Label = ('{0,-26} {1,-6} {2}' -f $id, $m, $notes[$id]) }) } $null = Invoke-Menu 'Модели NEXUS AI: выберите основную (её получат Claude Code, Codex, Aider)' $rows.ToArray() '↑↓ · Пробел/Enter - выбрать · xN - списание за 1 токен' $script:AllModels = $rows[0].On $picked = $rows | Where-Object { $_.Kind -eq 'radio' -and $_.On } | Select-Object -First 1 $script:Model = if ($picked) { $picked.Id } else { $main } } else { Say ' Модель по умолчанию (её можно менять в любой момент в самой программе):' White for ($i = 0; $i -lt $options.Count; $i++) { $o = $options[$i] $m = if ($null -ne $o.Mult) { ('x{0}' -f ([double]$o.Mult).ToString('0.0#', [Globalization.CultureInfo]::InvariantCulture)) } else { '' } Say (' {0}) {1,-22} {2,-6} {3}' -f ($i + 1), $o.Id, $m, $o.Note) } Say ' xN - коэффициент списания: сколько токенов баланса уходит на 1 токен диалога.' DarkGray $a = Ask ' Номер модели [Enter = 1]' '1' $n = 0 if ([int]::TryParse($a, [ref]$n) -and $n -ge 1 -and $n -le $options.Count) { $script:Model = $options[$n - 1].Id } elseif ($a -match '^[a-z0-9][a-z0-9.\-:/]+$') { $script:Model = $a } else { $script:Model = $options[0].Id } } $script:FastModel = $script:Model foreach ($f in $FastCandidates) { if ($null -eq $script:Catalog -or $script:Catalog.ContainsKey($f)) { $script:FastModel = $f; break } } Ok "Модель: $($script:Model)" } function Build-Plan { $plan = New-Object System.Collections.Generic.List[object] $add = { param($Label, $Selected, $Action, $Arg) $plan.Add([pscustomobject]@{ Label = $Label; Selected = [bool]$Selected; Action = $Action; Arg = $Arg }) } foreach ($e in $Editors) { if (-not $e.Found) { continue } & $add $(if (Test-Extension $e 'saoudrizwan.claude-dev') { "$($e.Name): агент Cline - подключить к NEXUS AI" } else { "$($e.Name): агент Cline - установить и подключить к NEXUS AI" }) $true 'cline' $e if (Test-Extension $e 'anthropic.claude-code') { & $add "$($e.Name): расширение Claude Code - подключить" $true 'claude-ext' $e } if (Test-Extension $e 'kilocode.kilo-code') { $script:HasKilo = $true } if (Test-Extension $e 'continue.continue') { $script:HasContinue = $true } } $node = Find-Cli @('node.exe', 'node') $git = Find-Cli @('git.exe', 'git') $claude = Find-Cli @('claude.cmd', 'claude.exe', 'claude') if (-not $claude -and -not $TestRoot) { $p = Join-Path $UserHome '.local\bin\claude.exe'; if (Test-Path $p) { $claude = $p } } $codex = Find-Cli @('codex.cmd', 'codex.exe', 'codex') $qwen = Find-Cli @('qwen.cmd', 'qwen') $oc = Find-Cli @('opencode.cmd', 'opencode.exe', 'opencode') $kilo = Find-Cli @('kilo.cmd', 'kilo.exe', 'kilo') if ($claude) { & $add 'Claude Code (терминал) - подключить' $true 'claude' $null } else { & $add 'Claude Code (терминал) - установить и подключить' $false 'claude-install' $null } if ($codex) { & $add 'Codex CLI - подключить' $true 'codex' $null } else { & $add 'Codex CLI - установить и подключить' $false 'codex-install' $null } if ($qwen) { & $add 'Qwen Code - подключить' $true 'qwen' $null } else { & $add 'Qwen Code - установить и подключить' $false 'qwen-install' $null } if ($oc -or (Test-Path (Join-Path $UserHome '.config\opencode'))) { & $add 'OpenCode - подключить' $true 'opencode' $null } else { & $add 'OpenCode - установить и подключить' $false 'opencode-install' $null } if ($kilo -or $script:HasKilo -or (Test-Path (Join-Path $UserHome '.config\kilo'))) { & $add 'Kilo Code - подключить' $true 'kilo' $null } $aider = Find-Cli @('aider.exe', 'aider.cmd', 'aider') if ($aider -or (Test-Path (Join-Path $UserHome '.aider.conf.yml'))) { & $add 'Aider - подключить' $true 'aider' $null } if ($script:HasContinue -or (Test-Path (Join-Path $UserHome '.continue'))) { & $add 'Continue (VS Code / JetBrains) - подключить' $true 'continue' $null } & $add 'NEXUS Desktop - установить приложение для Windows' $false 'desktop' $null $nexusCode = Test-Path (Join-Path $UserHome '.nexus-ai\code\opencode.json') & $add $(if ($nexusCode) { 'NEXUS CLI (агент в терминале) - обновить' } else { 'NEXUS CLI (агент в терминале) - установить' }) ([bool]($oc -or $nexusCode)) 'nexus-code' $null $studio = Test-RobloxStudio if ($studio -or ([string]$env:NEXUS_ONLY) -match 'roblox') { $has = Test-Path (Join-Path (Get-RobloxPluginsDir) 'NEXUS-AI.rbxmx') & $add $(if ($has) { 'Roblox Studio: плагин NEXUS AI - обновить' } else { 'Roblox Studio: плагин NEXUS AI - установить' }) $studio 'roblox' $null } if (-not $node) { & $add 'Node.js LTS - установить (нужен для установки CLI выше)' $false 'node' $null } if (-not $git) { & $add 'Git для Windows - установить (нужен Claude Code)' $false 'git' $null } $pol = Get-EffectivePolicy if (@('Restricted', 'AllSigned') -contains $pol.Policy -and @('MachinePolicy', 'UserPolicy') -notcontains $pol.Scope) { & $add 'PowerShell: разрешить запуск claude/codex (иначе ошибка "running scripts is disabled")' $true 'policy' $null } & $add 'Проверить связь в конце (короткий запрос, ~30 токенов)' $true 'check' $null if ($env:NEXUS_ONLY) { # a site button for one program: only its items (and the connection check) are ticked $only = @(([string]$env:NEXUS_ONLY).Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + @('check') foreach ($p in $plan) { $p.Selected = $only -contains $p.Action } } $script:Detected = @{ Node = $node; Git = $git; Claude = $claude; Codex = $codex; Qwen = $qwen; OpenCode = $oc } return , $plan } function Show-Detected { $found = @(); $missing = @() foreach ($e in $Editors) { if ($e.Found) { $found += $e.Name } else { $missing += $e.Name } } foreach ($k in @('Claude', 'Codex', 'Qwen', 'OpenCode', 'Node', 'Git')) { $title = @{ Claude = 'Claude Code'; Codex = 'Codex CLI'; Qwen = 'Qwen Code'; OpenCode = 'OpenCode'; Node = 'Node.js'; Git = 'Git' }[$k] if ($script:Detected[$k]) { $found += $title } else { $missing += $title } } Say (' Найдено: ' + $(if ($found.Count) { $found -join ', ' } else { '-' })) Green Say (' Не найдено: ' + $(if ($missing.Count) { $missing -join ', ' } else { '-' })) DarkGray Say '' } function Choose-Items($Plan) { if ($Auto) { return } if (Test-Tui) { # a fresh menu starts with nothing ticked; the site's one-program buttons (NEXUS_ONLY) keep their ticks if (-not $env:NEXUS_ONLY) { foreach ($p in $Plan) { $p.Selected = $false } } $rows = @($Plan | ForEach-Object { [pscustomobject]@{ Kind = 'check'; On = $_.Selected; Label = $_.Label; Item = $_ } }) $res = Invoke-Menu 'Что настроить:' $rows '↑↓ · Пробел - отметить · A - всё · Enter - выполнить · R - откат · Q - выход' @('quit', 'restore') foreach ($r in $rows) { $r.Item.Selected = $r.On } if ($res -eq 'quit') { $script:Quit = $true } if ($res -eq 'restore') { $script:DoRestore = $true } return } while ($true) { Say ' Что настроить:' White for ($i = 0; $i -lt $Plan.Count; $i++) { $mark = if ($Plan[$i].Selected) { '[x]' } else { '[ ]' } $color = if ($Plan[$i].Selected) { 'White' } else { 'DarkGray' } Say (' {0} {1,2}. {2}' -f $mark, ($i + 1), $Plan[$i].Label) $color } Say ' Enter - выполнить отмеченное | номера через пробел - отметить/снять | A - всё | R - откатить прошлую установку | Q - выход' DarkGray $a = Read-Host ' >' if ($null -eq $a) { $a = '' } $a = $a.Trim().ToLower() if ($a -eq '') { return } if ($a -eq 'q' -or $a -eq '0' -or $a -eq 'й') { $script:Quit = $true; return } if ($a -eq 'r' -or $a -eq 'к') { $script:DoRestore = $true; return } if ($a -eq 'a' -or $a -eq 'ф') { foreach ($p in $Plan) { $p.Selected = $true }; continue } foreach ($tok in ($a -split '[\s,;]+')) { $n = 0 if ([int]::TryParse($tok, [ref]$n) -and $n -ge 1 -and $n -le $Plan.Count) { $Plan[$n - 1].Selected = -not $Plan[$n - 1].Selected } } Say '' } } function Run-Plan($Plan) { $sel = @($Plan | Where-Object { $_.Selected }) $actions = @($sel | ForEach-Object { $_.Action }) $needNpm = (@($actions | Where-Object { $_ -like '*-install' }).Count -gt 0) $needNode = $needNpm -and -not $script:Detected.Node if ($needNode -and $actions -notcontains 'node') { $nodeItem = $Plan | Where-Object { $_.Action -eq 'node' } | Select-Object -First 1 if ($nodeItem) { $nodeItem.Selected = $true; $sel = @($Plan | Where-Object { $_.Selected }) } } # порядок: сначала системные зависимости, потом установки, потом настройка $order = @('policy', 'node', 'git', 'claude-install', 'codex-install', 'qwen-install', 'opencode-install', 'cline', 'claude-ext', 'claude', 'codex', 'qwen', 'opencode', 'kilo', 'aider', 'continue', 'desktop', 'nexus-code', 'roblox', 'check') $sel = @($sel | Sort-Object { [array]::IndexOf($order, $_.Action) }) foreach ($item in $sel) { try { switch ($item.Action) { 'policy' { Apply-ExecutionPolicy } 'node' { if (Install-Winget 'OpenJS.NodeJS.LTS' 'Node.js') { $script:Detected.Node = 'installed' } else { throw 'Node.js не установлен' } } 'git' { if (-not (Install-Winget 'Git.Git' 'Git')) { throw 'Git не установлен' } } 'claude-install' { if (Install-NpmTool '@anthropic-ai/claude-code' 'Claude Code') { Apply-ClaudeCode } else { throw 'Claude Code не установлен' } } 'codex-install' { if (Install-NpmTool '@openai/codex' 'Codex CLI') { Apply-Codex } else { throw 'Codex CLI не установлен' } } 'qwen-install' { if (Install-NpmTool '@qwen-code/qwen-code' 'Qwen Code') { Apply-Qwen } else { throw 'Qwen Code не установлен' } } 'opencode-install' { if (Install-NpmTool 'opencode-ai' 'OpenCode') { Apply-OpenCode } else { throw 'OpenCode не установлен' } } 'cline' { Apply-EditorCline $item.Arg } 'check' { if ($env:NEXUS_SKIP_CHECK -ne '1') { Test-Model } } 'claude-ext' { Apply-EditorClaudeExt $item.Arg } 'claude' { Apply-ClaudeCode } 'codex' { Apply-Codex } 'qwen' { Apply-Qwen } 'opencode' { Apply-OpenCode } 'kilo' { Apply-Kilo } 'aider' { Apply-Aider } 'continue' { Apply-Continue } 'desktop' { Apply-NexusDesktop } 'nexus-code' { Apply-NexusCode } 'roblox' { Apply-Roblox } } Log "done: $($item.Action)" } catch { Fail ("{0}: {1}" -f $item.Label, $_.Exception.Message) Log "failed: $($item.Action): $($_.Exception.Message)" $global:NexusExit = 1 } } return @($sel | Where-Object { $_.Action -ne 'check' }).Count } function Main-Quiet { $title = @{ 'nexus-code' = 'NEXUS CLI - установка'; roblox = 'NEXUS AI для Roblox Studio - установка' }[$QuietMode] Say '' Say " $title" White Say ' ----------------------------------------------' DarkYellow Choose-Server $script:Key = Read-ValidKey if (-not $script:Key) { Fail 'Без действующего ключа установить нельзя. Ключ приходит в чат заказа FunPay.'; $global:NexusExit = 2; return } # the model: the one chosen before (a reinstall keeps it), else the recommended one; every model is listed if (-not $env:NEXUS_MODEL) { try { $prev = [string](Read-JsonFile (Join-Path $UserHome '.nexus-ai\code\opencode.json')).model if ($prev -like 'nexus/*') { $env:NEXUS_MODEL = $prev.Substring(6) } } catch {} } if (-not $env:NEXUS_MODEL) { $cat = Get-Catalog foreach ($c in $Curated) { if ($null -eq $cat -or $cat.ContainsKey($c.Id)) { $env:NEXUS_MODEL = $c.Id; break } } } Choose-Model $script:Next = New-Object System.Collections.Generic.List[string] $script:Notes = New-Object System.Collections.Generic.List[string] try { if ($QuietMode -eq 'nexus-code') { Apply-NexusCode } else { if (-not (Test-RobloxStudio)) { Warn 'Roblox Studio не найдена - плагин всё равно кладу в папку плагинов, Studio подхватит его после установки.' } Apply-Roblox } } catch { Fail $_.Exception.Message $global:NexusExit = 1 } Clear-KeyTraces Say '' if ($global:NexusExit -ne 0) { Say ' Установка не завершена - см. сообщения [X] выше.' Yellow; return } Say ' ==============================================' Green if ($QuietMode -eq 'nexus-code') { Say ' Готово! NEXUS CLI установлен.' Green Say '' Say ' Перезапустите терминал: закройте все окна PowerShell / командной' White Say ' строки и откройте новое. Затем в папке проекта наберите: nexus' White Say '' Say ' nexus balance - баланс, nexus topup - пополнить, nexus update - обновить' DarkGray } else { Say ' Готово! Плагин NEXUS AI установлен.' Green Say '' Say ' Перезапустите Roblox Studio -> вкладка Plugins -> кнопка NEXUS AI.' White Say ' На первый запрос Studio спросит доступ к сайту и к скриптам - разрешите.' DarkGray } Say ' ==============================================' Green Log ("quiet install: {0} model={1} exit={2}" -f $QuietMode, $script:Model, $global:NexusExit) } function Main { if (-not $Auto -and $env:NEXUS_TEXT -ne '1') { $script:NexusGuiUsed = $true $global:NexusExit = Start-NexusTui -EngineSource $script:NexusTuiEngineSource -Gateways $script:NexusTuiGateways -Preview:($env:NEXUS_TUI_PREVIEW -eq '1') return } if ($QuietMode -and $env:NEXUS_ACTION -ne 'restore') { Main-Quiet; return } Show-Banner if ($env:NEXUS_ACTION -eq 'restore') { Invoke-Restore; return } Choose-Server $script:Key = Read-ValidKey if (-not $script:Key) { Fail 'Без действующего ключа настроить нельзя. Ключ приходит в чат заказа FunPay.'; $global:NexusExit = 2; return } Say '' Choose-Model Say '' $script:Next = New-Object System.Collections.Generic.List[string] $script:Notes = New-Object System.Collections.Generic.List[string] $script:ClineDone = $false $script:HasKilo = $false $script:HasContinue = $false Detect-Editors $plan = Build-Plan Show-Detected Choose-Items $plan if ($script:Quit) { Say ' Выход без изменений.'; $global:NexusExit = 3; return } if ($script:DoRestore) { Invoke-Restore; return } Say '' $n = Run-Plan $plan Clear-KeyTraces Say '' if ($n -eq 0) { Warn 'Ничего не выбрано - изменений нет.'; $global:NexusExit = 3; return } Say ' ==========================================================' DarkYellow if ($global:NexusExit -eq 0) { Say ' Готово! Что дальше:' Green } else { Say ' Готово с ошибками (см. [X] выше). Что дальше:' Yellow } foreach ($x in $script:Next) { Say " * $x" } foreach ($x in $script:Notes) { Say " ! $x" Yellow } Say " Баланс и расход: $Site/stats.html" DarkGray if (Test-Path $RunDir) { Say " Копии изменённых файлов: $RunDir (откат - запустите ещё раз и введите R)" DarkGray } Say ' ==========================================================' DarkYellow Log ("installed: model={0} items={1} exit={2}" -f $script:Model, $n, $global:NexusExit) } # BEGIN GENERATED NEXUS TUI $script:NexusTuiEngineSource = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('I19fTkVYVVNfUFMxX18KIyBORVhVU19BSSAtINGD0YHRgtCw0L3QvtCy0YnQuNC6INCyIDEg0LrQu9C40Log0LTQu9GPIFdpbmRvd3MgKFdpbmRvd3MgUG93ZXJTaGVsbCA1LjErKS4KIwojINCn0YLQviDQtNC10LvQsNC10YI6CiMgICAxLiDQv9GA0L7QstC10YDRj9C10YIg0LLQsNGIINC60LvRjtGHINC4INC/0L7QutCw0LfRi9Cy0LDQtdGCINCx0LDQu9Cw0L3RgTsKIyAgIDIuINC90LDRhdC+0LTQuNGCIEN1cnNvciAvIFZTIENvZGUgLyBXaW5kc3VyZiDQuCDRgtC10YDQvNC40L3QsNC70YzQvdGL0LUg0LDQs9C10L3RgtGLIChDbGF1ZGUgQ29kZSwgQ29kZXgsIFF3ZW4gQ29kZSwgT3BlbkNvZGUsIEtpbG8gQ29kZSk7CiMgICAgICDRgdGC0LDQstC40YIgTkVYVVMgQ0xJICjQsNCz0LXQvdGCINCyINGC0LXRgNC80LjQvdCw0LvQtSk6INGP0LTRgNC+INCyIH4vLm5leHVzLWFpL2NvZGUvZW5naW5lLCDQvdCw0YHRgtGA0L7QudC60Lgg0LIgfi8ubmV4dXMtYWkvY29kZSwKIyAgICAgINC60L7QvNCw0L3QtNGLIG5leHVzINC4IG5leHVzLWNvZGUg0LIgfi8ubmV4dXMtYWkvYmluICjQv9Cw0L/QutCwINC00L7QsdCw0LLQu9GP0LXRgtGB0Y8g0LIgUEFUSCk7CiMgICAzLiDQv9GA0L7Qv9C40YHRi9Cy0LDQtdGCINCyINC40YUg0L3QsNGB0YLRgNC+0LnQutC4INCw0LTRgNC10YEg0YjQu9GO0LfQsCDQuCDQstCw0Ygg0LrQu9GO0YcgKNCyIEN1cnNvci9WUyBDb2RlINGB0YLQsNCy0LjRgiDQsNCz0LXQvdGCIENsaW5lLCDRg9C20LUg0L/QvtC00LrQu9GO0YfRkdC90L3Ri9C5INC6IE5FWFVTIEFJKS4KIyDQn9C10YDQtdC0INC60LDQttC00YvQvCDQuNC30LzQtdC90LXQvdC40LXQvCDQutC+0L/QuNGPINGE0LDQudC70LAg0YHQvtGF0YDQsNC90Y/QtdGC0YHRjyDQsiAlVVNFUlBST0ZJTEUlXC5uZXh1cy1haVxiYWNrdXBzXDzQstGA0LXQvNGPPlwuCiMg0J7RgtC60LDRgiDQv9C+0YHQu9C10LTQvdC10Lkg0YPRgdGC0LDQvdC+0LLQutC4OiDQt9Cw0L/Rg9GB0YLQuNGC0LUg0YPRgdGC0LDQvdC+0LLRidC40Log0LXRidGRINGA0LDQtyDQuCDQstCy0LXQtNC40YLQtSBSICjQuNC70LggTkVYVVNfQUNUSU9OPXJlc3RvcmUpLgojCiMg0J/QtdGA0LXQvNC10L3QvdGL0LU6IE5FWFVTX0tFWSAo0LrQu9GO0YcpLCBORVhVU19NT0RFTCAo0LzQvtC00LXQu9GMINC/0L4g0YPQvNC+0LvRh9Cw0L3QuNGOKSwgTkVYVVNfQVVUTz0xICjQsdC10Lcg0LLQvtC/0YDQvtGB0L7QsiksCiMgICAgICAgICAgICAgTkVYVVNfQUNUSU9OPXJlc3RvcmUgKNC+0YLQutCw0YIpLCBORVhVU19TSVRFICjQsNC00YDQtdGBINGI0LvRjtC30LApLCBORVhVU19URVNUX1JPT1QgKNC/0LXRgdC+0YfQvdC40YbQsCDQtNC70Y8g0YLQtdGB0YLQvtCyKSwKIyAgICAgICAgICAgICBORVhVU19PTkxZPW5leHVzLWNvZGUgKNC+0YLQvNC10YLQuNGC0Ywg0YLQvtC70YzQutC+INGN0YLQuCDQv9GD0L3QutGC0YssINGH0LXRgNC10Lcg0LfQsNC/0Y/RgtGD0Y47INC60L3QvtC/0LrQsCBORVhVUyBDTEkg0L3QsCDRgdCw0LnRgtC1KSwKIyAgICAgICAgICAgICBORVhVU19TS0lQX0NIRUNLPTEgKNCx0LXQtyDQv9GA0L7QstC10YDQvtGH0L3QvtCz0L4g0LfQsNC/0YDQvtGB0LAg0LIg0LrQvtC90YbQtTsg0YLQsNC6INGA0LDQsdC+0YLQsNC10YIgYG5leHVzIHVwZGF0ZWApLgoKJEVycm9yQWN0aW9uUHJlZmVyZW5jZSA9ICdDb250aW51ZScKdHJ5IHsgW0NvbnNvbGVdOjpPdXRwdXRFbmNvZGluZyA9IE5ldy1PYmplY3QgU3lzdGVtLlRleHQuVVRGOEVuY29kaW5nICRmYWxzZSB9IGNhdGNoIHt9CnRyeSB7IFtOZXQuU2VydmljZVBvaW50TWFuYWdlcl06OlNlY3VyaXR5UHJvdG9jb2wgPSBbTmV0LlNlcnZpY2VQb2ludE1hbmFnZXJdOjpTZWN1cml0eVByb3RvY29sIC1ib3IgMzA3MiB9IGNhdGNoIHt9CgokZ2xvYmFsOk5leHVzRXhpdCA9IDAKJHNjcmlwdDpOZXh1c1NldHVwVmVyc2lvbiA9ICcyMDI2LjExLjAzLjEnCiRTaXRlID0gaWYgKCRlbnY6TkVYVVNfU0lURSkgeyAkZW52Ok5FWFVTX1NJVEUuVHJpbUVuZCgnLycpIH0gZWxzZSB7ICdodHRwczovL25leHVzYWxsaW4uc3RvcmUnIH0KJE9wZW5BaUJhc2UgPSAiJFNpdGUvdjEiCiRGaWxlc1NpdGUgPSAkU2l0ZSAgICMgaW5zdGFsbGVyIGZpbGVzIGFsd2F5cyBjb21lIGZyb20gaGVyZSwgZXZlbiB3aGVuIHRoZSBBUEkgZ29lcyB0aHJvdWdoIGEgZmFzdGVyIHNlcnZlcgokVXRmOE5vQm9tID0gTmV3LU9iamVjdCBTeXN0ZW0uVGV4dC5VVEY4RW5jb2RpbmcgJGZhbHNlCiRTdGFtcCA9IEdldC1EYXRlIC1Gb3JtYXQgJ3l5eXlNTWRkX0hIbW1zcycKJEF1dG8gPSAoJGVudjpORVhVU19BVVRPIC1lcSAnMScpCiMgb25lLXByb2dyYW0gaW5zdGFsbGVycyBmcm9tIHRoZSBzaXRlIChORVhVU19DTElfU2V0dXAuYmF0LCBORVhVU19Sb2Jsb3hfU2V0dXAuYmF0KTogbm8gbWVudSwgbm8gcXVlc3Rpb25zCiRRdWlldE1vZGUgPSBAeyBjbGkgPSAnbmV4dXMtY29kZSc7IHJvYmxveCA9ICdyb2Jsb3gnIH1bW3N0cmluZ10kZW52Ok5FWFVTX01PREVdCiRUZXN0Um9vdCA9ICRlbnY6TkVYVVNfVEVTVF9ST09UCgppZiAoJFRlc3RSb290KSB7CiAgICAkVXNlckhvbWUgPSBKb2luLVBhdGggJFRlc3RSb290ICdob21lJwogICAgJEFwcERhdGEgPSBKb2luLVBhdGggJFRlc3RSb290ICdBcHBEYXRhXFJvYW1pbmcnCiAgICAkTG9jYWxBcHAgPSBKb2luLVBhdGggJFRlc3RSb290ICdBcHBEYXRhXExvY2FsJwp9IGVsc2UgewogICAgJFVzZXJIb21lID0gW0Vudmlyb25tZW50XTo6R2V0Rm9sZGVyUGF0aCgnVXNlclByb2ZpbGUnKQogICAgJEFwcERhdGEgPSBbRW52aXJvbm1lbnRdOjpHZXRGb2xkZXJQYXRoKCdBcHBsaWNhdGlvbkRhdGEnKQogICAgJExvY2FsQXBwID0gW0Vudmlyb25tZW50XTo6R2V0Rm9sZGVyUGF0aCgnTG9jYWxBcHBsaWNhdGlvbkRhdGEnKQp9CiROZXh1c0RpciA9IEpvaW4tUGF0aCAkVXNlckhvbWUgJy5uZXh1cy1haScKJEJhY2t1cFJvb3QgPSBKb2luLVBhdGggJE5leHVzRGlyICdiYWNrdXBzJwokUnVuRGlyID0gSm9pbi1QYXRoICRCYWNrdXBSb290ICRTdGFtcAokbiA9IDIKd2hpbGUgKFRlc3QtUGF0aCAkUnVuRGlyKSB7ICRSdW5EaXIgPSBKb2luLVBhdGggJEJhY2t1cFJvb3QgKCJ7MH1fezF9IiAtZiAkU3RhbXAsICRuKTsgJG4rKyB9ICAgIyDQutCw0LbQtNGL0Lkg0LfQsNC/0YPRgdC6IC0g0YHQstC+0Y8g0L/QsNC/0LrQsAokTG9nRmlsZSA9IEpvaW4tUGF0aCAkTmV4dXNEaXIgJ2luc3RhbGwubG9nJwoKIyDilIDilIAg0LLRi9Cy0L7QtCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCmZ1bmN0aW9uIFNheShbc3RyaW5nXSRUZXh0ID0gJycsIFtzdHJpbmddJENvbG9yID0gJ0dyYXknKSB7IFdyaXRlLUhvc3QgJFRleHQgLUZvcmVncm91bmRDb2xvciAkQ29sb3IgfQpmdW5jdGlvbiBPayhbc3RyaW5nXSRUZXh0KSB7IFdyaXRlLUhvc3QgJyAgW09LXSAnIC1Gb3JlZ3JvdW5kQ29sb3IgR3JlZW4gLU5vTmV3bGluZTsgV3JpdGUtSG9zdCAkVGV4dCB9CmZ1bmN0aW9uIFdhcm4oW3N0cmluZ10kVGV4dCkgeyBXcml0ZS1Ib3N0ICcgIFshXSAgJyAtRm9yZWdyb3VuZENvbG9yIFllbGxvdyAtTm9OZXdsaW5lOyBXcml0ZS1Ib3N0ICRUZXh0IH0KZnVuY3Rpb24gRmFpbChbc3RyaW5nXSRUZXh0KSB7IFdyaXRlLUhvc3QgJyAgW1hdICAnIC1Gb3JlZ3JvdW5kQ29sb3IgUmVkIC1Ob05ld2xpbmU7IFdyaXRlLUhvc3QgJFRleHQgfQpmdW5jdGlvbiBTdGVwKFtzdHJpbmddJFRleHQpIHsgV3JpdGUtSG9zdCAnICAtPiAgICcgLUZvcmVncm91bmRDb2xvciBEYXJrQ3lhbiAtTm9OZXdsaW5lOyBXcml0ZS1Ib3N0ICRUZXh0IH0KZnVuY3Rpb24gTG9nKFtzdHJpbmddJFRleHQpIHsKICAgIHRyeSB7CiAgICAgICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAkTmV4dXNEaXIpKSB7IE5ldy1JdGVtIC1JdGVtVHlwZSBEaXJlY3RvcnkgLUZvcmNlIC1QYXRoICROZXh1c0RpciB8IE91dC1OdWxsIH0KICAgICAgICBBZGQtQ29udGVudCAtUGF0aCAkTG9nRmlsZSAtVmFsdWUgKCJ7MH0gIHsxfSIgLWYgKEdldC1EYXRlIC1Gb3JtYXQgJ3MnKSwgJFRleHQpIC1FbmNvZGluZyBVVEY4CiAgICB9IGNhdGNoIHt9Cn0KZnVuY3Rpb24gQXNrKFtzdHJpbmddJFByb21wdCwgW3N0cmluZ10kRGVmYXVsdCA9ICcnKSB7CiAgICBpZiAoJEF1dG8pIHsgcmV0dXJuICREZWZhdWx0IH0KICAgICRhID0gUmVhZC1Ib3N0ICRQcm9tcHQKICAgIGlmICgkbnVsbCAtZXEgJGEpIHsgcmV0dXJuICREZWZhdWx0IH0KICAgICRhID0gJGEuVHJpbSgpCiAgICBpZiAoJGEgLWVxICcnKSB7IHJldHVybiAkRGVmYXVsdCB9CiAgICByZXR1cm4gJGEKfQpmdW5jdGlvbiBNYXNrKFtzdHJpbmddJGspIHsgaWYgKCRrLkxlbmd0aCAtZ3QgMTIpIHsgcmV0dXJuICRrLlN1YnN0cmluZygwLCA2KSArICcuLi4nICsgJGsuU3Vic3RyaW5nKCRrLkxlbmd0aCAtIDQpIH0gcmV0dXJuICRrIH0KZnVuY3Rpb24gRm10KFtvYmplY3RdJG4pIHsKICAgIHRyeSB7IHJldHVybiAoW2RlY2ltYWxdJG4pLlRvU3RyaW5nKCcjLDAnLCBbR2xvYmFsaXphdGlvbi5DdWx0dXJlSW5mb106OkludmFyaWFudEN1bHR1cmUpLlJlcGxhY2UoJywnLCAnICcpIH0gY2F0Y2ggeyByZXR1cm4gW3N0cmluZ10kbiB9Cn0KCiMg4pSA4pSAINGA0LXQt9C10YDQstC90YvQtSDQutC+0L/QuNC4INC4INC+0YLQutCw0YIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgokc2NyaXB0OkJhY2tlZFVwID0gQHt9CmZ1bmN0aW9uIEJhY2t1cC1PbmNlKFtzdHJpbmddJFBhdGgpIHsKICAgIGlmICgkc2NyaXB0OkJhY2tlZFVwLkNvbnRhaW5zS2V5KCRQYXRoKSkgeyByZXR1cm4gfQogICAgJHNjcmlwdDpCYWNrZWRVcFskUGF0aF0gPSAkdHJ1ZQogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAkUnVuRGlyKSkgeyBOZXctSXRlbSAtSXRlbVR5cGUgRGlyZWN0b3J5IC1Gb3JjZSAtUGF0aCAkUnVuRGlyIHwgT3V0LU51bGwgfQogICAgJG1hbmlmZXN0ID0gSm9pbi1QYXRoICRSdW5EaXIgJ21hbmlmZXN0LnRzdicKICAgIGlmIChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRQYXRoKSB7CiAgICAgICAgJG5hbWUgPSAoJ3swOkQyfV8nIC1mICRzY3JpcHQ6QmFja2VkVXAuQ291bnQpICsgKFNwbGl0LVBhdGggJFBhdGggLUxlYWYpCiAgICAgICAgQ29weS1JdGVtIC1MaXRlcmFsUGF0aCAkUGF0aCAtRGVzdGluYXRpb24gKEpvaW4tUGF0aCAkUnVuRGlyICRuYW1lKSAtRm9yY2UKICAgICAgICBBZGQtQ29udGVudCAtUGF0aCAkbWFuaWZlc3QgLVZhbHVlICgiRklMRWB0JFBhdGhgdCRuYW1lYHQxIikgLUVuY29kaW5nIFVURjgKICAgIH0gZWxzZSB7CiAgICAgICAgQWRkLUNvbnRlbnQgLVBhdGggJG1hbmlmZXN0IC1WYWx1ZSAoIkZJTEVgdCRQYXRoYHRgdDAiKSAtRW5jb2RpbmcgVVRGOAogICAgfQp9CgpmdW5jdGlvbiBTYXZlLVRleHQoW3N0cmluZ10kUGF0aCwgW3N0cmluZ10kVGV4dCkgewogICAgQmFja3VwLU9uY2UgJFBhdGgKICAgICRkaXIgPSBTcGxpdC1QYXRoICRQYXRoIC1QYXJlbnQKICAgIGlmICgtbm90IChUZXN0LVBhdGggJGRpcikpIHsgTmV3LUl0ZW0gLUl0ZW1UeXBlIERpcmVjdG9yeSAtRm9yY2UgLVBhdGggJGRpciB8IE91dC1OdWxsIH0KICAgIFtJTy5GaWxlXTo6V3JpdGVBbGxUZXh0KCRQYXRoLCAkVGV4dCwgJFV0ZjhOb0JvbSkKfQoKZnVuY3Rpb24gU2V0LVVzZXJFbnYoW3N0cmluZ10kTmFtZSwgW3N0cmluZ10kVmFsdWUpIHsKICAgICRvbGQgPSBHZXQtVXNlckVudiAkTmFtZQogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAkUnVuRGlyKSkgeyBOZXctSXRlbSAtSXRlbVR5cGUgRGlyZWN0b3J5IC1Gb3JjZSAtUGF0aCAkUnVuRGlyIHwgT3V0LU51bGwgfQogICAgJGhhZCA9IGlmICgkbnVsbCAtbmUgJG9sZCkgeyAnMScgfSBlbHNlIHsgJzAnIH0KICAgIEFkZC1Db250ZW50IC1QYXRoIChKb2luLVBhdGggJFJ1bkRpciAnbWFuaWZlc3QudHN2JykgLVZhbHVlICgiRU5WYHQkTmFtZWB0JG9sZGB0JGhhZCIpIC1FbmNvZGluZyBVVEY4CiAgICBpZiAoJFRlc3RSb290KSB7CiAgICAgICAgQWRkLUNvbnRlbnQgLVBhdGggKEpvaW4tUGF0aCAkVGVzdFJvb3QgJ3VzZXItZW52LnR4dCcpIC1WYWx1ZSAiJE5hbWU9JFZhbHVlIiAtRW5jb2RpbmcgVVRGOAogICAgfSBlbHNlIHsKICAgICAgICBbRW52aXJvbm1lbnRdOjpTZXRFbnZpcm9ubWVudFZhcmlhYmxlKCROYW1lLCAkVmFsdWUsICdVc2VyJykKICAgIH0KICAgIFNldC1JdGVtIC1QYXRoICJFbnY6JE5hbWUiIC1WYWx1ZSAkVmFsdWUKfQoKZnVuY3Rpb24gR2V0LVVzZXJFbnYoW3N0cmluZ10kTmFtZSkgewogICAgaWYgKCRUZXN0Um9vdCkgewogICAgICAgICRmID0gSm9pbi1QYXRoICRUZXN0Um9vdCAndXNlci1lbnYtaW5pdGlhbC50eHQnCiAgICAgICAgaWYgKFRlc3QtUGF0aCAkZikgewogICAgICAgICAgICBmb3JlYWNoICgkbCBpbiBHZXQtQ29udGVudCAkZikgeyBpZiAoJGwgLWxpa2UgIiROYW1lPSoiKSB7IHJldHVybiAkbC5TdWJzdHJpbmcoJE5hbWUuTGVuZ3RoICsgMSkgfSB9CiAgICAgICAgfQogICAgICAgIHJldHVybiAkbnVsbAogICAgfQogICAgcmV0dXJuIFtFbnZpcm9ubWVudF06OkdldEVudmlyb25tZW50VmFyaWFibGUoJE5hbWUsICdVc2VyJykKfQoKZnVuY3Rpb24gSW52b2tlLVJlc3RvcmUgewogICAgJHJ1bnMgPSBAKCkKICAgIGlmIChUZXN0LVBhdGggJEJhY2t1cFJvb3QpIHsKICAgICAgICAkcnVucyA9IEAoR2V0LUNoaWxkSXRlbSAkQmFja3VwUm9vdCAtRGlyZWN0b3J5IHwgV2hlcmUtT2JqZWN0IHsgJF8uTmFtZSAtbWF0Y2ggJ15cZHs4fV9cZHs2fShfXGQrKT8kJyAtYW5kIChUZXN0LVBhdGggKEpvaW4tUGF0aCAkXy5GdWxsTmFtZSAnbWFuaWZlc3QudHN2JykpIH0gfCBTb3J0LU9iamVjdCB7ICRfLkNyZWF0aW9uVGltZVV0YyB9LCBOYW1lIC1EZXNjZW5kaW5nKQogICAgfQogICAgaWYgKCRydW5zLkNvdW50IC1lcSAwKSB7IFdhcm4gJ9Cd0LXRh9C10LPQviDQvtGC0LrQsNGC0YvQstCw0YLRjDog0YDQtdC30LXRgNCy0L3Ri9GFINC60L7Qv9C40Lkg0L3QtdGCLic7IHJldHVybiB9CiAgICAkcnVuID0gJHJ1bnNbMF0KICAgIFNheSAoIiAg0J7RgtC60LDRgtGL0LLQsNGOINGD0YHRgtCw0L3QvtCy0LrRgyDQvtGCIHswfS4uLiIgLWYgJHJ1bi5OYW1lKSBXaGl0ZQogICAgZm9yZWFjaCAoJGxpbmUgaW4gR2V0LUNvbnRlbnQgKEpvaW4tUGF0aCAkcnVuLkZ1bGxOYW1lICdtYW5pZmVzdC50c3YnKSAtRW5jb2RpbmcgVVRGOCkgewogICAgICAgICRwID0gJGxpbmUgLXNwbGl0ICJgdCIKICAgICAgICBpZiAoJHAuQ291bnQgLWx0IDQpIHsgY29udGludWUgfQogICAgICAgIGlmICgkcFswXSAtZXEgJ0ZJTEUnKSB7CiAgICAgICAgICAgIGlmICgkcFszXSAtZXEgJzEnKSB7IENvcHktSXRlbSAtTGl0ZXJhbFBhdGggKEpvaW4tUGF0aCAkcnVuLkZ1bGxOYW1lICRwWzJdKSAtRGVzdGluYXRpb24gJHBbMV0gLUZvcmNlOyBPayAi0LLQvtGB0YHRgtCw0L3QvtCy0LvQtdC9ICQoJHBbMV0pIiB9CiAgICAgICAgICAgIGVsc2VpZiAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkcFsxXSkgeyBSZW1vdmUtSXRlbSAtTGl0ZXJhbFBhdGggJHBbMV0gLUZvcmNlOyBPayAi0YPQtNCw0LvRkdC9INGB0L7Qt9C00LDQvdC90YvQuSDRhNCw0LnQuyAkKCRwWzFdKSIgfQogICAgICAgIH0gZWxzZWlmICgkcFswXSAtZXEgJ1BBVEhBREQnKSB7CiAgICAgICAgICAgIFJlbW92ZS1Vc2VyUGF0aCAkcFsxXQogICAgICAgICAgICBPayAi0L/QsNC/0LrQsCAkKCRwWzFdKSDRg9Cx0YDQsNC90LAg0LjQtyBQQVRIIgogICAgICAgIH0gZWxzZWlmICgkcFswXSAtZXEgJ0VOVicpIHsKICAgICAgICAgICAgJHZhbHVlID0gaWYgKCRwWzNdIC1lcSAnMScpIHsgJHBbMl0gfSBlbHNlIHsgJG51bGwgfQogICAgICAgICAgICBpZiAoLW5vdCAkVGVzdFJvb3QpIHsgW0Vudmlyb25tZW50XTo6U2V0RW52aXJvbm1lbnRWYXJpYWJsZSgkcFsxXSwgJHZhbHVlLCAnVXNlcicpIH0KICAgICAgICAgICAgT2sgItC/0LXRgNC10LzQtdC90L3QsNGPICQoJHBbMV0pINCy0L7Qt9Cy0YDQsNGJ0LXQvdCwIgogICAgICAgIH0KICAgIH0KICAgIFJlbmFtZS1JdGVtIC1MaXRlcmFsUGF0aCAkcnVuLkZ1bGxOYW1lIC1OZXdOYW1lICgkcnVuLk5hbWUgKyAnLnJlc3RvcmVkJykKICAgIFNheSAnJwogICAgT2sgJ9CT0L7RgtC+0LLQvi4g0KPRgdGC0LDQvdC+0LLQu9C10L3QvdGL0LUg0YDQsNGB0YjQuNGA0LXQvdC40Y8gKENsaW5lKSDQvdC1INGD0LTQsNC70Y/Qu9C40YHRjCAtINC40YUg0LzQvtC20L3QviDRg9C00LDQu9C40YLRjCDQsiDRgNC10LTQsNC60YLQvtGA0LUuJwogICAgTG9nICJyZXN0b3JlICQoJHJ1bi5OYW1lKSIKfQoKIyDilIDilIAgSlNPTiAvIEpTT05DOiDRh9C40YLQsNC10Lwg0YEg0LrQvtC80LzQtdC90YLQsNGA0LjRj9C80LgsINC80LXQvdGP0LXQvCDRgtC+0LvRjNC60L4g0L3Rg9C20L3Ri9C1INC60LvRjtGH0LgsINC+0YHRgtCw0LvRjNC90L7QuSDRhNCw0LnQuyDQvdC1INGC0YDQvtCz0LDQtdC8IOKUgOKUgAoKJHNjcmlwdDpUb2tSZSA9IFtyZWdleF0nIig/OlteIlxcXXxcXC4pKiJ8Ly9bXlxyXG5dKnwvXCpbXHNcU10qP1wqL3xbe31cW1xdOixdfFteXHN7fVxbXF06LCIvXSsnCgpmdW5jdGlvbiBHZXQtSnNvblRva2Vucyhbc3RyaW5nXSRUZXh0KSB7CiAgICAkbGlzdCA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtvYmplY3RdCiAgICBmb3JlYWNoICgkbSBpbiAkc2NyaXB0OlRva1JlLk1hdGNoZXMoJFRleHQpKSB7CiAgICAgICAgaWYgKCRtLlZhbHVlLlN0YXJ0c1dpdGgoJy8vJykgLW9yICRtLlZhbHVlLlN0YXJ0c1dpdGgoJy8qJykpIHsgY29udGludWUgfQogICAgICAgICRsaXN0LkFkZCgkbSkKICAgIH0KICAgIHJldHVybiAsICRsaXN0Cn0KCmZ1bmN0aW9uIENvbnZlcnRGcm9tLUpzb25jKFtzdHJpbmddJFRleHQpIHsKICAgICR0b2tzID0gR2V0LUpzb25Ub2tlbnMgJFRleHQKICAgIGlmICgkdG9rcy5Db3VudCAtZXEgMCkgeyByZXR1cm4gJG51bGwgfQogICAgJHBhcnRzID0gTmV3LU9iamVjdCBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0W3N0cmluZ10KICAgIGZvciAoJGkgPSAwOyAkaSAtbHQgJHRva3MuQ291bnQ7ICRpKyspIHsKICAgICAgICAkdiA9ICR0b2tzWyRpXS5WYWx1ZQogICAgICAgIGlmICgkdiAtZXEgJywnIC1hbmQgKCRpICsgMSkgLWx0ICR0b2tzLkNvdW50IC1hbmQgKCR0b2tzWyRpICsgMV0uVmFsdWUgLWVxICd9JyAtb3IgJHRva3NbJGkgKyAxXS5WYWx1ZSAtZXEgJ10nKSkgeyBjb250aW51ZSB9CiAgICAgICAgJHBhcnRzLkFkZCgkdikKICAgIH0KICAgIHJldHVybiAoKCRwYXJ0cyAtam9pbiAnICcpIHwgQ29udmVydEZyb20tSnNvbikKfQoKZnVuY3Rpb24gUmVhZC1Kc29uRmlsZShbc3RyaW5nXSRQYXRoKSB7CiAgICAjICRudWxsIC0g0YTQsNC50LvQsCDQvdC10YIg0LjQu9C4INC+0L0g0L/Rg9GB0YI7INC40YHQutC70Y7Rh9C10L3QuNC1IC0g0YTQsNC50Lsg0LXRgdGC0YwsINC90L4g0Y3RgtC+INC90LUgSlNPTi3QvtCx0YrQtdC60YIgKNGC0LDQutC40LUg0YTQsNC50LvRiyDQvdC1INGC0YDQvtCz0LDQtdC8KQogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFBhdGgpKSB7IHJldHVybiAkbnVsbCB9CiAgICAkdGV4dCA9IFtJTy5GaWxlXTo6UmVhZEFsbFRleHQoJFBhdGgpCiAgICBpZiAoLW5vdCAkdGV4dC5UcmltKCkpIHsgcmV0dXJuICRudWxsIH0KICAgICRvYmogPSBDb252ZXJ0RnJvbS1Kc29uYyAkdGV4dAogICAgaWYgKCRudWxsIC1lcSAkb2JqIC1vciAkb2JqLkdldFR5cGUoKS5GdWxsTmFtZSAtbmUgJ1N5c3RlbS5NYW5hZ2VtZW50LkF1dG9tYXRpb24uUFNDdXN0b21PYmplY3QnKSB7IHRocm93ICLQsiAkUGF0aCDQvdC1IEpTT04t0L7QsdGK0LXQutGCIiB9CiAgICByZXR1cm4gJG9iagp9CgpmdW5jdGlvbiBDb252ZXJ0VG8tSnNvblN0cihbc3RyaW5nXSRzKSB7CiAgICAkcyA9ICRzLlJlcGxhY2UoJ1wnLCAnXFwnKS5SZXBsYWNlKCciJywgJ1wiJykuUmVwbGFjZSgiYHIiLCAnXHInKS5SZXBsYWNlKCJgbiIsICdcbicpLlJlcGxhY2UoImB0IiwgJ1x0JykKICAgICRzID0gW3JlZ2V4XTo6UmVwbGFjZSgkcywgJ1tceDAwLVx4MWZdJywgeyBwYXJhbSgkbSkgJ1x1ezA6eDR9JyAtZiBbaW50XVtjaGFyXSRtLlZhbHVlIH0pCiAgICByZXR1cm4gJyInICsgJHMgKyAnIicKfQoKZnVuY3Rpb24gQ29udmVydFRvLU5leHVzSnNvbigkVmFsdWUsIFtpbnRdJExldmVsLCBbc3RyaW5nXSRVbml0LCBbc3RyaW5nXSROTCkgewogICAgaWYgKCRudWxsIC1lcSAkVmFsdWUpIHsgcmV0dXJuICdudWxsJyB9CiAgICBpZiAoJFZhbHVlIC1pcyBbYm9vbF0pIHsgaWYgKCRWYWx1ZSkgeyByZXR1cm4gJ3RydWUnIH0gZWxzZSB7IHJldHVybiAnZmFsc2UnIH0gfQogICAgaWYgKCRWYWx1ZSAtaXMgW3N0cmluZ10gLW9yICRWYWx1ZSAtaXMgW2NoYXJdKSB7IHJldHVybiAoQ29udmVydFRvLUpzb25TdHIgKFtzdHJpbmddJFZhbHVlKSkgfQogICAgaWYgKCRWYWx1ZSAtaXMgW2RvdWJsZV0gLW9yICRWYWx1ZSAtaXMgW3NpbmdsZV0pIHsgcmV0dXJuICRWYWx1ZS5Ub1N0cmluZygnUicsIFtHbG9iYWxpemF0aW9uLkN1bHR1cmVJbmZvXTo6SW52YXJpYW50Q3VsdHVyZSkgfQogICAgaWYgKCRWYWx1ZSAtaXMgW2ludF0gLW9yICRWYWx1ZSAtaXMgW2xvbmddIC1vciAkVmFsdWUgLWlzIFtkZWNpbWFsXSAtb3IgJFZhbHVlIC1pcyBbaW50MTZdIC1vciAkVmFsdWUgLWlzIFtieXRlXSAtb3IgJFZhbHVlIC1pcyBbdWludDMyXSAtb3IgJFZhbHVlIC1pcyBbdWludDY0XSkgewogICAgICAgIHJldHVybiAoW1N5c3RlbS5Db252ZXJ0XTo6VG9TdHJpbmcoJFZhbHVlLCBbR2xvYmFsaXphdGlvbi5DdWx0dXJlSW5mb106OkludmFyaWFudEN1bHR1cmUpKQogICAgfQogICAgJHBhZCA9ICRVbml0ICogKCRMZXZlbCArIDEpCiAgICAkZW5kID0gJFVuaXQgKiAkTGV2ZWwKICAgICRpc0RpY3QgPSAkVmFsdWUgLWlzIFtTeXN0ZW0uQ29sbGVjdGlvbnMuSURpY3Rpb25hcnldCiAgICAkaXNPYmogPSAkVmFsdWUuR2V0VHlwZSgpLkZ1bGxOYW1lIC1lcSAnU3lzdGVtLk1hbmFnZW1lbnQuQXV0b21hdGlvbi5QU0N1c3RvbU9iamVjdCcKICAgIGlmICgkaXNEaWN0IC1vciAkaXNPYmopIHsKICAgICAgICAkaXRlbXMgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgICAgIGlmICgkaXNEaWN0KSB7CiAgICAgICAgICAgIGZvcmVhY2ggKCRrIGluICRWYWx1ZS5LZXlzKSB7ICRpdGVtcy5BZGQoJHBhZCArIChDb252ZXJ0VG8tSnNvblN0ciAoW3N0cmluZ10kaykpICsgJzogJyArIChDb252ZXJ0VG8tTmV4dXNKc29uICRWYWx1ZVska10gKCRMZXZlbCArIDEpICRVbml0ICROTCkpIH0KICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBmb3JlYWNoICgkcCBpbiAkVmFsdWUuUFNPYmplY3QuUHJvcGVydGllcykgeyAkaXRlbXMuQWRkKCRwYWQgKyAoQ29udmVydFRvLUpzb25TdHIgJHAuTmFtZSkgKyAnOiAnICsgKENvbnZlcnRUby1OZXh1c0pzb24gJHAuVmFsdWUgKCRMZXZlbCArIDEpICRVbml0ICROTCkpIH0KICAgICAgICB9CiAgICAgICAgaWYgKCRpdGVtcy5Db3VudCAtZXEgMCkgeyByZXR1cm4gJ3t9JyB9CiAgICAgICAgcmV0dXJuICd7JyArICROTCArICgkaXRlbXMgLWpvaW4gKCcsJyArICROTCkpICsgJE5MICsgJGVuZCArICd9JwogICAgfQogICAgaWYgKCRWYWx1ZSAtaXMgW1N5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYWJsZV0pIHsKICAgICAgICAkaXRlbXMgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgICAgIGZvcmVhY2ggKCR4IGluICRWYWx1ZSkgeyAkaXRlbXMuQWRkKCRwYWQgKyAoQ29udmVydFRvLU5leHVzSnNvbiAkeCAoJExldmVsICsgMSkgJFVuaXQgJE5MKSkgfQogICAgICAgIGlmICgkaXRlbXMuQ291bnQgLWVxIDApIHsgcmV0dXJuICdbXScgfQogICAgICAgIHJldHVybiAnWycgKyAkTkwgKyAoJGl0ZW1zIC1qb2luICgnLCcgKyAkTkwpKSArICROTCArICRlbmQgKyAnXScKICAgIH0KICAgIHJldHVybiAoQ29udmVydFRvLUpzb25TdHIgKFtzdHJpbmddJFZhbHVlKSkKfQoKZnVuY3Rpb24gU2V0LUpzb25Ub3BMZXZlbChbc3RyaW5nXSRUZXh0LCBbc3RyaW5nXSRLZXksIFtzdHJpbmddJFZhbHVlSnNvbiwgW3N0cmluZ10kTkwsIFtzdHJpbmddJFVuaXQpIHsKICAgICR0b2tzID0gR2V0LUpzb25Ub2tlbnMgJFRleHQKICAgIGlmICgkdG9rcy5Db3VudCAtZXEgMCAtb3IgJHRva3NbMF0uVmFsdWUgLW5lICd7JykgeyB0aHJvdyAn0LrQvtGA0LXQvdGMINGE0LDQudC70LAgLSDQvdC1INC+0LHRitC10LrRgicgfQogICAgJGRlcHRoID0gMAogICAgJGNsb3NlID0gJG51bGwKICAgICRxdW90ZWQgPSBDb252ZXJ0VG8tSnNvblN0ciAkS2V5CiAgICBmb3IgKCRpID0gMDsgJGkgLWx0ICR0b2tzLkNvdW50OyAkaSsrKSB7CiAgICAgICAgJHYgPSAkdG9rc1skaV0uVmFsdWUKICAgICAgICBpZiAoJHYgLWVxICd7JyAtb3IgJHYgLWVxICdbJykgeyAkZGVwdGgrKzsgY29udGludWUgfQogICAgICAgIGlmICgkdiAtZXEgJ30nIC1vciAkdiAtZXEgJ10nKSB7ICRkZXB0aC0tOyBpZiAoJGRlcHRoIC1lcSAwKSB7ICRjbG9zZSA9ICRpOyBicmVhayB9OyBjb250aW51ZSB9CiAgICAgICAgaWYgKCRkZXB0aCAtZXEgMSAtYW5kICR2IC1jZXEgJHF1b3RlZCAtYW5kICgkaSArIDIpIC1sdCAkdG9rcy5Db3VudCAtYW5kICR0b2tzWyRpICsgMV0uVmFsdWUgLWVxICc6JykgewogICAgICAgICAgICAkaiA9ICRpICsgMgogICAgICAgICAgICAkc3RhcnQgPSAkdG9rc1skal0uSW5kZXgKICAgICAgICAgICAgJHN0b3AgPSAkdG9rc1skal0uSW5kZXggKyAkdG9rc1skal0uTGVuZ3RoCiAgICAgICAgICAgIGlmICgkdG9rc1skal0uVmFsdWUgLWVxICd7JyAtb3IgJHRva3NbJGpdLlZhbHVlIC1lcSAnWycpIHsKICAgICAgICAgICAgICAgICRkID0gMAogICAgICAgICAgICAgICAgZm9yICgkayA9ICRqOyAkayAtbHQgJHRva3MuQ291bnQ7ICRrKyspIHsKICAgICAgICAgICAgICAgICAgICAkdCA9ICR0b2tzWyRrXS5WYWx1ZQogICAgICAgICAgICAgICAgICAgIGlmICgkdCAtZXEgJ3snIC1vciAkdCAtZXEgJ1snKSB7ICRkKysgfQogICAgICAgICAgICAgICAgICAgIGVsc2VpZiAoJHQgLWVxICd9JyAtb3IgJHQgLWVxICddJykgeyAkZC0tOyBpZiAoJGQgLWVxIDApIHsgJHN0b3AgPSAkdG9rc1ska10uSW5kZXggKyAxOyBicmVhayB9IH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICByZXR1cm4gJFRleHQuU3Vic3RyaW5nKDAsICRzdGFydCkgKyAkVmFsdWVKc29uICsgJFRleHQuU3Vic3RyaW5nKCRzdG9wKQogICAgICAgIH0KICAgIH0KICAgIGlmICgkbnVsbCAtZXEgJGNsb3NlKSB7IHRocm93ICfQvdC1INC90LDQudC00LXQvSDQutC+0L3QtdGGINC+0LHRitC10LrRgtCwJyB9CiAgICAkcHJldiA9ICR0b2tzWyRjbG9zZSAtIDFdCiAgICAkcHJvcCA9ICRxdW90ZWQgKyAnOiAnICsgJFZhbHVlSnNvbgogICAgaWYgKCRwcmV2LlZhbHVlIC1lcSAneycpIHsKICAgICAgICByZXR1cm4gJFRleHQuU3Vic3RyaW5nKDAsICRwcmV2LkluZGV4ICsgMSkgKyAkTkwgKyAkVW5pdCArICRwcm9wICsgJE5MICsgJFRleHQuU3Vic3RyaW5nKCR0b2tzWyRjbG9zZV0uSW5kZXgpCiAgICB9CiAgICBpZiAoJHByZXYuVmFsdWUgLWVxICcsJykgeyAkYXQgPSAkcHJldi5JbmRleCArIDE7ICRzZXAgPSAkTkwgKyAkVW5pdCB9CiAgICBlbHNlIHsgJGF0ID0gJHByZXYuSW5kZXggKyAkcHJldi5MZW5ndGg7ICRzZXAgPSAnLCcgKyAkTkwgKyAkVW5pdCB9CiAgICByZXR1cm4gJFRleHQuU3Vic3RyaW5nKDAsICRhdCkgKyAkc2VwICsgJHByb3AgKyAkVGV4dC5TdWJzdHJpbmcoJGF0KQp9CgpmdW5jdGlvbiBVcGRhdGUtSnNvbkZpbGUoW3N0cmluZ10kUGF0aCwgJFZhbHVlcykgewogICAgIyBWYWx1ZXM6INGD0L/QvtGA0Y/QtNC+0YfQtdC90L3Ri9C5INGB0LvQvtCy0LDRgNGMICLQutC70Y7RhyDQstC10YDRhdC90LXQs9C+INGD0YDQvtCy0L3RjyIgLT4g0LfQvdCw0YfQtdC90LjQtS4g0J7RgdGC0LDQu9GM0L3Ri9C1INC60LvRjtGH0Lgg0Lgg0LrQvtC80LzQtdC90YLQsNGA0LjQuCDRgdC+0YXRgNCw0L3Rj9GO0YLRgdGPLgogICAgJHRleHQgPSAnJwogICAgaWYgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFBhdGgpIHsgJHRleHQgPSBbSU8uRmlsZV06OlJlYWRBbGxUZXh0KCRQYXRoKSB9CiAgICBpZiAoLW5vdCAkdGV4dC5UcmltKCkpIHsgJHRleHQgPSAie2BufSIgfQogICAgJE5MID0gaWYgKCR0ZXh0LkNvbnRhaW5zKCJgcmBuIikpIHsgImByYG4iIH0gZWxzZSB7ICJgbiIgfQogICAgJG0gPSBbcmVnZXhdOjpNYXRjaCgkdGV4dCwgJ1xuKFsgXHRdKykiJykKICAgICRVbml0ID0gaWYgKCRtLlN1Y2Nlc3MpIHsgJG0uR3JvdXBzWzFdLlZhbHVlIH0gZWxzZSB7ICcgICAgJyB9CiAgICAkbnVsbCA9IENvbnZlcnRGcm9tLUpzb25jICR0ZXh0CiAgICBmb3JlYWNoICgkayBpbiAkVmFsdWVzLktleXMpIHsKICAgICAgICAkanNvbiA9IENvbnZlcnRUby1OZXh1c0pzb24gJFZhbHVlc1ska10gMSAkVW5pdCAkTkwKICAgICAgICAkdGV4dCA9IFNldC1Kc29uVG9wTGV2ZWwgJHRleHQgJGsgJGpzb24gJE5MICRVbml0CiAgICB9CiAgICAkbnVsbCA9IENvbnZlcnRGcm9tLUpzb25jICR0ZXh0ICAgIyDRgNC10LfRg9C70YzRgtCw0YIg0L7QsdGP0LfQsNC9INC+0YHRgtCw0LLQsNGC0YzRgdGPINCy0LDQu9C40LTQvdGL0LwsINC40L3QsNGH0LUg0L3QuNGH0LXQs9C+INC90LUg0L/QuNGI0LXQvAogICAgU2F2ZS1UZXh0ICRQYXRoICR0ZXh0Cn0KCmZ1bmN0aW9uIE5ldy1PYmogeyByZXR1cm4gTmV3LU9iamVjdCBQU09iamVjdCB9CmZ1bmN0aW9uIFNldC1Qcm9wKCRPYmosIFtzdHJpbmddJE5hbWUsICRWYWx1ZSkgewogICAgaWYgKCRPYmouUFNPYmplY3QuUHJvcGVydGllc1skTmFtZV0pIHsgJE9iai4kTmFtZSA9ICRWYWx1ZSB9IGVsc2UgeyAkT2JqIHwgQWRkLU1lbWJlciAtTm90ZVByb3BlcnR5TmFtZSAkTmFtZSAtTm90ZVByb3BlcnR5VmFsdWUgJFZhbHVlIH0KfQpmdW5jdGlvbiBHZXQtUHJvcCgkT2JqLCBbc3RyaW5nXSROYW1lKSB7CiAgICBpZiAoJG51bGwgLW5lICRPYmogLWFuZCAkT2JqLlBTT2JqZWN0LlByb3BlcnRpZXNbJE5hbWVdKSB7IHJldHVybiAkT2JqLiROYW1lIH0KICAgIHJldHVybiAkbnVsbAp9CmZ1bmN0aW9uIEdldC1PYmpQcm9wKCRPYmosIFtzdHJpbmddJE5hbWUpIHsKICAgICR2ID0gR2V0LVByb3AgJE9iaiAkTmFtZQogICAgaWYgKCRudWxsIC1uZSAkdiAtYW5kICR2LkdldFR5cGUoKS5GdWxsTmFtZSAtZXEgJ1N5c3RlbS5NYW5hZ2VtZW50LkF1dG9tYXRpb24uUFNDdXN0b21PYmplY3QnKSB7IHJldHVybiAkdiB9CiAgICByZXR1cm4gKE5ldy1PYmopCn0KCiMg4pSA4pSAINC80LXQvdGOINCyINGC0LXRgNC80LjQvdCw0LvQtTog0YHRgtGA0LXQu9C60LgsINCf0YDQvtCx0LXQuywgQSwgRW50ZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgpmdW5jdGlvbiBUZXN0LVR1aSB7CiAgICAjIGEgcmVhbCBjb25zb2xlIG9ubHk6IHRlc3RzLCBORVhVU19BVVRPIGFuZCBwaXBlZCBpbnB1dCBrZWVwIHRoZSBudW1iZXJlZCB0ZXh0IG1lbnUKICAgIGlmICgkQXV0byAtb3IgJFRlc3RSb290KSB7IHJldHVybiAkZmFsc2UgfQogICAgdHJ5IHsgcmV0dXJuICgtbm90IFtDb25zb2xlXTo6SXNJbnB1dFJlZGlyZWN0ZWQpIC1hbmQgW0NvbnNvbGVdOjpXaW5kb3dIZWlnaHQgLWdlIDEyIC1hbmQgW0NvbnNvbGVdOjpXaW5kb3dXaWR0aCAtZ2UgNTAgfSBjYXRjaCB7IHJldHVybiAkZmFsc2UgfQp9CgpmdW5jdGlvbiBJbnZva2UtTWVudSB7CiAgICAjIFJvd3M6IG9iamVjdHMgd2l0aCBMYWJlbCwgT24sIEtpbmQgKCdjaGVjaycgfCAncmFkaW8nKS4gUmV0dXJucyAnb2snIHwgJ3F1aXQnIHwgJ3Jlc3RvcmUnLgogICAgcGFyYW0oW3N0cmluZ10kVGl0bGUsIFtvYmplY3RbXV0kUm93cywgW3N0cmluZ10kSGludCwgW3N0cmluZ1tdXSRFeHRyYSA9IEAoKSkKICAgICRuID0gJFJvd3MuQ291bnQKICAgICR2aWV3ID0gW01hdGhdOjpNYXgoNCwgW01hdGhdOjpNaW4oJG4sIFtDb25zb2xlXTo6V2luZG93SGVpZ2h0IC0gOCkpCiAgICAkaGVpZ2h0ID0gJHZpZXcgKyAyCiAgICAkd2lkdGggPSBbTWF0aF06Ok1heCg0MCwgW0NvbnNvbGVdOjpXaW5kb3dXaWR0aCAtIDEpCiAgICBmb3IgKCRpID0gMDsgJGkgLWx0ICRoZWlnaHQ7ICRpKyspIHsgV3JpdGUtSG9zdCAnJyB9CiAgICAkdG9wID0gW01hdGhdOjpNYXgoMCwgW0NvbnNvbGVdOjpDdXJzb3JUb3AgLSAkaGVpZ2h0KQogICAgJHBvcyA9IDA7ICRmaXJzdCA9IDAKICAgICRmaXQgPSB7IHBhcmFtKCR0KSAkdCA9ICR0LlBhZFJpZ2h0KCR3aWR0aCk7IGlmICgkdC5MZW5ndGggLWd0ICR3aWR0aCkgeyAkdCA9ICR0LlN1YnN0cmluZygwLCAkd2lkdGgpIH07ICR0IH0KICAgIHRyeSB7IFtDb25zb2xlXTo6Q3Vyc29yVmlzaWJsZSA9ICRmYWxzZSB9IGNhdGNoIHt9CiAgICB0cnkgewogICAgICAgIHdoaWxlICgkdHJ1ZSkgewogICAgICAgICAgICBpZiAoJHBvcyAtbHQgJGZpcnN0KSB7ICRmaXJzdCA9ICRwb3MgfSBlbHNlaWYgKCRwb3MgLWdlICRmaXJzdCArICR2aWV3KSB7ICRmaXJzdCA9ICRwb3MgLSAkdmlldyArIDEgfQogICAgICAgICAgICBbQ29uc29sZV06OlNldEN1cnNvclBvc2l0aW9uKDAsICR0b3ApCiAgICAgICAgICAgIFdyaXRlLUhvc3QgKCYgJGZpdCAoJyAgJyArICRUaXRsZSkpIC1Gb3JlZ3JvdW5kQ29sb3IgV2hpdGUKICAgICAgICAgICAgJGxhc3QgPSBbTWF0aF06Ok1pbigkbiwgJGZpcnN0ICsgJHZpZXcpCiAgICAgICAgICAgIGZvciAoJGkgPSAkZmlyc3Q7ICRpIC1sdCAkbGFzdDsgJGkrKykgewogICAgICAgICAgICAgICAgJHIgPSAkUm93c1skaV0KICAgICAgICAgICAgICAgICRib3ggPSBpZiAoJHIuS2luZCAtZXEgJ3JhZGlvJykgeyBpZiAoJHIuT24pIHsgJygqKScgfSBlbHNlIHsgJyggKScgfSB9IGVsc2UgeyBpZiAoJHIuT24pIHsgJ1t4XScgfSBlbHNlIHsgJ1sgXScgfSB9CiAgICAgICAgICAgICAgICAkbGluZSA9ICYgJGZpdCAoJyAgezB9IHsxfSB7Mn0nIC1mICQoaWYgKCRpIC1lcSAkcG9zKSB7ICc+JyB9IGVsc2UgeyAnICcgfSksICRib3gsICRyLkxhYmVsKQogICAgICAgICAgICAgICAgaWYgKCRpIC1lcSAkcG9zKSB7IFdyaXRlLUhvc3QgJGxpbmUgLUZvcmVncm91bmRDb2xvciBCbGFjayAtQmFja2dyb3VuZENvbG9yIERhcmtZZWxsb3cgfQogICAgICAgICAgICAgICAgZWxzZWlmICgkci5PbikgeyBXcml0ZS1Ib3N0ICRsaW5lIC1Gb3JlZ3JvdW5kQ29sb3IgV2hpdGUgfQogICAgICAgICAgICAgICAgZWxzZSB7IFdyaXRlLUhvc3QgJGxpbmUgLUZvcmVncm91bmRDb2xvciBHcmF5IH0KICAgICAgICAgICAgfQogICAgICAgICAgICBmb3IgKCRpID0gJGxhc3QgLSAkZmlyc3Q7ICRpIC1sdCAkdmlldzsgJGkrKykgeyBXcml0ZS1Ib3N0ICgmICRmaXQgJycpIH0KICAgICAgICAgICAgJG1vcmUgPSBpZiAoJG4gLWd0ICR2aWV3KSB7ICcgICBbezB9LXsxfSDQuNC3IHsyfV0nIC1mICgkZmlyc3QgKyAxKSwgJGxhc3QsICRuIH0gZWxzZSB7ICcnIH0KICAgICAgICAgICAgV3JpdGUtSG9zdCAoJiAkZml0ICgnICAnICsgJEhpbnQgKyAkbW9yZSkpIC1Gb3JlZ3JvdW5kQ29sb3IgRGFya0dyYXkKICAgICAgICAgICAgJGsgPSBbQ29uc29sZV06OlJlYWRLZXkoJHRydWUpCiAgICAgICAgICAgICRjaCA9IFtzdHJpbmddW2NoYXJdOjpUb0xvd2VyKCRrLktleUNoYXIpCiAgICAgICAgICAgIGlmICgkay5LZXkgLWVxICdVcEFycm93JykgeyAkcG9zID0gKCRwb3MgLSAxICsgJG4pICUgJG4gfQogICAgICAgICAgICBlbHNlaWYgKCRrLktleSAtZXEgJ0Rvd25BcnJvdycpIHsgJHBvcyA9ICgkcG9zICsgMSkgJSAkbiB9CiAgICAgICAgICAgIGVsc2VpZiAoJGsuS2V5IC1lcSAnUGFnZVVwJykgeyAkcG9zID0gW01hdGhdOjpNYXgoMCwgJHBvcyAtICR2aWV3KSB9CiAgICAgICAgICAgIGVsc2VpZiAoJGsuS2V5IC1lcSAnUGFnZURvd24nKSB7ICRwb3MgPSBbTWF0aF06Ok1pbigkbiAtIDEsICRwb3MgKyAkdmlldykgfQogICAgICAgICAgICBlbHNlaWYgKCRrLktleSAtZXEgJ0hvbWUnKSB7ICRwb3MgPSAwIH0KICAgICAgICAgICAgZWxzZWlmICgkay5LZXkgLWVxICdFbmQnKSB7ICRwb3MgPSAkbiAtIDEgfQogICAgICAgICAgICBlbHNlaWYgKCRrLktleSAtZXEgJ1NwYWNlYmFyJykgewogICAgICAgICAgICAgICAgJHIgPSAkUm93c1skcG9zXQogICAgICAgICAgICAgICAgaWYgKCRyLktpbmQgLWVxICdyYWRpbycpIHsgZm9yZWFjaCAoJHggaW4gJFJvd3MpIHsgaWYgKCR4LktpbmQgLWVxICdyYWRpbycpIHsgJHguT24gPSAkZmFsc2UgfSB9OyAkci5PbiA9ICR0cnVlIH0KICAgICAgICAgICAgICAgIGVsc2UgeyAkci5PbiA9IC1ub3QgJHIuT24gfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2VpZiAoJGsuS2V5IC1lcSAnRW50ZXInKSB7CiAgICAgICAgICAgICAgICAkciA9ICRSb3dzWyRwb3NdCiAgICAgICAgICAgICAgICBpZiAoJHIuS2luZCAtZXEgJ3JhZGlvJykgeyBmb3JlYWNoICgkeCBpbiAkUm93cykgeyBpZiAoJHguS2luZCAtZXEgJ3JhZGlvJykgeyAkeC5PbiA9ICRmYWxzZSB9IH07ICRyLk9uID0gJHRydWUgfQogICAgICAgICAgICAgICAgcmV0dXJuICdvaycKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlaWYgKCRrLktleSAtZXEgJ0VzY2FwZScgLW9yICgoJGNoIC1lcSAncScgLW9yICRjaCAtZXEgJ9C5JykgLWFuZCAkRXh0cmEgLWNvbnRhaW5zICdxdWl0JykpIHsgcmV0dXJuICdxdWl0JyB9CiAgICAgICAgICAgIGVsc2VpZiAoKCRjaCAtZXEgJ2EnIC1vciAkY2ggLWVxICfRhCcpIC1hbmQgQCgkUm93cyB8IFdoZXJlLU9iamVjdCB7ICRfLktpbmQgLWVxICdjaGVjaycgfSkuQ291bnQgLWd0IDEpIHsKICAgICAgICAgICAgICAgICRjaGVja3MgPSBAKCRSb3dzIHwgV2hlcmUtT2JqZWN0IHsgJF8uS2luZCAtZXEgJ2NoZWNrJyB9KQogICAgICAgICAgICAgICAgJGFsbCA9IEAoJGNoZWNrcyB8IFdoZXJlLU9iamVjdCB7IC1ub3QgJF8uT24gfSkuQ291bnQgLWd0IDAKICAgICAgICAgICAgICAgIGZvcmVhY2ggKCR4IGluICRjaGVja3MpIHsgJHguT24gPSAkYWxsIH0KICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlaWYgKCgkY2ggLWVxICdyJyAtb3IgJGNoIC1lcSAn0LonKSAtYW5kICRFeHRyYSAtY29udGFpbnMgJ3Jlc3RvcmUnKSB7IHJldHVybiAncmVzdG9yZScgfQogICAgICAgIH0KICAgIH0gZmluYWxseSB7CiAgICAgICAgdHJ5IHsgW0NvbnNvbGVdOjpDdXJzb3JWaXNpYmxlID0gJHRydWUgfSBjYXRjaCB7fQogICAgICAgIHRyeSB7IFtDb25zb2xlXTo6U2V0Q3Vyc29yUG9zaXRpb24oMCwgJHRvcCArICRoZWlnaHQpIH0gY2F0Y2gge30KICAgIH0KfQoKIyDilIDilIAg0L/QvtC40YHQuiDQv9GA0L7Qs9GA0LDQvNC8IOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKZnVuY3Rpb24gRmluZC1DbGkoW3N0cmluZ1tdXSROYW1lcykgewogICAgaWYgKCRUZXN0Um9vdCkgewogICAgICAgIGZvcmVhY2ggKCRuIGluICROYW1lcykgeyAkcCA9IEpvaW4tUGF0aCAkVGVzdFJvb3QgImJpblwkbiI7IGlmIChUZXN0LVBhdGggJHApIHsgcmV0dXJuICRwIH0gfQogICAgICAgIHJldHVybiAkbnVsbAogICAgfQogICAgZm9yZWFjaCAoJG4gaW4gJE5hbWVzKSB7CiAgICAgICAgJGMgPSBHZXQtQ29tbWFuZCAkbiAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEKICAgICAgICBpZiAoJGMpIHsgcmV0dXJuICRjLlNvdXJjZSB9CiAgICB9CiAgICByZXR1cm4gJG51bGwKfQoKZnVuY3Rpb24gUmVmcmVzaC1QYXRoIHsKICAgIGlmICgkVGVzdFJvb3QpIHsgcmV0dXJuIH0KICAgICRlbnY6UGF0aCA9IFtFbnZpcm9ubWVudF06OkdldEVudmlyb25tZW50VmFyaWFibGUoJ1BhdGgnLCAnTWFjaGluZScpICsgJzsnICsgW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZSgnUGF0aCcsICdVc2VyJykKfQoKJEVkaXRvcnMgPSBAKAogICAgW3BzY3VzdG9tb2JqZWN0XUB7IElkID0gJ2N1cnNvcic7IE5hbWUgPSAnQ3Vyc29yJzsgQ21kID0gJ2N1cnNvci5jbWQnOyBQYXRocyA9IEAoIiRMb2NhbEFwcFxQcm9ncmFtc1xjdXJzb3JccmVzb3VyY2VzXGFwcFxiaW5cY3Vyc29yLmNtZCIpOyBVc2VyID0gIiRBcHBEYXRhXEN1cnNvclxVc2VyIjsgRXh0ID0gIiRVc2VySG9tZVwuY3Vyc29yXGV4dGVuc2lvbnMiOyBDbGkgPSAkbnVsbDsgRm91bmQgPSAkZmFsc2UgfSwKICAgIFtwc2N1c3RvbW9iamVjdF1AeyBJZCA9ICd2c2NvZGUnOyBOYW1lID0gJ1ZTIENvZGUnOyBDbWQgPSAnY29kZS5jbWQnOyBQYXRocyA9IEAoIiRMb2NhbEFwcFxQcm9ncmFtc1xNaWNyb3NvZnQgVlMgQ29kZVxiaW5cY29kZS5jbWQiLCAiJGVudjpQcm9ncmFtRmlsZXNcTWljcm9zb2Z0IFZTIENvZGVcYmluXGNvZGUuY21kIik7IFVzZXIgPSAiJEFwcERhdGFcQ29kZVxVc2VyIjsgRXh0ID0gIiRVc2VySG9tZVwudnNjb2RlXGV4dGVuc2lvbnMiOyBDbGkgPSAkbnVsbDsgRm91bmQgPSAkZmFsc2UgfSwKICAgIFtwc2N1c3RvbW9iamVjdF1AeyBJZCA9ICd3aW5kc3VyZic7IE5hbWUgPSAnV2luZHN1cmYnOyBDbWQgPSAnd2luZHN1cmYuY21kJzsgUGF0aHMgPSBAKCIkTG9jYWxBcHBcUHJvZ3JhbXNcV2luZHN1cmZcYmluXHdpbmRzdXJmLmNtZCIpOyBVc2VyID0gIiRBcHBEYXRhXFdpbmRzdXJmXFVzZXIiOyBFeHQgPSAiJFVzZXJIb21lXC53aW5kc3VyZlxleHRlbnNpb25zIjsgQ2xpID0gJG51bGw7IEZvdW5kID0gJGZhbHNlIH0KKQoKZnVuY3Rpb24gRGV0ZWN0LUVkaXRvcnMgewogICAgZm9yZWFjaCAoJGUgaW4gJEVkaXRvcnMpIHsKICAgICAgICAkY2xpID0gJG51bGwKICAgICAgICBpZiAoJFRlc3RSb290KSB7ICRjbGkgPSBGaW5kLUNsaSBAKCRlLkNtZCkgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICBmb3JlYWNoICgkcCBpbiAkZS5QYXRocykgeyBpZiAoJHAgLWFuZCAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkcCkpIHsgJGNsaSA9ICRwOyBicmVhayB9IH0KICAgICAgICAgICAgaWYgKC1ub3QgJGNsaSkgeyAkY2xpID0gRmluZC1DbGkgQCgkZS5DbWQpIH0KICAgICAgICB9CiAgICAgICAgJGUuQ2xpID0gJGNsaQogICAgICAgICRlLkZvdW5kID0gW2Jvb2xdKCRjbGkgLW9yIChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRlLlVzZXIpKQogICAgfQp9CgpmdW5jdGlvbiBUZXN0LUV4dGVuc2lvbigkRWRpdG9yLCBbc3RyaW5nXSRQcmVmaXgpIHsKICAgIGlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRFZGl0b3IuRXh0KSkgeyByZXR1cm4gJGZhbHNlIH0KICAgIHJldHVybiBbYm9vbF0oR2V0LUNoaWxkSXRlbSAtTGl0ZXJhbFBhdGggJEVkaXRvci5FeHQgLURpcmVjdG9yeSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSB8IFdoZXJlLU9iamVjdCB7ICRfLk5hbWUgLWxpa2UgIiRQcmVmaXgtKiIgfSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEpCn0KCmZ1bmN0aW9uIEludm9rZS1Ub29sKFtzdHJpbmddJEZpbGUsIFtzdHJpbmdbXV0kQXJndW1lbnRzLCBbaW50XSRUaW1lb3V0U2VjID0gMzAwKSB7CiAgICAjINC30LDQv9GD0YHQutCw0LXRgiAuY21kLy5leGUsINC20LTRkdGCINGBINGC0LDQudC80LDRg9GC0L7QvCwg0LLQvtC30LLRgNCw0YnQsNC10YIg0LrQvtC0INCy0YvRhdC+0LTQsCDQuCDQstGL0LLQvtC0LgogICAgIyDQn9C+0LrQsCDQttC00ZHQvCAtINC/0LXRh9Cw0YLQsNC10Lwg0YLQvtGH0LrQuCwg0YfRgtC+0LHRiyDQtNC+0LvQs9Cw0Y8g0YPRgdGC0LDQvdC+0LLQutCwIChDdXJzb3Ig0YHRgtCw0LLQuNGCINGA0LDRgdGI0LjRgNC10L3QuNC1IDEtMiDQvNC40L3Rg9GC0YspINC90LUg0LLRi9Cz0LvRj9C00LXQu9CwINC30LDQstC40YHQsNC90LjQtdC8LgogICAgJG91dCA9IFtJTy5QYXRoXTo6R2V0VGVtcEZpbGVOYW1lKCk7ICRlcnIgPSBbSU8uUGF0aF06OkdldFRlbXBGaWxlTmFtZSgpCiAgICB0cnkgewogICAgICAgICRwID0gU3RhcnQtUHJvY2VzcyAtRmlsZVBhdGggJEZpbGUgLUFyZ3VtZW50TGlzdCAkQXJndW1lbnRzIC1Ob05ld1dpbmRvdyAtUGFzc1RocnUgLVJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgJG91dCAtUmVkaXJlY3RTdGFuZGFyZEVycm9yICRlcnIKICAgICAgICAkbnVsbCA9ICRwLkhhbmRsZSAgICMg0LHQtdC3INGN0YLQvtCz0L4gUG93ZXJTaGVsbCA1LjEg0YLQtdGA0Y/QtdGCINC60L7QtCDQstGL0YXQvtC00LAgKCRwLkV4aXRDb2RlID0g0L/Rg9GB0YLQviksINC4INGD0YHQv9C10YjQvdCw0Y8g0YPRgdGC0LDQvdC+0LLQutCwINGB0YfQuNGC0LDQu9Cw0YHRjCDQvtGI0LjQsdC60L7QuQogICAgICAgICRzdyA9IFtEaWFnbm9zdGljcy5TdG9wd2F0Y2hdOjpTdGFydE5ldygpOyAkZG90cyA9ICRmYWxzZQogICAgICAgIHdoaWxlICgtbm90ICRwLldhaXRGb3JFeGl0KDMwMDApKSB7CiAgICAgICAgICAgIGlmICgkc3cuRWxhcHNlZC5Ub3RhbFNlY29uZHMgLWdlICRUaW1lb3V0U2VjKSB7CiAgICAgICAgICAgICAgICB0cnkgeyAkcC5LaWxsKCkgfSBjYXRjaCB7fQogICAgICAgICAgICAgICAgaWYgKCRkb3RzKSB7IFdyaXRlLUhvc3QgJycgfQogICAgICAgICAgICAgICAgcmV0dXJuIEB7IENvZGUgPSAtMTsgVGV4dCA9ICfRgtCw0LnQvNCw0YPRgicgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmICgtbm90ICRkb3RzKSB7IFdyaXRlLUhvc3QgJyAgICAgICAnIC1Ob05ld2xpbmU7ICRkb3RzID0gJHRydWUgfQogICAgICAgICAgICBXcml0ZS1Ib3N0ICcuJyAtTm9OZXdsaW5lIC1Gb3JlZ3JvdW5kQ29sb3IgRGFya0dyYXkKICAgICAgICB9CiAgICAgICAgaWYgKCRkb3RzKSB7IFdyaXRlLUhvc3QgKCcgezB9IGMnIC1mIFtpbnRdJHN3LkVsYXBzZWQuVG90YWxTZWNvbmRzKSAtRm9yZWdyb3VuZENvbG9yIERhcmtHcmF5IH0KICAgICAgICAkcC5XYWl0Rm9yRXhpdCgpCiAgICAgICAgJHRleHQgPSAoKEdldC1Db250ZW50ICRvdXQgLVJhdyAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSkgKyAiYG4iICsgKEdldC1Db250ZW50ICRlcnIgLVJhdyAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSkpLlRyaW0oKQogICAgICAgIHJldHVybiBAeyBDb2RlID0gJHAuRXhpdENvZGU7IFRleHQgPSAkdGV4dCB9CiAgICB9IGNhdGNoIHsKICAgICAgICByZXR1cm4gQHsgQ29kZSA9IC0xOyBUZXh0ID0gJF8uRXhjZXB0aW9uLk1lc3NhZ2UgfQogICAgfSBmaW5hbGx5IHsKICAgICAgICBSZW1vdmUtSXRlbSAkb3V0LCAkZXJyIC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQogICAgfQp9CgpmdW5jdGlvbiBJbnN0YWxsLUV4dGVuc2lvbigkRWRpdG9yLCBbc3RyaW5nXSRJZCkgewogICAgaWYgKCRUZXN0Um9vdCkgewogICAgICAgIE5ldy1JdGVtIC1JdGVtVHlwZSBEaXJlY3RvcnkgLUZvcmNlIC1QYXRoIChKb2luLVBhdGggJEVkaXRvci5FeHQgKCRJZC5Ub0xvd2VyKCkgKyAnLTkuOS45JykpIHwgT3V0LU51bGwKICAgICAgICByZXR1cm4gJHRydWUKICAgIH0KICAgIGlmICgtbm90ICRFZGl0b3IuQ2xpKSB7IHJldHVybiAkZmFsc2UgfQogICAgJHIgPSBJbnZva2UtVG9vbCAkRWRpdG9yLkNsaSBAKCctLWluc3RhbGwtZXh0ZW5zaW9uJywgJElkLCAnLS1mb3JjZScpIDI0MAogICAgIyDRg9GB0L/QtdGFINC+0L/RgNC10LTQtdC70Y/QtdC8INC90LUg0YLQvtC70YzQutC+INC/0L4g0LrQvtC00YMg0LLRi9GF0L7QtNCwOiDRgNC10LTQsNC60YLQvtGAINC/0LjRiNC10YIg0L/RgNC10LTRg9C/0YDQtdC20LTQtdC90LjRjyDQsiBzdGRlcnIsCiAgICAjINCwINC40L3QvtCz0LTQsCDQstC+0LfQstGA0LDRidCw0LXRgiDQvdC1IDAg0L/RgNC4INGD0YHQv9C10YjQvdC+0Lkg0YPRgdGC0LDQvdC+0LLQutC1CiAgICBpZiAoJHIuQ29kZSAtZXEgMCAtb3IgJHIuVGV4dCAtbWF0Y2ggJ3N1Y2Nlc3NmdWxseSBpbnN0YWxsZWR8YWxyZWFkeSBpbnN0YWxsZWR80YPQttC1INGD0YHRgtCw0L3QvtCy0LvQtdC9JyAtb3IgKFRlc3QtRXh0ZW5zaW9uICRFZGl0b3IgJElkLlRvTG93ZXIoKSkpIHsgcmV0dXJuICR0cnVlIH0KICAgIExvZyAiZXh0ZW5zaW9uICRJZCBpbiAkKCRFZGl0b3IuTmFtZSkgZmFpbGVkIChjb2RlICQoJHIuQ29kZSkpOiAkKCRyLlRleHQpIgogICAgcmV0dXJuICRmYWxzZQp9CgojIOKUgOKUgCDQutC70Y7RhyDQuCDQutCw0YLQsNC70L7QsyDQvNC+0LTQtdC70LXQuSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCmZ1bmN0aW9uIEludm9rZS1BcGkoW3N0cmluZ10kUGF0aCwgW3N0cmluZ10kS2V5KSB7CiAgICAkaCA9IEB7fQogICAgaWYgKCRLZXkpIHsgJGhbJ0F1dGhvcml6YXRpb24nXSA9ICJCZWFyZXIgJEtleSIgfQogICAgcmV0dXJuIEludm9rZS1SZXN0TWV0aG9kIC1VcmkgKCRPcGVuQWlCYXNlICsgJFBhdGgpIC1IZWFkZXJzICRoIC1UaW1lb3V0U2VjIDI1IC1Vc2VCYXNpY1BhcnNpbmcKfQoKZnVuY3Rpb24gVGVzdC1LZXkoW3N0cmluZ10kS2V5KSB7CiAgICAjINCy0L7Qt9Cy0YDQsNGJ0LDQtdGCICdvaycgfCAnYmFkJyB8ICdleHBpcmVkJyB8ICdvZmZsaW5lJywg0L/QtdGH0LDRgtCw0LXRgiDQsdCw0LvQsNC90YEKICAgIHRyeSB7CiAgICAgICAgJGIgPSBJbnZva2UtQXBpICcvYmFsYW5jZScgJEtleQogICAgICAgIGlmICgkYi5pc191bmxpbWl0ZWQpIHsgT2sgKCLQmtC70Y7RhyB7MH0g0LDQutGC0LjQstC10L0gLSDQsdC10LfQu9C40LzQuNGCIiAtZiAoTWFzayAkS2V5KSkgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICAkZXh0cmEgPSAnJwogICAgICAgICAgICBpZiAoJGIuZXhwaXJlc19hdCkgeyB0cnkgeyAkZXh0cmEgPSAnIMK3INC00LXQudGB0YLQstGD0LXRgiDQtNC+ICcgKyAoW2RhdGV0aW1lXSRiLmV4cGlyZXNfYXQpLlRvU3RyaW5nKCdkZC5NTS55eXl5JykgfSBjYXRjaCB7fSB9CiAgICAgICAgICAgIE9rICgi0JrQu9GO0YcgezB9INCw0LrRgtC40LLQtdC9IC0g0LHQsNC70LDQvdGBIHsxfSDRgtC+0LrQtdC90L7QsnsyfSIgLWYgKE1hc2sgJEtleSksIChGbXQgJGIucmVtYWluaW5nX2NyZWRpdHMpLCAkZXh0cmEpCiAgICAgICAgfQogICAgICAgIGlmICgkYi5yZW1haW5pbmdfY3JlZGl0cyAtbGUgMCAtYW5kIC1ub3QgJGIuaXNfdW5saW1pdGVkKSB7IFdhcm4gJ9CR0LDQu9Cw0L3RgSDQv9GD0YHRgiAtINC/0YDQvtCz0YDQsNC80LzRiyDQv9C+0LTQutC70Y7Rh9Cw0YLRgdGPLCDQvdC+INC+0YLQstC10YLRiyDQv9C+0LnQtNGD0YIg0L/QvtGB0LvQtSDQv9C+0L/QvtC70L3QtdC90LjRjyDQvdCwIEZ1blBheS4nIH0KICAgICAgICByZXR1cm4gJ29rJwogICAgfSBjYXRjaCB7CiAgICAgICAgJGNvZGUgPSAkbnVsbAogICAgICAgIHRyeSB7ICRjb2RlID0gW2ludF0kXy5FeGNlcHRpb24uUmVzcG9uc2UuU3RhdHVzQ29kZSB9IGNhdGNoIHt9CiAgICAgICAgaWYgKCRjb2RlIC1lcSA0MDEgLW9yICRjb2RlIC1lcSA0MDMpIHsKICAgICAgICAgICAgaWYgKCIkKCRfLkVycm9yRGV0YWlscy5NZXNzYWdlKSIgLW1hdGNoICdleHBpcmVkJykgeyByZXR1cm4gJ2V4cGlyZWQnIH0KICAgICAgICAgICAgcmV0dXJuICdiYWQnCiAgICAgICAgfQogICAgICAgIFdhcm4gKCLQndC1INGD0LTQsNC70L7RgdGMINC/0YDQvtCy0LXRgNC40YLRjCDQutC70Y7RhyAoezB9KS4g0J/RgNC+0LTQvtC70LbQsNGOIC0g0L/RgNC+0LLQtdGA0YzRgtC1INC40L3RgtC10YDQvdC10YIg0L/QvtC30LbQtS4iIC1mICRfLkV4Y2VwdGlvbi5NZXNzYWdlKQogICAgICAgIHJldHVybiAnb2ZmbGluZScKICAgIH0KfQoKJEN1cmF0ZWQgPSBAKAogICAgQHsgSWQgPSAnZGVlcHNlZWstdjQtZmxhc2gnOyBOb3RlID0gJ9Cx0YvRgdGC0YDQsNGPLCDQtNC70Y8g0LrQvtC00LAg0Lgg0LDQs9C10L3RgtC+0LInIH0sCiAgICBAeyBJZCA9ICdjbGF1ZGUtaGFpa3UtNS41JzsgTm90ZSA9ICfQvNC+0LTQtdC70YwgQW50aHJvcGljOiDRgtC10LrRgdGC0Ysg0Lgg0LrQvtC0JyB9LAogICAgQHsgSWQgPSAnZ3B0LTYtbHVuYSc7IE5vdGUgPSAn0LzQvtC00LXQu9GMIE9wZW5BSScgfSwKICAgIEB7IElkID0gJ21pbmltYXgtbTIuNyc7IE5vdGUgPSAn0YEg0YDQsNGB0YHRg9C20LTQtdC90LjRj9C80LgnIH0sCiAgICBAeyBJZCA9ICdnbG0tNS4zLWZsYXNoJzsgTm90ZSA9ICfQsdGL0YHRgtGA0LDRjyDQuCDQtNC10YjRkdCy0LDRjycgfSwKICAgIEB7IElkID0gJ2dlbWluaS0yLjUtZmxhc2gnOyBOb3RlID0gJ9Cy0LjQtNC40YIg0LjQt9C+0LHRgNCw0LbQtdC90LjRjycgfQopCiRGYXN0Q2FuZGlkYXRlcyA9IEAoJ2NsYXVkZS1oYWlrdS01LjUnLCAnZ2xtLTUuMy1mbGFzaCcsICdkZWVwc2Vlay12NC1mbGFzaCcpCgpmdW5jdGlvbiBHZXQtQ2F0YWxvZyB7CiAgICAjIHRoZSBwdWJsaWMgY2F0YWxvZ3VlOiB0aGUgcmVhbCBtb2RlbHMgd2l0aCB0aGVpciBuYW1lcywgbXVsdGlwbGllcnMgYW5kIGNvbnRleHQgd2luZG93cy4KICAgICMgTm90IC92MS9tb2RlbHM6IHRoYXQgbGlzdCBhbHNvIGNhcnJpZXMgdGhlIGV4dHJhIGFsaWFzIGlkcyBtYWRlIGZvciBDdXJzb3IgKHNvbm5ldC01LCA0LjctZ3JvaywgLi4uKS4KICAgICRzY3JpcHQ6Q2F0YWxvZ05hbWVzID0gQHt9OyAkc2NyaXB0OkNhdGFsb2dDdHggPSBAe30KICAgIHRyeSB7CiAgICAgICAgJHIgPSBJbnZva2UtUmVzdE1ldGhvZCAtVXJpICIkU2l0ZS9hcGkvcHVibGljL21vZGVscyIgLVRpbWVvdXRTZWMgMjUgLVVzZUJhc2ljUGFyc2luZwogICAgICAgICMgYSBKU09OIGFycmF5IGFycml2ZXMgYXMgT2JqZWN0W10gKGFuZCAkci5tb2RlbHMgb24gaXQgd291bGQgZW51bWVyYXRlIG51bGxzKTsgYW4gb2JqZWN0IG1heSB3cmFwIGl0IGluIC5tb2RlbHMKICAgICAgICAkbGlzdCA9IGlmICgkciAtaXMgW2FycmF5XSkgeyAkciB9IGVsc2VpZiAoJG51bGwgLW5lICRyLm1vZGVscykgeyAkci5tb2RlbHMgfSBlbHNlIHsgQCgkcikgfQogICAgICAgICRtYXAgPSBAe30KICAgICAgICBmb3JlYWNoICgkbSBpbiBAKCRsaXN0KSkgewogICAgICAgICAgICAkaWQgPSBbc3RyaW5nXSRtLmlkCiAgICAgICAgICAgIGlmICgtbm90ICRpZCkgeyBjb250aW51ZSB9CiAgICAgICAgICAgICRtYXBbJGlkXSA9IGlmICgkbnVsbCAtbmUgJG0ucmF0ZSkgeyAkbS5yYXRlIH0gZWxzZSB7ICRtLm11bHRpcGxpZXIgfQogICAgICAgICAgICBpZiAoJG0ubmFtZSkgeyAkc2NyaXB0OkNhdGFsb2dOYW1lc1skaWRdID0gW3N0cmluZ10kbS5uYW1lIH0KICAgICAgICAgICAgaWYgKCRtLmNvbnRleHRfd2luZG93KSB7ICRzY3JpcHQ6Q2F0YWxvZ0N0eFskaWRdID0gW2ludF0kbS5jb250ZXh0X3dpbmRvdyB9CiAgICAgICAgfQogICAgICAgIGlmICgkbWFwLkNvdW50IC1ndCAwKSB7IHJldHVybiAkbWFwIH0KICAgIH0gY2F0Y2gge30KICAgIHJldHVybiAkbnVsbAp9CgpmdW5jdGlvbiBHZXQtTW9kZWxDb250ZXh0KFtzdHJpbmddJElkKSB7CiAgICBpZiAoJHNjcmlwdDpDYXRhbG9nQ3R4IC1hbmQgJHNjcmlwdDpDYXRhbG9nQ3R4WyRJZF0pIHsgcmV0dXJuIFtpbnRdJHNjcmlwdDpDYXRhbG9nQ3R4WyRJZF0gfQogICAgcmV0dXJuIDEyODAwMAp9CgojIFB1YmxpYyBORVhVUyBhZGRyZXNzZXMuIFRoZSBtYWluIG9uZSBoYXMgdGhlIHJlYWwgZG9tYWluOyBNb3Njb3cgd29ya3MgZnJvbSBSdXNzaWEgd2l0aG91dCBhIFZQTiwgdGhlIFVTIG9uZSBzdWl0cyBhIFZQTi4KJFNlcnZlcnMgPSBAKAogICAgW3BzY3VzdG9tb2JqZWN0XUB7IElkID0gJ3J1JzsgTmFtZSA9ICfQoNC+0YHRgdC40Y8nOyBVcmwgPSAnaHR0cHM6Ly8xNzgtMjM2LTI0Mi05Ni5uaXAuaW8nIH0sCiAgICBbcHNjdXN0b21vYmplY3RdQHsgSWQgPSAnZXUnOyBOYW1lID0gJ9Cd0LjQtNC10YDQu9Cw0L3QtNGLJzsgVXJsID0gJ2h0dHBzOi8vOTQtMjI4LTE2MS03NS5uaXAuaW8nIH0sCiAgICBbcHNjdXN0b21vYmplY3RdQHsgSWQgPSAndXMnOyBOYW1lID0gJ9Ch0KjQkCc7IFVybCA9ICdodHRwczovLzM0LTIyNy0yMDgtMjM4Lm5pcC5pbycgfQopCgpmdW5jdGlvbiBNZWFzdXJlLVNlcnZlcihbc3RyaW5nXSRVcmwpIHsKICAgICMgYmVzdCBvZiB0aHJlZSAvaGVhbHRoIHJvdW5kIHRyaXBzLCAkbnVsbCB3aGVuIHRoZSBzZXJ2ZXIgZG9lcyBub3QgYW5zd2VyCiAgICAkYmVzdCA9ICRudWxsCiAgICBmb3IgKCRpID0gMDsgJGkgLWx0IDM7ICRpKyspIHsKICAgICAgICAkc3cgPSBbRGlhZ25vc3RpY3MuU3RvcHdhdGNoXTo6U3RhcnROZXcoKQogICAgICAgIHRyeSB7CiAgICAgICAgICAgICRudWxsID0gSW52b2tlLVdlYlJlcXVlc3QgLVVyaSAiJFVybC9oZWFsdGgiIC1Vc2VCYXNpY1BhcnNpbmcgLVRpbWVvdXRTZWMgNQogICAgICAgICAgICAkbXMgPSBbaW50XSRzdy5FbGFwc2VkLlRvdGFsTWlsbGlzZWNvbmRzCiAgICAgICAgICAgIGlmICgkbnVsbCAtZXEgJGJlc3QgLW9yICRtcyAtbHQgJGJlc3QpIHsgJGJlc3QgPSAkbXMgfQogICAgICAgIH0gY2F0Y2gge30KICAgIH0KICAgIHJldHVybiAkYmVzdAp9CgpmdW5jdGlvbiBDaG9vc2UtU2VydmVyIHsKICAgICMgb25seSBmb3IgdGhlIHB1YmxpYyBhZGRyZXNzZXM6IHRlc3RzIGFuZCBhIGN1c3RvbSBORVhVU19TSVRFIHN0YXkgYXMgZ2l2ZW47IE5FWFVTX1NFUlZFUj1tYWlufHJ1fHVzIGZvcmNlcyBvbmUKICAgIGlmICgkVGVzdFJvb3QgLW9yICgkU2l0ZSAtbm90bWF0Y2ggJ15odHRwczovLyhuZXh1c2FsbGluXC5zdG9yZXxbMC05LV0rXC5uaXBcLmlvKSQnKSkgeyByZXR1cm4gfQogICAgU3RlcCAn0JLRi9Cx0LjRgNCw0Y4g0YHQsNC80YvQuSDQsdGL0YHRgtGA0YvQuSDRgdC10YDQstC10YAuLi4nCiAgICAkcmVzID0gQChmb3JlYWNoICgkcyBpbiAkU2VydmVycykgeyBbcHNjdXN0b21vYmplY3RdQHsgUyA9ICRzOyBNcyA9IChNZWFzdXJlLVNlcnZlciAkcy5VcmwpIH0gfSkKICAgIGlmICgtbm90ICRRdWlldE1vZGUpIHsgU2F5ICgnICAgICAgICcgKyAoKCRyZXMgfCBGb3JFYWNoLU9iamVjdCB7ICd7MH06IHsxfScgLWYgJF8uUy5OYW1lLCAkKGlmICgkbnVsbCAtbmUgJF8uTXMpIHsgIiQoJF8uTXMpINC80YEiIH0gZWxzZSB7ICfQvdC10YIg0YHQstGP0LfQuCcgfSkgfSkgLWpvaW4gJyB8ICcpKSBEYXJrR3JheSB9CiAgICAkcGljayA9ICRyZXMgfCBXaGVyZS1PYmplY3QgeyAkXy5TLklkIC1lcSAkKGlmICgkZW52Ok5FWFVTX1NFUlZFUiAtZXEgJ21haW4nKSB7ICdydScgfSBlbHNlIHsgW3N0cmluZ10kZW52Ok5FWFVTX1NFUlZFUiB9KSAtYW5kICRudWxsIC1uZSAkXy5NcyB9IHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMQogICAgaWYgKC1ub3QgJHBpY2spIHsKICAgICAgICAkYWxpdmUgPSBAKCRyZXMgfCBXaGVyZS1PYmplY3QgeyAkbnVsbCAtbmUgJF8uTXMgfSB8IFNvcnQtT2JqZWN0IE1zKQogICAgICAgIGlmICgkYWxpdmUuQ291bnQgLWVxIDApIHsgV2FybiAi0J3QuCDQvtC00LjQvSDRgdC10YDQstC10YAg0L3QtSDQvtGC0LLQtdGC0LjQuyAtINC+0YHRgtCw0LLQu9GP0Y4gJFNpdGUuINCf0YDQvtCy0LXRgNGM0YLQtSDQuNC90YLQtdGA0L3QtdGCLiI7IHJldHVybiB9CiAgICAgICAgJHBpY2sgPSAkYWxpdmVbMF0KICAgICAgICAkbWFpbiA9ICRyZXMgfCBXaGVyZS1PYmplY3QgeyAkXy5TLklkIC1lcSAnbWFpbicgLWFuZCAkbnVsbCAtbmUgJF8uTXMgfSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEKICAgICAgICAjIHRoZSBtYWluIGFkZHJlc3Mgc3RheXMgdW5sZXNzIGFub3RoZXIgb25lIGlzIGNsZWFybHkgZmFzdGVyCiAgICAgICAgaWYgKCRtYWluIC1hbmQgKCRtYWluLk1zIC1sZSAkcGljay5NcyAqIDEuMyAtb3IgKCRtYWluLk1zIC0gJHBpY2suTXMpIC1sdCA0MCkpIHsgJHBpY2sgPSAkbWFpbiB9CiAgICB9CiAgICAkc2NyaXB0OlNpdGUgPSAkcGljay5TLlVybAogICAgJHNjcmlwdDpPcGVuQWlCYXNlID0gIiQoJHNjcmlwdDpTaXRlKS92MSIKICAgIE9rICLQodC10YDQstC10YA6ICQoJHBpY2suUy5OYW1lKSAtICQoJHBpY2suTXMpINC80YEiCn0KCmZ1bmN0aW9uIFRlc3QtTW9kZWwgewogICAgIyBvbmUgc2hvcnQgcmVxdWVzdCB0aHJvdWdoIHRoZSBjb25maWd1cmVkIGFkZHJlc3MsIGtleSBhbmQgbW9kZWw6IHNob3dzIHRoYXQgYW5zd2VycyByZWFsbHkgY29tZQogICAgU3RlcCAi0J/RgNC+0LLQtdGA0Y/RjiDRgdCy0Y/Qt9GMOiDQutC+0YDQvtGC0LrQuNC5INC30LDQv9GA0L7RgSDQuiAkKCRzY3JpcHQ6TW9kZWwpLi4uIgogICAgJGJvZHkgPSBAeyBtb2RlbCA9ICRzY3JpcHQ6TW9kZWw7IG1heF90b2tlbnMgPSAxNjsgc3RyZWFtID0gJGZhbHNlOyBtZXNzYWdlcyA9IEAoQHsgcm9sZSA9ICd1c2VyJzsgY29udGVudCA9ICdSZXBseSB3aXRoIG9uZSB3b3JkOiBvaycgfSkgfSB8IENvbnZlcnRUby1Kc29uIC1EZXB0aCA1IC1Db21wcmVzcwogICAgJHN3ID0gW0RpYWdub3N0aWNzLlN0b3B3YXRjaF06OlN0YXJ0TmV3KCkKICAgIHRyeSB7CiAgICAgICAgJG51bGwgPSBJbnZva2UtUmVzdE1ldGhvZCAtVXJpICIkT3BlbkFpQmFzZS9jaGF0L2NvbXBsZXRpb25zIiAtTWV0aG9kIFBvc3QgLUhlYWRlcnMgQHsgQXV0aG9yaXphdGlvbiA9ICJCZWFyZXIgJCgkc2NyaXB0OktleSkiIH0gYAogICAgICAgICAgICAtQ29udGVudFR5cGUgJ2FwcGxpY2F0aW9uL2pzb247IGNoYXJzZXQ9dXRmLTgnIC1Cb2R5IChbVGV4dC5FbmNvZGluZ106OlVURjguR2V0Qnl0ZXMoJGJvZHkpKSAtVGltZW91dFNlYyA5MCAtVXNlQmFzaWNQYXJzaW5nCiAgICAgICAgT2sgKCLQodCy0Y/Qt9GMINC10YHRgtGMOiB7MH0g0L7RgtCy0LXRgtC40LvQsCDQt9CwIHsxfSDRgSIgLWYgJHNjcmlwdDpNb2RlbCwgJHN3LkVsYXBzZWQuVG90YWxTZWNvbmRzLlRvU3RyaW5nKCcwLjAnLCBbR2xvYmFsaXphdGlvbi5DdWx0dXJlSW5mb106OkludmFyaWFudEN1bHR1cmUpKQogICAgfSBjYXRjaCB7CiAgICAgICAgJGNvZGUgPSAkbnVsbAogICAgICAgIHRyeSB7ICRjb2RlID0gW2ludF0kXy5FeGNlcHRpb24uUmVzcG9uc2UuU3RhdHVzQ29kZSB9IGNhdGNoIHt9CiAgICAgICAgJHdoeSA9IGlmICgkY29kZSkgeyAiSFRUUCAkY29kZSIgfSBlbHNlIHsgJF8uRXhjZXB0aW9uLk1lc3NhZ2UgfQogICAgICAgIFdhcm4gItCf0YDQvtCy0LXRgNC+0YfQvdGL0Lkg0LfQsNC/0YDQvtGBINC90LUg0L/RgNC+0YjRkdC7ICgkd2h5KS4g0J/RgNC+0LPRgNCw0LzQvNGLINC90LDRgdGC0YDQvtC10L3Rizsg0LXRgdC70Lgg0L7RgtCy0LXRgtC+0LIg0L3QtdGCIC0g0L/RgNC+0LLQtdGA0YzRgtC1INCx0LDQu9Cw0L3RgTogJFNpdGUvc3RhdHMuaHRtbCIKICAgIH0KfQoKIyDilIDilIAg0LTQtdC50YHRgtCy0LjRjyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiMgQ2xpbmUgNCBrZWVwcyBpdHMgcHJvdmlkZXJzIGluIGZpbGVzIHVuZGVyIH4vLmNsaW5lL2RhdGEvc2V0dGluZ3MsIHNoYXJlZCBieSB0aGUgVlMgQ29kZSAvIEN1cnNvciAvIFdpbmRzdXJmIGV4dGVuc2lvbiwKIyBKZXRCcmFpbnMgYW5kIHRoZSBDbGluZSBDTEk6IG1vZGVscy5qc29uIGRlY2xhcmVzIHRoZSAiTkVYVVMgQUkiIHByb3ZpZGVyIHdpdGggZXZlcnkgbW9kZWwsIHByb3ZpZGVycy5qc29uIGhvbGRzIGl0cyBrZXkKIyBhbmQgbWFrZXMgaXQgdGhlIGN1cnJlbnQgcHJvdmlkZXIuIE90aGVyIHByb3ZpZGVycyB0aGUgdXNlciBoYXMgdGhlcmUgYXJlIGtlcHQuCmZ1bmN0aW9uIEdldC1DbGluZURhdGFEaXIgewogICAgaWYgKCRUZXN0Um9vdCkgeyByZXR1cm4gKEpvaW4tUGF0aCAkVXNlckhvbWUgJy5jbGluZVxkYXRhJykgfQogICAgaWYgKCRlbnY6Q0xJTkVfREFUQV9ESVIpIHsgcmV0dXJuICRlbnY6Q0xJTkVfREFUQV9ESVIgfQogICAgaWYgKCRlbnY6Q0xJTkVfRElSKSB7IHJldHVybiAoSm9pbi1QYXRoICRlbnY6Q0xJTkVfRElSICdkYXRhJykgfQogICAgcmV0dXJuIChKb2luLVBhdGggJFVzZXJIb21lICcuY2xpbmVcZGF0YScpCn0KCmZ1bmN0aW9uIEFwcGx5LUNsaW5lIHsKICAgIGlmICgkc2NyaXB0OkNsaW5lRG9uZSkgeyByZXR1cm4gfQogICAgJGRpciA9IEpvaW4tUGF0aCAoR2V0LUNsaW5lRGF0YURpcikgJ3NldHRpbmdzJwogICAgJG1vZGVscyA9IFtvcmRlcmVkXUB7fQogICAgZm9yZWFjaCAoJGlkIGluIChHZXQtQWxsTW9kZWxJZHMpKSB7CiAgICAgICAgJG1vZGVsc1skaWRdID0gW29yZGVyZWRdQHsgaWQgPSAkaWQ7IG5hbWUgPSAoR2V0LU1vZGVsTGFiZWwgJGlkKTsgY29udGV4dFdpbmRvdyA9IChHZXQtTW9kZWxDb250ZXh0ICRpZCk7IG1heElucHV0VG9rZW5zID0gKEdldC1Nb2RlbENvbnRleHQgJGlkKTsgY2FwYWJpbGl0aWVzID0gQCgnc3RyZWFtaW5nJywgJ3Rvb2xzJywgJ2ltYWdlcycpIH0KICAgIH0KICAgICRtUGF0aCA9IEpvaW4tUGF0aCAkZGlyICdtb2RlbHMuanNvbicKICAgICRjdXIgPSBSZWFkLUpzb25GaWxlICRtUGF0aAogICAgJHByb3ZzID0gR2V0LU9ialByb3AgJGN1ciAncHJvdmlkZXJzJwogICAgU2V0LVByb3AgJHByb3ZzICduZXh1cycgKFtvcmRlcmVkXUB7CiAgICAgICAgcHJvdmlkZXIgPSBbb3JkZXJlZF1AeyBuYW1lID0gJ05FWFVTIEFJJzsgYmFzZVVybCA9ICRPcGVuQWlCYXNlOyBkZWZhdWx0TW9kZWxJZCA9ICRzY3JpcHQ6TW9kZWw7IHByb3RvY29sID0gJ29wZW5haS1jaGF0JzsgY2xpZW50ID0gJ29wZW5haS1jb21wYXRpYmxlJyB9CiAgICAgICAgbW9kZWxzID0gJG1vZGVscwogICAgfSkKICAgIFVwZGF0ZS1Kc29uRmlsZSAkbVBhdGggKFtvcmRlcmVkXUB7IHZlcnNpb24gPSAxOyBwcm92aWRlcnMgPSAkcHJvdnMgfSkKCiAgICAkcFBhdGggPSBKb2luLVBhdGggJGRpciAncHJvdmlkZXJzLmpzb24nCiAgICAkY3VyID0gUmVhZC1Kc29uRmlsZSAkcFBhdGgKICAgICRwcm92cyA9IEdldC1PYmpQcm9wICRjdXIgJ3Byb3ZpZGVycycKICAgICRzZXR0aW5ncyA9IFtvcmRlcmVkXUB7IHByb3ZpZGVyID0gJ25leHVzJzsgYXBpS2V5ID0gJHNjcmlwdDpLZXk7IG1vZGVsID0gJHNjcmlwdDpNb2RlbDsgYmFzZVVybCA9ICRPcGVuQWlCYXNlIH0KICAgICRzdGFtcCA9IFtEYXRlVGltZV06OlV0Y05vdy5Ub1N0cmluZygieXl5eS1NTS1kZCdUJ0hIOm1tOnNzLmZmZidaJyIsIFtHbG9iYWxpemF0aW9uLkN1bHR1cmVJbmZvXTo6SW52YXJpYW50Q3VsdHVyZSkKICAgICRvbGQgPSBHZXQtUHJvcCAkcHJvdnMgJ25leHVzJwogICAgJG9sZFNldCA9IEdldC1Qcm9wICRvbGQgJ3NldHRpbmdzJwogICAgaWYgKCRvbGRTZXQgLWFuZCAoR2V0LVByb3AgJG9sZFNldCAnYXBpS2V5JykgLWVxICRzY3JpcHQ6S2V5IC1hbmQgKEdldC1Qcm9wICRvbGRTZXQgJ21vZGVsJykgLWVxICRzY3JpcHQ6TW9kZWwgLWFuZCAoR2V0LVByb3AgJG9sZFNldCAnYmFzZVVybCcpIC1lcSAkT3BlbkFpQmFzZSkgewogICAgICAgICRwcmV2ID0gR2V0LVByb3AgJG9sZCAndXBkYXRlZEF0JyAgICMgbm90aGluZyBjaGFuZ2VkOiBrZWVwIHRoZSBvbGQgdGltZSBzbyBhIHJlcGVhdGVkIHJ1biBsZWF2ZXMgdGhlIGZpbGUgYXMgaXQgaXMKICAgICAgICBpZiAoJHByZXYgLWlzIFtkYXRldGltZV0pIHsgJHByZXYgPSAkcHJldi5Ub1VuaXZlcnNhbFRpbWUoKS5Ub1N0cmluZygieXl5eS1NTS1kZCdUJ0hIOm1tOnNzLmZmZidaJyIsIFtHbG9iYWxpemF0aW9uLkN1bHR1cmVJbmZvXTo6SW52YXJpYW50Q3VsdHVyZSkgfQogICAgICAgIGlmICgkcHJldikgeyAkc3RhbXAgPSBbc3RyaW5nXSRwcmV2IH0KICAgIH0KICAgIFNldC1Qcm9wICRwcm92cyAnbmV4dXMnIChbb3JkZXJlZF1AeyBzZXR0aW5ncyA9ICRzZXR0aW5nczsgdXBkYXRlZEF0ID0gJHN0YW1wOyB0b2tlblNvdXJjZSA9ICdtYW51YWwnIH0pCiAgICBVcGRhdGUtSnNvbkZpbGUgJHBQYXRoIChbb3JkZXJlZF1AeyB2ZXJzaW9uID0gMTsgbGFzdFVzZWRQcm92aWRlciA9ICduZXh1cyc7IG1vZGVzID0gKEdldC1PYmpQcm9wICRjdXIgJ21vZGVzJyk7IHByb3ZpZGVycyA9ICRwcm92cyB9KQogICAgJHNjcmlwdDpDbGluZURvbmUgPSAkdHJ1ZQogICAgT2sgIkNsaW5lINC/0L7QtNC60LvRjtGH0ZHQvSDQuiBORVhVUyBBSSAo0LzQvtC00LXQu9GMICQoJHNjcmlwdDpNb2RlbCk7INC+0YHRgtCw0LvRjNC90YvQtSDQvNC+0LTQtdC70LggLSDQsiDRgdC/0LjRgdC60LUg0L/RgNC+0LLQsNC50LTQtdGA0LAgTkVYVVMgQUkpIgp9CgpmdW5jdGlvbiBBcHBseS1FZGl0b3JDbGluZSgkRWRpdG9yKSB7CiAgICBpZiAoVGVzdC1FeHRlbnNpb24gJEVkaXRvciAnc2FvdWRyaXp3YW4uY2xhdWRlLWRldicpIHsgT2sgIiQoJEVkaXRvci5OYW1lKTogQ2xpbmUg0YPQttC1INGD0YHRgtCw0L3QvtCy0LvQtdC9IiB9CiAgICBlbHNlIHsKICAgICAgICBTdGVwICIkKCRFZGl0b3IuTmFtZSk6INGD0YHRgtCw0L3QsNCy0LvQuNCy0LDRjiBDbGluZSAo0LTQviAxLTIg0LzQuNC90YPRgikuLi4iCiAgICAgICAgaWYgKEluc3RhbGwtRXh0ZW5zaW9uICRFZGl0b3IgJ3Nhb3Vkcml6d2FuLmNsYXVkZS1kZXYnKSB7IE9rICIkKCRFZGl0b3IuTmFtZSk6IENsaW5lINGD0YHRgtCw0L3QvtCy0LvQtdC9IiB9CiAgICAgICAgZWxzZSB7CiAgICAgICAgICAgIFdhcm4gIiQoJEVkaXRvci5OYW1lKTog0L3QtSDRg9C00LDQu9C+0YHRjCDQv9C+0YHRgtCw0LLQuNGC0YwgQ2xpbmUg0LDQstGC0L7QvNCw0YLQuNGH0LXRgdC60LguINCf0L7RgdGC0LDQstGM0YLQtSDQstGA0YPRh9C90YPRjjogQ3RybCtTaGlmdCtYIC0+IENsaW5lIC0+IEluc3RhbGwuINCd0LDRgdGC0YDQvtC50LrQuCDRg9C20LUg0LPQvtGC0L7QstGLINC4INC/0L7QtNGF0LLQsNGC0Y/RgtGB0Y8g0YHQsNC80LguIgogICAgICAgICAgICAkc2NyaXB0Ok5vdGVzLkFkZCgiJCgkRWRpdG9yLk5hbWUpOiDRg9GB0YLQsNC90L7QstC40YLQtSDRgNCw0YHRiNC40YDQtdC90LjQtSBDbGluZSAoQ3RybCtTaGlmdCtYIC0+IENsaW5lKSAtIE5FWFVTIEFJINC/0L7QtNC60LvRjtGH0LjRgtGB0Y8g0YHQsNC8LiIpCiAgICAgICAgfQogICAgfQogICAgQXBwbHktQ2xpbmUKICAgIGlmICgkRWRpdG9yLklkIC1lcSAnY3Vyc29yJykgewogICAgICAgICMgQ3Vyc29yIDMgb3BlbnMgaW4gdGhlIEFnZW50cyB3aW5kb3csIHdoZXJlIHRoaXJkLXBhcnR5IGV4dGVuc2lvbnMgYXJlIG5vdCBzaG93bgogICAgICAgICRzY3JpcHQ6TmV4dC5BZGQoIkN1cnNvcjog0L/QtdGA0LXQt9Cw0L/Rg9GB0YLQuNGC0LUgQ3Vyc29yINC4INC+0YLQutGA0L7QudGC0LUg0L7QutC90L4g0YDQtdC00LDQutGC0L7RgNCwIChGaWxlIC0+IE9wZW4gRWRpdG9yIFdpbmRvdyDQuNC70Lgg0L/QsNC/0LrRgyDQv9GA0L7QtdC60YLQsCkuICIgKwogICAgICAgICAgICAgICAgICAgICAgICAgIkNsaW5lIC0g0LfQvdCw0YfQvtC6INGB0LvQtdCy0LA7INC/0YDQvtCy0LDQudC00LXRgCBORVhVUyBBSSDRg9C20LUg0LLRi9Cx0YDQsNC9LiDQndC10YIg0LfQvdCw0YfQutCwOiBDdHJsK1NoaWZ0K1AgLT4gQ2xpbmU6IE9wZW4gSW4gTmV3IFRhYi4iKQogICAgfSBlbHNlIHsKICAgICAgICAkc2NyaXB0Ok5leHQuQWRkKCIkKCRFZGl0b3IuTmFtZSk6INC/0LXRgNC10LfQsNC/0YPRgdGC0LjRgtC1INGA0LXQtNCw0LrRgtC+0YAgLT4g0YHQu9C10LLQsCDQt9C90LDRh9C+0LogQ2xpbmUgLT4g0L/QuNGI0LjRgtC1INC30LDQtNCw0YfRgyAo0L/RgNC+0LLQsNC50LTQtdGAIE5FWFVTIEFJINGD0LbQtSDQstGL0LHRgNCw0L0pLiIpCiAgICB9Cn0KCmZ1bmN0aW9uIEFwcGx5LUVkaXRvckNsYXVkZUV4dCgkRWRpdG9yKSB7CiAgICAkcGF0aCA9IEpvaW4tUGF0aCAkRWRpdG9yLlVzZXIgJ3NldHRpbmdzLmpzb24nCiAgICAkY3VyID0gUmVhZC1Kc29uRmlsZSAkcGF0aAogICAgJGxpc3QgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbb2JqZWN0XQogICAgJG91cnMgPSBAKCdBTlRIUk9QSUNfQkFTRV9VUkwnLCAnQU5USFJPUElDX0FVVEhfVE9LRU4nLCAnQU5USFJPUElDX01PREVMJywgJ0FOVEhST1BJQ19BUElfS0VZJywgJ0NMQVVERV9DT0RFX0VOQUJMRV9HQVRFV0FZX01PREVMX0RJU0NPVkVSWScpCiAgICAkZXhpc3RpbmcgPSBHZXQtUHJvcCAkY3VyICdjbGF1ZGVDb2RlLmVudmlyb25tZW50VmFyaWFibGVzJwogICAgaWYgKCRleGlzdGluZykgeyBmb3JlYWNoICgkZSBpbiBAKCRleGlzdGluZykpIHsgaWYgKCRvdXJzIC1ub3Rjb250YWlucyBbc3RyaW5nXShHZXQtUHJvcCAkZSAnbmFtZScpKSB7ICRsaXN0LkFkZCgkZSkgfSB9IH0KICAgICRsaXN0LkFkZChbb3JkZXJlZF1AeyBuYW1lID0gJ0FOVEhST1BJQ19CQVNFX1VSTCc7IHZhbHVlID0gJFNpdGUgfSkKICAgICRsaXN0LkFkZChbb3JkZXJlZF1AeyBuYW1lID0gJ0FOVEhST1BJQ19BVVRIX1RPS0VOJzsgdmFsdWUgPSAkc2NyaXB0OktleSB9KQogICAgJGxpc3QuQWRkKFtvcmRlcmVkXUB7IG5hbWUgPSAnQU5USFJPUElDX01PREVMJzsgdmFsdWUgPSAkc2NyaXB0Ok1vZGVsIH0pCiAgICAkbGlzdC5BZGQoW29yZGVyZWRdQHsgbmFtZSA9ICdDTEFVREVfQ09ERV9FTkFCTEVfR0FURVdBWV9NT0RFTF9ESVNDT1ZFUlknOyB2YWx1ZSA9ICcxJyB9KQogICAgVXBkYXRlLUpzb25GaWxlICRwYXRoIChbb3JkZXJlZF1AeyAnY2xhdWRlQ29kZS5lbnZpcm9ubWVudFZhcmlhYmxlcycgPSAkbGlzdC5Ub0FycmF5KCk7ICdjbGF1ZGVDb2RlLmRpc2FibGVMb2dpblByb21wdCcgPSAkdHJ1ZSB9KQogICAgT2sgIiQoJEVkaXRvci5OYW1lKTog0YDQsNGB0YjQuNGA0LXQvdC40LUgQ2xhdWRlIENvZGUg0L/QvtC00LrQu9GO0YfQtdC90L4iCn0KCmZ1bmN0aW9uIEFwcGx5LUNsYXVkZUNvZGUgewogICAgJHBhdGggPSBKb2luLVBhdGggJFVzZXJIb21lICcuY2xhdWRlXHNldHRpbmdzLmpzb24nCiAgICAkY3VyID0gUmVhZC1Kc29uRmlsZSAkcGF0aAogICAgJGVudk9iaiA9IEdldC1PYmpQcm9wICRjdXIgJ2VudicKICAgIGlmICgkZW52T2JqLlBTT2JqZWN0LlByb3BlcnRpZXNbJ0FOVEhST1BJQ19BUElfS0VZJ10pIHsgJGVudk9iai5QU09iamVjdC5Qcm9wZXJ0aWVzLlJlbW92ZSgnQU5USFJPUElDX0FQSV9LRVknKSB9CiAgICBTZXQtUHJvcCAkZW52T2JqICdBTlRIUk9QSUNfQkFTRV9VUkwnICRTaXRlCiAgICBTZXQtUHJvcCAkZW52T2JqICdBTlRIUk9QSUNfQVVUSF9UT0tFTicgJHNjcmlwdDpLZXkKICAgIFNldC1Qcm9wICRlbnZPYmogJ0FOVEhST1BJQ19NT0RFTCcgJHNjcmlwdDpNb2RlbAogICAgU2V0LVByb3AgJGVudk9iaiAnQU5USFJPUElDX0RFRkFVTFRfSEFJS1VfTU9ERUwnICRzY3JpcHQ6RmFzdE1vZGVsCiAgICBTZXQtUHJvcCAkZW52T2JqICdBTlRIUk9QSUNfU01BTExfRkFTVF9NT0RFTCcgJHNjcmlwdDpGYXN0TW9kZWwKICAgIFNldC1Qcm9wICRlbnZPYmogJ0NMQVVERV9DT0RFX0VOQUJMRV9HQVRFV0FZX01PREVMX0RJU0NPVkVSWScgJzEnCiAgICBVcGRhdGUtSnNvbkZpbGUgJHBhdGggKFtvcmRlcmVkXUB7IGVudiA9ICRlbnZPYmogfSkKCiAgICAjINC/0LXRgNCy0YvQuSDQt9Cw0L/Rg9GB0Log0LHQtdC3INGN0LrRgNCw0L3QsCDQstGF0L7QtNCwINCyINCw0LrQutCw0YPQvdGCIEFudGhyb3BpYwogICAgJHN0YXRlID0gSm9pbi1QYXRoICRVc2VySG9tZSAnLmNsYXVkZS5qc29uJwogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJHN0YXRlKSkgewogICAgICAgIFNhdmUtVGV4dCAkc3RhdGUgIntgbiAgYCJoYXNDb21wbGV0ZWRPbmJvYXJkaW5nYCI6IHRydWVgbn1gbiIKICAgIH0gZWxzZSB7CiAgICAgICAgJHQgPSBbSU8uRmlsZV06OlJlYWRBbGxUZXh0KCRzdGF0ZSkKICAgICAgICBpZiAoJHQgLW1hdGNoICciaGFzQ29tcGxldGVkT25ib2FyZGluZyJccyo6XHMqZmFsc2UnKSB7CiAgICAgICAgICAgIFNhdmUtVGV4dCAkc3RhdGUgKFtyZWdleF06OlJlcGxhY2UoJHQsICciaGFzQ29tcGxldGVkT25ib2FyZGluZyJccyo6XHMqZmFsc2UnLCAnImhhc0NvbXBsZXRlZE9uYm9hcmRpbmciOiB0cnVlJywgMSkpCiAgICAgICAgfSBlbHNlaWYgKCR0IC1ub3RtYXRjaCAnImhhc0NvbXBsZXRlZE9uYm9hcmRpbmciJykgewogICAgICAgICAgICAkaSA9ICR0LkluZGV4T2YoJ3snKQogICAgICAgICAgICBpZiAoJGkgLWdlIDApIHsgU2F2ZS1UZXh0ICRzdGF0ZSAoJHQuU3Vic3RyaW5nKDAsICRpICsgMSkgKyAiYG4gIGAiaGFzQ29tcGxldGVkT25ib2FyZGluZ2AiOiB0cnVlLCIgKyAkdC5TdWJzdHJpbmcoJGkgKyAxKSkgfQogICAgICAgIH0KICAgIH0KCiAgICAjINGB0YLQsNGA0YvQtSDQv9C+0YHRgtC+0Y/QvdC90YvQtSDQv9C10YDQtdC80LXQvdC90YvQtSBXaW5kb3dzINC/0LXRgNC10LrRgNGL0LLQsNGO0YIgc2V0dGluZ3MuanNvbiAtINC+0LHQvdC+0LLQu9GP0LXQvCDQuNGFCiAgICBmb3JlYWNoICgkcGFpciBpbiBAKEAoJ0FOVEhST1BJQ19CQVNFX1VSTCcsICRTaXRlKSwgQCgnQU5USFJPUElDX0FVVEhfVE9LRU4nLCAkc2NyaXB0OktleSksIEAoJ0FOVEhST1BJQ19NT0RFTCcsICRzY3JpcHQ6TW9kZWwpKSkgewogICAgICAgIGlmICgkbnVsbCAtbmUgKEdldC1Vc2VyRW52ICRwYWlyWzBdKSkgeyBTZXQtVXNlckVudiAkcGFpclswXSAkcGFpclsxXTsgT2sgItC+0LHQvdC+0LLQu9C10L3QsCDQv9C10YDQtdC80LXQvdC90LDRjyBXaW5kb3dzICQoJHBhaXJbMF0pIiB9CiAgICB9CiAgICAkYXBpS2V5ID0gR2V0LVVzZXJFbnYgJ0FOVEhST1BJQ19BUElfS0VZJwogICAgaWYgKCRhcGlLZXkpIHsKICAgICAgICBXYXJuICfQkiBXaW5kb3dzINC30LDQtNCw0L3QsCDQv9C10YDQtdC80LXQvdC90LDRjyBBTlRIUk9QSUNfQVBJX0tFWSAo0LTRgNGD0LPQvtC5INC60LvRjtGHKS4g0JXRgdC70LggQ2xhdWRlIENvZGUg0YHQv9GA0L7RgdC40YIsINC40YHQv9C+0LvRjNC30L7QstCw0YLRjCDQu9C4INC10ZEsINC+0YLQstC10YLRjNGC0LUgTm8uJwogICAgfQogICAgT2sgIkNsYXVkZSBDb2RlINC/0L7QtNC60LvRjtGH0ZHQvSAo0LzQvtC00LXQu9GMICQoJHNjcmlwdDpNb2RlbCksINGE0L7QvdC+0LLQsNGPICQoJHNjcmlwdDpGYXN0TW9kZWwpKSIKICAgICRzY3JpcHQ6TmV4dC5BZGQoJ0NsYXVkZSBDb2RlOiDQvtGC0LrRgNC+0LnRgtC1INCd0J7QktCe0JUg0L7QutC90L4g0YLQtdGA0LzQuNC90LDQu9CwINCyINC/0LDQv9C60LUg0L/RgNC+0LXQutGC0LAgLT4gY2xhdWRlJykKfQoKZnVuY3Rpb24gQXBwbHktQ29kZXggewogICAgJGRpciA9IGlmICgkZW52OkNPREVYX0hPTUUgLWFuZCAtbm90ICRUZXN0Um9vdCkgeyAkZW52OkNPREVYX0hPTUUgfSBlbHNlIHsgSm9pbi1QYXRoICRVc2VySG9tZSAnLmNvZGV4JyB9CiAgICAkcGF0aCA9IEpvaW4tUGF0aCAkZGlyICdjb25maWcudG9tbCcKICAgICRsaW5lcyA9IEAoKQogICAgaWYgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJHBhdGgpIHsgJGxpbmVzID0gQChbSU8uRmlsZV06OlJlYWRBbGxMaW5lcygkcGF0aCkpIH0KICAgICR0b3AgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgJHJlc3QgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgJGluVGFibGVzID0gJGZhbHNlOyAkc2tpcCA9ICRmYWxzZQogICAgZm9yZWFjaCAoJGwgaW4gJGxpbmVzKSB7CiAgICAgICAgaWYgKCRsIC1tYXRjaCAnXlxzKlxbJykgewogICAgICAgICAgICAkaW5UYWJsZXMgPSAkdHJ1ZQogICAgICAgICAgICAkc2tpcCA9ICgkbCAtbWF0Y2ggJ15ccypcW1xzKm1vZGVsX3Byb3ZpZGVyc1xzKlwuXHMqIj90b2tpbmUiP1xzKihcXXxcLiknKQogICAgICAgIH0KICAgICAgICBpZiAoLW5vdCAkaW5UYWJsZXMpIHsKICAgICAgICAgICAgaWYgKCRsIC1tYXRjaCAnXlxzKihtb2RlbHxtb2RlbF9wcm92aWRlcilccyo9JykgeyBjb250aW51ZSB9CiAgICAgICAgICAgICR0b3AuQWRkKCRsKQogICAgICAgIH0gZWxzZWlmICgtbm90ICRza2lwKSB7ICRyZXN0LkFkZCgkbCkgfQogICAgfQogICAgd2hpbGUgKCR0b3AuQ291bnQgLWd0IDAgLWFuZCAtbm90ICR0b3BbMF0uVHJpbSgpKSB7ICR0b3AuUmVtb3ZlQXQoMCkgfQogICAgd2hpbGUgKCR0b3AuQ291bnQgLWd0IDAgLWFuZCAtbm90ICR0b3BbJHRvcC5Db3VudCAtIDFdLlRyaW0oKSkgeyAkdG9wLlJlbW92ZUF0KCR0b3AuQ291bnQgLSAxKSB9CiAgICB3aGlsZSAoJHJlc3QuQ291bnQgLWd0IDAgLWFuZCAtbm90ICRyZXN0WyRyZXN0LkNvdW50IC0gMV0uVHJpbSgpKSB7ICRyZXN0LlJlbW92ZUF0KCRyZXN0LkNvdW50IC0gMSkgfQogICAgJG91dCA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtzdHJpbmddCiAgICAkb3V0LkFkZCgibW9kZWwgPSBgIiQoJHNjcmlwdDpNb2RlbClgIiIpCiAgICAkb3V0LkFkZCgnbW9kZWxfcHJvdmlkZXIgPSAidG9raW5lIicpCiAgICBmb3JlYWNoICgkbCBpbiAkdG9wKSB7ICRvdXQuQWRkKCRsKSB9CiAgICBpZiAoJHJlc3QuQ291bnQgLWd0IDApIHsgJG91dC5BZGQoJycpOyBmb3JlYWNoICgkbCBpbiAkcmVzdCkgeyAkb3V0LkFkZCgkbCkgfSB9CiAgICAkb3V0LkFkZCgnJykKICAgICRvdXQuQWRkKCdbbW9kZWxfcHJvdmlkZXJzLnRva2luZV0nKQogICAgJG91dC5BZGQoJ25hbWUgPSAiTkVYVVNfQUkgR2F0ZXdheSInKQogICAgJG91dC5BZGQoImJhc2VfdXJsID0gYCIkT3BlbkFpQmFzZWAiIikKICAgICRvdXQuQWRkKCd3aXJlX2FwaSA9ICJyZXNwb25zZXMiJykKICAgICRvdXQuQWRkKCJleHBlcmltZW50YWxfYmVhcmVyX3Rva2VuID0gYCIkKCRzY3JpcHQ6S2V5KWAiIikKICAgIFNhdmUtVGV4dCAkcGF0aCAoKCRvdXQgLWpvaW4gImBuIikgKyAiYG4iKQogICAgT2sgIkNvZGV4IENMSSDQv9C+0LTQutC70Y7Rh9GR0L0gKNC80L7QtNC10LvRjCAkKCRzY3JpcHQ6TW9kZWwpKSIKICAgICRzY3JpcHQ6TmV4dC5BZGQoJ0NvZGV4OiDQvtGC0LrRgNC+0LnRgtC1INCd0J7QktCe0JUg0L7QutC90L4g0YLQtdGA0LzQuNC90LDQu9CwINCyINC/0LDQv9C60LUg0L/RgNC+0LXQutGC0LAgLT4gY29kZXgnKQp9Cgokc2NyaXB0OkFsbE1vZGVscyA9IChbc3RyaW5nXSRlbnY6TkVYVVNfQUxMX01PREVMUyAtbmUgJzAnKQoKZnVuY3Rpb24gR2V0LU1vZGVsRW50cmllcyB7CiAgICAjIHRoZSBtYWluIG1vZGVsIGZpcnN0LCB0aGVuIHRoZSByZWNvbW1lbmRlZCBvbmVzOyB3aXRoICJhbGwgbW9kZWxzIiBldmVyeSBjYXRhbG9ndWUgbW9kZWwgZm9sbG93cwogICAgJGlkcyA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtzdHJpbmddCiAgICAkaWRzLkFkZCgkc2NyaXB0Ok1vZGVsKQogICAgZm9yZWFjaCAoJGMgaW4gJEN1cmF0ZWQpIHsgaWYgKC1ub3QgJGlkcy5Db250YWlucygkYy5JZCkgLWFuZCAoJG51bGwgLWVxICRzY3JpcHQ6Q2F0YWxvZyAtb3IgJHNjcmlwdDpDYXRhbG9nLkNvbnRhaW5zS2V5KCRjLklkKSkpIHsgJGlkcy5BZGQoJGMuSWQpIH0gfQogICAgaWYgKCRzY3JpcHQ6QWxsTW9kZWxzIC1hbmQgJHNjcmlwdDpDYXRhbG9nKSB7CiAgICAgICAgZm9yZWFjaCAoJGsgaW4gKEdldC1DYXRhbG9nT3JkZXIpKSB7IGlmICgtbm90ICRpZHMuQ29udGFpbnMoJGspKSB7ICRpZHMuQWRkKCRrKSB9IH0KICAgIH0KICAgIHJldHVybiAsICRpZHMKfQoKZnVuY3Rpb24gR2V0LUNhdGFsb2dPcmRlciB7CiAgICAjIGNhdGFsb2d1ZSBpZHMsIGNoZWFwZXN0IGZpcnN0CiAgICBpZiAoLW5vdCAkc2NyaXB0OkNhdGFsb2cpIHsgcmV0dXJuIEAoKSB9CiAgICByZXR1cm4gQCgkc2NyaXB0OkNhdGFsb2cuS2V5cyB8IFNvcnQtT2JqZWN0IHsgW2RvdWJsZV0kKGlmICgkbnVsbCAtbmUgJHNjcmlwdDpDYXRhbG9nWyRfXSkgeyAkc2NyaXB0OkNhdGFsb2dbJF9dIH0gZWxzZSB7IDEgfSkgfSwgeyAkXyB9KQp9CgpmdW5jdGlvbiBBcHBseS1Rd2VuIHsKICAgICRwYXRoID0gSm9pbi1QYXRoICRVc2VySG9tZSAnLnF3ZW5cc2V0dGluZ3MuanNvbicKICAgICRjdXIgPSBSZWFkLUpzb25GaWxlICRwYXRoCiAgICAkcHJvdmlkZXJzID0gR2V0LU9ialByb3AgJGN1ciAnbW9kZWxQcm92aWRlcnMnCiAgICAkbGlzdCA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtvYmplY3RdCiAgICAkZXhpc3RpbmcgPSBHZXQtUHJvcCAkcHJvdmlkZXJzICdvcGVuYWknCiAgICAjIGVudHJpZXMgb2YgZXZlcnkgTkVYVVMgYWRkcmVzcyBhcmUgcmVwbGFjZWQgKGEgc2VydmVyIHN3aXRjaCB1c2VkIHRvIGxlYXZlIHRoZSBvbGQgb25lcyBhcyBkdXBsaWNhdGVzKQogICAgJG91cnMgPSBAKCRTZXJ2ZXJzIHwgRm9yRWFjaC1PYmplY3QgeyAkXy5VcmwgfSkgKyBAKCRTaXRlKQogICAgaWYgKCRleGlzdGluZykgewogICAgICAgIGZvcmVhY2ggKCRlIGluIEAoJGV4aXN0aW5nKSkgewogICAgICAgICAgICAkdSA9IFtzdHJpbmddKEdldC1Qcm9wICRlICdiYXNlVXJsJykKICAgICAgICAgICAgaWYgKC1ub3QgKCRvdXJzIHwgV2hlcmUtT2JqZWN0IHsgJHUgLWxpa2UgIiRfKiIgfSkpIHsgJGxpc3QuQWRkKCRlKSB9CiAgICAgICAgfQogICAgfQogICAgZm9yZWFjaCAoJGlkIGluIChHZXQtTW9kZWxFbnRyaWVzKSkgewogICAgICAgICRsaXN0LkFkZChbb3JkZXJlZF1AeyBpZCA9ICRpZDsgbmFtZSA9ICIkaWQgKE5FWFVTX0FJKSI7IGJhc2VVcmwgPSAkT3BlbkFpQmFzZTsgZW52S2V5ID0gJ05FWFVTX0FQSV9LRVknIH0pCiAgICB9CiAgICBTZXQtUHJvcCAkcHJvdmlkZXJzICdvcGVuYWknICRsaXN0LlRvQXJyYXkoKQogICAgJGVudk9iaiA9IEdldC1PYmpQcm9wICRjdXIgJ2VudicKICAgIFNldC1Qcm9wICRlbnZPYmogJ05FWFVTX0FQSV9LRVknICRzY3JpcHQ6S2V5CiAgICAkc2VjdXJpdHkgPSBHZXQtT2JqUHJvcCAkY3VyICdzZWN1cml0eScKICAgICRhdXRoID0gR2V0LU9ialByb3AgJHNlY3VyaXR5ICdhdXRoJwogICAgU2V0LVByb3AgJGF1dGggJ3NlbGVjdGVkVHlwZScgJ29wZW5haScKICAgIFNldC1Qcm9wICRzZWN1cml0eSAnYXV0aCcgJGF1dGgKICAgICRtb2RlbE9iaiA9IEdldC1PYmpQcm9wICRjdXIgJ21vZGVsJwogICAgU2V0LVByb3AgJG1vZGVsT2JqICduYW1lJyAkc2NyaXB0Ok1vZGVsCiAgICBVcGRhdGUtSnNvbkZpbGUgJHBhdGggKFtvcmRlcmVkXUB7IG1vZGVsUHJvdmlkZXJzID0gJHByb3ZpZGVyczsgZW52ID0gJGVudk9iajsgc2VjdXJpdHkgPSAkc2VjdXJpdHk7IG1vZGVsID0gJG1vZGVsT2JqIH0pCiAgICBPayAiUXdlbiBDb2RlINC/0L7QtNC60LvRjtGH0ZHQvSAo0LzQvtC00LXQu9GMICQoJHNjcmlwdDpNb2RlbCkpIgogICAgJHNjcmlwdDpOZXh0LkFkZCgnUXdlbiBDb2RlOiDQvtGC0LrRgNC+0LnRgtC1INCd0J7QktCe0JUg0L7QutC90L4g0YLQtdGA0LzQuNC90LDQu9CwIC0+IHF3ZW4nKQp9CgojIGV2ZXJ5IGNhdGFsb2d1ZSBtb2RlbCwgY2hlYXBlc3QgZmlyc3QgKHRoZSBjaG9zZW4gYW5kIHRoZSByZWNvbW1lbmRlZCBvbmVzIGxlYWQpCmZ1bmN0aW9uIEdldC1BbGxNb2RlbElkcyB7IHJldHVybiAoR2V0LU1vZGVsRW50cmllcykgfQoKIyAiPGlkPiDCtyDDlzxtdWx0aXBsaWVyPiIsIGFzIG9uIHRoZSBwcmljaW5nIHBhZ2UKZnVuY3Rpb24gR2V0LU1vZGVsTGFiZWwoW3N0cmluZ10kSWQpIHsKICAgICMgIkNsYXVkZSBTb25uZXQgNS41IMK3IMOXOCI6IHRoZSBjYXRhbG9ndWUgbmFtZSBhbmQgbXVsdGlwbGllciwgYXMgb24gdGhlIHByaWNpbmcgcGFnZQogICAgJG5hbWUgPSBpZiAoJHNjcmlwdDpDYXRhbG9nTmFtZXMgLWFuZCAkc2NyaXB0OkNhdGFsb2dOYW1lc1skSWRdKSB7ICRzY3JpcHQ6Q2F0YWxvZ05hbWVzWyRJZF0gfSBlbHNlIHsgJElkIH0KICAgIGlmICgkc2NyaXB0OkNhdGFsb2cgLWFuZCAkbnVsbCAtbmUgJHNjcmlwdDpDYXRhbG9nWyRJZF0pIHsKICAgICAgICByZXR1cm4gKCd7MH0gezF9IHsyfXszfScgLWYgJG5hbWUsIFtjaGFyXTB4MDBCNywgW2NoYXJdMHgwMEQ3LCAoW2RvdWJsZV0kc2NyaXB0OkNhdGFsb2dbJElkXSkuVG9TdHJpbmcoW0dsb2JhbGl6YXRpb24uQ3VsdHVyZUluZm9dOjpJbnZhcmlhbnRDdWx0dXJlKSkKICAgIH0KICAgIHJldHVybiAkbmFtZQp9CgpmdW5jdGlvbiBHZXQtT3BlbkNvZGVQcm92aWRlciB7CiAgICAkbW9kZWxzID0gW29yZGVyZWRdQHt9CiAgICBmb3JlYWNoICgkaWQgaW4gKEdldC1BbGxNb2RlbElkcykpIHsgJG1vZGVsc1skaWRdID0gW29yZGVyZWRdQHsgbmFtZSA9IChHZXQtTW9kZWxMYWJlbCAkaWQpIH0gfQogICAgcmV0dXJuIFtvcmRlcmVkXUB7IG5wbSA9ICdAYWktc2RrL29wZW5haS1jb21wYXRpYmxlJzsgbmFtZSA9ICdORVhVU19BSSc7IG9wdGlvbnMgPSBbb3JkZXJlZF1AeyBiYXNlVVJMID0gJE9wZW5BaUJhc2U7IGFwaUtleSA9ICRzY3JpcHQ6S2V5IH07IG1vZGVscyA9ICRtb2RlbHMgfQp9CgpmdW5jdGlvbiBBcHBseS1PcGVuQ29kZUxpa2UoW3N0cmluZ10kUGF0aCwgW3N0cmluZ10kVGl0bGUpIHsKICAgICRjdXIgPSBSZWFkLUpzb25GaWxlICRQYXRoCiAgICAkdmFsdWVzID0gW29yZGVyZWRdQHt9CiAgICBpZiAoLW5vdCAoR2V0LVByb3AgJGN1ciAnJHNjaGVtYScpIC1hbmQgQCgnT3BlbkNvZGUnLCAnTkVYVVMgQ0xJJykgLWNvbnRhaW5zICRUaXRsZSkgeyAkdmFsdWVzWyckc2NoZW1hJ10gPSAnaHR0cHM6Ly9vcGVuY29kZS5haS9jb25maWcuanNvbicgfQogICAgJHByb3YgPSBHZXQtT2JqUHJvcCAkY3VyICdwcm92aWRlcicKICAgIFNldC1Qcm9wICRwcm92ICduZXh1cycgKEdldC1PcGVuQ29kZVByb3ZpZGVyKQogICAgJHZhbHVlc1sncHJvdmlkZXInXSA9ICRwcm92CiAgICAkdmFsdWVzWydtb2RlbCddID0gIm5leHVzLyQoJHNjcmlwdDpNb2RlbCkiCiAgICBpZiAoLW5vdCAoR2V0LVByb3AgJGN1ciAnc21hbGxfbW9kZWwnKSkgeyAkdmFsdWVzWydzbWFsbF9tb2RlbCddID0gIm5leHVzLyQoJHNjcmlwdDpGYXN0TW9kZWwpIiB9CiAgICBVcGRhdGUtSnNvbkZpbGUgJFBhdGggJHZhbHVlcwogICAgT2sgIiRUaXRsZSDQv9C+0LTQutC70Y7Rh9GR0L0gKNC80L7QtNC10LvRjCBuZXh1cy8kKCRzY3JpcHQ6TW9kZWwpKSIKfQoKZnVuY3Rpb24gQXBwbHktT3BlbkNvZGUgewogICAgQXBwbHktT3BlbkNvZGVMaWtlIChKb2luLVBhdGggJFVzZXJIb21lICcuY29uZmlnXG9wZW5jb2RlXG9wZW5jb2RlLmpzb24nKSAnT3BlbkNvZGUnCiAgICAkc2NyaXB0Ok5leHQuQWRkKCdPcGVuQ29kZTog0LIg0L/QsNC/0LrQtSDQv9GA0L7QtdC60YLQsCAtPiBvcGVuY29kZSAo0LzQvtC00LXQu9C4INCyIC9tb2RlbHMsINC/0YDQvtCy0LDQudC00LXRgCBORVhVU19BSSknKQp9CgojIE5FWFVTIENMSSAodGVybWluYWwgYWdlbnQpID0gYSBwaW5uZWQgcmVsZWFzZSBvZiBpdHMgb3Blbi1zb3VyY2UgZW5naW5lICsgb3VyIHNldHVwIGluIH4vLm5leHVzLWFpL2NvZGUuCiMgVGhlIGVuZ2luZSBpcyB0aGUgc2lnbmVkIGJpbmFyeSBmcm9tIHRoZSBucG0gcmVnaXN0cnkgKGNoZWNrZWQgYnkgc2hhNTEyIGFuZCBzaWduYXR1cmUpLCBrZXB0IGFzCiMgfi8ubmV4dXMtYWkvY29kZS9lbmdpbmUvbmV4dXMtY2xpLmV4ZTogbm8gTm9kZS5qcyBuZWVkZWQsIG5vIHVwZGF0ZSBwcm9tcHRzLCBvdGhlciB0b29scyBvbiB0aGUgY29tcHV0ZXIgdW50b3VjaGVkLgojIEZpbGVzIGNvbWUgZnJvbSAkU2l0ZS9pbnN0YWxsL25leHVzLWNvZGUvICh0aGVtZSwgcGx1Z2lucywgbGF1bmNoZXIpOyB0aGUgbGF1bmNoZXIgZ29lcyB0byB+Ly5uZXh1cy1haS9iaW4gKG9uIFBBVEgpLgokTmV4dXNFbmdpbmVWZXJzaW9uID0gJzIuMi4xJwokTmV4dXNFbmdpbmVTaGEgPSBAewogICAgJ3dpbmRvd3MteDY0JyAgICAgICAgICA9ICdzaGE1MTItWGNlNkhHZVk2U1ZSb29qY2U1YmNEQVJ6WHBUK1E1cXdPVXBYbGNWUlJCemEzUW1tWkZ6Z01KdzRzZkJWWk15dEVBNUlEeFFSemd5NG9EQ1VKc0tZYkE9PScKICAgICd3aW5kb3dzLXg2NC1iYXNlbGluZScgPSAnc2hhNTEyLXhaR1p2ZzZ2T1dyZ0hLVml6cEJRaVhoc3FJdjZ1LzZOdWZ3aXhPS2FzbzZtcW9lNy93SGdOZExFcE4wOEZoanpKTGJ6czhhdFp3Q0VmQlZZWjJQd0R3PT0nCiAgICAnd2luZG93cy1hcm02NCcgICAgICAgID0gJ3NoYTUxMi1EUWdxKzZTUWlCaW02NXJoRzdDSGd6aUpCTEx6bS9ROENFMHBGOVNqV1gzc2Z3STdNaEowRzBWMTI3Y1k4YlZxRUc3Qml0bVV1T0xFLzM5R0djayt5dz09Jwp9CiROZXh1c0VuZ2luZU1pcnJvcnMgPSBAKCdodHRwczovL3JlZ2lzdHJ5Lm5wbWpzLm9yZycsICdodHRwczovL3JlZ2lzdHJ5Lm5wbW1pcnJvci5jb20nKQoKZnVuY3Rpb24gR2V0LUVuZ2luZVBsYXRmb3JtIHsKICAgICRhcmNoID0gJycKICAgIHRyeSB7ICRhcmNoID0gW3N0cmluZ11bUnVudGltZS5JbnRlcm9wU2VydmljZXMuUnVudGltZUluZm9ybWF0aW9uXTo6T1NBcmNoaXRlY3R1cmUgfSBjYXRjaCB7fQogICAgaWYgKCRhcmNoIC1lcSAnQXJtNjQnIC1vciAkZW52OlBST0NFU1NPUl9BUkNISVRFQ1RVUkUgLWVxICdBUk02NCcgLW9yICRlbnY6UFJPQ0VTU09SX0FSQ0hJVEVXNjQzMiAtZXEgJ0FSTTY0JykgeyByZXR1cm4gJ3dpbmRvd3MtYXJtNjQnIH0KICAgICMgQ1BVcyB3aXRob3V0IEFWWDIgbmVlZCB0aGUgImJhc2VsaW5lIiBidWlsZCAoUEZfQVZYMl9JTlNUUlVDVElPTlNfQVZBSUxBQkxFID0gNDApCiAgICB0cnkgewogICAgICAgICRrID0gQWRkLVR5cGUgLU1lbWJlckRlZmluaXRpb24gJ1tEbGxJbXBvcnQoImtlcm5lbDMyLmRsbCIpXSBwdWJsaWMgc3RhdGljIGV4dGVybiBib29sIElzUHJvY2Vzc29yRmVhdHVyZVByZXNlbnQoaW50IGYpOycgLU5hbWUgJ05leHVzQ3B1JyAtTmFtZXNwYWNlICdOZXh1c1NldHVwJyAtUGFzc1RocnUgLUVycm9yQWN0aW9uIFN0b3AKICAgICAgICBpZiAoLW5vdCAkazo6SXNQcm9jZXNzb3JGZWF0dXJlUHJlc2VudCg0MCkpIHsgcmV0dXJuICd3aW5kb3dzLXg2NC1iYXNlbGluZScgfQogICAgfSBjYXRjaCB7fQogICAgcmV0dXJuICd3aW5kb3dzLXg2NCcKfQoKZnVuY3Rpb24gR2V0LUZpbGVXaXRoUHJvZ3Jlc3MoW3N0cmluZ10kVXJsLCBbc3RyaW5nXSREZXN0LCBbc3RyaW5nXSRMYWJlbCkgewogICAgIyBzdHJlYW1lZCBkb3dubG9hZCB3aXRoIGEgcHJvZ3Jlc3MgYmFyIGluIHBsYWNlOiBb4paI4paI4paI4paI4paI4paR4paR4paR4paR4paRXSAgNTIlICA2Mi8xMTkg0JzQkSAgOC40INCc0JEv0YEKICAgICRyZXEgPSBbTmV0Lkh0dHBXZWJSZXF1ZXN0XTo6Q3JlYXRlKCRVcmwpCiAgICAkcmVxLlRpbWVvdXQgPSAzMDAwMAogICAgJHJlcS5SZWFkV3JpdGVUaW1lb3V0ID0gNjAwMDAKICAgICRyZXEuVXNlckFnZW50ID0gJ05FWFVTLUFJLVNldHVwJwogICAgJHJlc3AgPSAkcmVxLkdldFJlc3BvbnNlKCkKICAgICR0b3RhbCA9IFtkb3VibGVdJHJlc3AuQ29udGVudExlbmd0aAogICAgJGluID0gJHJlc3AuR2V0UmVzcG9uc2VTdHJlYW0oKQogICAgJG91dCA9IFtJTy5GaWxlXTo6Q3JlYXRlKCREZXN0KQogICAgdHJ5IHsKICAgICAgICAkYnVmID0gTmV3LU9iamVjdCBieXRlW10gMjYyMTQ0CiAgICAgICAgJGRvbmUgPSAwLjAKICAgICAgICAkc3cgPSBbRGlhZ25vc3RpY3MuU3RvcHdhdGNoXTo6U3RhcnROZXcoKQogICAgICAgICRsYXN0RHJhdyA9IC0xLjAKICAgICAgICAkd2lkdGggPSAyNAogICAgICAgIHRyeSB7ICR3aWR0aCA9IFtNYXRoXTo6TWF4KDEwLCBbTWF0aF06Ok1pbigzMCwgW0NvbnNvbGVdOjpXaW5kb3dXaWR0aCAtIDUyKSkgfSBjYXRjaCB7fQogICAgICAgIHdoaWxlICgoJG4gPSAkaW4uUmVhZCgkYnVmLCAwLCAkYnVmLkxlbmd0aCkpIC1ndCAwKSB7CiAgICAgICAgICAgICRvdXQuV3JpdGUoJGJ1ZiwgMCwgJG4pCiAgICAgICAgICAgICRkb25lICs9ICRuCiAgICAgICAgICAgICR0ID0gJHN3LkVsYXBzZWQuVG90YWxTZWNvbmRzCiAgICAgICAgICAgIGlmICgkdCAtICRsYXN0RHJhdyAtZ2UgMC4xNSkgewogICAgICAgICAgICAgICAgJGxhc3REcmF3ID0gJHQKICAgICAgICAgICAgICAgICRtYiA9ICRkb25lIC8gMU1CCiAgICAgICAgICAgICAgICAkc3BlZWQgPSBpZiAoJHQgLWd0IDApIHsgJG1iIC8gJHQgfSBlbHNlIHsgMCB9CiAgICAgICAgICAgICAgICBpZiAoJHRvdGFsIC1ndCAwKSB7CiAgICAgICAgICAgICAgICAgICAgJHAgPSBbTWF0aF06Ok1pbigxLjAsICRkb25lIC8gJHRvdGFsKQogICAgICAgICAgICAgICAgICAgICRmaWxsID0gW2ludF1bTWF0aF06OlJvdW5kKCRwICogJHdpZHRoKQogICAgICAgICAgICAgICAgICAgICRiYXIgPSAoW3N0cmluZ11bY2hhcl0weDI1ODgpICogJGZpbGwgKyAoW3N0cmluZ11bY2hhcl0weDI1OTEpICogKCR3aWR0aCAtICRmaWxsKQogICAgICAgICAgICAgICAgICAgICRsaW5lID0gKCcgIHswfSBbezF9XSB7MiwzfSUgIHszOjB9L3s0OjB9INCc0JEgIHs1OjAuMH0g0JzQkS/RgScgLWYgJExhYmVsLCAkYmFyLCBbaW50XSgkcCAqIDEwMCksICRtYiwgKCR0b3RhbCAvIDFNQiksICRzcGVlZCkKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgJGxpbmUgPSAoJyAgezB9ICB7MTowfSDQnNCRICB7MjowLjB9INCc0JEv0YEnIC1mICRMYWJlbCwgJG1iLCAkc3BlZWQpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBXcml0ZS1Ib3N0ICgiYHIiICsgJGxpbmUgKyAnICAgJykgLU5vTmV3bGluZSAtRm9yZWdyb3VuZENvbG9yIEN5YW4KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAkYmFyID0gKFtzdHJpbmddW2NoYXJdMHgyNTg4KSAqICR3aWR0aAogICAgICAgIFdyaXRlLUhvc3QgKCJgciIgKyAoJyAgezB9IFt7MX1dIDEwMCUgIHsyOjB9INCc0JEgINCz0L7RgtC+0LLQviAgICAgJyAtZiAkTGFiZWwsICRiYXIsICgkZG9uZSAvIDFNQikpKSAtRm9yZWdyb3VuZENvbG9yIEdyZWVuCiAgICB9IGZpbmFsbHkgewogICAgICAgICRvdXQuQ2xvc2UoKTsgJGluLkNsb3NlKCk7ICRyZXNwLkNsb3NlKCkKICAgIH0KfQoKZnVuY3Rpb24gSW5zdGFsbC1OZXh1c0VuZ2luZSB7CiAgICAjIHJldHVybnMgdGhlIHBhdGggb2YgbmV4dXMtY2xpLmV4ZSBvciAkbnVsbAogICAgJGRpciA9IEpvaW4tUGF0aCAkVXNlckhvbWUgJy5uZXh1cy1haVxjb2RlXGVuZ2luZScKICAgICRleGUgPSBKb2luLVBhdGggJGRpciAnbmV4dXMtY2xpLmV4ZScKICAgICRwbGF0ID0gR2V0LUVuZ2luZVBsYXRmb3JtCiAgICAkd2FudCA9ICIkTmV4dXNFbmdpbmVWZXJzaW9uICRwbGF0IgogICAgJHN0YW1wID0gSm9pbi1QYXRoICRkaXIgJ3ZlcnNpb24udHh0JwogICAgaWYgKChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRleGUpIC1hbmQgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJHN0YW1wKSAtYW5kIChbSU8uRmlsZV06OlJlYWRBbGxUZXh0KCRzdGFtcCkuVHJpbSgpIC1lcSAkd2FudCkpIHsKICAgICAgICBPayAiTkVYVVMgQ0xJOiDRj9C00YDQviAkTmV4dXNFbmdpbmVWZXJzaW9uINGD0LbQtSDRg9GB0YLQsNC90L7QstC70LXQvdC+IgogICAgICAgIHJldHVybiAkZXhlCiAgICB9CiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoICRkaXIpKSB7IE5ldy1JdGVtIC1JdGVtVHlwZSBEaXJlY3RvcnkgLUZvcmNlIC1QYXRoICRkaXIgfCBPdXQtTnVsbCB9CiAgICBpZiAoJFRlc3RSb290KSB7CiAgICAgICAgW0lPLkZpbGVdOjpXcml0ZUFsbFRleHQoJGV4ZSwgJ3Rlc3QgZW5naW5lJykKICAgICAgICBbSU8uRmlsZV06OldyaXRlQWxsVGV4dCgkc3RhbXAsICR3YW50KQogICAgICAgIHJldHVybiAkZXhlCiAgICB9CiAgICAkbmFtZSA9ICJjbGktJHBsYXQiCiAgICAkdG1wID0gSm9pbi1QYXRoIChbSU8uUGF0aF06OkdldFRlbXBQYXRoKCkpICgibmV4dXMtZW5naW5lLSIgKyBbZ3VpZF06Ok5ld0d1aWQoKS5Ub1N0cmluZygnTicpKQogICAgTmV3LUl0ZW0gLUl0ZW1UeXBlIERpcmVjdG9yeSAtRm9yY2UgLVBhdGggJHRtcCB8IE91dC1OdWxsCiAgICB0cnkgewogICAgICAgICR0Z3ogPSBKb2luLVBhdGggJHRtcCAnZW5naW5lLnRneicKICAgICAgICAkb2tEb3dubG9hZCA9ICRmYWxzZQogICAgICAgICRvbGRQcm9ncmVzcyA9ICRQcm9ncmVzc1ByZWZlcmVuY2UKICAgICAgICAkUHJvZ3Jlc3NQcmVmZXJlbmNlID0gJ1NpbGVudGx5Q29udGludWUnCiAgICAgICAgZm9yZWFjaCAoJG0gaW4gJE5leHVzRW5naW5lTWlycm9ycykgewogICAgICAgICAgICBpZiAoJG0gLW5lICROZXh1c0VuZ2luZU1pcnJvcnNbMF0pIHsgU3RlcCAiTkVYVVMgQ0xJOiDQv9GA0L7QsdGD0Y4g0LfQsNC/0LDRgdC90L7QuSDQsNC00YDQtdGBICgkKChbdXJpXSRtKS5Ib3N0KSkuLi4iIH0KICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgIEdldC1GaWxlV2l0aFByb2dyZXNzICIkbS9Ab3BlbmNvZGUvJG5hbWUvLS8kbmFtZS0kTmV4dXNFbmdpbmVWZXJzaW9uLnRneiIgJHRneiAn0JfQsNCz0YDRg9C30LrQsCBORVhVUyBDTEknCiAgICAgICAgICAgICAgICAkZnMgPSBbSU8uRmlsZV06Ok9wZW5SZWFkKCR0Z3opCiAgICAgICAgICAgICAgICB0cnkgeyAkc2hhID0gW0NvbnZlcnRdOjpUb0Jhc2U2NFN0cmluZygoTmV3LU9iamVjdCBTZWN1cml0eS5DcnlwdG9ncmFwaHkuU0hBNTEyTWFuYWdlZCkuQ29tcHV0ZUhhc2goJGZzKSkgfSBmaW5hbGx5IHsgJGZzLkNsb3NlKCkgfQogICAgICAgICAgICAgICAgaWYgKCJzaGE1MTItJHNoYSIgLWVxICROZXh1c0VuZ2luZVNoYVskcGxhdF0pIHsgJG9rRG93bmxvYWQgPSAkdHJ1ZTsgYnJlYWsgfQogICAgICAgICAgICAgICAgV2FybiAnTkVYVVMgQ0xJOiDRhNCw0LnQuyDQv9C+0LLRgNC10LbQtNGR0L0g0L/RgNC4INGB0LrQsNGH0LjQstCw0L3QuNC4ICjQutC+0L3RgtGA0L7Qu9GM0L3QsNGPINGB0YPQvNC80LAg0L3QtSDRgdC+0YjQu9Cw0YHRjCksINC/0YDQvtCx0YPRjiDQtNGA0YPQs9C+0Lkg0LDQtNGA0LXRgScKICAgICAgICAgICAgfSBjYXRjaCB7IFdyaXRlLUhvc3QgJyc7IFdhcm4gIk5FWFVTIENMSTog0L3QtSDRgdC60LDRh9Cw0LvQvtGB0Ywg0YEgJCgoW3VyaV0kbSkuSG9zdCk6ICQoJF8uRXhjZXB0aW9uLk1lc3NhZ2UpIiB9CiAgICAgICAgfQogICAgICAgICRQcm9ncmVzc1ByZWZlcmVuY2UgPSAkb2xkUHJvZ3Jlc3MKICAgICAgICBpZiAoLW5vdCAkb2tEb3dubG9hZCkgeyBGYWlsICdORVhVUyBDTEk6INC90LUg0YPQtNCw0LvQvtGB0Ywg0YHQutCw0YfQsNGC0Ywg0Y/QtNGA0L4uINCf0YDQvtCy0LXRgNGM0YLQtSDQuNC90YLQtdGA0L3QtdGCINC4INC30LDQv9GD0YHRgtC40YLQtSDRg9GB0YLQsNC90L7QstGJ0LjQuiDQtdGJ0ZEg0YDQsNC3Lic7IHJldHVybiAkbnVsbCB9CiAgICAgICAgJHRhciA9IEpvaW4tUGF0aCAkZW52OlN5c3RlbVJvb3QgJ1N5c3RlbTMyXHRhci5leGUnCiAgICAgICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAkdGFyKSkgeyAkdGFyID0gJ3Rhci5leGUnIH0KICAgICAgICAkciA9IEludm9rZS1Ub29sICR0YXIgQCgnLXh6ZicsICR0Z3osICctQycsICR0bXApIDMwMAogICAgICAgICRzcmMgPSBKb2luLVBhdGggJHRtcCAncGFja2FnZVxiaW5cb3BlbmNvZGUuZXhlJwogICAgICAgIGlmICgkci5Db2RlIC1uZSAwIC1vciAtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRzcmMpKSB7IEZhaWwgIk5FWFVTIENMSTog0L3QtSDRg9C00LDQu9C+0YHRjCDRgNCw0YHQv9Cw0LrQvtCy0LDRgtGMINGP0LTRgNC+LiAkKCRyLlRleHQpIjsgcmV0dXJuICRudWxsIH0KICAgICAgICAkc2lnID0gR2V0LUF1dGhlbnRpY29kZVNpZ25hdHVyZSAtTGl0ZXJhbFBhdGggJHNyYwogICAgICAgIGlmICgkc2lnLlN0YXR1cyAtbmUgJ1ZhbGlkJykgeyBGYWlsICJORVhVUyBDTEk6INC/0L7QtNC/0LjRgdGMINGP0LTRgNCwINC90LUg0L/RgNC+0YjQu9CwINC/0YDQvtCy0LXRgNC60YMgKCQoJHNpZy5TdGF0dXMpKSwg0YPRgdGC0LDQvdC+0LLQutCwINC+0YHRgtCw0L3QvtCy0LvQtdC90LAuIjsgcmV0dXJuICRudWxsIH0KICAgICAgICB0cnkgeyBDb3B5LUl0ZW0gLUxpdGVyYWxQYXRoICRzcmMgLURlc3RpbmF0aW9uICRleGUgLUZvcmNlIC1FcnJvckFjdGlvbiBTdG9wIH0KICAgICAgICBjYXRjaCB7IEZhaWwgJ05FWFVTIENMSSDRgdC10LnRh9Cw0YEg0LfQsNC/0YPRidC10L0gLSDQt9Cw0LrRgNC+0LnRgtC1INC10LPQviDQvtC60L3QviAoQ3RybCtDKSDQuCDQt9Cw0L/Rg9GB0YLQuNGC0LUg0YPRgdGC0LDQvdC+0LLRidC40Log0LXRidGRINGA0LDQty4nOyByZXR1cm4gJG51bGwgfQogICAgICAgIFtJTy5GaWxlXTo6V3JpdGVBbGxUZXh0KCRzdGFtcCwgJHdhbnQpCiAgICAgICAgT2sgIk5FWFVTIENMSTog0Y/QtNGA0L4gJE5leHVzRW5naW5lVmVyc2lvbiDRg9GB0YLQsNC90L7QstC70LXQvdC+ICjQv9C+0LTQv9C40YHRjCDQv9GA0L7QstC10YDQtdC90LApIgogICAgICAgIHJldHVybiAkZXhlCiAgICB9IGZpbmFsbHkgewogICAgICAgIFJlbW92ZS1JdGVtIC1MaXRlcmFsUGF0aCAkdG1wIC1SZWN1cnNlIC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQogICAgfQp9CgpmdW5jdGlvbiBBZGQtVXNlclBhdGgoW3N0cmluZ10kRGlyKSB7CiAgICAjIGFkZHMgYSBmb2xkZXIgdG8gdGhlIHVzZXIgUEFUSCwga2VlcGluZyB0aGUgcmVnaXN0cnkgdmFsdWUgdHlwZSAoUkVHX0VYUEFORF9TWik7IHVuZG9uZSBieSB0aGUgcm9sbGJhY2sKICAgIGlmICgkVGVzdFJvb3QpIHsgQWRkLUNvbnRlbnQgLVBhdGggKEpvaW4tUGF0aCAkVGVzdFJvb3QgJ3VzZXItcGF0aC50eHQnKSAtVmFsdWUgJERpciAtRW5jb2RpbmcgVVRGODsgcmV0dXJuIH0KICAgICRrZXkgPSBbTWljcm9zb2Z0LldpbjMyLlJlZ2lzdHJ5XTo6Q3VycmVudFVzZXIuT3BlblN1YktleSgnRW52aXJvbm1lbnQnLCAkdHJ1ZSkKICAgIHRyeSB7CiAgICAgICAgJHJhdyA9IFtzdHJpbmddJGtleS5HZXRWYWx1ZSgnUGF0aCcsICcnLCAnRG9Ob3RFeHBhbmRFbnZpcm9ubWVudE5hbWVzJykKICAgICAgICAkcGFydHMgPSBAKCRyYXcuU3BsaXQoJzsnKSB8IFdoZXJlLU9iamVjdCB7ICRfIH0pCiAgICAgICAgaWYgKCRwYXJ0cyB8IFdoZXJlLU9iamVjdCB7IFtFbnZpcm9ubWVudF06OkV4cGFuZEVudmlyb25tZW50VmFyaWFibGVzKCRfKS5UcmltRW5kKCdcJykgLWllcSAkRGlyLlRyaW1FbmQoJ1wnKSB9KSB7IHJldHVybiB9CiAgICAgICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAkUnVuRGlyKSkgeyBOZXctSXRlbSAtSXRlbVR5cGUgRGlyZWN0b3J5IC1Gb3JjZSAtUGF0aCAkUnVuRGlyIHwgT3V0LU51bGwgfQogICAgICAgIEFkZC1Db250ZW50IC1QYXRoIChKb2luLVBhdGggJFJ1bkRpciAnbWFuaWZlc3QudHN2JykgLVZhbHVlICgiUEFUSEFERGB0JERpcmB0YHQxIikgLUVuY29kaW5nIFVURjgKICAgICAgICAka2luZCA9IFtNaWNyb3NvZnQuV2luMzIuUmVnaXN0cnlWYWx1ZUtpbmRdOjpFeHBhbmRTdHJpbmcKICAgICAgICB0cnkgeyAka2luZCA9ICRrZXkuR2V0VmFsdWVLaW5kKCdQYXRoJykgfSBjYXRjaCB7fQogICAgICAgICRrZXkuU2V0VmFsdWUoJ1BhdGgnLCAoKCRwYXJ0cyArICREaXIpIC1qb2luICc7JyksICRraW5kKQogICAgfSBmaW5hbGx5IHsgJGtleS5DbG9zZSgpIH0KICAgICMgdGVsbCBFeHBsb3JlciBhbmQgbmV3IHRlcm1pbmFscyBhYm91dCB0aGUgY2hhbmdlIChzZXR0aW5nIGFueSB1c2VyIHZhcmlhYmxlIGJyb2FkY2FzdHMgV01fU0VUVElOR0NIQU5HRSkKICAgIHRyeSB7IFtFbnZpcm9ubWVudF06OlNldEVudmlyb25tZW50VmFyaWFibGUoJ05FWFVTX0FJX1BBVEhfVVBEQVRFRCcsICRudWxsLCAnVXNlcicpIH0gY2F0Y2gge30KICAgIGlmICgoJGVudjpQYXRoLlNwbGl0KCc7JykgfCBXaGVyZS1PYmplY3QgeyAkXy5UcmltRW5kKCdcJykgLWllcSAkRGlyLlRyaW1FbmQoJ1wnKSB9KS5Db3VudCAtZXEgMCkgeyAkZW52OlBhdGggPSAkZW52OlBhdGguVHJpbUVuZCgnOycpICsgJzsnICsgJERpciB9Cn0KCmZ1bmN0aW9uIFJlbW92ZS1Vc2VyUGF0aChbc3RyaW5nXSREaXIpIHsKICAgIGlmICgkVGVzdFJvb3QpIHsgcmV0dXJuIH0KICAgICRrZXkgPSBbTWljcm9zb2Z0LldpbjMyLlJlZ2lzdHJ5XTo6Q3VycmVudFVzZXIuT3BlblN1YktleSgnRW52aXJvbm1lbnQnLCAkdHJ1ZSkKICAgIHRyeSB7CiAgICAgICAgJHJhdyA9IFtzdHJpbmddJGtleS5HZXRWYWx1ZSgnUGF0aCcsICcnLCAnRG9Ob3RFeHBhbmRFbnZpcm9ubWVudE5hbWVzJykKICAgICAgICAkcGFydHMgPSBAKCRyYXcuU3BsaXQoJzsnKSB8IFdoZXJlLU9iamVjdCB7ICRfIC1hbmQgKFtFbnZpcm9ubWVudF06OkV4cGFuZEVudmlyb25tZW50VmFyaWFibGVzKCRfKS5UcmltRW5kKCdcJykgLWluZSAkRGlyLlRyaW1FbmQoJ1wnKSkgfSkKICAgICAgICAka2luZCA9IFtNaWNyb3NvZnQuV2luMzIuUmVnaXN0cnlWYWx1ZUtpbmRdOjpFeHBhbmRTdHJpbmcKICAgICAgICB0cnkgeyAka2luZCA9ICRrZXkuR2V0VmFsdWVLaW5kKCdQYXRoJykgfSBjYXRjaCB7fQogICAgICAgICRrZXkuU2V0VmFsdWUoJ1BhdGgnLCAoJHBhcnRzIC1qb2luICc7JyksICRraW5kKQogICAgfSBmaW5hbGx5IHsgJGtleS5DbG9zZSgpIH0KICAgIHRyeSB7IFtFbnZpcm9ubWVudF06OlNldEVudmlyb25tZW50VmFyaWFibGUoJ05FWFVTX0FJX1BBVEhfVVBEQVRFRCcsICRudWxsLCAnVXNlcicpIH0gY2F0Y2gge30KfQoKZnVuY3Rpb24gR2V0LVNpdGVUZXh0KFtzdHJpbmddJFJlbCkgewogICAgJHRtcCA9IFtJTy5QYXRoXTo6R2V0VGVtcEZpbGVOYW1lKCkKICAgIHRyeSB7CiAgICAgICAgSW52b2tlLVdlYlJlcXVlc3QgLVVyaSAiJEZpbGVzU2l0ZS9pbnN0YWxsL25leHVzLWNvZGUvJFJlbCIgLU91dEZpbGUgJHRtcCAtVXNlQmFzaWNQYXJzaW5nIC1UaW1lb3V0U2VjIDYwIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgJHRleHQgPSBbSU8uRmlsZV06OlJlYWRBbGxUZXh0KCR0bXAsIFtUZXh0LkVuY29kaW5nXTo6VVRGOCkKICAgICAgICBpZiAoLW5vdCAkdGV4dC5UcmltKCkgLW9yICR0ZXh0IC1tYXRjaCAnKD9pcyleXHMqKDwhZG9jdHlwZVxzK2h0bWxcYnw8aHRtbFxiKScpIHsKICAgICAgICAgICAgdGhyb3cgItCh0LXRgNCy0LXRgCDQstC10YDQvdGD0Lsg0YHRgtGA0LDQvdC40YbRgyDQstC80LXRgdGC0L4g0YTQsNC50LvQsCAkUmVsLiDQn9C+0LLRgtC+0YDQuNGC0LUg0YPRgdGC0LDQvdC+0LLQutGDINC/0L7Qt9C20LUuIgogICAgICAgIH0KICAgICAgICByZXR1cm4gJHRleHQKICAgIH0gZmluYWxseSB7IFJlbW92ZS1JdGVtICR0bXAgLUZvcmNlIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIH0KfQoKIyBsb29rICh0aGVtZSwgbG9nbywgYmFsYW5jZSwgL2JhbGFuY2UgL3RvcHVwKSwgdGhlIE5FWFVTIG5hbWUgZm9yIHRoZSBtb2RlbCwgbGVhbi1jb250ZXh0ICsgUG9ueXRhaWwKIyAob2xkIHRvb2wgb3V0cHV0cyBhcmUgbm90IHNlbnQgYWdhaW4sIHRoZSBtb2RlbCByZWFkcyBhbmQgd3JpdGVzIGxlc3M6IGFib3V0IC01NSUgdG9rZW5zKQokTmV4dXNDb2RlRXh0cmFzID0gQCgKICAgIEAoJ25leHVzL3R1aS5qcycsICduZXh1c1x0dWkuanMnKSwKICAgIEAoJ25leHVzLXRoZW1lLmpzb24nLCAndGhlbWVzXG5leHVzLmpzb24nKSwKICAgIEAoJ25leHVzLXNlcnZlci5qcycsICdwbHVnaW5zXG5leHVzLmpzJyksCiAgICBAKCdsZWFuLWNvbnRleHQuanMnLCAncGx1Z2luc1xsZWFuLWNvbnRleHQuanMnKSwKICAgIEAoJ3Bvbnl0YWlsLWxvYWRlci5qcycsICdwbHVnaW5zXHBvbnl0YWlsLmpzJyksCiAgICBAKCdwb255dGFpbC9MSUNFTlNFJywgJ3Bvbnl0YWlsXExJQ0VOU0UnKSwKICAgIEAoJ3Bvbnl0YWlsL2hvb2tzL3Bvbnl0YWlsLWNvbmZpZy5qcycsICdwb255dGFpbFxob29rc1xwb255dGFpbC1jb25maWcuanMnKSwKICAgIEAoJ3Bvbnl0YWlsL2hvb2tzL3Bvbnl0YWlsLWluc3RydWN0aW9ucy5qcycsICdwb255dGFpbFxob29rc1xwb255dGFpbC1pbnN0cnVjdGlvbnMuanMnKSwKICAgIEAoJ3Bvbnl0YWlsL29wZW5jb2RlL2NvbW1hbmQvcG9ueXRhaWwtYXVkaXQubWQnLCAncG9ueXRhaWxcLm9wZW5jb2RlXGNvbW1hbmRccG9ueXRhaWwtYXVkaXQubWQnKSwKICAgIEAoJ3Bvbnl0YWlsL29wZW5jb2RlL2NvbW1hbmQvcG9ueXRhaWwtZGVidC5tZCcsICdwb255dGFpbFwub3BlbmNvZGVcY29tbWFuZFxwb255dGFpbC1kZWJ0Lm1kJyksCiAgICBAKCdwb255dGFpbC9vcGVuY29kZS9jb21tYW5kL3Bvbnl0YWlsLWdhaW4ubWQnLCAncG9ueXRhaWxcLm9wZW5jb2RlXGNvbW1hbmRccG9ueXRhaWwtZ2Fpbi5tZCcpLAogICAgQCgncG9ueXRhaWwvb3BlbmNvZGUvY29tbWFuZC9wb255dGFpbC1oZWxwLm1kJywgJ3Bvbnl0YWlsXC5vcGVuY29kZVxjb21tYW5kXHBvbnl0YWlsLWhlbHAubWQnKSwKICAgIEAoJ3Bvbnl0YWlsL29wZW5jb2RlL2NvbW1hbmQvcG9ueXRhaWwtcmV2aWV3Lm1kJywgJ3Bvbnl0YWlsXC5vcGVuY29kZVxjb21tYW5kXHBvbnl0YWlsLXJldmlldy5tZCcpLAogICAgQCgncG9ueXRhaWwvb3BlbmNvZGUvY29tbWFuZC9wb255dGFpbC5tZCcsICdwb255dGFpbFwub3BlbmNvZGVcY29tbWFuZFxwb255dGFpbC5tZCcpLAogICAgQCgncG9ueXRhaWwvb3BlbmNvZGUvcGx1Z2lucy9wb255dGFpbC1mcm9udG1hdHRlci5janMnLCAncG9ueXRhaWxcLm9wZW5jb2RlXHBsdWdpbnNccG9ueXRhaWwtZnJvbnRtYXR0ZXIuY2pzJyksCiAgICBAKCdwb255dGFpbC9vcGVuY29kZS9wbHVnaW5zL3Bvbnl0YWlsLm1qcycsICdwb255dGFpbFwub3BlbmNvZGVccGx1Z2luc1xwb255dGFpbC5tanMnKSwKICAgIEAoJ3Bvbnl0YWlsL3BhY2thZ2UuanNvbicsICdwb255dGFpbFxwYWNrYWdlLmpzb24nKSwKICAgIEAoJ3Bvbnl0YWlsL3NraWxscy9wb255dGFpbC1hdWRpdC9TS0lMTC5tZCcsICdwb255dGFpbFxza2lsbHNccG9ueXRhaWwtYXVkaXRcU0tJTEwubWQnKSwKICAgIEAoJ3Bvbnl0YWlsL3NraWxscy9wb255dGFpbC1kZWJ0L1NLSUxMLm1kJywgJ3Bvbnl0YWlsXHNraWxsc1xwb255dGFpbC1kZWJ0XFNLSUxMLm1kJyksCiAgICBAKCdwb255dGFpbC9za2lsbHMvcG9ueXRhaWwtZ2Fpbi9TS0lMTC5tZCcsICdwb255dGFpbFxza2lsbHNccG9ueXRhaWwtZ2FpblxTS0lMTC5tZCcpLAogICAgQCgncG9ueXRhaWwvc2tpbGxzL3Bvbnl0YWlsLWhlbHAvU0tJTEwubWQnLCAncG9ueXRhaWxcc2tpbGxzXHBvbnl0YWlsLWhlbHBcU0tJTEwubWQnKSwKICAgIEAoJ3Bvbnl0YWlsL3NraWxscy9wb255dGFpbC1yZXZpZXcvU0tJTEwubWQnLCAncG9ueXRhaWxcc2tpbGxzXHBvbnl0YWlsLXJldmlld1xTS0lMTC5tZCcpLAogICAgQCgncG9ueXRhaWwvc2tpbGxzL3Bvbnl0YWlsL1NLSUxMLm1kJywgJ3Bvbnl0YWlsXHNraWxsc1xwb255dGFpbFxTS0lMTC5tZCcpCikKCmZ1bmN0aW9uIFJlbW92ZS1PbGQoW3N0cmluZ10kUGF0aCkgewogICAgIyBmaWxlcyBvZiBvbGRlciBORVhVUyBDTEkgdmVyc2lvbnM7IGJhY2tlZCB1cCBmaXJzdCwgc28gdGhlIHJvbGxiYWNrIGJyaW5ncyB0aGVtIGJhY2sKICAgIGlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRQYXRoKSkgeyByZXR1cm4gfQogICAgQmFja3VwLU9uY2UgJFBhdGgKICAgIFJlbW92ZS1JdGVtIC1MaXRlcmFsUGF0aCAkUGF0aCAtRm9yY2UgLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKfQoKZnVuY3Rpb24gQXBwbHktTmV4dXNEZXNrdG9wIHsKICAgICRtYW5pZmVzdCA9IEludm9rZS1SZXN0TWV0aG9kIC1VcmkgIiRGaWxlc1NpdGUvaW5zdGFsbC9kZXNrdG9wLXJlbGVhc2UuanNvbiIgLVRpbWVvdXRTZWMgMjAgLUVycm9yQWN0aW9uIFN0b3AKICAgIGlmICgkbWFuaWZlc3QuZmlsZSAtbmUgJ05FWFVTLURlc2t0b3AtU2V0dXAuZXhlJyAtb3IgW3N0cmluZ10kbWFuaWZlc3Quc2hhMjU2IC1ub3RtYXRjaCAnXlthLWZBLUYwLTldezY0fSQnIC1vciBbbG9uZ10kbWFuaWZlc3Quc2l6ZSAtbGUgMCkgewogICAgICAgIHRocm93ICfQodC10YDQstC10YAg0LLQtdGA0L3Rg9C7INC90LXQstC10YDQvdGL0LUg0YHQstC10LTQtdC90LjRjyDQvtCxINGD0YHRgtCw0L3QvtCy0YnQuNC60LUgRGVza3RvcCcKICAgIH0KICAgICRkaXIgPSBKb2luLVBhdGggJE5leHVzRGlyICdkb3dubG9hZHMnCiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoICRkaXIpKSB7IE5ldy1JdGVtIC1JdGVtVHlwZSBEaXJlY3RvcnkgLUZvcmNlIC1QYXRoICRkaXIgfCBPdXQtTnVsbCB9CiAgICAkZGVzdCA9IEpvaW4tUGF0aCAkZGlyICdORVhVUy1EZXNrdG9wLVNldHVwLmV4ZScKICAgICR2YWxpZCA9IChUZXN0LVBhdGggJGRlc3QpIC1hbmQgKEdldC1JdGVtICRkZXN0KS5MZW5ndGggLWVxIFtsb25nXSRtYW5pZmVzdC5zaXplCiAgICBpZiAoJHZhbGlkKSB7ICR2YWxpZCA9IChHZXQtRmlsZUhhc2ggJGRlc3QgLUFsZ29yaXRobSBTSEEyNTYpLkhhc2ggLWVxICRtYW5pZmVzdC5zaGEyNTYgfQogICAgaWYgKC1ub3QgJHZhbGlkKSB7IEdldC1GaWxlV2l0aFByb2dyZXNzICIkRmlsZXNTaXRlL2luc3RhbGwvTkVYVVMtRGVza3RvcC1TZXR1cC5leGUiICRkZXN0ICdORVhVUyBEZXNrdG9wJyB9CiAgICBpZiAoKEdldC1JdGVtICRkZXN0KS5MZW5ndGggLW5lIFtsb25nXSRtYW5pZmVzdC5zaXplIC1vciAoR2V0LUZpbGVIYXNoICRkZXN0IC1BbGdvcml0aG0gU0hBMjU2KS5IYXNoIC1uZSAkbWFuaWZlc3Quc2hhMjU2KSB7CiAgICAgICAgdGhyb3cgJ9Cj0YHRgtCw0L3QvtCy0YnQuNC6IERlc2t0b3Ag0LfQsNCz0YDRg9C20LXQvSDQvdC1INC/0L7Qu9C90L7RgdGC0YzRjiDQuNC70Lgg0L/QvtCy0YDQtdC20LTRkdC9OyDQv9C+0LLRgtC+0YDQuNGC0LUg0YPRgdGC0LDQvdC+0LLQutGDJwogICAgfQogICAgJHN0cmVhbSA9IFtJTy5GaWxlXTo6T3BlblJlYWQoJGRlc3QpCiAgICB0cnkgeyBpZiAoJHN0cmVhbS5SZWFkQnl0ZSgpIC1uZSA3NyAtb3IgJHN0cmVhbS5SZWFkQnl0ZSgpIC1uZSA5MCkgeyB0aHJvdyAn0J3QtdCy0LXRgNC90YvQuSDRhNC+0YDQvNCw0YIg0YPRgdGC0LDQvdC+0LLRidC40LrQsCBEZXNrdG9wJyB9IH0gZmluYWxseSB7ICRzdHJlYW0uQ2xvc2UoKSB9CiAgICBTdGVwICfQntGC0LrRgNGL0LLQsNGOINC80LDRgdGC0LXRgCBORVhVUyBEZXNrdG9wOiDQt9Cw0LLQtdGA0YjQuNGC0LUg0YPRgdGC0LDQvdC+0LLQutGDINCyINC10LPQviDQvtC60L3QtScKICAgICRwcm9jZXNzID0gU3RhcnQtUHJvY2VzcyAtRmlsZVBhdGggJGRlc3QgLVBhc3NUaHJ1IC1XYWl0CiAgICBpZiAoJHByb2Nlc3MuRXhpdENvZGUgLW5vdGluIEAoMCwgMzAxMCkpIHsgdGhyb3cgItCc0LDRgdGC0LXRgCBEZXNrdG9wINC30LDQstC10YDRiNC40LvRgdGPINGBINC60L7QtNC+0LwgJCgkcHJvY2Vzcy5FeGl0Q29kZSkiIH0KICAgICRzY3JpcHQ6TmV4dC5BZGQoJ9Ce0YLQutGA0L7QudGC0LUgTkVYVVMgRGVza3RvcCDQuCDQstGB0YLQsNCy0YzRgtC1INC60LvRjtGHINC/0YDQuCDQv9C10YDQstC+0Lwg0LfQsNC/0YPRgdC60LUuJykKICAgIE9rICfQnNCw0YHRgtC10YAgRGVza3RvcCDQt9Cw0LLQtdGA0YjRkdC9Jwp9CgpmdW5jdGlvbiBBcHBseS1OZXh1c0NvZGUgewogICAgJGVuZ2luZSA9IEluc3RhbGwtTmV4dXNFbmdpbmUKICAgIGlmICgtbm90ICRlbmdpbmUpIHsgdGhyb3cgJ05FWFVTIENMSTog0YPRgdGC0LDQvdC+0LLQutCwINGP0LTRgNCwINC90LUg0LfQsNCy0LXRgNGI0LXQvdCwLicgfQogICAgJGRpciA9IEpvaW4tUGF0aCAkVXNlckhvbWUgJy5uZXh1cy1haVxjb2RlJwogICAgQXBwbHktT3BlbkNvZGVMaWtlIChKb2luLVBhdGggJGRpciAnb3BlbmNvZGUuanNvbicpICdORVhVUyBDTEknCiAgICAjIG9ubHkgdGhlIE5FWFVTIEFJIHByb3ZpZGVyIGluIHRoZSBtb2RlbCBsaXN0OyBubyBzaGFyZSBsaW5rczsgdGhlIGVuZ2luZSBuZXZlciB1cGRhdGVzIGl0c2VsZgogICAgVXBkYXRlLUpzb25GaWxlIChKb2luLVBhdGggJGRpciAnb3BlbmNvZGUuanNvbicpIChbb3JkZXJlZF1AeyBlbmFibGVkX3Byb3ZpZGVycyA9IEAoJ25leHVzJyk7IHNoYXJlID0gJ2Rpc2FibGVkJzsgYXV0b3VwZGF0ZSA9ICRmYWxzZSB9KQogICAgIyBpbnRlcmZhY2Ugc2V0dGluZ3M6IG91ciB0aGVtZSBhbmQgcGx1Z2luOyBvdGhlciBzZXR0aW5ncyB0aGUgdXNlciBjaGFuZ2VkIHN0YXkKICAgICRjbGlQYXRoID0gSm9pbi1QYXRoICRkaXIgJ2NsaS5qc29uJwogICAgJGNsaSA9ICRudWxsCiAgICB0cnkgeyAkY2xpID0gUmVhZC1Kc29uRmlsZSAkY2xpUGF0aCB9IGNhdGNoIHsgJGNsaSA9ICRudWxsIH0KICAgICRwbHVnaW5zID0gTmV3LU9iamVjdCBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0W29iamVjdF0KICAgICRwbHVnaW5zLkFkZCgnLi9uZXh1cycpCiAgICBmb3JlYWNoICgkcCBpbiBAKEdldC1Qcm9wICRjbGkgJ3BsdWdpbnMnKSkgewogICAgICAgIGlmICgkbnVsbCAtZXEgJHApIHsgY29udGludWUgfQogICAgICAgICRpZCA9IGlmICgkcCAtaXMgW3N0cmluZ10pIHsgJHAgfSBlbHNlIHsgW3N0cmluZ10oR2V0LVByb3AgJHAgJ3BhY2thZ2UnKSB9CiAgICAgICAgaWYgKCRpZCAtYW5kICRpZCAtbmUgJy4vbmV4dXMnIC1hbmQgJGlkIC1ub3RsaWtlICcuL3R1aS8qJykgeyAkcGx1Z2lucy5BZGQoJHApIH0KICAgIH0KICAgICRkZWZhdWx0cyA9IENvbnZlcnRGcm9tLUpzb25jIChHZXQtU2l0ZVRleHQgJ2NsaS5qc29uJykKICAgICR2YWx1ZXMgPSBbb3JkZXJlZF1AeyAnJHNjaGVtYScgPSAnaHR0cHM6Ly9vcGVuY29kZS5haS92Mi9jbGkuanNvbic7IHRoZW1lID0gJGRlZmF1bHRzLnRoZW1lOyBwbHVnaW5zID0gJHBsdWdpbnMuVG9BcnJheSgpOyBoaWdobGlnaHRzID0gJGZhbHNlIH0KICAgIGZvcmVhY2ggKCRrIGluIEAoJ3Nlc3Npb24nLCAnY3Vyc29yJywgJ21pbmknLCAnYW5pbWF0aW9ucycpKSB7IGlmICgkbnVsbCAtZXEgKEdldC1Qcm9wICRjbGkgJGspKSB7ICR2YWx1ZXNbJGtdID0gJGRlZmF1bHRzLiRrIH0gfQogICAgaWYgKCRjbGkpIHsgVXBkYXRlLUpzb25GaWxlICRjbGlQYXRoICR2YWx1ZXMgfSBlbHNlIHsgU2F2ZS1UZXh0ICRjbGlQYXRoICgoQ29udmVydFRvLU5leHVzSnNvbiAkdmFsdWVzIDAgJyAgJyAiYG4iKSArICJgbiIpIH0KICAgICMgYSBwcml2YXRlIHNlcnZlciBwZXIgd2luZG93OiBuZXZlciBtaXhlcyB3aXRoIGFub3RoZXIgY29weSBvZiB0aGUgZW5naW5lIHJ1bm5pbmcgb24gdGhpcyBjb21wdXRlcgogICAgJHN2Y1BhdGggPSBKb2luLVBhdGggJGRpciAnc2VydmljZS5qc29uJwogICAgaWYgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJHN2Y1BhdGgpIHsgVXBkYXRlLUpzb25GaWxlICRzdmNQYXRoIChbb3JkZXJlZF1AeyBkaXNhYmxlZCA9ICR0cnVlIH0pIH0gZWxzZSB7IFNhdmUtVGV4dCAkc3ZjUGF0aCAie2BuICBgImRpc2FibGVkYCI6IHRydWVgbn1gbiIgfQogICAgIyBhbiBlbXB0eSBtb2RlbCBjYXRhbG9ndWU6IHRoZSBtb2RlbHMgY29tZSBmcm9tIG9wZW5jb2RlLmpzb24gKHNlZSBuZXh1cy1jb2RlLmNtZCkKICAgIGlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoIChKb2luLVBhdGggJGRpciAnbW9kZWxzLmpzb24nKSkpIHsgU2F2ZS1UZXh0IChKb2luLVBhdGggJGRpciAnbW9kZWxzLmpzb24nKSAie31gbiIgfQogICAgU2F2ZS1UZXh0IChKb2luLVBhdGggJGRpciAnbmV4dXMtY29kZS10b29scy5wczEnKSAoR2V0LVNpdGVUZXh0ICduZXh1cy1jb2RlLXRvb2xzLnBzMScpCiAgICBmb3JlYWNoICgkZiBpbiAkTmV4dXNDb2RlRXh0cmFzKSB7IFNhdmUtVGV4dCAoSm9pbi1QYXRoICRkaXIgJGZbMV0pIChHZXQtU2l0ZVRleHQgJGZbMF0pIH0KICAgICMgTkVYVVMgQ0xJIDEueCBmaWxlcyAodGVybWluYWwgVUkgb2YgdGhlIG9sZCBlbmdpbmUpCiAgICBmb3JlYWNoICgkb2xkIGluIEAoJ3R1aS5qc29uJywgJ3R1aVxuZXh1cy1sb2dvLmpzJywgJ3BsdWdpbnNcbmV4dXMtYmFsYW5jZS5qcycpKSB7IFJlbW92ZS1PbGQgKEpvaW4tUGF0aCAkZGlyICRvbGQpIH0KICAgIE9rICdORVhVUyBDTEk6INGN0LrQvtC90L7QvNC40Y8g0YLQvtC60LXQvdC+0LIg0LLQutC70Y7Rh9C10L3QsCAobGVhbi1jb250ZXh0ICsgUG9ueXRhaWw7INCy0YvQutC70Y7Rh9C40YLRjCBQb255dGFpbDogL3Bvbnl0YWlsIG9mZiknCgogICAgIyBjb21tYW5kczogfi8ubmV4dXMtYWkvYmluIG9uIFBBVEg7IGxhdW5jaGVycyBvZiBvbGRlciB2ZXJzaW9ucyAobmV4dCB0byBvcGVuY29kZS5jbWQpIGFyZSB1cGRhdGVkIHRvbwogICAgJGJpbiA9IEpvaW4tUGF0aCAkVXNlckhvbWUgJy5uZXh1cy1haVxiaW4nCiAgICAkdGV4dCA9IChHZXQtU2l0ZVRleHQgJ25leHVzLWNvZGUuY21kJykgLXJlcGxhY2UgIig/PCFccilcbiIsICJgcmBuIgogICAgU2F2ZS1UZXh0IChKb2luLVBhdGggJGJpbiAnbmV4dXMtY29kZS5jbWQnKSAkdGV4dAogICAgU2F2ZS1UZXh0IChKb2luLVBhdGggJGJpbiAnbmV4dXMuY21kJykgJHRleHQKICAgIEFkZC1Vc2VyUGF0aCAkYmluCiAgICAkY21kID0gJ25leHVzJwogICAgZm9yZWFjaCAoJG5hbWUgaW4gQCgnbmV4dXMuY21kJywgJ25leHVzLWNvZGUuY21kJykpIHsKICAgICAgICAkZm91bmQgPSBGaW5kLUNsaSBAKCRuYW1lKQogICAgICAgIGlmICgtbm90ICRmb3VuZCAtb3IgKFNwbGl0LVBhdGggJGZvdW5kIC1QYXJlbnQpLlRyaW1FbmQoJ1wnKSAtaWVxICRiaW4uVHJpbUVuZCgnXCcpKSB7IGNvbnRpbnVlIH0KICAgICAgICBpZiAoW0lPLkZpbGVdOjpSZWFkQWxsVGV4dCgkZm91bmQpIC1tYXRjaCAnTkVYVVMnKSB7IFNhdmUtVGV4dCAkZm91bmQgJHRleHQgfQogICAgICAgIGVsc2VpZiAoJG5hbWUgLWVxICduZXh1cy5jbWQnKSB7ICRjbWQgPSAnbmV4dXMtY29kZSc7IFdhcm4gIk5FWFVTIENMSTog0LrQvtC80LDQvdC00LAgbmV4dXMg0LfQsNC90Y/RgtCwINC00YDRg9Cz0L7QuSDQv9GA0L7Qs9GA0LDQvNC80L7QuSAoJGZvdW5kKSAtINC40YHQv9C+0LvRjNC30YPQudGC0LUgbmV4dXMtY29kZSIgfQogICAgfQogICAgT2sgIk5FWFVTIENMSSDRg9GB0YLQsNC90L7QstC70LXQvTog0LrQvtC80LDQvdC00LAgJGNtZCIKICAgICRzY3JpcHQ6TmV4dC5BZGQoIk5FWFVTIENMSTog0L7RgtC60YDQvtC50YLQtSDQndCe0JLQntCVINC+0LrQvdC+INGC0LXRgNC80LjQvdCw0LvQsCDQsiDQv9Cw0L/QutC1INC/0YDQvtC10LrRgtCwIC0+ICRjbWQgICAo0LHQsNC70LDQvdGBOiAkY21kIGJhbGFuY2UsINC00L7QutGD0L/QuNGC0Yw6ICRjbWQgdG9wdXAsINC+0LHQvdC+0LLQuNGC0Yw6ICRjbWQgdXBkYXRlKSIpCn0KCiMgTkVYVVMgQUkgZm9yIFJvYmxveCBTdHVkaW86IHRoZSBwbHVnaW4gZmlsZSAocm9ibG94LXBsdWdpbi9idWlsZC5weSkgd2l0aCB0aGUga2V5IGFuZCBzZXJ2ZXIgZmlsbGVkIGluLAojIHB1dCBpbnRvIFN0dWRpbydzIGxvY2FsIHBsdWdpbnMgZm9sZGVyLiBTdHVkaW8gbG9hZHMgaXQgb24gdGhlIG5leHQgc3RhcnQuCmZ1bmN0aW9uIEdldC1Sb2Jsb3hQbHVnaW5zRGlyIHsgcmV0dXJuIChKb2luLVBhdGggJExvY2FsQXBwICdSb2Jsb3hcUGx1Z2lucycpIH0KZnVuY3Rpb24gVGVzdC1Sb2Jsb3hTdHVkaW8gewogICAgJHZlcnNpb25zID0gSm9pbi1QYXRoICRMb2NhbEFwcCAnUm9ibG94XFZlcnNpb25zJwogICAgaWYgKFRlc3QtUGF0aCAoR2V0LVJvYmxveFBsdWdpbnNEaXIpKSB7IHJldHVybiAkdHJ1ZSB9CiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoICR2ZXJzaW9ucykpIHsgcmV0dXJuICRmYWxzZSB9CiAgICByZXR1cm4gW2Jvb2xdKEdldC1DaGlsZEl0ZW0gLUxpdGVyYWxQYXRoICR2ZXJzaW9ucyAtRGlyZWN0b3J5IC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIHwgV2hlcmUtT2JqZWN0IHsgVGVzdC1QYXRoIChKb2luLVBhdGggJF8uRnVsbE5hbWUgJ1JvYmxveFN0dWRpb0JldGEuZXhlJykgfSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEpCn0KZnVuY3Rpb24gQXBwbHktUm9ibG94IHsKICAgICR0bXAgPSBbSU8uUGF0aF06OkdldFRlbXBGaWxlTmFtZSgpCiAgICB0cnkgewogICAgICAgIEludm9rZS1XZWJSZXF1ZXN0IC1VcmkgIiRGaWxlc1NpdGUvaW5zdGFsbC9yb2Jsb3gvTkVYVVMtQUkucmJ4bXgiIC1PdXRGaWxlICR0bXAgLVVzZUJhc2ljUGFyc2luZyAtVGltZW91dFNlYyA2MCAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgICR4bWwgPSBbSU8uRmlsZV06OlJlYWRBbGxUZXh0KCR0bXAsIFtUZXh0LkVuY29kaW5nXTo6VVRGOCkKICAgIH0gZmluYWxseSB7IFJlbW92ZS1JdGVtICR0bXAgLUZvcmNlIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIH0KICAgIGlmICgkeG1sIC1ub3RtYXRjaCAnKD9zKV5ccyo8cm9ibG94XGIuKjwvcm9ibG94PlxzKiQnKSB7IHRocm93ICfQodC10YDQstC10YAg0LLQtdGA0L3Rg9C7INC90LXQstC10YDQvdGL0Lkg0YTQsNC50Lsg0L/Qu9Cw0LPQuNC90LAgUm9ibG94LiDQn9C+0LLRgtC+0YDQuNGC0LUg0YPRgdGC0LDQvdC+0LLQutGDINC/0L7Qt9C20LUuJyB9CiAgICAkbnVsbCA9IFt4bWxdJHhtbAogICAgJGVzYyA9IHsgcGFyYW0oJHMpIChbc3RyaW5nXSRzKS5SZXBsYWNlKCcmJywgJyZhbXA7JykuUmVwbGFjZSgnPCcsICcmbHQ7JykuUmVwbGFjZSgnPicsICcmZ3Q7JykuUmVwbGFjZSgnIicsICcmcXVvdDsnKSB9CiAgICAkeG1sID0gJHhtbC5SZXBsYWNlKCdfX05FWFVTX0tFWV9fJywgKCYgJGVzYyAkc2NyaXB0OktleSkpLlJlcGxhY2UoJ19fTkVYVVNfU0lURV9fJywgKCYgJGVzYyAkU2l0ZSkpCiAgICBTYXZlLVRleHQgKEpvaW4tUGF0aCAoR2V0LVJvYmxveFBsdWdpbnNEaXIpICdORVhVUy1BSS5yYnhteCcpICR4bWwKICAgIE9rICdSb2Jsb3ggU3R1ZGlvOiDQv9C70LDQs9C40L0gTkVYVVMgQUkg0YPRgdGC0LDQvdC+0LLQu9C10L0gKNC60LvRjtGHINGD0LbQtSDQstC90YPRgtGA0LgpJwogICAgJHNjcmlwdDpOZXh0LkFkZCgnUm9ibG94IFN0dWRpbzog0L/QtdGA0LXQt9Cw0L/Rg9GB0YLQuNGC0LUgU3R1ZGlvIC0+INCy0LrQu9Cw0LTQutCwIFBsdWdpbnMgLT4g0LrQvdC+0L/QutCwIE5FWFVTIEFJLiDQndCwINC/0LXRgNCy0YvQuSDQt9Cw0L/RgNC+0YEgU3R1ZGlvINGB0L/RgNC+0YHQuNGCINC00L7RgdGC0YPQvyDQuiDRgdCw0LnRgtGDINC4INC6INGB0LrRgNC40L/RgtCw0LwgLSDRgNCw0LfRgNC10YjQuNGC0LUuJykKfQoKIyBBaWRlcjogfi8uYWlkZXIuY29uZi55bWwgaG9sZHMgcGxhaW4gImtleTogdmFsdWUiIGxpbmVzOyBvbmx5IG91ciB0aHJlZSBrZXlzIGFyZSBzZXQsIGV2ZXJ5dGhpbmcgZWxzZSBzdGF5cy4KZnVuY3Rpb24gQXBwbHktQWlkZXIgewogICAgJHBhdGggPSBKb2luLVBhdGggJFVzZXJIb21lICcuYWlkZXIuY29uZi55bWwnCiAgICAkdmFsdWVzID0gW29yZGVyZWRdQHsgJ29wZW5haS1hcGktYmFzZScgPSAkT3BlbkFpQmFzZTsgJ29wZW5haS1hcGkta2V5JyA9ICRzY3JpcHQ6S2V5OyAnbW9kZWwnID0gIm9wZW5haS8kKCRzY3JpcHQ6TW9kZWwpIiB9CiAgICAkbGluZXMgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgaWYgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJHBhdGgpIHsgZm9yZWFjaCAoJGwgaW4gW0lPLkZpbGVdOjpSZWFkQWxsTGluZXMoJHBhdGgpKSB7ICRsaW5lcy5BZGQoJGwpIH0gfQogICAgZm9yZWFjaCAoJGsgaW4gJHZhbHVlcy5LZXlzKSB7CiAgICAgICAgJHYgPSAkayArICc6ICInICsgKFtzdHJpbmddJHZhbHVlc1ska10pLlJlcGxhY2UoJyInLCAnXCInKSArICciJwogICAgICAgICRpID0gLTEKICAgICAgICBmb3IgKCRqID0gMDsgJGogLWx0ICRsaW5lcy5Db3VudDsgJGorKykgeyBpZiAoJGxpbmVzWyRqXSAtbWF0Y2ggKCdeJyArIFtyZWdleF06OkVzY2FwZSgkaykgKyAnXHMqOicpKSB7ICRpID0gJGo7IGJyZWFrIH0gfQogICAgICAgIGlmICgkaSAtZ2UgMCkgeyAkbGluZXNbJGldID0gJHYgfSBlbHNlIHsgJGxpbmVzLkFkZCgkdikgfQogICAgfQogICAgU2F2ZS1UZXh0ICRwYXRoICgoJGxpbmVzIC1qb2luICJgbiIpICsgImBuIikKICAgIE9rICJBaWRlciDQv9C+0LTQutC70Y7Rh9GR0L0gKNC80L7QtNC10LvRjCBvcGVuYWkvJCgkc2NyaXB0Ok1vZGVsKSkiCiAgICAkc2NyaXB0Ok5leHQuQWRkKCdBaWRlcjog0LIg0L/QsNC/0LrQtSDQv9GA0L7QtdC60YLQsCAtPiBhaWRlciAo0LzQvtC00LXQu9GMINC40LcgTkVYVVMgQUkg0YPQttC1INCy0YvQsdGA0LDQvdCwOyDQtNGA0YPQs9Cw0Y86IGFpZGVyIC0tbW9kZWwgb3BlbmFpLzxpZD4pJykKfQoKIyBDb250aW51ZSAoVlMgQ29kZSAvIEpldEJyYWlucyk6IH4vLmNvbnRpbnVlL2NvbmZpZy55YW1sLiBBIGZpbGUgdGhlIHVzZXIgd3JvdGUgaXMgbmV2ZXIgdG91Y2hlZDsgb3VycyBjYXJyaWVzIGEgbWFya2VyLgokQ29udGludWVNYXJrID0gJyMgTkVYVVMgQUkgLSB3cml0dGVuIGJ5IHRoZSBORVhVUyBBSSBpbnN0YWxsZXI7IHJ1biBpdCBhZ2FpbiB0byB1cGRhdGUuIERlbGV0ZSB0aGlzIGxpbmUgdG8ga2VlcCB5b3VyIG93biBlZGl0cy4nCmZ1bmN0aW9uIEFwcGx5LUNvbnRpbnVlIHsKICAgICRwYXRoID0gSm9pbi1QYXRoICRVc2VySG9tZSAnLmNvbnRpbnVlXGNvbmZpZy55YW1sJwogICAgaWYgKChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRwYXRoKSAtYW5kIC1ub3QgKFtJTy5GaWxlXTo6UmVhZEFsbFRleHQoJHBhdGgpLlN0YXJ0c1dpdGgoJENvbnRpbnVlTWFyaykpKSB7CiAgICAgICAgV2FybiAnQ29udGludWU6INGDINCy0LDRgSDRgdCy0L7QuSBjb25maWcueWFtbCAtINC10LPQviDQvdC1INGC0YDQvtCz0LDRji4g0JTQvtCx0LDQstGM0YLQtSDQvNC+0LTQtdC70Ywg0LLRgNGD0YfQvdGD0Y46IHByb3ZpZGVyIG9wZW5haSwgYXBpQmFzZSAnICsgJE9wZW5BaUJhc2UKICAgICAgICAkc2NyaXB0Ok5vdGVzLkFkZCgiQ29udGludWU6INGB0LLQvtC5IGNvbmZpZy55YW1sINC90LUg0LjQt9C80LXQvdGR0L0uINCc0L7QtNC10LvRjCBORVhVUyBBSTogcHJvdmlkZXI6IG9wZW5haSwgYXBpQmFzZTogJE9wZW5BaUJhc2UsIGFwaUtleTog0LLQsNGIINC60LvRjtGHIikKICAgICAgICByZXR1cm4KICAgIH0KICAgICRxID0geyBwYXJhbSgkdCkgJyInICsgKFtzdHJpbmddJHQpLlJlcGxhY2UoJ1wnLCAnXFwnKS5SZXBsYWNlKCciJywgJ1wiJykgKyAnIicgfQogICAgJG91dCA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtzdHJpbmddCiAgICAkb3V0LkFkZCgkQ29udGludWVNYXJrKTsgJG91dC5BZGQoJ25hbWU6IE5FWFVTIEFJJyk7ICRvdXQuQWRkKCd2ZXJzaW9uOiAxLjAuMCcpOyAkb3V0LkFkZCgnc2NoZW1hOiB2MScpOyAkb3V0LkFkZCgnbW9kZWxzOicpCiAgICBmb3JlYWNoICgkaWQgaW4gKEdldC1Nb2RlbEVudHJpZXMpKSB7CiAgICAgICAgJG91dC5BZGQoJyAgLSBuYW1lOiAnICsgKCYgJHEgKEdldC1Nb2RlbExhYmVsICRpZCkpKQogICAgICAgICRvdXQuQWRkKCcgICAgcHJvdmlkZXI6IG9wZW5haScpCiAgICAgICAgJG91dC5BZGQoJyAgICBtb2RlbDogJyArICgmICRxICRpZCkpCiAgICAgICAgJG91dC5BZGQoJyAgICBhcGlCYXNlOiAnICsgKCYgJHEgJE9wZW5BaUJhc2UpKQogICAgICAgICRvdXQuQWRkKCcgICAgYXBpS2V5OiAnICsgKCYgJHEgJHNjcmlwdDpLZXkpKQogICAgICAgICRvdXQuQWRkKCcgICAgcm9sZXM6IFtjaGF0LCBlZGl0LCBhcHBseV0nKQogICAgICAgICRvdXQuQWRkKCcgICAgY2FwYWJpbGl0aWVzOiBbdG9vbF91c2VdJykKICAgICAgICAkb3V0LkFkZCgnICAgIGRlZmF1bHRDb21wbGV0aW9uT3B0aW9uczonKQogICAgICAgICRvdXQuQWRkKCcgICAgICBjb250ZXh0TGVuZ3RoOiAnICsgKEdldC1Nb2RlbENvbnRleHQgJGlkKSkKICAgIH0KICAgIFNhdmUtVGV4dCAkcGF0aCAoKCRvdXQgLWpvaW4gImBuIikgKyAiYG4iKQogICAgT2sgIkNvbnRpbnVlINC/0L7QtNC60LvRjtGH0ZHQvSAo0LzQvtC00LXQu9C4IE5FWFVTIEFJLCDQvtGB0L3QvtCy0L3QsNGPICQoJHNjcmlwdDpNb2RlbCkpIgogICAgJHNjcmlwdDpOZXh0LkFkZCgnQ29udGludWU6INC/0LXRgNC10LfQsNC/0YPRgdGC0LjRgtC1INGA0LXQtNCw0LrRgtC+0YAgLT4g0L/QsNC90LXQu9GMIENvbnRpbnVlIC0+INC80L7QtNC10LvQuCBORVhVUyBBSSDRg9C20LUg0LIg0YHQv9C40YHQutC1JykKfQoKZnVuY3Rpb24gQXBwbHktS2lsbyB7CiAgICBBcHBseS1PcGVuQ29kZUxpa2UgKEpvaW4tUGF0aCAkVXNlckhvbWUgJy5jb25maWdca2lsb1xraWxvLmpzb24nKSAnS2lsbyBDb2RlJwogICAgJHNjcmlwdDpOZXh0LkFkZCgnS2lsbyBDb2RlOiDQv9C10YDQtdC30LDQv9GD0YHRgtC40YLQtSDRgNC10LTQsNC60YLQvtGAIC0g0LIg0YHQv9C40YHQutC1INC80L7QtNC10LvQtdC5INC/0L7Rj9Cy0LjRgtGB0Y8g0L/RgNC+0LLQsNC50LTQtdGAIE5FWFVTX0FJJykKfQoKZnVuY3Rpb24gSW5zdGFsbC1OcG1Ub29sKFtzdHJpbmddJFBhY2thZ2UsIFtzdHJpbmddJFRpdGxlKSB7CiAgICAkbnBtID0gRmluZC1DbGkgQCgnbnBtLmNtZCcpCiAgICBpZiAoLW5vdCAkbnBtKSB7IEZhaWwgIiR7VGl0bGV9OiDQvdGD0LbQtdC9IE5vZGUuanMgLSDRgdC90LDRh9Cw0LvQsCDRg9GB0YLQsNC90L7QstC40YLQtSDQtdCz0L4gKNC/0YPQvdC60YIgTm9kZS5qcyDQsiDQvNC10L3RjikiOyByZXR1cm4gJGZhbHNlIH0KICAgIGlmICgkVGVzdFJvb3QpIHsgcmV0dXJuICR0cnVlIH0KICAgIFN0ZXAgIiR7VGl0bGV9OiDRg9GB0YLQsNC90LDQstC70LjQstCw0Y4gJFBhY2thZ2UgKNC00L4gMiDQvNC40L3Rg9GCKS4uLiIKICAgICRyID0gSW52b2tlLVRvb2wgJG5wbSBAKCdpbnN0YWxsJywgJy1nJywgJFBhY2thZ2UpIDQyMAogICAgaWYgKCRyLkNvZGUgLW5lIDApIHsgRmFpbCAiJHtUaXRsZX06IG5wbSDQvdC1INGB0LzQvtCzINGD0YHRgtCw0L3QvtCy0LjRgtGMINC/0LDQutC10YIuICQoJHIuVGV4dC5TcGxpdCgiYG4iKSB8IFNlbGVjdC1PYmplY3QgLUxhc3QgMykiOyByZXR1cm4gJGZhbHNlIH0KICAgIE9rICIkVGl0bGUg0YPRgdGC0LDQvdC+0LLQu9C10L0iCiAgICByZXR1cm4gJHRydWUKfQoKZnVuY3Rpb24gSW5zdGFsbC1XaW5nZXQoW3N0cmluZ10kSWQsIFtzdHJpbmddJFRpdGxlKSB7CiAgICBpZiAoJFRlc3RSb290KSB7IHJldHVybiAkdHJ1ZSB9CiAgICAkd2cgPSBGaW5kLUNsaSBAKCd3aW5nZXQuZXhlJykKICAgIGlmICgtbm90ICR3ZykgeyBGYWlsICIke1RpdGxlfTog0L3QtSDQvdCw0LnQtNC10L0gd2luZ2V0LiDQodC60LDRh9Cw0LnRgtC1INCy0YDRg9GH0L3Rg9GOOiAkKGlmICgkSWQgLWxpa2UgJypOb2RlKicpIHsgJ2h0dHBzOi8vbm9kZWpzLm9yZycgfSBlbHNlIHsgJ2h0dHBzOi8vZ2l0LXNjbS5jb20vZG93bmxvYWQvd2luJyB9KSI7IHJldHVybiAkZmFsc2UgfQogICAgU3RlcCAiJHtUaXRsZX06INGD0YHRgtCw0L3QsNCy0LvQuNCy0LDRjiDRh9C10YDQtdC3IHdpbmdldCAoV2luZG93cyDQvNC+0LbQtdGCINC/0L7Qv9GA0L7RgdC40YLRjCDQv9C+0LTRgtCy0LXRgNC20LTQtdC90LjQtSkuLi4iCiAgICAkciA9IEludm9rZS1Ub29sICR3ZyBAKCdpbnN0YWxsJywgJy1lJywgJy0taWQnLCAkSWQsICctLXNpbGVudCcsICctLWFjY2VwdC1zb3VyY2UtYWdyZWVtZW50cycsICctLWFjY2VwdC1wYWNrYWdlLWFncmVlbWVudHMnKSA5MDAKICAgIFJlZnJlc2gtUGF0aAogICAgaWYgKCRyLkNvZGUgLW5lIDAgLWFuZCAkci5UZXh0IC1ub3RtYXRjaCAnYWxyZWFkeSBpbnN0YWxsZWR80YPQttC1INGD0YHRgtCw0L3QvtCyJykgeyBGYWlsICIke1RpdGxlfTogd2luZ2V0INCy0LXRgNC90YPQuyDQvtGI0LjQsdC60YMgKCQoJHIuQ29kZSkpLiI7IHJldHVybiAkZmFsc2UgfQogICAgT2sgIiRUaXRsZSDRg9GB0YLQsNC90L7QstC70LXQvSIKICAgIHJldHVybiAkdHJ1ZQp9CgpmdW5jdGlvbiBHZXQtRWZmZWN0aXZlUG9saWN5IHsKICAgIHRyeSB7CiAgICAgICAgJGxpc3QgPSBHZXQtRXhlY3V0aW9uUG9saWN5IC1MaXN0CiAgICAgICAgZm9yZWFjaCAoJHNjb3BlIGluIEAoJ01hY2hpbmVQb2xpY3knLCAnVXNlclBvbGljeScsICdDdXJyZW50VXNlcicsICdMb2NhbE1hY2hpbmUnKSkgewogICAgICAgICAgICAkcCA9ICgkbGlzdCB8IFdoZXJlLU9iamVjdCB7IFtzdHJpbmddJF8uU2NvcGUgLWVxICRzY29wZSB9KS5FeGVjdXRpb25Qb2xpY3kKICAgICAgICAgICAgaWYgKCRwIC1hbmQgW3N0cmluZ10kcCAtbmUgJ1VuZGVmaW5lZCcpIHsgcmV0dXJuIEB7IFNjb3BlID0gJHNjb3BlOyBQb2xpY3kgPSBbc3RyaW5nXSRwIH0gfQogICAgICAgIH0KICAgIH0gY2F0Y2gge30KICAgIHJldHVybiBAeyBTY29wZSA9ICdEZWZhdWx0JzsgUG9saWN5ID0gJ1Jlc3RyaWN0ZWQnIH0KfQoKZnVuY3Rpb24gQXBwbHktRXhlY3V0aW9uUG9saWN5IHsKICAgIGlmICgkVGVzdFJvb3QpIHsgT2sgJ1Bvd2VyU2hlbGw6INC30LDQv9GD0YHQuiBucG0t0LrQvtC80LDQvdC0INGA0LDQt9GA0LXRiNGR0L0gKNGC0LXRgdGCKSc7IHJldHVybiB9CiAgICB0cnkgewogICAgICAgIFNldC1FeGVjdXRpb25Qb2xpY3kgLVNjb3BlIEN1cnJlbnRVc2VyIC1FeGVjdXRpb25Qb2xpY3kgUmVtb3RlU2lnbmVkIC1Gb3JjZSAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgIE9rICdQb3dlclNoZWxsOiDRgNCw0LfRgNC10YjRkdC9INC30LDQv9GD0YHQuiBjbGF1ZGUgLyBjb2RleCDQuCDQtNGA0YPQs9C40YUgbnBtLdC60L7QvNCw0L3QtCAoUmVtb3RlU2lnbmVkINC00LvRjyDQstCw0YjQtdCz0L4g0L/QvtC70YzQt9C+0LLQsNGC0LXQu9GPKScKICAgIH0gY2F0Y2ggeyBXYXJuICJQb3dlclNoZWxsOiDQvdC1INGD0LTQsNC70L7RgdGMINC40LfQvNC10L3QuNGC0Ywg0L/QvtC70LjRgtC40LrRgyDQt9Cw0L/Rg9GB0LrQsCAoJCgkXy5FeGNlcHRpb24uTWVzc2FnZSkpIiB9Cn0KCmZ1bmN0aW9uIENsZWFyLUtleVRyYWNlcyB7CiAgICBpZiAoJFRlc3RSb290KSB7IHJldHVybiB9CiAgICB0cnkgewogICAgICAgICRtcnUgPSAnSEtDVTpcU29mdHdhcmVcTWljcm9zb2Z0XFdpbmRvd3NcQ3VycmVudFZlcnNpb25cRXhwbG9yZXJcUnVuTVJVJwogICAgICAgIGlmIChUZXN0LVBhdGggJG1ydSkgewogICAgICAgICAgICAkcHJvcHMgPSBHZXQtSXRlbVByb3BlcnR5ICRtcnUKICAgICAgICAgICAgJG9yZGVyID0gW3N0cmluZ10kcHJvcHMuTVJVTGlzdAogICAgICAgICAgICBmb3JlYWNoICgkcCBpbiAkcHJvcHMuUFNPYmplY3QuUHJvcGVydGllcykgewogICAgICAgICAgICAgICAgaWYgKCRwLk5hbWUuTGVuZ3RoIC1lcSAxIC1hbmQgW3N0cmluZ10kcC5WYWx1ZSAtbGlrZSAnKk5FWFVTX0tFWSonKSB7CiAgICAgICAgICAgICAgICAgICAgUmVtb3ZlLUl0ZW1Qcm9wZXJ0eSAtUGF0aCAkbXJ1IC1OYW1lICRwLk5hbWUgLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKICAgICAgICAgICAgICAgICAgICAkb3JkZXIgPSAkb3JkZXIuUmVwbGFjZSgkcC5OYW1lLCAnJykKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICBTZXQtSXRlbVByb3BlcnR5IC1QYXRoICRtcnUgLU5hbWUgTVJVTGlzdCAtVmFsdWUgJG9yZGVyIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlCiAgICAgICAgfQogICAgfSBjYXRjaCB7fQogICAgdHJ5IHsKICAgICAgICAkaGlzdCA9IEpvaW4tUGF0aCAkQXBwRGF0YSAnTWljcm9zb2Z0XFdpbmRvd3NcUG93ZXJTaGVsbFxQU1JlYWRMaW5lXENvbnNvbGVIb3N0X2hpc3RvcnkudHh0JwogICAgICAgIGlmIChUZXN0LVBhdGggJGhpc3QpIHsKICAgICAgICAgICAgIyByZWFkIGFuZCB3cml0dGVuIGFzIFVURi04IHdpdGhvdXQgQk9NIChQU1JlYWRMaW5lJ3MgZm9ybWF0KTogR2V0LUNvbnRlbnQgYWxvbmUgcmVhZHMgQU5TSSBhbmQgYnJva2UgQ3lyaWxsaWMgbGluZXMKICAgICAgICAgICAgJGFsbCA9IFtJTy5GaWxlXTo6UmVhZEFsbExpbmVzKCRoaXN0LCBbVGV4dC5FbmNvZGluZ106OlVURjgpCiAgICAgICAgICAgICRrZWVwID0gQCgkYWxsIHwgV2hlcmUtT2JqZWN0IHsgJF8gLW5vdG1hdGNoICdORVhVU19LRVknIH0pCiAgICAgICAgICAgIGlmICgka2VlcC5Db3VudCAtbmUgJGFsbC5Db3VudCkgeyBbSU8uRmlsZV06OldyaXRlQWxsTGluZXMoJGhpc3QsIFtzdHJpbmdbXV0ka2VlcCwgJFV0ZjhOb0JvbSkgfQogICAgICAgIH0KICAgIH0gY2F0Y2gge30KfQoKIyDilIDilIAg0LPQu9Cw0LLQvdGL0Lkg0YHRhtC10L3QsNGA0LjQuSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCmZ1bmN0aW9uIFNob3ctQmFubmVyIHsKICAgIFNheSAnJwogICAgU2F5ICcgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0nIERhcmtZZWxsb3cKICAgIFNheSAnICAgICBORVhVU19BSSAgLSAg0L/QvtC00LrQu9GO0YfQtdC90LjQtSDQv9GA0L7Qs9GA0LDQvNC8INCyIDEg0LrQu9C40LonIFdoaXRlCiAgICBTYXkgJyAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PScgRGFya1llbGxvdwogICAgU2F5ICIgICAgINCo0LvRjtC3OiAkU2l0ZSIgRGFya0dyYXkKICAgIFNheSAnJwp9CgpmdW5jdGlvbiBSZWFkLVZhbGlkS2V5IHsKICAgICRrID0gW3N0cmluZ10kZW52Ok5FWFVTX0tFWQogICAgJGsgPSAkay5UcmltKCkKICAgIGZvciAoJHRyeSA9IDA7ICR0cnkgLWx0IDQ7ICR0cnkrKykgewogICAgICAgIGlmICgtbm90ICRrKSB7CiAgICAgICAgICAgIGlmICgkQXV0bykgeyBGYWlsICfQmtC70Y7RhyDQvdC1INC/0LXRgNC10LTQsNC9IChORVhVU19LRVkpLic7IHJldHVybiAkbnVsbCB9CiAgICAgICAgICAgICRrID0gKFJlYWQtSG9zdCAnICDQktGB0YLQsNCy0YzRgtC1INCy0LDRiCDQutC70Y7RhyBzay0uLi4gKNC/0YDQsNCy0YvQuSDQutC70LjQuiDQuNC70LggQ3RybCtWKSDQuCDQvdCw0LbQvNC40YLQtSBFbnRlcicpLlRyaW0oKQogICAgICAgIH0KICAgICAgICBpZiAoJGsgLW5vdG1hdGNoICdec2stW0EtWmEtejAtOV9cLV17MTYsfSQnKSB7IEZhaWwgJ9Ct0YLQviDQvdC1INC/0L7RhdC+0LbQtSDQvdCwINC60LvRjtGHIE5FWFVTX0FJICjQvtC9INC90LDRh9C40L3QsNC10YLRgdGPINGBIHNrLSkuJzsgJGsgPSAnJzsgY29udGludWUgfQogICAgICAgICRzdGF0ZSA9IFRlc3QtS2V5ICRrCiAgICAgICAgaWYgKCRzdGF0ZSAtZXEgJ2V4cGlyZWQnKSB7IEZhaWwgJ9Ch0YDQvtC6INC00LXQudGB0YLQstC40Y8g0LrQu9GO0YfQsCDQuNGB0YLRkdC6LiDQmtGD0L/QuNGC0LUg0L/QsNC60LXRgiDQuNC70Lgg0L/QvtC00L/QuNGB0LrRgyDQvdCwIEZ1blBheSDRgSDRgtC+0LPQviDQttC1INCw0LrQutCw0YPQvdGC0LAgLSDRgtC+0LrQtdC90Ysg0L/RgNC40LTRg9GCINC90LAg0Y3RgtC+0YIg0LbQtSDQutC70Y7Rhy4nOyAkayA9ICcnOyBpZiAoJEF1dG8pIHsgcmV0dXJuICRudWxsIH07IGNvbnRpbnVlIH0KICAgICAgICBpZiAoJHN0YXRlIC1lcSAnYmFkJykgeyBGYWlsICfQmtC70Y7RhyDQvdC1INC90LDQudC00LXQvSDQuNC70Lgg0L7RgtC60LvRjtGH0ZHQvS4g0J/RgNC+0LLQtdGA0YzRgtC1LCDRh9GC0L4g0YHQutC+0L/QuNGA0L7QstCw0LvQuCDQtdCz0L4g0YbQtdC70LjQutC+0LwuJzsgJGsgPSAnJzsgaWYgKCRBdXRvKSB7IHJldHVybiAkbnVsbCB9OyBjb250aW51ZSB9CiAgICAgICAgcmV0dXJuICRrCiAgICB9CiAgICByZXR1cm4gJG51bGwKfQoKZnVuY3Rpb24gQ2hvb3NlLU1vZGVsIHsKICAgICRzY3JpcHQ6Q2F0YWxvZyA9IEdldC1DYXRhbG9nCiAgICAkb3B0aW9ucyA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtvYmplY3RdCiAgICBmb3JlYWNoICgkYyBpbiAkQ3VyYXRlZCkgewogICAgICAgIGlmICgkbnVsbCAtZXEgJHNjcmlwdDpDYXRhbG9nIC1vciAkc2NyaXB0OkNhdGFsb2cuQ29udGFpbnNLZXkoJGMuSWQpKSB7CiAgICAgICAgICAgICRtdWx0ID0gaWYgKCRzY3JpcHQ6Q2F0YWxvZykgeyAkc2NyaXB0OkNhdGFsb2dbJGMuSWRdIH0gZWxzZSB7ICRudWxsIH0KICAgICAgICAgICAgJG9wdGlvbnMuQWRkKFtwc2N1c3RvbW9iamVjdF1AeyBJZCA9ICRjLklkOyBOb3RlID0gJGMuTm90ZTsgTXVsdCA9ICRtdWx0IH0pCiAgICAgICAgfQogICAgfQogICAgJHdhbnQgPSBbc3RyaW5nXSRlbnY6TkVYVVNfTU9ERUwKICAgIGlmICgkd2FudCkgewogICAgICAgICRzY3JpcHQ6TW9kZWwgPSAkd2FudAogICAgfSBlbHNlaWYgKChUZXN0LVR1aSkgLWFuZCAkc2NyaXB0OkNhdGFsb2cpIHsKICAgICAgICAkbm90ZXMgPSBAe307IGZvcmVhY2ggKCRjIGluICRDdXJhdGVkKSB7ICRub3Rlc1skYy5JZF0gPSAkYy5Ob3RlIH0KICAgICAgICAkbWFpbiA9IGlmICgkb3B0aW9ucy5Db3VudCkgeyAkb3B0aW9uc1swXS5JZCB9IGVsc2UgeyAoR2V0LUNhdGFsb2dPcmRlcilbMF0gfQogICAgICAgICRyb3dzID0gTmV3LU9iamVjdCBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0W29iamVjdF0KICAgICAgICAkcm93cy5BZGQoW3BzY3VzdG9tb2JqZWN0XUB7IEtpbmQgPSAnY2hlY2snOyBPbiA9ICRzY3JpcHQ6QWxsTW9kZWxzOyBJZCA9ICcnOyBMYWJlbCA9ICgn0J/QvtC00LrQu9GO0YfQuNGC0Ywg0LLRgdC1INC80L7QtNC10LvQuCAoezB9KSAtINCyINC/0YDQvtCz0YDQsNC80LzQsNGFINCx0YPQtNC10YIg0LLQtdGB0Ywg0YHQv9C40YHQvtC6JyAtZiAkc2NyaXB0OkNhdGFsb2cuQ291bnQpIH0pCiAgICAgICAgZm9yZWFjaCAoJGlkIGluIChHZXQtQ2F0YWxvZ09yZGVyKSkgewogICAgICAgICAgICAkbSA9ICgneHswfScgLWYgKFtkb3VibGVdJHNjcmlwdDpDYXRhbG9nWyRpZF0pLlRvU3RyaW5nKCcwLjAjJywgW0dsb2JhbGl6YXRpb24uQ3VsdHVyZUluZm9dOjpJbnZhcmlhbnRDdWx0dXJlKSkKICAgICAgICAgICAgJHJvd3MuQWRkKFtwc2N1c3RvbW9iamVjdF1AeyBLaW5kID0gJ3JhZGlvJzsgT24gPSAoJGlkIC1lcSAkbWFpbik7IElkID0gJGlkOyBMYWJlbCA9ICgnezAsLTI2fSB7MSwtNn0gezJ9JyAtZiAkaWQsICRtLCAkbm90ZXNbJGlkXSkgfSkKICAgICAgICB9CiAgICAgICAgJG51bGwgPSBJbnZva2UtTWVudSAn0JzQvtC00LXQu9C4IE5FWFVTIEFJOiDQstGL0LHQtdGA0LjRgtC1INC+0YHQvdC+0LLQvdGD0Y4gKNC10ZEg0L/QvtC70YPRh9Cw0YIgQ2xhdWRlIENvZGUsIENvZGV4LCBBaWRlciknICRyb3dzLlRvQXJyYXkoKSAn4oaR4oaTIMK3INCf0YDQvtCx0LXQuy9FbnRlciAtINCy0YvQsdGA0LDRgtGMIMK3IHhOIC0g0YHQv9C40YHQsNC90LjQtSDQt9CwIDEg0YLQvtC60LXQvScKICAgICAgICAkc2NyaXB0OkFsbE1vZGVscyA9ICRyb3dzWzBdLk9uCiAgICAgICAgJHBpY2tlZCA9ICRyb3dzIHwgV2hlcmUtT2JqZWN0IHsgJF8uS2luZCAtZXEgJ3JhZGlvJyAtYW5kICRfLk9uIH0gfCBTZWxlY3QtT2JqZWN0IC1GaXJzdCAxCiAgICAgICAgJHNjcmlwdDpNb2RlbCA9IGlmICgkcGlja2VkKSB7ICRwaWNrZWQuSWQgfSBlbHNlIHsgJG1haW4gfQogICAgfSBlbHNlIHsKICAgICAgICBTYXkgJyAg0JzQvtC00LXQu9GMINC/0L4g0YPQvNC+0LvRh9Cw0L3QuNGOICjQtdGRINC80L7QttC90L4g0LzQtdC90Y/RgtGMINCyINC70Y7QsdC+0Lkg0LzQvtC80LXQvdGCINCyINGB0LDQvNC+0Lkg0L/RgNC+0LPRgNCw0LzQvNC1KTonIFdoaXRlCiAgICAgICAgZm9yICgkaSA9IDA7ICRpIC1sdCAkb3B0aW9ucy5Db3VudDsgJGkrKykgewogICAgICAgICAgICAkbyA9ICRvcHRpb25zWyRpXQogICAgICAgICAgICAkbSA9IGlmICgkbnVsbCAtbmUgJG8uTXVsdCkgeyAoJ3h7MH0nIC1mIChbZG91YmxlXSRvLk11bHQpLlRvU3RyaW5nKCcwLjAjJywgW0dsb2JhbGl6YXRpb24uQ3VsdHVyZUluZm9dOjpJbnZhcmlhbnRDdWx0dXJlKSkgfSBlbHNlIHsgJycgfQogICAgICAgICAgICBTYXkgKCcgICAgezB9KSB7MSwtMjJ9IHsyLC02fSB7M30nIC1mICgkaSArIDEpLCAkby5JZCwgJG0sICRvLk5vdGUpCiAgICAgICAgfQogICAgICAgIFNheSAnICAgICAgIHhOIC0g0LrQvtGN0YTRhNC40YbQuNC10L3RgiDRgdC/0LjRgdCw0L3QuNGPOiDRgdC60L7Qu9GM0LrQviDRgtC+0LrQtdC90L7QsiDQsdCw0LvQsNC90YHQsCDRg9GF0L7QtNC40YIg0L3QsCAxINGC0L7QutC10L0g0LTQuNCw0LvQvtCz0LAuJyBEYXJrR3JheQogICAgICAgICRhID0gQXNrICcgINCd0L7QvNC10YAg0LzQvtC00LXQu9C4IFtFbnRlciA9IDFdJyAnMScKICAgICAgICAkbiA9IDAKICAgICAgICBpZiAoW2ludF06OlRyeVBhcnNlKCRhLCBbcmVmXSRuKSAtYW5kICRuIC1nZSAxIC1hbmQgJG4gLWxlICRvcHRpb25zLkNvdW50KSB7ICRzY3JpcHQ6TW9kZWwgPSAkb3B0aW9uc1skbiAtIDFdLklkIH0KICAgICAgICBlbHNlaWYgKCRhIC1tYXRjaCAnXlthLXowLTldW2EtejAtOS5cLTovXSskJykgeyAkc2NyaXB0Ok1vZGVsID0gJGEgfQogICAgICAgIGVsc2UgeyAkc2NyaXB0Ok1vZGVsID0gJG9wdGlvbnNbMF0uSWQgfQogICAgfQogICAgJHNjcmlwdDpGYXN0TW9kZWwgPSAkc2NyaXB0Ok1vZGVsCiAgICBmb3JlYWNoICgkZiBpbiAkRmFzdENhbmRpZGF0ZXMpIHsgaWYgKCRudWxsIC1lcSAkc2NyaXB0OkNhdGFsb2cgLW9yICRzY3JpcHQ6Q2F0YWxvZy5Db250YWluc0tleSgkZikpIHsgJHNjcmlwdDpGYXN0TW9kZWwgPSAkZjsgYnJlYWsgfSB9CiAgICBPayAi0JzQvtC00LXQu9GMOiAkKCRzY3JpcHQ6TW9kZWwpIgp9CgpmdW5jdGlvbiBCdWlsZC1QbGFuIHsKICAgICRwbGFuID0gTmV3LU9iamVjdCBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0W29iamVjdF0KICAgICRhZGQgPSB7CiAgICAgICAgcGFyYW0oJExhYmVsLCAkU2VsZWN0ZWQsICRBY3Rpb24sICRBcmcpCiAgICAgICAgJHBsYW4uQWRkKFtwc2N1c3RvbW9iamVjdF1AeyBMYWJlbCA9ICRMYWJlbDsgU2VsZWN0ZWQgPSBbYm9vbF0kU2VsZWN0ZWQ7IEFjdGlvbiA9ICRBY3Rpb247IEFyZyA9ICRBcmcgfSkKICAgIH0KICAgIGZvcmVhY2ggKCRlIGluICRFZGl0b3JzKSB7CiAgICAgICAgaWYgKC1ub3QgJGUuRm91bmQpIHsgY29udGludWUgfQogICAgICAgICYgJGFkZCAkKGlmIChUZXN0LUV4dGVuc2lvbiAkZSAnc2FvdWRyaXp3YW4uY2xhdWRlLWRldicpIHsgIiQoJGUuTmFtZSk6INCw0LPQtdC90YIgQ2xpbmUgLSDQv9C+0LTQutC70Y7Rh9C40YLRjCDQuiBORVhVUyBBSSIgfSBlbHNlIHsgIiQoJGUuTmFtZSk6INCw0LPQtdC90YIgQ2xpbmUgLSDRg9GB0YLQsNC90L7QstC40YLRjCDQuCDQv9C+0LTQutC70Y7Rh9C40YLRjCDQuiBORVhVUyBBSSIgfSkgJHRydWUgJ2NsaW5lJyAkZQogICAgICAgIGlmIChUZXN0LUV4dGVuc2lvbiAkZSAnYW50aHJvcGljLmNsYXVkZS1jb2RlJykgeyAmICRhZGQgIiQoJGUuTmFtZSk6INGA0LDRgdGI0LjRgNC10L3QuNC1IENsYXVkZSBDb2RlIC0g0L/QvtC00LrQu9GO0YfQuNGC0YwiICR0cnVlICdjbGF1ZGUtZXh0JyAkZSB9CiAgICAgICAgaWYgKFRlc3QtRXh0ZW5zaW9uICRlICdraWxvY29kZS5raWxvLWNvZGUnKSB7ICRzY3JpcHQ6SGFzS2lsbyA9ICR0cnVlIH0KICAgICAgICBpZiAoVGVzdC1FeHRlbnNpb24gJGUgJ2NvbnRpbnVlLmNvbnRpbnVlJykgeyAkc2NyaXB0Okhhc0NvbnRpbnVlID0gJHRydWUgfQogICAgfQogICAgJG5vZGUgPSBGaW5kLUNsaSBAKCdub2RlLmV4ZScsICdub2RlJykKICAgICRnaXQgPSBGaW5kLUNsaSBAKCdnaXQuZXhlJywgJ2dpdCcpCiAgICAkY2xhdWRlID0gRmluZC1DbGkgQCgnY2xhdWRlLmNtZCcsICdjbGF1ZGUuZXhlJywgJ2NsYXVkZScpCiAgICBpZiAoLW5vdCAkY2xhdWRlIC1hbmQgLW5vdCAkVGVzdFJvb3QpIHsgJHAgPSBKb2luLVBhdGggJFVzZXJIb21lICcubG9jYWxcYmluXGNsYXVkZS5leGUnOyBpZiAoVGVzdC1QYXRoICRwKSB7ICRjbGF1ZGUgPSAkcCB9IH0KICAgICRjb2RleCA9IEZpbmQtQ2xpIEAoJ2NvZGV4LmNtZCcsICdjb2RleC5leGUnLCAnY29kZXgnKQogICAgJHF3ZW4gPSBGaW5kLUNsaSBAKCdxd2VuLmNtZCcsICdxd2VuJykKICAgICRvYyA9IEZpbmQtQ2xpIEAoJ29wZW5jb2RlLmNtZCcsICdvcGVuY29kZS5leGUnLCAnb3BlbmNvZGUnKQogICAgJGtpbG8gPSBGaW5kLUNsaSBAKCdraWxvLmNtZCcsICdraWxvLmV4ZScsICdraWxvJykKCiAgICBpZiAoJGNsYXVkZSkgeyAmICRhZGQgJ0NsYXVkZSBDb2RlICjRgtC10YDQvNC40L3QsNC7KSAtINC/0L7QtNC60LvRjtGH0LjRgtGMJyAkdHJ1ZSAnY2xhdWRlJyAkbnVsbCB9CiAgICBlbHNlIHsgJiAkYWRkICdDbGF1ZGUgQ29kZSAo0YLQtdGA0LzQuNC90LDQuykgLSDRg9GB0YLQsNC90L7QstC40YLRjCDQuCDQv9C+0LTQutC70Y7Rh9C40YLRjCcgJGZhbHNlICdjbGF1ZGUtaW5zdGFsbCcgJG51bGwgfQogICAgaWYgKCRjb2RleCkgeyAmICRhZGQgJ0NvZGV4IENMSSAtINC/0L7QtNC60LvRjtGH0LjRgtGMJyAkdHJ1ZSAnY29kZXgnICRudWxsIH0KICAgIGVsc2UgeyAmICRhZGQgJ0NvZGV4IENMSSAtINGD0YHRgtCw0L3QvtCy0LjRgtGMINC4INC/0L7QtNC60LvRjtGH0LjRgtGMJyAkZmFsc2UgJ2NvZGV4LWluc3RhbGwnICRudWxsIH0KICAgIGlmICgkcXdlbikgeyAmICRhZGQgJ1F3ZW4gQ29kZSAtINC/0L7QtNC60LvRjtGH0LjRgtGMJyAkdHJ1ZSAncXdlbicgJG51bGwgfQogICAgZWxzZSB7ICYgJGFkZCAnUXdlbiBDb2RlIC0g0YPRgdGC0LDQvdC+0LLQuNGC0Ywg0Lgg0L/QvtC00LrQu9GO0YfQuNGC0YwnICRmYWxzZSAncXdlbi1pbnN0YWxsJyAkbnVsbCB9CiAgICBpZiAoJG9jIC1vciAoVGVzdC1QYXRoIChKb2luLVBhdGggJFVzZXJIb21lICcuY29uZmlnXG9wZW5jb2RlJykpKSB7ICYgJGFkZCAnT3BlbkNvZGUgLSDQv9C+0LTQutC70Y7Rh9C40YLRjCcgJHRydWUgJ29wZW5jb2RlJyAkbnVsbCB9CiAgICBlbHNlIHsgJiAkYWRkICdPcGVuQ29kZSAtINGD0YHRgtCw0L3QvtCy0LjRgtGMINC4INC/0L7QtNC60LvRjtGH0LjRgtGMJyAkZmFsc2UgJ29wZW5jb2RlLWluc3RhbGwnICRudWxsIH0KICAgIGlmICgka2lsbyAtb3IgJHNjcmlwdDpIYXNLaWxvIC1vciAoVGVzdC1QYXRoIChKb2luLVBhdGggJFVzZXJIb21lICcuY29uZmlnXGtpbG8nKSkpIHsgJiAkYWRkICdLaWxvIENvZGUgLSDQv9C+0LTQutC70Y7Rh9C40YLRjCcgJHRydWUgJ2tpbG8nICRudWxsIH0KICAgICRhaWRlciA9IEZpbmQtQ2xpIEAoJ2FpZGVyLmV4ZScsICdhaWRlci5jbWQnLCAnYWlkZXInKQogICAgaWYgKCRhaWRlciAtb3IgKFRlc3QtUGF0aCAoSm9pbi1QYXRoICRVc2VySG9tZSAnLmFpZGVyLmNvbmYueW1sJykpKSB7ICYgJGFkZCAnQWlkZXIgLSDQv9C+0LTQutC70Y7Rh9C40YLRjCcgJHRydWUgJ2FpZGVyJyAkbnVsbCB9CiAgICBpZiAoJHNjcmlwdDpIYXNDb250aW51ZSAtb3IgKFRlc3QtUGF0aCAoSm9pbi1QYXRoICRVc2VySG9tZSAnLmNvbnRpbnVlJykpKSB7ICYgJGFkZCAnQ29udGludWUgKFZTIENvZGUgLyBKZXRCcmFpbnMpIC0g0L/QvtC00LrQu9GO0YfQuNGC0YwnICR0cnVlICdjb250aW51ZScgJG51bGwgfQogICAgJiAkYWRkICdORVhVUyBEZXNrdG9wIC0g0YPRgdGC0LDQvdC+0LLQuNGC0Ywg0L/RgNC40LvQvtC20LXQvdC40LUg0LTQu9GPIFdpbmRvd3MnICRmYWxzZSAnZGVza3RvcCcgJG51bGwKICAgICRuZXh1c0NvZGUgPSBUZXN0LVBhdGggKEpvaW4tUGF0aCAkVXNlckhvbWUgJy5uZXh1cy1haVxjb2RlXG9wZW5jb2RlLmpzb24nKQogICAgJiAkYWRkICQoaWYgKCRuZXh1c0NvZGUpIHsgJ05FWFVTIENMSSAo0LDQs9C10L3RgiDQsiDRgtC10YDQvNC40L3QsNC70LUpIC0g0L7QsdC90L7QstC40YLRjCcgfSBlbHNlIHsgJ05FWFVTIENMSSAo0LDQs9C10L3RgiDQsiDRgtC10YDQvNC40L3QsNC70LUpIC0g0YPRgdGC0LDQvdC+0LLQuNGC0YwnIH0pIChbYm9vbF0oJG9jIC1vciAkbmV4dXNDb2RlKSkgJ25leHVzLWNvZGUnICRudWxsCiAgICAkc3R1ZGlvID0gVGVzdC1Sb2Jsb3hTdHVkaW8KICAgIGlmICgkc3R1ZGlvIC1vciAoW3N0cmluZ10kZW52Ok5FWFVTX09OTFkpIC1tYXRjaCAncm9ibG94JykgewogICAgICAgICRoYXMgPSBUZXN0LVBhdGggKEpvaW4tUGF0aCAoR2V0LVJvYmxveFBsdWdpbnNEaXIpICdORVhVUy1BSS5yYnhteCcpCiAgICAgICAgJiAkYWRkICQoaWYgKCRoYXMpIHsgJ1JvYmxveCBTdHVkaW86INC/0LvQsNCz0LjQvSBORVhVUyBBSSAtINC+0LHQvdC+0LLQuNGC0YwnIH0gZWxzZSB7ICdSb2Jsb3ggU3R1ZGlvOiDQv9C70LDQs9C40L0gTkVYVVMgQUkgLSDRg9GB0YLQsNC90L7QstC40YLRjCcgfSkgJHN0dWRpbyAncm9ibG94JyAkbnVsbAogICAgfQogICAgaWYgKC1ub3QgJG5vZGUpIHsgJiAkYWRkICdOb2RlLmpzIExUUyAtINGD0YHRgtCw0L3QvtCy0LjRgtGMICjQvdGD0LbQtdC9INC00LvRjyDRg9GB0YLQsNC90L7QstC60LggQ0xJINCy0YvRiNC1KScgJGZhbHNlICdub2RlJyAkbnVsbCB9CiAgICBpZiAoLW5vdCAkZ2l0KSB7ICYgJGFkZCAnR2l0INC00LvRjyBXaW5kb3dzIC0g0YPRgdGC0LDQvdC+0LLQuNGC0YwgKNC90YPQttC10L0gQ2xhdWRlIENvZGUpJyAkZmFsc2UgJ2dpdCcgJG51bGwgfQogICAgJHBvbCA9IEdldC1FZmZlY3RpdmVQb2xpY3kKICAgIGlmIChAKCdSZXN0cmljdGVkJywgJ0FsbFNpZ25lZCcpIC1jb250YWlucyAkcG9sLlBvbGljeSAtYW5kIEAoJ01hY2hpbmVQb2xpY3knLCAnVXNlclBvbGljeScpIC1ub3Rjb250YWlucyAkcG9sLlNjb3BlKSB7CiAgICAgICAgJiAkYWRkICdQb3dlclNoZWxsOiDRgNCw0LfRgNC10YjQuNGC0Ywg0LfQsNC/0YPRgdC6IGNsYXVkZS9jb2RleCAo0LjQvdCw0YfQtSDQvtGI0LjQsdC60LAgInJ1bm5pbmcgc2NyaXB0cyBpcyBkaXNhYmxlZCIpJyAkdHJ1ZSAncG9saWN5JyAkbnVsbAogICAgfQogICAgJiAkYWRkICfQn9GA0L7QstC10YDQuNGC0Ywg0YHQstGP0LfRjCDQsiDQutC+0L3RhtC1ICjQutC+0YDQvtGC0LrQuNC5INC30LDQv9GA0L7RgSwgfjMwINGC0L7QutC10L3QvtCyKScgJHRydWUgJ2NoZWNrJyAkbnVsbAogICAgaWYgKCRlbnY6TkVYVVNfT05MWSkgewogICAgICAgICMgYSBzaXRlIGJ1dHRvbiBmb3Igb25lIHByb2dyYW06IG9ubHkgaXRzIGl0ZW1zIChhbmQgdGhlIGNvbm5lY3Rpb24gY2hlY2spIGFyZSB0aWNrZWQKICAgICAgICAkb25seSA9IEAoKFtzdHJpbmddJGVudjpORVhVU19PTkxZKS5TcGxpdCgnLCcpIHwgRm9yRWFjaC1PYmplY3QgeyAkXy5UcmltKCkgfSB8IFdoZXJlLU9iamVjdCB7ICRfIH0pICsgQCgnY2hlY2snKQogICAgICAgIGZvcmVhY2ggKCRwIGluICRwbGFuKSB7ICRwLlNlbGVjdGVkID0gJG9ubHkgLWNvbnRhaW5zICRwLkFjdGlvbiB9CiAgICB9CiAgICAkc2NyaXB0OkRldGVjdGVkID0gQHsgTm9kZSA9ICRub2RlOyBHaXQgPSAkZ2l0OyBDbGF1ZGUgPSAkY2xhdWRlOyBDb2RleCA9ICRjb2RleDsgUXdlbiA9ICRxd2VuOyBPcGVuQ29kZSA9ICRvYyB9CiAgICByZXR1cm4gLCAkcGxhbgp9CgpmdW5jdGlvbiBTaG93LURldGVjdGVkIHsKICAgICRmb3VuZCA9IEAoKTsgJG1pc3NpbmcgPSBAKCkKICAgIGZvcmVhY2ggKCRlIGluICRFZGl0b3JzKSB7IGlmICgkZS5Gb3VuZCkgeyAkZm91bmQgKz0gJGUuTmFtZSB9IGVsc2UgeyAkbWlzc2luZyArPSAkZS5OYW1lIH0gfQogICAgZm9yZWFjaCAoJGsgaW4gQCgnQ2xhdWRlJywgJ0NvZGV4JywgJ1F3ZW4nLCAnT3BlbkNvZGUnLCAnTm9kZScsICdHaXQnKSkgewogICAgICAgICR0aXRsZSA9IEB7IENsYXVkZSA9ICdDbGF1ZGUgQ29kZSc7IENvZGV4ID0gJ0NvZGV4IENMSSc7IFF3ZW4gPSAnUXdlbiBDb2RlJzsgT3BlbkNvZGUgPSAnT3BlbkNvZGUnOyBOb2RlID0gJ05vZGUuanMnOyBHaXQgPSAnR2l0JyB9WyRrXQogICAgICAgIGlmICgkc2NyaXB0OkRldGVjdGVkWyRrXSkgeyAkZm91bmQgKz0gJHRpdGxlIH0gZWxzZSB7ICRtaXNzaW5nICs9ICR0aXRsZSB9CiAgICB9CiAgICBTYXkgKCcgINCd0LDQudC00LXQvdC+OiAgICAnICsgJChpZiAoJGZvdW5kLkNvdW50KSB7ICRmb3VuZCAtam9pbiAnLCAnIH0gZWxzZSB7ICctJyB9KSkgR3JlZW4KICAgIFNheSAoJyAg0J3QtSDQvdCw0LnQtNC10L3QvjogJyArICQoaWYgKCRtaXNzaW5nLkNvdW50KSB7ICRtaXNzaW5nIC1qb2luICcsICcgfSBlbHNlIHsgJy0nIH0pKSBEYXJrR3JheQogICAgU2F5ICcnCn0KCmZ1bmN0aW9uIENob29zZS1JdGVtcygkUGxhbikgewogICAgaWYgKCRBdXRvKSB7IHJldHVybiB9CiAgICBpZiAoVGVzdC1UdWkpIHsKICAgICAgICAjIGEgZnJlc2ggbWVudSBzdGFydHMgd2l0aCBub3RoaW5nIHRpY2tlZDsgdGhlIHNpdGUncyBvbmUtcHJvZ3JhbSBidXR0b25zIChORVhVU19PTkxZKSBrZWVwIHRoZWlyIHRpY2tzCiAgICAgICAgaWYgKC1ub3QgJGVudjpORVhVU19PTkxZKSB7IGZvcmVhY2ggKCRwIGluICRQbGFuKSB7ICRwLlNlbGVjdGVkID0gJGZhbHNlIH0gfQogICAgICAgICRyb3dzID0gQCgkUGxhbiB8IEZvckVhY2gtT2JqZWN0IHsgW3BzY3VzdG9tb2JqZWN0XUB7IEtpbmQgPSAnY2hlY2snOyBPbiA9ICRfLlNlbGVjdGVkOyBMYWJlbCA9ICRfLkxhYmVsOyBJdGVtID0gJF8gfSB9KQogICAgICAgICRyZXMgPSBJbnZva2UtTWVudSAn0KfRgtC+INC90LDRgdGC0YDQvtC40YLRjDonICRyb3dzICfihpHihpMgwrcg0J/RgNC+0LHQtdC7IC0g0L7RgtC80LXRgtC40YLRjCDCtyBBIC0g0LLRgdGRIMK3IEVudGVyIC0g0LLRi9C/0L7Qu9C90LjRgtGMIMK3IFIgLSDQvtGC0LrQsNGCIMK3IFEgLSDQstGL0YXQvtC0JyBAKCdxdWl0JywgJ3Jlc3RvcmUnKQogICAgICAgIGZvcmVhY2ggKCRyIGluICRyb3dzKSB7ICRyLkl0ZW0uU2VsZWN0ZWQgPSAkci5PbiB9CiAgICAgICAgaWYgKCRyZXMgLWVxICdxdWl0JykgeyAkc2NyaXB0OlF1aXQgPSAkdHJ1ZSB9CiAgICAgICAgaWYgKCRyZXMgLWVxICdyZXN0b3JlJykgeyAkc2NyaXB0OkRvUmVzdG9yZSA9ICR0cnVlIH0KICAgICAgICByZXR1cm4KICAgIH0KICAgIHdoaWxlICgkdHJ1ZSkgewogICAgICAgIFNheSAnICDQp9GC0L4g0L3QsNGB0YLRgNC+0LjRgtGMOicgV2hpdGUKICAgICAgICBmb3IgKCRpID0gMDsgJGkgLWx0ICRQbGFuLkNvdW50OyAkaSsrKSB7CiAgICAgICAgICAgICRtYXJrID0gaWYgKCRQbGFuWyRpXS5TZWxlY3RlZCkgeyAnW3hdJyB9IGVsc2UgeyAnWyBdJyB9CiAgICAgICAgICAgICRjb2xvciA9IGlmICgkUGxhblskaV0uU2VsZWN0ZWQpIHsgJ1doaXRlJyB9IGVsc2UgeyAnRGFya0dyYXknIH0KICAgICAgICAgICAgU2F5ICgnICAgIHswfSB7MSwyfS4gezJ9JyAtZiAkbWFyaywgKCRpICsgMSksICRQbGFuWyRpXS5MYWJlbCkgJGNvbG9yCiAgICAgICAgfQogICAgICAgIFNheSAnICBFbnRlciAtINCy0YvQv9C+0LvQvdC40YLRjCDQvtGC0LzQtdGH0LXQvdC90L7QtSB8INC90L7QvNC10YDQsCDRh9C10YDQtdC3INC/0YDQvtCx0LXQuyAtINC+0YLQvNC10YLQuNGC0Ywv0YHQvdGP0YLRjCB8IEEgLSDQstGB0ZEgfCBSIC0g0L7RgtC60LDRgtC40YLRjCDQv9GA0L7RiNC70YPRjiDRg9GB0YLQsNC90L7QstC60YMgfCBRIC0g0LLRi9GF0L7QtCcgRGFya0dyYXkKICAgICAgICAkYSA9IFJlYWQtSG9zdCAnICA+JwogICAgICAgIGlmICgkbnVsbCAtZXEgJGEpIHsgJGEgPSAnJyB9CiAgICAgICAgJGEgPSAkYS5UcmltKCkuVG9Mb3dlcigpCiAgICAgICAgaWYgKCRhIC1lcSAnJykgeyByZXR1cm4gfQogICAgICAgIGlmICgkYSAtZXEgJ3EnIC1vciAkYSAtZXEgJzAnIC1vciAkYSAtZXEgJ9C5JykgeyAkc2NyaXB0OlF1aXQgPSAkdHJ1ZTsgcmV0dXJuIH0KICAgICAgICBpZiAoJGEgLWVxICdyJyAtb3IgJGEgLWVxICfQuicpIHsgJHNjcmlwdDpEb1Jlc3RvcmUgPSAkdHJ1ZTsgcmV0dXJuIH0KICAgICAgICBpZiAoJGEgLWVxICdhJyAtb3IgJGEgLWVxICfRhCcpIHsgZm9yZWFjaCAoJHAgaW4gJFBsYW4pIHsgJHAuU2VsZWN0ZWQgPSAkdHJ1ZSB9OyBjb250aW51ZSB9CiAgICAgICAgZm9yZWFjaCAoJHRvayBpbiAoJGEgLXNwbGl0ICdbXHMsO10rJykpIHsKICAgICAgICAgICAgJG4gPSAwCiAgICAgICAgICAgIGlmIChbaW50XTo6VHJ5UGFyc2UoJHRvaywgW3JlZl0kbikgLWFuZCAkbiAtZ2UgMSAtYW5kICRuIC1sZSAkUGxhbi5Db3VudCkgeyAkUGxhblskbiAtIDFdLlNlbGVjdGVkID0gLW5vdCAkUGxhblskbiAtIDFdLlNlbGVjdGVkIH0KICAgICAgICB9CiAgICAgICAgU2F5ICcnCiAgICB9Cn0KCmZ1bmN0aW9uIFJ1bi1QbGFuKCRQbGFuKSB7CiAgICAkc2VsID0gQCgkUGxhbiB8IFdoZXJlLU9iamVjdCB7ICRfLlNlbGVjdGVkIH0pCiAgICAkYWN0aW9ucyA9IEAoJHNlbCB8IEZvckVhY2gtT2JqZWN0IHsgJF8uQWN0aW9uIH0pCiAgICAkbmVlZE5wbSA9IChAKCRhY3Rpb25zIHwgV2hlcmUtT2JqZWN0IHsgJF8gLWxpa2UgJyotaW5zdGFsbCcgfSkuQ291bnQgLWd0IDApCiAgICAkbmVlZE5vZGUgPSAkbmVlZE5wbSAtYW5kIC1ub3QgJHNjcmlwdDpEZXRlY3RlZC5Ob2RlCiAgICBpZiAoJG5lZWROb2RlIC1hbmQgJGFjdGlvbnMgLW5vdGNvbnRhaW5zICdub2RlJykgewogICAgICAgICRub2RlSXRlbSA9ICRQbGFuIHwgV2hlcmUtT2JqZWN0IHsgJF8uQWN0aW9uIC1lcSAnbm9kZScgfSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEKICAgICAgICBpZiAoJG5vZGVJdGVtKSB7ICRub2RlSXRlbS5TZWxlY3RlZCA9ICR0cnVlOyAkc2VsID0gQCgkUGxhbiB8IFdoZXJlLU9iamVjdCB7ICRfLlNlbGVjdGVkIH0pIH0KICAgIH0KICAgICMg0L/QvtGA0Y/QtNC+0Lo6INGB0L3QsNGH0LDQu9CwINGB0LjRgdGC0LXQvNC90YvQtSDQt9Cw0LLQuNGB0LjQvNC+0YHRgtC4LCDQv9C+0YLQvtC8INGD0YHRgtCw0L3QvtCy0LrQuCwg0L/QvtGC0L7QvCDQvdCw0YHRgtGA0L7QudC60LAKICAgICRvcmRlciA9IEAoJ3BvbGljeScsICdub2RlJywgJ2dpdCcsICdjbGF1ZGUtaW5zdGFsbCcsICdjb2RleC1pbnN0YWxsJywgJ3F3ZW4taW5zdGFsbCcsICdvcGVuY29kZS1pbnN0YWxsJywgJ2NsaW5lJywgJ2NsYXVkZS1leHQnLCAnY2xhdWRlJywgJ2NvZGV4JywgJ3F3ZW4nLCAnb3BlbmNvZGUnLCAna2lsbycsICdhaWRlcicsICdjb250aW51ZScsICdkZXNrdG9wJywgJ25leHVzLWNvZGUnLCAncm9ibG94JywgJ2NoZWNrJykKICAgICRzZWwgPSBAKCRzZWwgfCBTb3J0LU9iamVjdCB7IFthcnJheV06OkluZGV4T2YoJG9yZGVyLCAkXy5BY3Rpb24pIH0pCiAgICBmb3JlYWNoICgkaXRlbSBpbiAkc2VsKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgc3dpdGNoICgkaXRlbS5BY3Rpb24pIHsKICAgICAgICAgICAgICAgICdwb2xpY3knIHsgQXBwbHktRXhlY3V0aW9uUG9saWN5IH0KICAgICAgICAgICAgICAgICdub2RlJyB7IGlmIChJbnN0YWxsLVdpbmdldCAnT3BlbkpTLk5vZGVKUy5MVFMnICdOb2RlLmpzJykgeyAkc2NyaXB0OkRldGVjdGVkLk5vZGUgPSAnaW5zdGFsbGVkJyB9IGVsc2UgeyB0aHJvdyAnTm9kZS5qcyDQvdC1INGD0YHRgtCw0L3QvtCy0LvQtdC9JyB9IH0KICAgICAgICAgICAgICAgICdnaXQnIHsgaWYgKC1ub3QgKEluc3RhbGwtV2luZ2V0ICdHaXQuR2l0JyAnR2l0JykpIHsgdGhyb3cgJ0dpdCDQvdC1INGD0YHRgtCw0L3QvtCy0LvQtdC9JyB9IH0KICAgICAgICAgICAgICAgICdjbGF1ZGUtaW5zdGFsbCcgeyBpZiAoSW5zdGFsbC1OcG1Ub29sICdAYW50aHJvcGljLWFpL2NsYXVkZS1jb2RlJyAnQ2xhdWRlIENvZGUnKSB7IEFwcGx5LUNsYXVkZUNvZGUgfSBlbHNlIHsgdGhyb3cgJ0NsYXVkZSBDb2RlINC90LUg0YPRgdGC0LDQvdC+0LLQu9C10L0nIH0gfQogICAgICAgICAgICAgICAgJ2NvZGV4LWluc3RhbGwnIHsgaWYgKEluc3RhbGwtTnBtVG9vbCAnQG9wZW5haS9jb2RleCcgJ0NvZGV4IENMSScpIHsgQXBwbHktQ29kZXggfSBlbHNlIHsgdGhyb3cgJ0NvZGV4IENMSSDQvdC1INGD0YHRgtCw0L3QvtCy0LvQtdC9JyB9IH0KICAgICAgICAgICAgICAgICdxd2VuLWluc3RhbGwnIHsgaWYgKEluc3RhbGwtTnBtVG9vbCAnQHF3ZW4tY29kZS9xd2VuLWNvZGUnICdRd2VuIENvZGUnKSB7IEFwcGx5LVF3ZW4gfSBlbHNlIHsgdGhyb3cgJ1F3ZW4gQ29kZSDQvdC1INGD0YHRgtCw0L3QvtCy0LvQtdC9JyB9IH0KICAgICAgICAgICAgICAgICdvcGVuY29kZS1pbnN0YWxsJyB7IGlmIChJbnN0YWxsLU5wbVRvb2wgJ29wZW5jb2RlLWFpJyAnT3BlbkNvZGUnKSB7IEFwcGx5LU9wZW5Db2RlIH0gZWxzZSB7IHRocm93ICdPcGVuQ29kZSDQvdC1INGD0YHRgtCw0L3QvtCy0LvQtdC9JyB9IH0KICAgICAgICAgICAgICAgICdjbGluZScgeyBBcHBseS1FZGl0b3JDbGluZSAkaXRlbS5BcmcgfQogICAgICAgICAgICAgICAgJ2NoZWNrJyB7IGlmICgkZW52Ok5FWFVTX1NLSVBfQ0hFQ0sgLW5lICcxJykgeyBUZXN0LU1vZGVsIH0gfQogICAgICAgICAgICAgICAgJ2NsYXVkZS1leHQnIHsgQXBwbHktRWRpdG9yQ2xhdWRlRXh0ICRpdGVtLkFyZyB9CiAgICAgICAgICAgICAgICAnY2xhdWRlJyB7IEFwcGx5LUNsYXVkZUNvZGUgfQogICAgICAgICAgICAgICAgJ2NvZGV4JyB7IEFwcGx5LUNvZGV4IH0KICAgICAgICAgICAgICAgICdxd2VuJyB7IEFwcGx5LVF3ZW4gfQogICAgICAgICAgICAgICAgJ29wZW5jb2RlJyB7IEFwcGx5LU9wZW5Db2RlIH0KICAgICAgICAgICAgICAgICdraWxvJyB7IEFwcGx5LUtpbG8gfQogICAgICAgICAgICAgICAgJ2FpZGVyJyB7IEFwcGx5LUFpZGVyIH0KICAgICAgICAgICAgICAgICdjb250aW51ZScgeyBBcHBseS1Db250aW51ZSB9CiAgICAgICAgICAgICAgICAnZGVza3RvcCcgeyBBcHBseS1OZXh1c0Rlc2t0b3AgfQogICAgICAgICAgICAgICAgJ25leHVzLWNvZGUnIHsgQXBwbHktTmV4dXNDb2RlIH0KICAgICAgICAgICAgICAgICdyb2Jsb3gnIHsgQXBwbHktUm9ibG94IH0KICAgICAgICAgICAgfQogICAgICAgICAgICBMb2cgImRvbmU6ICQoJGl0ZW0uQWN0aW9uKSIKICAgICAgICB9IGNhdGNoIHsKICAgICAgICAgICAgRmFpbCAoInswfTogezF9IiAtZiAkaXRlbS5MYWJlbCwgJF8uRXhjZXB0aW9uLk1lc3NhZ2UpCiAgICAgICAgICAgIExvZyAiZmFpbGVkOiAkKCRpdGVtLkFjdGlvbik6ICQoJF8uRXhjZXB0aW9uLk1lc3NhZ2UpIgogICAgICAgICAgICAkZ2xvYmFsOk5leHVzRXhpdCA9IDEKICAgICAgICB9CiAgICB9CiAgICByZXR1cm4gQCgkc2VsIHwgV2hlcmUtT2JqZWN0IHsgJF8uQWN0aW9uIC1uZSAnY2hlY2snIH0pLkNvdW50Cn0KCmZ1bmN0aW9uIE1haW4tUXVpZXQgewogICAgJHRpdGxlID0gQHsgJ25leHVzLWNvZGUnID0gJ05FWFVTIENMSSAtINGD0YHRgtCw0L3QvtCy0LrQsCc7IHJvYmxveCA9ICdORVhVUyBBSSDQtNC70Y8gUm9ibG94IFN0dWRpbyAtINGD0YHRgtCw0L3QvtCy0LrQsCcgfVskUXVpZXRNb2RlXQogICAgU2F5ICcnCiAgICBTYXkgIiAgJHRpdGxlIiBXaGl0ZQogICAgU2F5ICcgIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0nIERhcmtZZWxsb3cKICAgIENob29zZS1TZXJ2ZXIKICAgICRzY3JpcHQ6S2V5ID0gUmVhZC1WYWxpZEtleQogICAgaWYgKC1ub3QgJHNjcmlwdDpLZXkpIHsgRmFpbCAn0JHQtdC3INC00LXQudGB0YLQstGD0Y7RidC10LPQviDQutC70Y7Rh9CwINGD0YHRgtCw0L3QvtCy0LjRgtGMINC90LXQu9GM0LfRjy4g0JrQu9GO0Ycg0L/RgNC40YXQvtC00LjRgiDQsiDRh9Cw0YIg0LfQsNC60LDQt9CwIEZ1blBheS4nOyAkZ2xvYmFsOk5leHVzRXhpdCA9IDI7IHJldHVybiB9CiAgICAjIHRoZSBtb2RlbDogdGhlIG9uZSBjaG9zZW4gYmVmb3JlIChhIHJlaW5zdGFsbCBrZWVwcyBpdCksIGVsc2UgdGhlIHJlY29tbWVuZGVkIG9uZTsgZXZlcnkgbW9kZWwgaXMgbGlzdGVkCiAgICBpZiAoLW5vdCAkZW52Ok5FWFVTX01PREVMKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgJHByZXYgPSBbc3RyaW5nXShSZWFkLUpzb25GaWxlIChKb2luLVBhdGggJFVzZXJIb21lICcubmV4dXMtYWlcY29kZVxvcGVuY29kZS5qc29uJykpLm1vZGVsCiAgICAgICAgICAgIGlmICgkcHJldiAtbGlrZSAnbmV4dXMvKicpIHsgJGVudjpORVhVU19NT0RFTCA9ICRwcmV2LlN1YnN0cmluZyg2KSB9CiAgICAgICAgfSBjYXRjaCB7fQogICAgfQogICAgaWYgKC1ub3QgJGVudjpORVhVU19NT0RFTCkgewogICAgICAgICRjYXQgPSBHZXQtQ2F0YWxvZwogICAgICAgIGZvcmVhY2ggKCRjIGluICRDdXJhdGVkKSB7IGlmICgkbnVsbCAtZXEgJGNhdCAtb3IgJGNhdC5Db250YWluc0tleSgkYy5JZCkpIHsgJGVudjpORVhVU19NT0RFTCA9ICRjLklkOyBicmVhayB9IH0KICAgIH0KICAgIENob29zZS1Nb2RlbAogICAgJHNjcmlwdDpOZXh0ID0gTmV3LU9iamVjdCBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0W3N0cmluZ10KICAgICRzY3JpcHQ6Tm90ZXMgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgdHJ5IHsKICAgICAgICBpZiAoJFF1aWV0TW9kZSAtZXEgJ25leHVzLWNvZGUnKSB7IEFwcGx5LU5leHVzQ29kZSB9IGVsc2UgewogICAgICAgICAgICBpZiAoLW5vdCAoVGVzdC1Sb2Jsb3hTdHVkaW8pKSB7IFdhcm4gJ1JvYmxveCBTdHVkaW8g0L3QtSDQvdCw0LnQtNC10L3QsCAtINC/0LvQsNCz0LjQvSDQstGB0ZEg0YDQsNCy0L3QviDQutC70LDQtNGDINCyINC/0LDQv9C60YMg0L/Qu9Cw0LPQuNC90L7QsiwgU3R1ZGlvINC/0L7QtNGF0LLQsNGC0LjRgiDQtdCz0L4g0L/QvtGB0LvQtSDRg9GB0YLQsNC90L7QstC60LguJyB9CiAgICAgICAgICAgIEFwcGx5LVJvYmxveAogICAgICAgIH0KICAgIH0gY2F0Y2ggewogICAgICAgIEZhaWwgJF8uRXhjZXB0aW9uLk1lc3NhZ2UKICAgICAgICAkZ2xvYmFsOk5leHVzRXhpdCA9IDEKICAgIH0KICAgIENsZWFyLUtleVRyYWNlcwogICAgU2F5ICcnCiAgICBpZiAoJGdsb2JhbDpOZXh1c0V4aXQgLW5lIDApIHsgU2F5ICcgINCj0YHRgtCw0L3QvtCy0LrQsCDQvdC1INC30LDQstC10YDRiNC10L3QsCAtINGB0LwuINGB0L7QvtCx0YnQtdC90LjRjyBbWF0g0LLRi9GI0LUuJyBZZWxsb3c7IHJldHVybiB9CiAgICBTYXkgJyAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PScgR3JlZW4KICAgIGlmICgkUXVpZXRNb2RlIC1lcSAnbmV4dXMtY29kZScpIHsKICAgICAgICBTYXkgJyAgINCT0L7RgtC+0LLQviEgTkVYVVMgQ0xJINGD0YHRgtCw0L3QvtCy0LvQtdC9LicgR3JlZW4KICAgICAgICBTYXkgJycKICAgICAgICBTYXkgJyAgINCf0LXRgNC10LfQsNC/0YPRgdGC0LjRgtC1INGC0LXRgNC80LjQvdCw0Ls6INC30LDQutGA0L7QudGC0LUg0LLRgdC1INC+0LrQvdCwIFBvd2VyU2hlbGwgLyDQutC+0LzQsNC90LTQvdC+0LknIFdoaXRlCiAgICAgICAgU2F5ICcgICDRgdGC0YDQvtC60Lgg0Lgg0L7RgtC60YDQvtC50YLQtSDQvdC+0LLQvtC1LiDQl9Cw0YLQtdC8INCyINC/0LDQv9C60LUg0L/RgNC+0LXQutGC0LAg0L3QsNCx0LXRgNC40YLQtTogIG5leHVzJyBXaGl0ZQogICAgICAgIFNheSAnJwogICAgICAgIFNheSAnICAgbmV4dXMgYmFsYW5jZSAtINCx0LDQu9Cw0L3RgSwgIG5leHVzIHRvcHVwIC0g0L/QvtC/0L7Qu9C90LjRgtGMLCAgbmV4dXMgdXBkYXRlIC0g0L7QsdC90L7QstC40YLRjCcgRGFya0dyYXkKICAgIH0gZWxzZSB7CiAgICAgICAgU2F5ICcgICDQk9C+0YLQvtCy0L4hINCf0LvQsNCz0LjQvSBORVhVUyBBSSDRg9GB0YLQsNC90L7QstC70LXQvS4nIEdyZWVuCiAgICAgICAgU2F5ICcnCiAgICAgICAgU2F5ICcgICDQn9C10YDQtdC30LDQv9GD0YHRgtC40YLQtSBSb2Jsb3ggU3R1ZGlvIC0+INCy0LrQu9Cw0LTQutCwIFBsdWdpbnMgLT4g0LrQvdC+0L/QutCwIE5FWFVTIEFJLicgV2hpdGUKICAgICAgICBTYXkgJyAgINCd0LAg0L/QtdGA0LLRi9C5INC30LDQv9GA0L7RgSBTdHVkaW8g0YHQv9GA0L7RgdC40YIg0LTQvtGB0YLRg9C/INC6INGB0LDQudGC0YMg0Lgg0Log0YHQutGA0LjQv9GC0LDQvCAtINGA0LDQt9GA0LXRiNC40YLQtS4nIERhcmtHcmF5CiAgICB9CiAgICBTYXkgJyAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PScgR3JlZW4KICAgIExvZyAoInF1aWV0IGluc3RhbGw6IHswfSBtb2RlbD17MX0gZXhpdD17Mn0iIC1mICRRdWlldE1vZGUsICRzY3JpcHQ6TW9kZWwsICRnbG9iYWw6TmV4dXNFeGl0KQp9CgpmdW5jdGlvbiBNYWluIHsKICAgIGlmICgtbm90ICRBdXRvIC1hbmQgJGVudjpORVhVU19URVhUIC1uZSAnMScpIHsKICAgICAgICAkc2NyaXB0Ok5leHVzR3VpVXNlZCA9ICR0cnVlCiAgICAgICAgJGdsb2JhbDpOZXh1c0V4aXQgPSBTdGFydC1OZXh1c1R1aSAtRW5naW5lU291cmNlICRzY3JpcHQ6TmV4dXNUdWlFbmdpbmVTb3VyY2UgLUdhdGV3YXlzICRzY3JpcHQ6TmV4dXNUdWlHYXRld2F5cyAtUHJldmlldzooJGVudjpORVhVU19UVUlfUFJFVklFVyAtZXEgJzEnKQogICAgICAgIHJldHVybgogICAgfQogICAgaWYgKCRRdWlldE1vZGUgLWFuZCAkZW52Ok5FWFVTX0FDVElPTiAtbmUgJ3Jlc3RvcmUnKSB7IE1haW4tUXVpZXQ7IHJldHVybiB9CiAgICBTaG93LUJhbm5lcgogICAgaWYgKCRlbnY6TkVYVVNfQUNUSU9OIC1lcSAncmVzdG9yZScpIHsgSW52b2tlLVJlc3RvcmU7IHJldHVybiB9CgogICAgQ2hvb3NlLVNlcnZlcgogICAgJHNjcmlwdDpLZXkgPSBSZWFkLVZhbGlkS2V5CiAgICBpZiAoLW5vdCAkc2NyaXB0OktleSkgeyBGYWlsICfQkdC10Lcg0LTQtdC50YHRgtCy0YPRjtGJ0LXQs9C+INC60LvRjtGH0LAg0L3QsNGB0YLRgNC+0LjRgtGMINC90LXQu9GM0LfRjy4g0JrQu9GO0Ycg0L/RgNC40YXQvtC00LjRgiDQsiDRh9Cw0YIg0LfQsNC60LDQt9CwIEZ1blBheS4nOyAkZ2xvYmFsOk5leHVzRXhpdCA9IDI7IHJldHVybiB9CiAgICBTYXkgJycKICAgIENob29zZS1Nb2RlbAogICAgU2F5ICcnCiAgICAkc2NyaXB0Ok5leHQgPSBOZXctT2JqZWN0IFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3Rbc3RyaW5nXQogICAgJHNjcmlwdDpOb3RlcyA9IE5ldy1PYmplY3QgU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdFtzdHJpbmddCiAgICAkc2NyaXB0OkNsaW5lRG9uZSA9ICRmYWxzZQogICAgJHNjcmlwdDpIYXNLaWxvID0gJGZhbHNlCiAgICAkc2NyaXB0Okhhc0NvbnRpbnVlID0gJGZhbHNlCiAgICBEZXRlY3QtRWRpdG9ycwogICAgJHBsYW4gPSBCdWlsZC1QbGFuCiAgICBTaG93LURldGVjdGVkCiAgICBDaG9vc2UtSXRlbXMgJHBsYW4KICAgIGlmICgkc2NyaXB0OlF1aXQpIHsgU2F5ICcgINCS0YvRhdC+0LQg0LHQtdC3INC40LfQvNC10L3QtdC90LjQuS4nOyAkZ2xvYmFsOk5leHVzRXhpdCA9IDM7IHJldHVybiB9CiAgICBpZiAoJHNjcmlwdDpEb1Jlc3RvcmUpIHsgSW52b2tlLVJlc3RvcmU7IHJldHVybiB9CiAgICBTYXkgJycKICAgICRuID0gUnVuLVBsYW4gJHBsYW4KICAgIENsZWFyLUtleVRyYWNlcwogICAgU2F5ICcnCiAgICBpZiAoJG4gLWVxIDApIHsgV2FybiAn0J3QuNGH0LXQs9C+INC90LUg0LLRi9Cx0YDQsNC90L4gLSDQuNC30LzQtdC90LXQvdC40Lkg0L3QtdGCLic7ICRnbG9iYWw6TmV4dXNFeGl0ID0gMzsgcmV0dXJuIH0KICAgIFNheSAnICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09JyBEYXJrWWVsbG93CiAgICBpZiAoJGdsb2JhbDpOZXh1c0V4aXQgLWVxIDApIHsgU2F5ICcgICDQk9C+0YLQvtCy0L4hINCn0YLQviDQtNCw0LvRjNGI0LU6JyBHcmVlbiB9IGVsc2UgeyBTYXkgJyAgINCT0L7RgtC+0LLQviDRgSDQvtGI0LjQsdC60LDQvNC4ICjRgdC8LiBbWF0g0LLRi9GI0LUpLiDQp9GC0L4g0LTQsNC70YzRiNC1OicgWWVsbG93IH0KICAgIGZvcmVhY2ggKCR4IGluICRzY3JpcHQ6TmV4dCkgeyBTYXkgIiAgICogJHgiIH0KICAgIGZvcmVhY2ggKCR4IGluICRzY3JpcHQ6Tm90ZXMpIHsgU2F5ICIgICAhICR4IiBZZWxsb3cgfQogICAgU2F5ICIgICDQkdCw0LvQsNC90YEg0Lgg0YDQsNGB0YXQvtC0OiAkU2l0ZS9zdGF0cy5odG1sIiBEYXJrR3JheQogICAgaWYgKFRlc3QtUGF0aCAkUnVuRGlyKSB7IFNheSAiICAg0JrQvtC/0LjQuCDQuNC30LzQtdC90ZHQvdC90YvRhSDRhNCw0LnQu9C+0LI6ICRSdW5EaXIgKNC+0YLQutCw0YIgLSDQt9Cw0L/Rg9GB0YLQuNGC0LUg0LXRidGRINGA0LDQtyDQuCDQstCy0LXQtNC40YLQtSBSKSIgRGFya0dyYXkgfQogICAgU2F5ICcgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0nIERhcmtZZWxsb3cKICAgIExvZyAoImluc3RhbGxlZDogbW9kZWw9ezB9IGl0ZW1zPXsxfSBleGl0PXsyfSIgLWYgJHNjcmlwdDpNb2RlbCwgJG4sICRnbG9iYWw6TmV4dXNFeGl0KQp9Cg==')) $script:NexusTuiGateways = @(('[{"id":"ru","name":"Россия","city":"Москва","origin":"https://178-236-242-96.nip.io","apiBaseUrl":"https://178-236-242-96.nip.io/v1","anthropicBaseUrl":"https://178-236-242-96.nip.io","aliases":["https://nexusallin.store"]},{"id":"eu","name":"Нидерланды","city":"Амстердам","origin":"https://94-228-161-75.nip.io","apiBaseUrl":"https://94-228-161-75.nip.io/v1","anthropicBaseUrl":"https://94-228-161-75.nip.io","aliases":["https://tokine.94-228-161-75.nip.io"]},{"id":"us","name":"США","city":"Вирджиния","origin":"https://34-227-208-238.nip.io","apiBaseUrl":"https://34-227-208-238.nip.io/v1","anthropicBaseUrl":"https://34-227-208-238.nip.io","aliases":[]}]' | ConvertFrom-Json) | ForEach-Object { $_ }) # NEXUS setup worker: shared non-interactive operations and health probes. function Get-NexusGuiOrderedItems($Items, $Detected) { $selected = @($Items | Where-Object { $_.Selected }) if (@($selected | Where-Object { $_.Action -like '*-install' }).Count -and -not $Detected.Node) { $node = $Items | Where-Object { $_.Action -eq 'node' } | Select-Object -First 1 if ($node -and $selected.Action -notcontains 'node') { $selected += $node } } $order = @('policy', 'node', 'git', 'desktop', 'claude-install', 'codex-install', 'qwen-install', 'opencode-install', 'cline', 'claude-ext', 'claude', 'codex', 'qwen', 'opencode', 'kilo', 'aider', 'continue', 'nexus-code', 'roblox', 'check') return @($selected | Sort-Object { [array]::IndexOf($order, $_.Action) }) } function Invoke-NexusGuiWorkerOperation([string]$Kind, $Payload) { if ($Payload.Site) { $script:Site = $Payload.Site.TrimEnd('/'); $script:OpenAiBase = "$Site/v1" } if ($Payload.Key) { $script:Key = $Payload.Key } switch ($Kind) { 'discover' { if ($script:GuiShared.Preview) { $previewItems = @( [pscustomobject]@{ Label = 'NEXUS Desktop'; Action = 'desktop'; Selected = $false; Arg = $null }, [pscustomobject]@{ Label = 'NEXUS CLI'; Action = 'nexus-code'; Selected = $false; Arg = $null }, [pscustomobject]@{ Label = 'Roblox Studio: плагин NEXUS AI'; Action = 'roblox'; Selected = $false; Arg = $null }, [pscustomobject]@{ Label = 'VS Code: агент Cline'; Action = 'cline'; Selected = $false; Arg = $null }, [pscustomobject]@{ Label = 'Claude Code'; Action = 'claude-install'; Selected = $false; Arg = $null }, [pscustomobject]@{ Label = 'Codex CLI'; Action = 'codex-install'; Selected = $false; Arg = $null } ) $only = if ($env:NEXUS_ONLY) { @($env:NEXUS_ONLY.Split(',')) } elseif ($env:NEXUS_MODE -eq 'cli') { @('nexus-code') } elseif ($env:NEXUS_MODE -eq 'roblox') { @('roblox') } else { @() } foreach ($item in $previewItems) { $item.Selected = $only -contains $item.Action } return @{ Items = $previewItems; Detected = @{ Node = 'preview' }; RestoreAvailable = $true } } $script:ClineDone = $false; $script:HasKilo = $false; $script:HasContinue = $false Detect-Editors $plan = Build-Plan $items = @($plan | ForEach-Object { $_ }) if ($items.Action -notcontains 'roblox') { $items += [pscustomobject]@{ Label = 'Roblox Studio: плагин NEXUS AI - установить'; Action = 'roblox'; Selected = $false; Arg = $null } } if ((Get-Command Apply-NexusDesktop -ErrorAction SilentlyContinue) -and $items.Action -notcontains 'desktop') { $items = @([pscustomobject]@{ Label = 'NEXUS Desktop - установить приложение'; Action = 'desktop'; Selected = $false; Arg = $null }) + $items } $only = if ($env:NEXUS_ONLY) { @($env:NEXUS_ONLY.Split(',')) } elseif ($env:NEXUS_MODE -eq 'cli') { @('nexus-code') } elseif ($env:NEXUS_MODE -eq 'roblox') { @('roblox') } else { @() } foreach ($item in $items) { $item.Selected = $only -contains $item.Action } $restoreAvailable = [bool](Get-ChildItem -LiteralPath $BackupRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d{8}_\d{6}(_\d+)?$' -and (Test-Path (Join-Path $_.FullName 'manifest.tsv')) } | Select-Object -First 1) return @{ Items = $items; Detected = $script:Detected; RestoreAvailable = $restoreAvailable } } 'catalog' { if ($script:GuiShared.Preview) { return @{ Models = @( @{ Id = 'deepseek-v4-flash'; Label = 'DeepSeek V4 Flash · ×1.5' }, @{ Id = 'claude-haiku-5.5'; Label = 'Claude Haiku 5.5 · ×1.5' }, @{ Id = 'gpt-6-luna'; Label = 'GPT-6 Luna · ×2' } ) } } $script:Catalog = Get-Catalog if (-not $script:Catalog -or -not $script:Catalog.Count) { throw 'Не удалось получить список моделей. Проверьте соединение и нажмите «Обновить».' } $script:GuiCatalogSite = $script:Site $rows = @(foreach ($id in (Get-CatalogOrder)) { @{ Id = $id; Label = (Get-ModelLabel $id) } }) return @{ Models = $rows } } 'validate' { if ($Payload.Key -notmatch '^sk-[A-Za-z0-9_\-]{16,}$') { return @{ State = 'format'; Message = 'Вставьте ключ целиком: он начинается с sk-.' } } if ($script:GuiShared.Preview) { return @{ State = 'ok'; Message = 'Демонстрация · ключ принят, баланс 10 млн токенов' } } $state = Test-Key $Payload.Key $message = switch ($state) { 'ok' { 'Ключ активен. Можно продолжать.' } 'expired' { 'Срок ключа истёк. Пополните его на FunPay.' } 'bad' { 'Ключ не найден или отключён. Проверьте его целиком.' } default { 'Сервер не ответил. Проверьте сеть или выберите другой шлюз.' } } return @{ State = $state; Message = $message } } 'restore' { if (-not $script:GuiShared.Preview) { $backup = Get-ChildItem -LiteralPath $BackupRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d{8}_\d{6}(_\d+)?$' -and (Test-Path (Join-Path $_.FullName 'manifest.tsv')) } | Select-Object -First 1 if (-not $backup) { throw 'Сохранённых настроек для восстановления пока нет.' } Invoke-Restore # A GUI session may install again after rollback; the next run needs a fresh backup. $script:BackedUp = @{} $script:Stamp = Get-Date -Format 'yyyyMMdd_HHmmss' $script:RunDir = Join-Path $BackupRoot $Stamp $suffix = 2 while ((Test-Path $RunDir) -or (Test-Path ($RunDir + '.restored'))) { $script:RunDir = Join-Path $BackupRoot ("{0}_{1}" -f $Stamp, $suffix); $suffix++ } } return @{ Message = 'Последние сохранённые настройки восстановлены. Установленные программы остаются на компьютере.' } } 'install' { if ($Payload.Key -notmatch '^sk-[A-Za-z0-9_\-]{16,}$') { throw 'Вставьте ключ целиком: он начинается с sk-.' } $script:Model = [string]$Payload.Model if (-not $Model) { throw 'Выберите модель.' } $script:AllModels = [bool]$Payload.AllModels $script:ClineDone = $false $script:Next = New-Object System.Collections.Generic.List[string] $script:Notes = New-Object System.Collections.Generic.List[string] if (-not $script:GuiShared.Preview) { if ($script:GuiCatalogSite -ne $script:Site) { throw 'После смены шлюза обновите список моделей.' } $state = Test-Key $Payload.Key if ($state -ne 'ok') { throw 'Ключ не удалось проверить. Нажмите «Проверить ключ» или выберите другой шлюз.' } if (-not $script:Catalog -or -not $script:Catalog.ContainsKey($Model)) { throw 'Модель недоступна. Обновите список моделей.' } } $script:FastModel = $Model foreach ($candidate in $FastCandidates) { if ($script:Catalog -and $script:Catalog.ContainsKey($candidate)) { $script:FastModel = $candidate; break } } $items = @(Get-NexusGuiOrderedItems $Payload.Items $script:Detected) if (-not $items.Count) { throw 'Выберите хотя бы одну программу.' } $done = 0; $errors = 0 foreach ($item in $items) { if ($script:GuiShared.CancelRequested) { break } $script:GuiShared.Events.Enqueue(@{ Type = 'item'; Id = $item.Id; State = 'running'; Text = $item.Label; Completed = $done; Total = $items.Count }) try { $global:NexusExit = 0 if ($script:GuiShared.Preview) { Start-Sleep -Milliseconds 650 } elseif ($item.Action -eq 'desktop') { Apply-NexusDesktop } else { $item.Selected = $true; $null = Run-Plan @($item) } if ($global:NexusExit -ne 0) { throw 'Шаг завершился с ошибкой. Подробности ниже.' } $done++ $script:GuiShared.Events.Enqueue(@{ Type = 'item'; Id = $item.Id; State = 'done'; Text = $item.Label; Completed = $done; Total = $items.Count }) } catch { $done++; $errors++ $safeMessage = $_.Exception.Message if ($script:Key) { $safeMessage = $safeMessage.Replace($script:Key, 'sk-***') } $script:GuiShared.Events.Enqueue(@{ Type = 'item'; Id = $item.Id; State = 'error'; Text = $safeMessage; Completed = $done; Total = $items.Count }) } } $canRestore = [bool]$script:GuiShared.Preview if (-not $canRestore -and (Test-Path $BackupRoot)) { $canRestore = [bool](Get-ChildItem -LiteralPath $BackupRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d{8}_\d{6}(_\d+)?$' -and (Test-Path (Join-Path $_.FullName 'manifest.tsv')) } | Select-Object -First 1) } return @{ Completed = $done; Total = $items.Count; Errors = $errors; Cancelled = [bool]$script:GuiShared.CancelRequested; Next = @($script:Next); Notes = @($script:Notes); RestoreAvailable = $canRestore } } default { throw "Unknown GUI operation: $Kind" } } } function New-NexusGuiWorker([string]$EngineSource, [switch]$Preview) { $shared = [hashtable]::Synchronized(@{ Events = (New-Object 'System.Collections.Concurrent.ConcurrentQueue[object]'); CancelRequested = $false; Preview = [bool]$Preview }) $runspace = [RunspaceFactory]::CreateRunspace() $runspace.ApartmentState = 'STA'; $runspace.ThreadOptions = 'ReuseThread'; $runspace.Open() $powershell = [PowerShell]::Create(); $powershell.Runspace = $runspace $bootstrap = { param($Source, $Shared, $OperationDefinition, $OrderDefinition) if ($Source) { . ([ScriptBlock]::Create($Source)) } $script:GuiShared = $Shared $Auto = $true $ProgressPreference = 'SilentlyContinue' Set-Item Function:Invoke-NexusGuiWorkerOperation ([ScriptBlock]::Create($OperationDefinition)) Set-Item Function:Get-NexusGuiOrderedItems ([ScriptBlock]::Create($OrderDefinition)) function Send-GuiMessage([string]$Text, [string]$Level = 'info') { if ($script:Key) { $Text = $Text.Replace($script:Key, 'sk-***') } $script:GuiShared.Events.Enqueue(@{ Type = 'message'; Text = $Text; Level = $Level }) } function Say([string]$Text = '', [string]$Color = '') { Send-GuiMessage $Text } function Ok([string]$Text) { Send-GuiMessage $Text 'ok' } function Step([string]$Text) { Send-GuiMessage $Text } function Warn([string]$Text) { Send-GuiMessage $Text 'warning' } function Fail([string]$Text) { Send-GuiMessage $Text 'error'; throw $Text } function Write-Host { param([Parameter(ValueFromRemainingArguments = $true)]$Rest) } function Ask { throw 'Это действие требует ввода. Выберите его в окне установщика.' } function Read-Host { throw 'Ввод из терминала отключён в графическом установщике.' } } try { $null = $powershell.AddScript($bootstrap.ToString()).AddArgument($EngineSource).AddArgument($shared).AddArgument(${function:Invoke-NexusGuiWorkerOperation}.ToString()).AddArgument(${function:Get-NexusGuiOrderedItems}.ToString()).Invoke() if ($powershell.HadErrors) { throw ($powershell.Streams.Error | Out-String) } } catch { $runspace.Dispose(); throw } finally { $powershell.Dispose() } return [pscustomobject]@{ Runspace = $runspace; Shared = $shared; Operation = $null } } function Start-NexusGuiTask($Worker, [string]$Kind, $Payload = @{}) { if ($Worker.Operation) { throw 'Дождитесь завершения текущего действия.' } $powershell = [PowerShell]::Create(); $powershell.Runspace = $Worker.Runspace $null = $powershell.AddScript('param($Kind,$Payload) Invoke-NexusGuiWorkerOperation $Kind $Payload').AddArgument($Kind).AddArgument($Payload) $operation = [pscustomobject]@{ PowerShell = $powershell; Async = $powershell.BeginInvoke(); Kind = $Kind; Worker = $Worker; Secret = [string]$Payload.Key } $Worker.Operation = $operation return $operation } function Receive-NexusGuiTask($Operation) { if (-not $Operation.Async.IsCompleted) { return $null } try { $result = @($Operation.PowerShell.EndInvoke($Operation.Async)) if ($Operation.PowerShell.HadErrors) { throw ($Operation.PowerShell.Streams.Error | Out-String) } return @{ Data = $(if ($result.Count) { $result[-1] } else { $null }); Error = $null } } catch { $message = $_.Exception.Message if ($Operation.Secret) { $message = $message.Replace($Operation.Secret, 'sk-***') } return @{ Data = $null; Error = $message } } finally { $Operation.Worker.Operation = $null; $Operation.PowerShell.Dispose() } } function Close-NexusGuiWorker($Worker) { if (-not $Worker) { return } if ($Worker.Operation) { $Worker.Shared.CancelRequested = $true; return } $Worker.Runspace.Close(); $Worker.Runspace.Dispose() } function Test-NexusGuiHealth([string]$Content) { try { $health = $Content | ConvertFrom-Json -ErrorAction Stop return $health.status -eq 'ok' -and $health.service -eq 'nexus-gateway' -and [bool]$health.version -and $health.database -is [bool] -and $health.database } catch { return $false } } function New-NexusGuiPing([string]$Url) { $ps = [PowerShell]::Create() $null = $ps.AddScript({ param($Url, $HealthValidator) Set-Item Function:Test-NexusGuiHealth ([ScriptBlock]::Create($HealthValidator)) try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072 } catch {} $best = $null for ($i = 0; $i -lt 2; $i++) { $timer = [Diagnostics.Stopwatch]::StartNew() try { $response = Invoke-WebRequest -Uri ($Url.TrimEnd('/') + '/health') -TimeoutSec 4 -UseBasicParsing -ErrorAction Stop if ([int]$response.StatusCode -eq 200 -and (Test-NexusGuiHealth ([string]$response.Content))) { $ms = [int]$timer.Elapsed.TotalMilliseconds if ($null -eq $best -or $ms -lt $best) { $best = $ms } } } catch {} } return @{ Url = $Url; Milliseconds = $best } }.ToString()).AddArgument($Url).AddArgument(${function:Test-NexusGuiHealth}.ToString()) return [pscustomobject]@{ PowerShell = $ps; Async = $ps.BeginInvoke(); Url = $Url } } # Mouse-driven console setup. Rendering and actions are pure; only Start-NexusTui owns the terminal. function New-NexusTuiState { param([object[]]$Gateways = @(), [switch]$Preview) # Windows PowerShell 5.1 preserves a ConvertFrom-Json array as one pipeline object. while ($Gateways.Count -eq 1 -and $Gateways[0] -is [array]) { $Gateways = $Gateways[0] } if (-not $Gateways.Count) { $Gateways = @( @{ Id = 'ru'; Name = 'Россия'; Origin = 'https://178-236-242-96.nip.io' }, @{ Id = 'eu'; Name = 'Нидерланды'; Origin = 'https://94-228-161-75.nip.io' }, @{ Id = 'us'; Name = 'США'; Origin = 'https://34-227-208-238.nip.io' } ) } $entries = @(foreach ($gateway in $Gateways) { $url = if ($gateway.Url -is [string]) { $gateway.Url } elseif ($gateway.Origin -is [string]) { $gateway.Origin } else { '' } if (-not $url -or $url -notmatch '^https://[^/\s]+(?:/[^\s]*)?$') { throw 'Не удалось прочитать адрес шлюза. Скачайте свежую копию установщика.' } @{ Id = $gateway.Id; Name = $gateway.Name; Url = $url.TrimEnd('/'); City = $gateway.City; Aliases = @($gateway.Aliases); Milliseconds = $null; Health = 'pending' } }) $site = if ($entries.Count) { $entries[0].Url } else { '' } $preferred = $entries | Where-Object { $_.Url -eq ([string]$env:NEXUS_SITE).TrimEnd('/') -or $_.Aliases -contains ([string]$env:NEXUS_SITE).TrimEnd('/') } | Select-Object -First 1 if ($preferred) { $site = $preferred.Url } $state = @{ Mode = 'main'; KeyVisible = $false; Key = [string]$env:NEXUS_KEY; KeyFocus = $false; KeyStatus = 'Ключ приходит в чат заказа FunPay. Нажмите «Вставить».' Site = $site; Gateways = $entries; CatalogSite = ''; Model = ''; Models = @(); AllModels = $true Items = @(); ItemStatuses = @{}; ProgramOffset = 0; ProgramPage = 1; ModelOffset = 0; ModelPage = 1; LogOffset = 0 Busy = $false; Kind = ''; Command = $null; CancelRequested = $false; ShouldClose = $false; ExitCode = 3 Message = 'Подготовка установщика…'; Log = (New-Object System.Collections.Generic.List[string]); Hover = ''; Progress = 0 RestoreAvailable = $false; PingUpdated = ''; PingBusy = $false; NextPingRefresh = [DateTime]::MinValue; Preview = [bool]$Preview } if ($Preview) { $state.Models = @( @{ Id = 'deepseek-v4-flash'; Label = 'DeepSeek V4 Flash · ×1.5' }, @{ Id = 'claude-haiku-5.5'; Label = 'Claude Haiku 5.5 · ×1.5' }, @{ Id = 'gpt-6-luna'; Label = 'GPT-6 Luna · ×2' } ) $state.Model = $state.Models[0].Id; $state.CatalogSite = $site $state.Items = @( [pscustomobject]@{ Id = 'item0'; Label = 'NEXUS Desktop'; Action = 'desktop'; Selected = $false; Arg = $null }, [pscustomobject]@{ Id = 'item1'; Label = 'NEXUS CLI'; Action = 'nexus-code'; Selected = $false; Arg = $null }, [pscustomobject]@{ Id = 'item2'; Label = 'Roblox Studio: плагин NEXUS AI'; Action = 'roblox'; Selected = $false; Arg = $null }, [pscustomobject]@{ Id = 'item3'; Label = 'VS Code: агент Cline'; Action = 'cline'; Selected = $false; Arg = $null }, [pscustomobject]@{ Id = 'item4'; Label = 'Claude Code'; Action = 'claude-install'; Selected = $false; Arg = $null }, [pscustomobject]@{ Id = 'item5'; Label = 'Codex CLI'; Action = 'codex-install'; Selected = $false; Arg = $null } ) $only = if ($env:NEXUS_ONLY) { @($env:NEXUS_ONLY.Split(',')) } elseif ($env:NEXUS_MODE -eq 'cli') { @('nexus-code') } elseif ($env:NEXUS_MODE -eq 'roblox') { @('roblox') } else { @() } foreach ($item in $state.Items) { $item.Selected = $only -contains $item.Action } foreach ($entry in $state.Gateways) { $entry.Milliseconds = switch ($entry.Id) { 'ru' { 28 }; 'eu' { 72 }; default { 151 } }; $entry.Health = 'ok' } $state.PingUpdated = 'Демонстрация'; $state.RestoreAvailable = $true; $state.Message = 'Выберите программы мышью и нажмите «Установить».' } return $state } function Add-NexusTuiText($Frame, [int]$X, [int]$Y, [string]$Text, [string]$Tone = 'normal', [int]$Limit = 0) { if ($Y -lt 0 -or $Y -ge $Frame.Height -or $X -lt 0 -or $X -ge $Frame.Width) { return } $available = [Math]::Max(0, $Frame.Width - $X - 1) if (-not $available) { return } if ($Limit -gt 0) { $available = [Math]::Min($available, $Limit) } $textValue = [regex]::Replace($Text, '[\x00-\x1f\x7f]', ' ') if ($textValue.Length -gt $available) { $textValue = if ($available -gt 1) { $textValue.Substring(0, $available - 1) + '…' } else { $textValue.Substring(0, $available) } } if (-not $textValue.Length) { return } $line = $Frame.Lines[$Y] $Frame.Lines[$Y] = $line.Substring(0, $X) + $textValue + $line.Substring($X + $textValue.Length) $Frame.Spans.Add(@{ X = $X; Y = $Y; Text = $textValue; Tone = $Tone }) } function Add-NexusTuiHit($Frame, [string]$Id, [int]$X, [int]$Y, [int]$Width, [int]$Height = 1) { if ($X -lt 0 -or $Y -lt 0 -or $X -ge $Frame.Width -or $Y -ge $Frame.Height) { return } $hitWidth = [Math]::Min($Width, $Frame.Width - $X - 1) if ($hitWidth -gt 0 -and $Height -gt 0) { $Frame.HitBoxes.Add(@{ Id = $Id; X = $X; Y = $Y; Width = $hitWidth; Height = [Math]::Min($Height, $Frame.Height - $Y) }) } } function Add-NexusTuiButton($Frame, $State, [string]$Id, [int]$X, [int]$Y, [string]$Label, [bool]$Enabled = $true, [bool]$Primary = $false) { $text = '[ ' + $Label + ' ]' $tone = if (-not $Enabled) { 'muted' } elseif ($State.Hover -eq $Id) { 'hover' } elseif ($Primary) { 'accent' } else { 'button' } Add-NexusTuiText $Frame $X $Y $text $tone if ($Enabled) { Add-NexusTuiHit $Frame $Id $X $Y $text.Length } return $text.Length } function Test-NexusTuiReady($State) { return -not $State.Busy -and $State.Key.Trim() -match '^sk-[A-Za-z0-9_\-]{16,}$' -and [bool]$State.Site -and $State.CatalogSite -eq $State.Site -and [bool]$State.Model -and @($State.Models | Where-Object { $_.Id -eq $State.Model }).Count -gt 0 -and @($State.Items | Where-Object { $_.Selected -and $_.Action -ne 'check' }).Count -gt 0 } function Get-NexusTuiFrame { param($State, [int]$Width = 110, [int]$Height = 32) $Width = [Math]::Max(1, $Width); $Height = [Math]::Max(1, $Height) $frame = @{ Width = $Width; Height = $Height; Lines = [string[]]@(for ($i = 0; $i -lt $Height; $i++) { ' ' * $Width }); HitBoxes = (New-Object System.Collections.Generic.List[object]); Spans = (New-Object System.Collections.Generic.List[object]) } if ($Width -lt 76 -or $Height -lt 24) { Add-NexusTuiText $frame 1 0 'NEXUS AI' 'accent' Add-NexusTuiText $frame 1 2 'Увеличьте окно терминала' 'normal' Add-NexusTuiText $frame 1 3 'Минимум: 76 столбцов × 24 строки.' 'muted' $null = Add-NexusTuiButton $frame $State 'close' 1 ([Math]::Min(5, $Height - 1)) $(if ($State.Busy) { 'Остановить' } else { 'Закрыть' }) return $frame } Add-NexusTuiText $frame 2 0 '▲ NEXUS AI' 'accent' Add-NexusTuiText $frame 16 0 'БЫСТРАЯ УСТАНОВКА' 'muted' if ($State.Preview) { Add-NexusTuiText $frame ($Width - 30) 0 'Предпросмотр · без изменений' 'warn' } Add-NexusTuiText $frame 2 1 'Выберите шлюз, ключ, модель и программы — всё доступно мышью.' 'muted' Add-NexusTuiText $frame 2 2 ('─' * ($Width - 4)) 'line' $cardWidth = [int][Math]::Floor(($Width - 6) / 3) for ($i = 0; $i -lt [Math]::Min(3, $State.Gateways.Count); $i++) { $entry = $State.Gateways[$i]; $x = 2 + $i * ($cardWidth + 1); $chosen = $State.Site -eq $entry.Url $tone = if ($State.Hover -eq ('gateway:' + $entry.Id)) { 'hover' } elseif ($chosen) { 'accent' } else { 'muted' } Add-NexusTuiText $frame $x 3 ('┌' + ('─' * ($cardWidth - 2)) + '┐') $tone Add-NexusTuiText $frame $x 4 ('│ ' + $(if ($chosen) { '● ' } else { '○ ' }) + $entry.Name) $tone ($cardWidth - 1) Add-NexusTuiText $frame ($x + $cardWidth - 1) 4 '│' $tone $latency = if ($entry.Health -eq 'pending') { 'проверяем доступность…' } elseif ($null -ne $entry.Milliseconds) { "$($entry.Milliseconds) мс · доступен" } else { 'нет ответа' } Add-NexusTuiText $frame $x 5 ('│ ' + $latency) $(if ($entry.Health -eq 'ok') { 'good' } else { 'muted' }) ($cardWidth - 1) Add-NexusTuiText $frame ($x + $cardWidth - 1) 5 '│' $tone Add-NexusTuiText $frame $x 6 ('└' + ('─' * ($cardWidth - 2)) + '┘') $tone if (-not $State.Busy) { Add-NexusTuiHit $frame ('gateway:' + $entry.Id) $x 3 $cardWidth 4 } } $pingText = if ($State.PingBusy) { 'Измеряем HTTPS…' } elseif ($State.PingUpdated) { 'Обновлено ' + $State.PingUpdated + ' · каждые 10 с' } else { 'Пинг по HTTPS · обновление каждые 10 с' } Add-NexusTuiText $frame 2 7 $pingText 'muted' ($Width - 22) $null = Add-NexusTuiButton $frame $State 'ping:refresh' ($Width - 18) 7 'Обновить' (-not $State.PingBusy) $keyWidth = $Width - 46 $shown = if ($State.KeyVisible) { $State.Key } elseif ($State.Key.Length) { '•' * [Math]::Min($State.Key.Length, $keyWidth - 9) } else { 'нажмите «Вставить»' } Add-NexusTuiText $frame 2 8 ('Ключ: ' + $shown) $(if ($State.KeyFocus) { 'accent' } else { 'normal' }) $keyWidth if (-not $State.Busy) { Add-NexusTuiHit $frame 'key:focus' 2 8 $keyWidth } $x = $Width - 42 $x += (Add-NexusTuiButton $frame $State 'key:paste' $x 8 'Вставить' (-not $State.Busy)) + 1 $x += (Add-NexusTuiButton $frame $State 'key:toggle' $x 8 $(if ($State.KeyVisible) { 'Скрыть' } else { 'Показать' }) (-not $State.Busy)) + 1 $null = Add-NexusTuiButton $frame $State 'key:check' $x 8 'Проверить' (-not $State.Busy) Add-NexusTuiText $frame 2 9 $State.KeyStatus 'muted' ($Width - 4) $model = $State.Models | Where-Object { $_.Id -eq $State.Model } | Select-Object -First 1 $label = if ($model) { $model.Label } else { 'получаем список…' } Add-NexusTuiText $frame 2 10 ('Модель: ' + $label + ' ▾') $(if ($State.Hover -eq 'models:open') { 'hover' } else { 'normal' }) ($Width - 29) if (-not $State.Busy -and $State.Models.Count) { Add-NexusTuiHit $frame 'models:open' 2 10 ($Width - 31) } $null = Add-NexusTuiButton $frame $State 'models:refresh' ($Width - 27) 10 'R' (-not $State.Busy) $null = Add-NexusTuiButton $frame $State 'models:all' ($Width - 19) 10 $(if ($State.AllModels) { 'x Все модели' } else { 'Все модели' }) (-not $State.Busy) $selected = @($State.Items | Where-Object { $_.Selected -and $_.Action -ne 'check' }).Count Add-NexusTuiText $frame 2 11 ("Программы · выбрано $selected") 'accent' $null = Add-NexusTuiButton $frame $State 'programs:all' ($Width - 26) 11 'Всё' (-not $State.Busy) $null = Add-NexusTuiButton $frame $State 'programs:none' ($Width - 16) 11 'Снять' (-not $State.Busy) $availableRows = [Math]::Max(1, $Height - 18) $State.ProgramPage = $availableRows $State.ProgramOffset = [Math]::Max(0, [Math]::Min($State.ProgramOffset, [Math]::Max(0, $State.Items.Count - $availableRows))) for ($i = 0; $i -lt $availableRows; $i++) { $index = $State.ProgramOffset + $i if ($index -ge $State.Items.Count) { break } $item = $State.Items[$index]; $status = $State.ItemStatuses[$item.Id] $statusText = switch ($status) { 'running' { 'Выполняется…' }; 'done' { 'OK Готово' }; 'error' { 'Ошибка ↓' }; default { '' } } $tone = if ($State.Hover -eq "program:$index") { 'hover' } elseif ($item.Selected) { 'normal' } else { 'muted' } Add-NexusTuiText $frame 2 (12 + $i) ($(if ($item.Selected) { '[x] ' } else { '[ ] ' }) + $item.Label) $tone ($Width - 21) Add-NexusTuiText $frame ($Width - 18) (12 + $i) $statusText $(if ($status -eq 'done') { 'good' } elseif ($status -eq 'error') { 'error' } else { 'warn' }) 15 if (-not $State.Busy) { Add-NexusTuiHit $frame "program:$index" 2 (12 + $i) ($Width - 5) } } if ($State.Items.Count -gt $availableRows) { $null = Add-NexusTuiButton $frame $State 'programs:up' ($Width - 7) 12 '↑' $null = Add-NexusTuiButton $frame $State 'programs:down' ($Width - 7) (11 + $availableRows) '↓' } Add-NexusTuiText $frame 2 ($Height - 6) ('─' * ($Width - 4)) 'line' Add-NexusTuiText $frame 2 ($Height - 5) $State.Message $(if ($State.ExitCode -eq 1) { 'warn' } else { 'normal' }) ($Width - 4) $barWidth = $Width - 16; $filled = [int][Math]::Floor($barWidth * [Math]::Max(0, [Math]::Min(100, $State.Progress)) / 100) Add-NexusTuiText $frame 2 ($Height - 4) (('━' * $filled) + ('─' * ($barWidth - $filled)) + (' {0,3}%' -f [int]$State.Progress)) $(if ($State.Busy) { 'accent' } else { 'muted' }) $x = 2 $x += (Add-NexusTuiButton $frame $State 'install' $x ($Height - 3) 'Установить' (Test-NexusTuiReady $State) $true) + 2 $x += (Add-NexusTuiButton $frame $State 'cancel' $x ($Height - 3) $(if ($State.Busy) { 'Остановить' } else { 'Закрыть' }) (-not $State.Busy -or $State.Kind -eq 'install')) + 2 $x += (Add-NexusTuiButton $frame $State 'restore' $x ($Height - 3) 'Восстановить' (-not $State.Busy -and $State.RestoreAvailable)) + 2 $null = Add-NexusTuiButton $frame $State 'log:open' $x ($Height - 3) 'Подробности' Add-NexusTuiText $frame 2 ($Height - 2) 'Мышь: выбрать · колесо: прокрутить · настройки сохраняются с резервной копией.' 'muted' ($Width - 4) if ($State.Mode -ne 'main') { $left = 4; $top = 3; $popupWidth = $Width - 8; $popupHeight = $Height - 7 for ($row = $top; $row -lt $top + $popupHeight; $row++) { Add-NexusTuiText $frame $left $row (' ' * $popupWidth) 'popup' } # Modal hitboxes replace all underlying controls. $frame.HitBoxes.Clear() Add-NexusTuiText $frame $left $top ('┌' + ('─' * ($popupWidth - 2)) + '┐') 'accent' Add-NexusTuiText $frame $left ($top + $popupHeight - 1) ('└' + ('─' * ($popupWidth - 2)) + '┘') 'accent' if ($State.Mode -eq 'models') { Add-NexusTuiText $frame ($left + 2) ($top + 1) 'Выберите основную модель' 'accent' $null = Add-NexusTuiButton $frame $State 'models:select-all' ($left + 2) ($top + 2) 'Выбрать все модели' $null = Add-NexusTuiButton $frame $State 'models:clear-all' ($left + 28) ($top + 2) 'Снять выбор всех' Add-NexusTuiText $frame ($left + 2) ($top + 3) $(if ($State.AllModels) { "В программы будут добавлены все модели ($($State.Models.Count))." } else { 'В программах останутся основная и рекомендованные модели.' }) 'muted' ($popupWidth - 5) $page = [Math]::Max(1, $popupHeight - 7); $State.ModelPage = $page $State.ModelOffset = [Math]::Max(0, [Math]::Min($State.ModelOffset, [Math]::Max(0, $State.Models.Count - $page))) for ($row = 0; $row -lt $page; $row++) { $index = $State.ModelOffset + $row if ($index -ge $State.Models.Count) { break } $model = $State.Models[$index] Add-NexusTuiText $frame ($left + 2) ($top + 4 + $row) ($(if ($State.Model -eq $model.Id) { '● ' } else { '○ ' }) + $model.Label) $(if ($State.Hover -eq "model:$index") { 'hover' } else { 'normal' }) ($popupWidth - 8) Add-NexusTuiHit $frame "model:$index" ($left + 2) ($top + 4 + $row) ($popupWidth - 5) } $null = Add-NexusTuiButton $frame $State 'models:prev' ($left + 2) ($top + $popupHeight - 2) '↑ Предыдущие' $null = Add-NexusTuiButton $frame $State 'models:next' ($left + 20) ($top + $popupHeight - 2) 'Следующие ↓' $null = Add-NexusTuiButton $frame $State 'models:close' ($left + $popupWidth - 16) ($top + $popupHeight - 2) 'Закрыть' } elseif ($State.Mode -eq 'restore-confirm') { Add-NexusTuiText $frame ($left + 3) ($top + 3) 'Восстановить предыдущие настройки?' 'accent' Add-NexusTuiText $frame ($left + 3) ($top + 5) 'Вернём файлы из последней резервной копии.' 'normal' ($popupWidth - 6) Add-NexusTuiText $frame ($left + 3) ($top + 6) 'Установленные программы останутся на компьютере.' 'muted' ($popupWidth - 6) $null = Add-NexusTuiButton $frame $State 'restore:yes' ($left + 3) ($top + 9) 'Восстановить' $true $true $null = Add-NexusTuiButton $frame $State 'restore:no' ($left + 24) ($top + 9) 'Отмена' } elseif ($State.Mode -eq 'log') { Add-NexusTuiText $frame ($left + 2) ($top + 1) 'Подробности установки' 'accent' $page = [Math]::Max(1, $popupHeight - 5) $State.LogOffset = [Math]::Max(0, [Math]::Min($State.LogOffset, [Math]::Max(0, $State.Log.Count - $page))) for ($row = 0; $row -lt $page; $row++) { $index = $State.LogOffset + $row if ($index -ge $State.Log.Count) { break } $text = $State.Log[$index] if ($State.Key) { $text = $text.Replace($State.Key, 'sk-***') } Add-NexusTuiText $frame ($left + 2) ($top + 2 + $row) $text 'normal' ($popupWidth - 5) } $null = Add-NexusTuiButton $frame $State 'log:up' ($left + 2) ($top + $popupHeight - 2) '↑' $null = Add-NexusTuiButton $frame $State 'log:down' ($left + 10) ($top + $popupHeight - 2) '↓' $null = Add-NexusTuiButton $frame $State 'models:close' ($left + $popupWidth - 16) ($top + $popupHeight - 2) 'Закрыть' } } return $frame } function Find-NexusTuiHit($Frame, [int]$X, [int]$Y) { for ($i = $Frame.HitBoxes.Count - 1; $i -ge 0; $i--) { $hit = $Frame.HitBoxes[$i] if ($X -ge $hit.X -and $X -lt ($hit.X + $hit.Width) -and $Y -ge $hit.Y -and $Y -lt ($hit.Y + $hit.Height)) { return [string]$hit.Id } } return '' } function Invoke-NexusTuiAction($State, [string]$Id, [int]$Wheel = 0) { if ($Wheel) { if ($State.Mode -eq 'models') { $State.ModelOffset = [Math]::Max(0, $State.ModelOffset - $Wheel * 3) } elseif ($State.Mode -eq 'log') { $State.LogOffset = [Math]::Max(0, $State.LogOffset - $Wheel * 3) } else { $State.ProgramOffset = [Math]::Max(0, $State.ProgramOffset - $Wheel * 3) } return } if (-not $Id) { return } if ($Id -eq 'cancel' -or $Id -eq 'close') { if ($State.Busy) { if ($State.Kind -eq 'install') { $State.CancelRequested = $true; $State.Message = 'Остановимся после текущего шага. Дождитесь его завершения.' } } else { $State.ShouldClose = $true } return } if ($Id -eq 'log:open') { $State.Mode = 'log'; $State.LogOffset = [Math]::Max(0, $State.Log.Count - 8); return } if ($Id -eq 'models:close' -or $Id -eq 'restore:no') { $State.Mode = 'main'; return } if ($Id -eq 'programs:up') { $State.ProgramOffset = [Math]::Max(0, $State.ProgramOffset - $State.ProgramPage); return } if ($Id -eq 'programs:down') { $State.ProgramOffset += $State.ProgramPage; return } if ($Id -eq 'models:prev') { $State.ModelOffset = [Math]::Max(0, $State.ModelOffset - $State.ModelPage); return } if ($Id -eq 'models:next') { $State.ModelOffset += $State.ModelPage; return } if ($Id -eq 'log:up') { $State.LogOffset = [Math]::Max(0, $State.LogOffset - 3); return } if ($Id -eq 'log:down') { $State.LogOffset += 3; return } if ($Id -eq 'ping:refresh') { $State.NextPingRefresh = [DateTime]::MinValue; return } if ($State.Busy) { return } switch -Regex ($Id) { '^gateway:(.+)$' { $entry = $State.Gateways | Where-Object { $_.Id -eq $Matches[1] } | Select-Object -First 1 if ($entry) { $State.Site = $entry.Url; $State.CatalogSite = ''; $State.Models = @(); $State.Model = '' $State.KeyStatus = 'Шлюз выбран. Проверим ключ перед установкой.' $State.Busy = $true; $State.Kind = 'catalog'; $State.Command = @{ Kind = 'catalog'; Payload = @{ Site = $State.Site } }; $State.Message = 'Получаем модели выбранного шлюза…' } } '^program:(\d+)$' { $index = [int]$Matches[1]; if ($index -lt $State.Items.Count) { $State.Items[$index].Selected = -not $State.Items[$index].Selected } } '^model:(\d+)$' { $index = [int]$Matches[1]; if ($index -lt $State.Models.Count) { $State.Model = $State.Models[$index].Id; $State.Mode = 'main' } } '^key:paste$' { $State.Command = @{ Kind = 'clipboard'; Payload = @{} }; $State.KeyFocus = $false } '^key:toggle$' { $State.KeyVisible = -not $State.KeyVisible } '^key:focus$' { $State.KeyFocus = $true } '^key:check$' { $State.Busy = $true; $State.Kind = 'validate'; $State.Command = @{ Kind = 'validate'; Payload = @{ Site = $State.Site; Key = $State.Key.Trim() } }; $State.Message = 'Проверяем ключ…' } '^models:open$' { if ($State.Models.Count) { $State.Mode = 'models'; $State.ModelOffset = 0 } } '^models:refresh$' { $State.Models = @(); $State.Model = ''; $State.CatalogSite = ''; $State.Busy = $true; $State.Kind = 'catalog'; $State.Command = @{ Kind = 'catalog'; Payload = @{ Site = $State.Site } }; $State.Message = 'Обновляем список моделей…' } '^models:all$' { $State.AllModels = -not $State.AllModels } '^models:select-all$' { $State.AllModels = $true; $State.Message = "В программы будут добавлены все модели ($($State.Models.Count))." } '^models:clear-all$' { $State.AllModels = $false; $State.Message = 'Добавим основную и рекомендованные модели.' } '^programs:all$' { foreach ($item in $State.Items) { $item.Selected = $true } } '^programs:none$' { foreach ($item in $State.Items) { $item.Selected = $false } } '^install$' { if (Test-NexusTuiReady $State) { $State.Busy = $true; $State.Kind = 'install'; $State.CancelRequested = $false; $State.Progress = 0 $State.Command = @{ Kind = 'install'; Payload = @{ Site = $State.Site; Key = $State.Key.Trim(); Model = $State.Model; AllModels = $State.AllModels; Items = $State.Items } } $State.Message = 'Проверяем ключ и начинаем установку…' } } '^restore$' { if ($State.RestoreAvailable) { $State.Mode = 'restore-confirm' } } '^restore:yes$' { if ($State.Mode -eq 'restore-confirm' -and $State.RestoreAvailable) { $State.Mode = 'main'; $State.Busy = $true; $State.Kind = 'restore'; $State.Command = @{ Kind = 'restore'; Payload = @{} }; $State.Message = 'Восстанавливаем настройки…' } } } } function Add-NexusTuiLog($State, [string]$Text) { if (-not $Text.Trim()) { return } if ($State.Key) { $Text = $Text.Replace($State.Key, 'sk-***') } $State.Log.Add($Text.Trim()) while ($State.Log.Count -gt 250) { $State.Log.RemoveAt(0) } } function Receive-NexusTuiOperation($State, [string]$Kind, $Result) { $State.Busy = $false if ($Result.Error) { $State.Message = 'Не удалось завершить действие. Откройте «Подробности».'; $State.ExitCode = 1; Add-NexusTuiLog $State $Result.Error; return } $data = $Result.Data switch ($Kind) { 'discover' { $State.Items = @($data.Items); $index = 0 foreach ($item in $State.Items) { $item | Add-Member -NotePropertyName Id -NotePropertyValue ("item$index") -Force; $index++ } $State.RestoreAvailable = [bool]$data.RestoreAvailable $State.Busy = $true; $State.Kind = 'catalog'; $State.Command = @{ Kind = 'catalog'; Payload = @{ Site = $State.Site } }; $State.Message = 'Получаем список моделей…' } 'catalog' { $State.Models = @($data.Models); $State.CatalogSite = $State.Site $preferred = $State.Models | Where-Object { $_.Id -eq $State.Model -or $_.Id -eq $env:NEXUS_MODEL } | Select-Object -First 1 $State.Model = if ($preferred) { $preferred.Id } elseif ($State.Models.Count) { $State.Models[0].Id } else { '' } $State.Message = 'Выберите программы мышью и нажмите «Установить».' } 'validate' { $State.KeyStatus = $data.Message; $State.Message = $data.Message } 'restore' { $State.Message = $data.Message; $State.RestoreAvailable = $false; $State.ExitCode = 0 } 'install' { $State.ExitCode = if ($data.Errors) { 1 } elseif ($data.Cancelled) { 3 } else { 0 } $State.Message = if ($data.Cancelled) { 'Остановлено. Выполненные шаги сохранены.' } elseif ($data.Errors) { 'Готово с ошибками. Откройте «Подробности».' } else { 'Готово! Перезапустите подключённые программы.' } $State.RestoreAvailable = [bool]$data.RestoreAvailable foreach ($line in @($data.Next) + @($data.Notes)) { Add-NexusTuiLog $State $line } } } } function Initialize-NexusTuiNative { if (-not ('NexusSetup.ConsoleOutput' -as [type])) { Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; namespace NexusSetup { public static class ConsoleOutput { [DllImport("kernel32.dll")] private static extern IntPtr GetStdHandle(int n); [DllImport("kernel32.dll", EntryPoint="WriteConsoleW", CharSet=CharSet.Unicode, ExactSpelling=true, SetLastError=true)] private static extern bool WriteUnicode(IntPtr handle, string text, uint count, out uint written, IntPtr reserved); public static void WriteText(string text) { // Console.Out can retain an OEM writer after the native code page changes. Keep Unicode end to end. IntPtr handle = GetStdHandle(-11); for (int offset = 0; offset < text.Length;) { int length = Math.Min(8192, text.Length - offset); if (offset + length < text.Length && Char.IsHighSurrogate(text[offset + length - 1])) length--; string part = text.Substring(offset, length); uint written; if (!WriteUnicode(handle, part, (uint)part.Length, out written, IntPtr.Zero)) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Unicode console output failed"); if (written == 0 || written > part.Length) throw new System.IO.IOException("Unicode console output was incomplete"); offset += (int)written; } } } } '@ } if ('NexusSetup.ConsoleInput' -as [type]) { return } Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; namespace NexusSetup { [StructLayout(LayoutKind.Sequential)] public struct Coord { public short X, Y; } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct KeyRecord { [MarshalAs(UnmanagedType.Bool)] public bool Down; public ushort Repeat, VirtualKey, Scan; public char Character; public uint Controls; } [StructLayout(LayoutKind.Sequential)] public struct MouseRecord { public Coord Position; public uint Buttons, Controls, Flags; } [StructLayout(LayoutKind.Explicit, Size=20)] public struct InputRecord { [FieldOffset(0)] public ushort Type; [FieldOffset(4)] public KeyRecord Key; [FieldOffset(4)] public MouseRecord Mouse; [FieldOffset(4)] public Coord Size; } public static class ConsoleInput { [DllImport("kernel32.dll")] public static extern IntPtr GetStdHandle(int n); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool GetConsoleMode(IntPtr handle, out uint mode); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool SetConsoleMode(IntPtr handle, uint mode); [DllImport("kernel32.dll", EntryPoint="ReadConsoleInputW", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool Read(IntPtr handle, [Out] InputRecord[] records, uint count, out uint read); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool GetNumberOfConsoleInputEvents(IntPtr handle, out uint count); } } '@ } function Write-NexusTuiFrame($Frame) { $esc = [char]27 $colors = @{ normal = '38;2;225;231;241'; muted = '38;2;131;148;174'; accent = '38;2;248;132;77'; line = '38;2;51;66;89'; good = '38;2;105;213;174'; warn = '38;2;242;190;112'; error = '38;2;244;132;124'; button = '38;2;208;220;239;48;2;31;43;63'; hover = '38;2;255;255;255;48;2;81;62;48'; popup = '38;2;225;231;241;48;2;20;30;46' } $builder = New-Object Text.StringBuilder $null = $builder.Append("$esc[0m$esc[38;2;225;231;241m$esc[48;2;11;16;26m") for ($row = 0; $row -lt $Frame.Height; $row++) { $null = $builder.Append("$esc[$($row + 1);1H$esc[2K").Append($Frame.Lines[$row].Substring(0, [Math]::Max(0, $Frame.Width - 1))) } foreach ($span in $Frame.Spans) { if ($span.X -lt 0 -or $span.Y -lt 0 -or $span.Y -ge $Frame.Height) { continue } $available = [Math]::Max(0, $Frame.Width - 1 - $span.X) if (-not $available) { continue } $spanText = $span.Text.Substring(0, [Math]::Min($span.Text.Length, $available)) $color = $colors[$span.Tone]; if (-not $color) { $color = $colors.normal } $null = $builder.Append("$esc[$($span.Y + 1);$($span.X + 1)H$esc[0m$esc[48;2;11;16;26m$esc[${color}m").Append($spanText) } $null = $builder.Append("$esc[0m$esc[?25l") [NexusSetup.ConsoleOutput]::WriteText($builder.ToString()) } function Start-NexusTui { param([string]$EngineSource = '', [object[]]$Gateways = @(), [switch]$Preview, [switch]$NoShow) $state = New-NexusTuiState -Gateways $Gateways -Preview:$Preview if ($NoShow) { return @{ State = $state; Frame = (Get-NexusTuiFrame $state 110 32) } } if ([Threading.Thread]::CurrentThread.ApartmentState -ne 'STA') { throw 'Запустите установщик через PowerShell -STA.' } Initialize-NexusTuiNative $inputHandle = [NexusSetup.ConsoleInput]::GetStdHandle(-10); $outputHandle = [NexusSetup.ConsoleInput]::GetStdHandle(-11) [uint32]$inputMode = 0; [uint32]$outputMode = 0 if (-not [NexusSetup.ConsoleInput]::GetConsoleMode($inputHandle, [ref]$inputMode) -or -not [NexusSetup.ConsoleInput]::GetConsoleMode($outputHandle, [ref]$outputMode)) { throw 'Откройте установщик в обычном терминале Windows.' } $oldEncoding = [Console]::OutputEncoding; $oldCursor = [Console]::CursorVisible; $esc = [char]27 $worker = $null; $operation = $null; $pings = @(); $lastFrame = ''; $mouseDown = $false; $renderAt = [DateTime]::MinValue try { [Console]::OutputEncoding = New-Object Text.UTF8Encoding $false $newInput = ($inputMode -band (-bnot 0x0247)) -bor 0x0098 if (-not [NexusSetup.ConsoleInput]::SetConsoleMode($inputHandle, $newInput) -or -not [NexusSetup.ConsoleInput]::SetConsoleMode($outputHandle, ($outputMode -bor 0x0004 -bor 0x0001))) { throw 'Этот терминал не поддерживает интерактивную установку.' } [NexusSetup.ConsoleOutput]::WriteText("$esc[?1049h$esc[2J$esc[H$esc[?25l"); [Console]::CursorVisible = $false $worker = New-NexusGuiWorker -EngineSource $EngineSource -Preview:$Preview if (-not $Preview) { $state.Busy = $true; $state.Kind = 'discover'; $state.Command = @{ Kind = 'discover'; Payload = @{} } } $records = New-Object 'NexusSetup.InputRecord[]' 64 while (-not $state.ShouldClose) { $width = [Console]::WindowWidth; $height = [Console]::WindowHeight $frame = Get-NexusTuiFrame $state $width $height [uint32]$count = 0 if ([NexusSetup.ConsoleInput]::GetNumberOfConsoleInputEvents($inputHandle, [ref]$count) -and $count) { [uint32]$read = 0 if ([NexusSetup.ConsoleInput]::Read($inputHandle, $records, [uint32][Math]::Min(64, $count), [ref]$read)) { for ($i = 0; $i -lt $read; $i++) { $record = $records[$i] if ($record.Type -eq 2) { $mouse = $record.Mouse $x = [int]$mouse.Position.X - [Console]::WindowLeft; $y = [int]$mouse.Position.Y - [Console]::WindowTop $state.Hover = Find-NexusTuiHit $frame $x $y if ($mouse.Flags -band 4) { $delta = [BitConverter]::ToInt16([BitConverter]::GetBytes([uint32]$mouse.Buttons), 2); Invoke-NexusTuiAction $state '' $(if ($delta -gt 0) { 1 } else { -1 }) } elseif (($mouse.Buttons -band 1) -and -not $mouseDown) { Invoke-NexusTuiAction $state $state.Hover } $mouseDown = [bool]($mouse.Buttons -band 1) # Later events in the same batch must use the newly opened modal or scroll page. $frame = Get-NexusTuiFrame $state $width $height } elseif ($record.Type -eq 1 -and $record.Key.Down) { $key = $record.Key if ($key.VirtualKey -eq 27) { if ($state.Mode -ne 'main') { $state.Mode = 'main' } else { Invoke-NexusTuiAction $state 'cancel' } } elseif ($key.Character -eq [char]3) { Invoke-NexusTuiAction $state 'cancel' } elseif (-not $state.Busy -and $state.KeyFocus) { if ($key.VirtualKey -eq 8) { if ($state.Key.Length) { $state.Key = $state.Key.Substring(0, $state.Key.Length - 1) } } elseif ($key.VirtualKey -eq 13) { $state.KeyFocus = $false } elseif ($key.Character -eq [char]22) { $state.Command = @{ Kind = 'clipboard'; Payload = @{} } } elseif ([int]$key.Character -ge 32 -and [int]$key.Character -le 126 -and $state.Key.Length -lt 250) { $state.Key += [string]$key.Character } } } } } } if ($state.CancelRequested -and $worker) { $worker.Shared.CancelRequested = $true } if ($worker) { $eventData = $null while ($worker.Shared.Events.TryDequeue([ref]$eventData)) { if ($eventData.Type -eq 'message') { Add-NexusTuiLog $state $eventData.Text } elseif ($eventData.Type -eq 'item') { $state.ItemStatuses[$eventData.Id] = $eventData.State if ($eventData.Total) { $state.Progress = 100 * $eventData.Completed / $eventData.Total } if ($eventData.State -eq 'running') { $state.Message = $eventData.Text } if ($eventData.State -eq 'error') { Add-NexusTuiLog $state $eventData.Text } } } } if ($operation) { $result = Receive-NexusGuiTask $operation if ($result) { $kind = $operation.Kind; $operation = $null; Receive-NexusTuiOperation $state $kind $result } } if ($state.Command -and -not $operation) { $command = $state.Command; $state.Command = $null if ($command.Kind -eq 'clipboard') { try { Add-Type -AssemblyName System.Windows.Forms; $state.Key = [Windows.Forms.Clipboard]::GetText().Trim(); $state.KeyStatus = 'Ключ вставлен. Нажмите «Проверить».' } catch { $state.KeyStatus = 'Буфер обмена занят. Нажмите «Вставить» ещё раз.' } } else { if ($command.Kind -eq 'install') { $worker.Shared.CancelRequested = $false } $operation = Start-NexusGuiTask $worker $command.Kind $command.Payload } } foreach ($ping in @($pings)) { if (-not $ping.Async.IsCompleted) { continue } try { $result = @($ping.PowerShell.EndInvoke($ping.Async))[-1] $entry = $state.Gateways | Where-Object { $_.Url -eq $ping.Url } | Select-Object -First 1 $entry.Milliseconds = $result.Milliseconds; $entry.Health = if ($null -ne $result.Milliseconds) { 'ok' } else { 'offline' } } catch { $entry = $state.Gateways | Where-Object { $_.Url -eq $ping.Url } | Select-Object -First 1; if ($entry) { $entry.Milliseconds = $null; $entry.Health = 'offline' } } finally { $ping.PowerShell.Dispose(); $pings = @($pings | Where-Object { $_ -ne $ping }) } if (-not $pings.Count) { $state.PingBusy = $false; $state.PingUpdated = [DateTime]::Now.ToString('HH:mm:ss'); $state.NextPingRefresh = [DateTime]::Now.AddSeconds(10) } } if (-not $pings.Count -and [DateTime]::Now -ge $state.NextPingRefresh) { if ($Preview) { $state.PingUpdated = 'Демонстрация'; $state.NextPingRefresh = [DateTime]::Now.AddSeconds(10) } else { $state.PingBusy = $true; foreach ($entry in $state.Gateways) { $entry.Health = 'pending'; $pings += New-NexusGuiPing $entry.Url } } } if ([DateTime]::Now -ge $renderAt) { $frame = Get-NexusTuiFrame $state ([Console]::WindowWidth) ([Console]::WindowHeight) $signature = ($frame.Lines -join "`n") + '|' + $state.Hover if ($signature -ne $lastFrame) { Write-NexusTuiFrame $frame; $lastFrame = $signature } $renderAt = [DateTime]::Now.AddMilliseconds(70) } Start-Sleep -Milliseconds 25 } } finally { foreach ($ping in $pings) { try { $ping.PowerShell.Stop(); $ping.PowerShell.Dispose() } catch {} } if ($worker) { Close-NexusGuiWorker $worker } $state.Key = ''; $state.KeyVisible = $false try { [NexusSetup.ConsoleOutput]::WriteText("$esc[0m$esc[?25h$esc[?1049l"); [Console]::CursorVisible = $oldCursor } catch {} $null = [NexusSetup.ConsoleInput]::SetConsoleMode($inputHandle, $inputMode) $null = [NexusSetup.ConsoleInput]::SetConsoleMode($outputHandle, $outputMode) [Console]::OutputEncoding = $oldEncoding } $global:NexusExit = $state.ExitCode return $state.ExitCode } # END GENERATED NEXUS TUI try { Main } catch { $global:NexusExit = 1 $startupMessage = [string]$_.Exception.Message foreach ($secret in @([string]$env:NEXUS_KEY, [string]$script:Key)) { if ($secret) { $startupMessage = $startupMessage.Replace($secret, 'sk-***') } } $startupMessage = [regex]::Replace($startupMessage, '(?i)(sk-|thk_|dahl_)[A-Za-z0-9_-]{6,}', '$1***') if ($startupMessage.Length -gt 400) { $startupMessage = $startupMessage.Substring(0, 400) } Write-Host ('NEXUS: ошибка запуска [' + $script:NexusSetupVersion + ']') -ForegroundColor Red Write-Host $startupMessage -ForegroundColor Yellow Write-Host ('Код: ' + $_.Exception.GetType().Name + '; строка: ' + $_.InvocationInfo.ScriptLineNumber) -ForegroundColor DarkGray if (-not $Auto) { $null = Read-Host 'Нажмите Enter, чтобы закрыть окно' } }