public/Invoke-Octa.ps1
|
function Wait-OctaKeypress { <# Shared "press any key to return to the menu" step after running a category/tool - factored out so every action site (categories, tools, Quick Clean, Undo, Help) uses the exact same prompt instead of repeating it. #> param([Parameter(Mandatory)]$Strings) Write-Host '' Write-Host -NoNewline $Strings.pressKeyToContinue [Console]::ReadKey($true) | Out-Null } function Invoke-Octa { <# .SYNOPSIS Interactive entry point: language menu (first run) -> session-level restore point gate (010, every run) -> banner + main menu -> dispatch, looping back to the menu after each action so the whole session (every category, the built-in tools, undo) happens from this one running terminal - the user never needs to re-invoke bin/octa.ps1 with a different flag. FR-018, FR-022, FR-023. 010: the main menu is Quick Assist (today's Quick Clean bundle, unchanged) and Quick Custom (wraps the Categorías/Herramientas pickers that used to sit at this top level) - see specs/010-quick-assist-restore-point. The restore-point decision is made exactly once per session, before this menu ever renders (Invoke-OctaSessionRestorePointGate), so every apply call below passes -NoRestorePoint - re-attempting per action was pure repeated friction given Windows' own 24h creation-frequency throttle. 009/010: categories and built-in tools are grouped (main menu -> group picker -> leaf picker) instead of one flat 32-item list, following real user feedback that a flat list got overwhelming as more categories/tools were added. Running a leaf item returns to the group screen it was picked from, not all the way back to the top-level main menu. 010: every screen (a menu, or a running action's own output) is drawn through Show-OctaFrame, which redraws in place relative to the current cursor position (not a stored absolute row - see Show-OctaFrame's own comment for why) so the whole session feels like one fixed, updating screen instead of each action's output piling up in the terminal's ordinary scrollback history. ponytail: direct single-keypress shortcuts are guaranteed unique for items 1-9 within any one screen (main menu, a group picker, or a leaf picker never has more than 9 items at once); items 10+ are still always reachable via Up/Down + Enter, just without a guaranteed single-digit direct key. #> [CmdletBinding()] param() $strings = Get-OctaStrings # 010: restore point moved to once per session, right after language resolves and before the # main menu ever renders - see private/RestorePoint.ps1's Invoke-OctaSessionRestorePointGate # for why. Declining here exits before anything else in the session happens. if (-not (Invoke-OctaSessionRestorePointGate -Strings $strings)) { return } # -SkipRiskScan: this menu never displays HighestRiskLevel (only DisplayName/Description), # so the ~20s it took to live-scan all 20 categories just to compute a badge nothing read # was pure waste - see Get-OctaCategory's own comment for how this was found. $categories = @(Get-OctaCategory -SkipRiskScan) Reset-OctaFrameAnchor $categoryGroups = @( [pscustomobject]@{ Key = '1'; Label = $strings.groupPrivacyAi; Description = $strings.groupPrivacyAiDesc; Ids = @('telemetry', 'background-activity', 'copilot', 'recall', 'ai-features') } [pscustomobject]@{ Key = '2'; Label = $strings.groupBloatware; Description = $strings.groupBloatwareDesc; Ids = @('appx', 'oem-suites', 'onedrive', 'widgets') } [pscustomobject]@{ Key = '3'; Label = $strings.groupPerformance; Description = $strings.groupPerformanceDesc; Ids = @('performance', 'power', 'gpu', 'services') } [pscustomobject]@{ Key = '4'; Label = $strings.groupDesktop; Description = $strings.groupDesktopDesc; Ids = @('explorer-start', 'desktop', 'preferences', 'user-experience', 'gaming') } [pscustomobject]@{ Key = '5'; Label = $strings.groupAdvanced; Description = $strings.groupAdvancedDesc; Ids = @('windows-features', 'registry-orphans') } ) $toolCatalog = @( [pscustomobject]@{ Id = 'disk-cleanup'; Label = $strings.menuDiskCleanup; Description = $strings.descDiskCleanup; Action = { Invoke-OctaDiskCleanup -NoRestorePoint:$true | Out-Null } } [pscustomobject]@{ Id = 'disk-usage'; Label = $strings.menuDiskUsage; Description = $strings.descDiskUsage; Action = { Show-OctaDiskUsage | Out-Null } } [pscustomobject]@{ Id = 'disk-health'; Label = $strings.menuDiskHealth; Description = $strings.descDiskHealth; Action = { Show-OctaDiskHealth | Out-Null } } [pscustomobject]@{ Id = 'uninstaller'; Label = $strings.menuUninstaller; Description = $strings.descUninstaller; Action = { Invoke-OctaUninstaller | Out-Null } } [pscustomobject]@{ Id = 'startup-manager'; Label = $strings.menuStartupManager; Description = $strings.descStartupManager; Action = { Invoke-OctaStartupManager | Out-Null } } [pscustomobject]@{ Id = 'tasks'; Label = $strings.menuTasks; Description = $strings.descTasks; Action = { Invoke-OctaTaskBrowser | Out-Null } } [pscustomobject]@{ Id = 'scheduler'; Label = $strings.menuScheduler; Description = $strings.descScheduler; Action = { Invoke-OctaScheduler | Out-Null } } [pscustomobject]@{ Id = 'software-updater'; Label = $strings.menuSoftwareUpdater; Description = $strings.descSoftwareUpdater; Action = { Invoke-OctaSoftwareUpdater | Out-Null } } [pscustomobject]@{ Id = 'dashboard'; Label = $strings.menuDashboard; Description = $strings.descDashboard; Action = { Show-OctaDashboard | Out-Null } } [pscustomobject]@{ Id = 'history'; Label = $strings.menuHistory; Description = $strings.descHistory; Action = { Show-OctaHistory | Out-Null } } [pscustomobject]@{ Id = 'network-cleanup'; Label = $strings.menuNetworkCleanup; Description = $strings.descNetworkCleanup; Action = { Invoke-OctaNetworkCleanup | Out-Null } } [pscustomobject]@{ Id = 'malware-scan'; Label = $strings.menuMalwareScan; Description = $strings.descMalwareScan; Action = { Invoke-OctaMalwareScan | Format-List } } ) $toolGroups = @( [pscustomobject]@{ Key = '1'; Label = $strings.groupDiskStorage; Description = $strings.groupDiskStorageDesc; ToolIds = @('disk-cleanup', 'disk-usage', 'disk-health', 'uninstaller') } [pscustomobject]@{ Key = '2'; Label = $strings.groupSystemMgmt; Description = $strings.groupSystemMgmtDesc; ToolIds = @('startup-manager', 'tasks', 'scheduler', 'software-updater') } [pscustomobject]@{ Key = '3'; Label = $strings.groupInfoHistory; Description = $strings.groupInfoHistoryDesc; ToolIds = @('dashboard', 'history') } [pscustomobject]@{ Key = '4'; Label = $strings.groupNetworkSecurity; Description = $strings.groupNetworkSecurityDesc; ToolIds = @('network-cleanup', 'malware-scan') } ) while ($true) { # 010: two primary paths instead of a flat list - Quick Assist (today's Quick Clean, # unchanged) and Quick Custom (a new screen wrapping the same Categorías/Herramientas # pickers that used to sit at this top level - see the 'c' case below). $mainItems = @( [pscustomobject]@{ Key = 'q'; Label = $strings.menuQuickAssist; Description = $strings.menuQuickAssistDesc } [pscustomobject]@{ Key = 'c'; Label = $strings.menuQuickCustom; Description = $strings.menuQuickCustomDesc } [pscustomobject]@{ Key = 'u'; Label = $strings.menuUndo; Description = $strings.menuUndoDesc } [pscustomobject]@{ Key = 'l'; Label = $strings.menuLanguage; Description = $strings.menuLanguageDesc } [pscustomobject]@{ Key = 'h'; Label = $strings.menuHelp; Description = $strings.menuHelpDesc } [pscustomobject]@{ Key = 'x'; Label = $strings.menuExit; Description = $strings.menuExitDesc } ) $choice = Show-OctaFrame -Draw { Show-OctaMenu -Items $mainItems -Header $strings.mainMenuHeader -NavHint $strings.menuNavHint } switch ($choice) { 'q' { Show-OctaFrame -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $strings.menuQuickAssist) Invoke-OctaQuickClean -Apply -NoRestorePoint:$true | Format-List Wait-OctaKeypress -Strings $strings } | Out-Null } 'c' { while ($true) { $customItems = @( [pscustomobject]@{ Key = '1'; Label = $strings.menuCategories; Description = $strings.menuCategoriesDesc } [pscustomobject]@{ Key = '2'; Label = $strings.menuTools; Description = $strings.menuToolsDesc } [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } ) $customChoice = Show-OctaFrame -Draw { Show-OctaMenu -Items $customItems -Header $strings.menuQuickCustom -NavHint $strings.menuNavHint } if ($customChoice -eq 'b') { break } switch ($customChoice) { '1' { while ($true) { $groupItems = @($categoryGroups | ForEach-Object { [pscustomobject]@{ Key = $_.Key; Label = $_.Label; Description = $_.Description } }) $groupItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $groupChoice = Show-OctaFrame -Draw { Show-OctaMenu -Items $groupItems -Header $strings.categoryGroupHeader -NavHint $strings.menuNavHint } if ($groupChoice -eq 'b') { break } $group = $categoryGroups | Where-Object { $_.Key -eq $groupChoice } while ($true) { $leafCategories = @($categories | Where-Object { $_.Id -in $group.Ids }) $leafItems = @() for ($i = 0; $i -lt $leafCategories.Count; $i++) { $leafItems += [pscustomobject]@{ Key = ($i + 1).ToString(); Label = $leafCategories[$i].DisplayName; Description = $leafCategories[$i].Description } } $leafItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $leafChoice = Show-OctaFrame -Draw { Show-OctaMenu -Items $leafItems -Header $group.Label -NavHint $strings.menuNavHint } if ($leafChoice -eq 'b') { break } $index = [int]$leafChoice - 1 if ($index -ge 0 -and $index -lt $leafCategories.Count) { Show-OctaFrame -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $leafCategories[$index].DisplayName) Invoke-OctaCategory -CategoryId $leafCategories[$index].Id -Apply -NoRestorePoint:$true | Format-List Wait-OctaKeypress -Strings $strings } | Out-Null } } } } '2' { while ($true) { $groupItems = @($toolGroups | ForEach-Object { [pscustomobject]@{ Key = $_.Key; Label = $_.Label; Description = $_.Description } }) $groupItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $groupChoice = Show-OctaFrame -Draw { Show-OctaMenu -Items $groupItems -Header $strings.toolGroupHeader -NavHint $strings.menuNavHint } if ($groupChoice -eq 'b') { break } $group = $toolGroups | Where-Object { $_.Key -eq $groupChoice } while ($true) { $leafTools = @($toolCatalog | Where-Object { $_.Id -in $group.ToolIds }) $leafItems = @() for ($i = 0; $i -lt $leafTools.Count; $i++) { $leafItems += [pscustomobject]@{ Key = ($i + 1).ToString(); Label = $leafTools[$i].Label; Description = $leafTools[$i].Description } } $leafItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $leafChoice = Show-OctaFrame -Draw { Show-OctaMenu -Items $leafItems -Header $group.Label -NavHint $strings.menuNavHint } if ($leafChoice -eq 'b') { break } $index = [int]$leafChoice - 1 if ($index -ge 0 -and $index -lt $leafTools.Count) { Show-OctaFrame -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $leafTools[$index].Label) & $leafTools[$index].Action Wait-OctaKeypress -Strings $strings } | Out-Null } } } } } } } 'u' { Show-OctaFrame -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $strings.menuUndo) Undo-OctaRun -RunId 'latest' | Format-List Wait-OctaKeypress -Strings $strings } | Out-Null } 'l' { # Was not wrapped in Show-OctaFrame at all before - its own banner+prompt just # printed wherever the cursor happened to be after the previous menu, instead of # replacing it in place, which is exactly the "everything stacks up" symptom a # real user reported. $culture = Show-OctaFrame -Draw { Show-OctaLanguageMenu } Set-OctaLanguage -Culture $culture $strings = Get-OctaStrings } 'h' { Show-OctaFrame -Draw { Show-OctaBanner Write-Host 'https://github.com/jpiribe/octa (see README for full reference)' Wait-OctaKeypress -Strings $strings } | Out-Null } 'x' { return } } } } |