windowsweep 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/AI-INTEGRATION-GUIDE.md +203 -0
  2. package/CHANGELOG.md +124 -0
  3. package/LICENSE +21 -21
  4. package/README.md +88 -49
  5. package/VERSION +1 -1
  6. package/lib/actions.ps1 +212 -209
  7. package/lib/config.ps1 +108 -99
  8. package/lib/constants.ps1 +103 -89
  9. package/lib/fs.ps1 +189 -180
  10. package/lib/log.ps1 +140 -140
  11. package/lib/safety.ps1 +541 -493
  12. package/lib/scan.ps1 +109 -94
  13. package/lib/ui.ps1 +275 -235
  14. package/modules/android_avd.ps1 +36 -36
  15. package/modules/app_caches.ps1 +58 -58
  16. package/modules/browsers.ps1 +33 -33
  17. package/modules/build_tools.ps1 +35 -35
  18. package/modules/crash_trap.ps1 +89 -89
  19. package/modules/disk_usage.ps1 +49 -49
  20. package/modules/docker.ps1 +93 -93
  21. package/modules/editors.ps1 +150 -150
  22. package/modules/globals_audit.ps1 +236 -0
  23. package/modules/health.ps1 +101 -101
  24. package/modules/installed_programs.ps1 +118 -0
  25. package/modules/menu.ps1 +63 -61
  26. package/modules/notify.ps1 +60 -0
  27. package/modules/orphaned_appdata.ps1 +189 -0
  28. package/modules/personal.ps1 +126 -124
  29. package/modules/pkg_managers.ps1 +73 -69
  30. package/modules/projects.ps1 +160 -143
  31. package/modules/release_helpers.ps1 +438 -434
  32. package/modules/reports.ps1 +212 -212
  33. package/modules/runner.ps1 +242 -190
  34. package/modules/self_test_extra.ps1 +537 -86
  35. package/modules/startup_audit.ps1 +126 -0
  36. package/modules/system_admin.ps1 +220 -220
  37. package/modules/test_runners.ps1 +24 -24
  38. package/modules/walkthrough.ps1 +80 -79
  39. package/modules/windows_user.ps1 +118 -112
  40. package/package.json +4 -3
  41. package/windowsweep.ps1 +322 -296
package/lib/config.ps1 CHANGED
@@ -1,99 +1,108 @@
1
- # config.ps1 - persistent settings (~\.windowsweep\config.json) and the developer question.
2
-
3
- function Get-DefaultConfig {
4
- return [ordered]@{
5
- schema = 1
6
- developer = $null
7
- developerAskedAt = $null
8
- days = 100
9
- tempDays = 3
10
- largeFileMb = 100
11
- scanRoots = @()
12
- excludePaths = @()
13
- welcomed = $false
14
- }
15
- }
16
-
17
- function Import-Config {
18
- <# .SYNOPSIS Load config.json over the defaults; a broken file is reported and ignored. #>
19
- $ws = $Script:WS
20
- $cfg = Get-DefaultConfig
21
- $path = $ws.ConfigPath
22
- if (Test-Path -LiteralPath $path) {
23
- try {
24
- $raw = Get-Content -LiteralPath $path -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
25
- foreach ($k in @($cfg.Keys)) {
26
- $prop = $raw.PSObject.Properties[$k]
27
- if ($null -ne $prop) { $cfg[$k] = $prop.Value }
28
- }
29
- if ($null -eq $cfg.scanRoots) { $cfg.scanRoots = @() }
30
- if ($null -eq $cfg.excludePaths) { $cfg.excludePaths = @() }
31
- } catch {
32
- Write-Warn "config file unreadable, using defaults: $path ($($_.Exception.Message))"
33
- }
34
- }
35
- $ws.Config = $cfg
36
- return $cfg
37
- }
38
-
39
- function Save-Config {
40
- $ws = $Script:WS
41
- try {
42
- $dir = Split-Path -Parent $ws.ConfigPath
43
- if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
44
- [IO.File]::WriteAllText($ws.ConfigPath, ($ws.Config | ConvertTo-Json -Depth 4), (New-Object System.Text.UTF8Encoding($false)))
45
- } catch {
46
- Write-Warn "could not save config: $($_.Exception.Message)"
47
- }
48
- }
49
-
50
- function Get-ToolchainHints {
51
- <# .SYNOPSIS Names of developer tools found on this machine (used to suggest the developer answer). #>
52
- $found = @()
53
- foreach ($c in @('node', 'npm', 'yarn', 'pnpm', 'bun', 'deno', 'git', 'docker', 'python', 'pip', 'uv', 'java', 'gradle', 'dotnet', 'cargo', 'go', 'flutter', 'composer', 'php', 'code', 'cursor')) {
54
- if (Get-Command $c -ErrorAction SilentlyContinue) { $found += $c }
55
- }
56
- $dirs = @{
57
- 'Gradle caches' = "$env:USERPROFILE\.gradle"; 'Android SDK' = "$env:LOCALAPPDATA\Android\Sdk"; 'VS Code' = "$env:APPDATA\Code"
58
- 'Docker Desktop' = "$env:LOCALAPPDATA\Docker"; 'JetBrains IDEs' = "$env:LOCALAPPDATA\JetBrains"; 'Visual Studio' = "$env:LOCALAPPDATA\Microsoft\VisualStudio"
59
- }
60
- foreach ($k in $dirs.Keys) { if (Test-Path -LiteralPath $dirs[$k]) { $found += $k } }
61
- return $found
62
- }
63
-
64
- function Resolve-DeveloperMode {
65
- <# .SYNOPSIS Decide developer mode: flag > saved answer > interactive question > conservative default (yes). #>
66
- $ws = $Script:WS
67
- if ($null -ne $ws.DeveloperFlag) { $ws.Developer = [bool]$ws.DeveloperFlag; $ws.DeveloperSource = 'flag'; return }
68
- if ($ws.ForgetDeveloper) { $ws.Config.developer = $null; $ws.Config.developerAskedAt = $null }
69
- if ($null -ne $ws.Config.developer) { $ws.Developer = [bool]$ws.Config.developer; $ws.DeveloperSource = 'config'; return }
70
- if ($ws.Interactive -and -not $ws.Yes) {
71
- $hints = @(Get-ToolchainHints)
72
- Write-Box 'One question before anything else' 'It decides how package and build caches are treated'
73
- Write-Plain ' Are you a developer on this machine?'
74
- Write-Note 'yes = package/build/test-runner caches are only pruned when idle 100+ days (recent work stays fast);'
75
- Write-Note ' the newest version of every versioned tool cache is always kept.'
76
- Write-Note 'no = those caches are cleared completely; project scans are skipped.'
77
- if ($hints.Count -gt 0) { Write-Note ("detected developer tooling: " + ($hints -join ', ')) }
78
- $ans = Confirm-Ui -Prompt 'Are you a developer on this machine?' -Default 'y' -NoAutoYes
79
- $ws.Developer = $ans
80
- $ws.DeveloperSource = 'answer'
81
- $ws.Config.developer = $ans
82
- $ws.Config.developerAskedAt = (Get-Date).ToString('yyyy-MM-dd')
83
- Save-Config
84
- if ($ans) { Write-Ok 'developer mode ON - saved to config.json (change with --forget-developer)' } else { Write-Ok 'developer mode OFF - saved to config.json (change with --forget-developer)' }
85
- return
86
- }
87
- $ws.Developer = $true
88
- $ws.DeveloperSource = 'default'
89
- Write-Note 'developer question not asked (non-interactive run) - defaulting to developer mode ON, the conservative choice; pass --not-developer to override'
90
- }
91
-
92
- function Confirm-PurgeAllOnce {
93
- <# .SYNOPSIS --purge-all from a console asks for a typed word once per run; --yes is the confirmation in batch runs. #>
94
- $ws = $Script:WS
95
- if (-not $ws.PurgeAll -or $ws.DryRun -or $ws.Yes) { return }
96
- if (Confirm-Typed -Prompt 'FULL PURGE: --purge-all empties every cache target completely, including files you used yesterday.' -Word 'purge') { Write-Ok 'purge-all confirmed for this run'; return }
97
- $ws.PurgeAll = $false
98
- Write-Info 'purge-all declined - this run prunes by the idle window instead'
99
- }
1
+ # config.ps1 - persistent settings (~\.windowsweep\config.json) and the developer question.
2
+
3
+ function Get-DefaultConfig {
4
+ return [ordered]@{
5
+ schema = 1
6
+ developer = $null
7
+ developerAskedAt = $null
8
+ days = 100
9
+ tempDays = 3
10
+ largeFileMb = 100
11
+ scanRoots = @()
12
+ excludePaths = @()
13
+ welcomed = $false
14
+ }
15
+ }
16
+
17
+ function Import-Config {
18
+ <# .SYNOPSIS Load config.json over the defaults; a broken file is reported and ignored. #>
19
+ $ws = $Script:WS
20
+ $cfg = Get-DefaultConfig
21
+ $path = $ws.ConfigPath
22
+ if (Test-Path -LiteralPath $path) {
23
+ try {
24
+ $raw = Get-Content -LiteralPath $path -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
25
+ foreach ($k in @($cfg.Keys)) {
26
+ $prop = $raw.PSObject.Properties[$k]
27
+ if ($null -ne $prop) { $cfg[$k] = $prop.Value }
28
+ }
29
+ if ($null -eq $cfg.scanRoots) { $cfg.scanRoots = @() }
30
+ if ($null -eq $cfg.excludePaths) { $cfg.excludePaths = @() }
31
+ } catch {
32
+ Write-Warn "config file unreadable, using defaults: $path ($($_.Exception.Message))"
33
+ }
34
+ }
35
+ $ws.Config = $cfg
36
+ return $cfg
37
+ }
38
+
39
+ function Save-Config {
40
+ $ws = $Script:WS
41
+ try {
42
+ $dir = Split-Path -Parent $ws.ConfigPath
43
+ if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
44
+ [IO.File]::WriteAllText($ws.ConfigPath, ($ws.Config | ConvertTo-Json -Depth 4), (New-Object System.Text.UTF8Encoding($false)))
45
+ } catch {
46
+ Write-Warn "could not save config: $($_.Exception.Message)"
47
+ }
48
+ }
49
+
50
+ function Get-ToolchainHints {
51
+ <# .SYNOPSIS Names of developer tools found on this machine (used to suggest the developer answer). #>
52
+ $found = @()
53
+ foreach ($c in @('node', 'npm', 'yarn', 'pnpm', 'bun', 'deno', 'git', 'docker', 'python', 'pip', 'uv', 'java', 'gradle', 'dotnet', 'cargo', 'go', 'flutter', 'composer', 'php', 'code', 'cursor')) {
54
+ if (Get-Command $c -ErrorAction SilentlyContinue) { $found += $c }
55
+ }
56
+ $dirs = @{
57
+ 'Gradle caches' = "$env:USERPROFILE\.gradle"; 'Android SDK' = "$env:LOCALAPPDATA\Android\Sdk"; 'VS Code' = "$env:APPDATA\Code"
58
+ 'Docker Desktop' = "$env:LOCALAPPDATA\Docker"; 'JetBrains IDEs' = "$env:LOCALAPPDATA\JetBrains"; 'Visual Studio' = "$env:LOCALAPPDATA\Microsoft\VisualStudio"
59
+ }
60
+ foreach ($k in $dirs.Keys) { if (Test-Path -LiteralPath $dirs[$k]) { $found += $k } }
61
+ return $found
62
+ }
63
+
64
+ function Resolve-DeveloperMode {
65
+ <# .SYNOPSIS Decide developer mode: flag > saved answer > interactive question > conservative default (yes).
66
+ -ReadOnly resolves from the flag or the saved answer ONLY. A read-only mode may not ask the question,
67
+ may not write config.json, and may not invent a decision the person never made - so when neither a flag
68
+ nor a saved answer exists it leaves Developer $null and the health report keeps saying "not decided yet",
69
+ which is the truth. Without this, --scan ignored --developer and --not-developer outright: the flag was
70
+ parsed, stored in DeveloperFlag, and never read, so section 0 printed "not decided yet" whatever you
71
+ passed and --json returned developer: null. The desktop window passes those flags to --scan
72
+ (desktop/src/lib/engine.ts) and could not read back the setting it had just sent. #>
73
+ param([switch] $ReadOnly)
74
+ $ws = $Script:WS
75
+ if ($null -ne $ws.DeveloperFlag) { $ws.Developer = [bool]$ws.DeveloperFlag; $ws.DeveloperSource = 'flag'; return }
76
+ if ($ws.ForgetDeveloper) { $ws.Config.developer = $null; $ws.Config.developerAskedAt = $null }
77
+ if ($null -ne $ws.Config.developer) { $ws.Developer = [bool]$ws.Config.developer; $ws.DeveloperSource = 'config'; return }
78
+ if ($ReadOnly) { return }
79
+ if ($ws.Interactive -and -not $ws.Yes) {
80
+ $hints = @(Get-ToolchainHints)
81
+ Write-Box 'One question before anything else' 'It decides how package and build caches are treated'
82
+ Write-Plain ' Are you a developer on this machine?'
83
+ Write-Note "yes = package/build/test-runner caches are only pruned when idle $($ws.Days)+ days (recent work stays fast);"
84
+ Write-Note ' the newest version of every versioned tool cache is kept, unless --purge-all.'
85
+ Write-Note 'no = those caches are cleared completely; project scans are skipped.'
86
+ if ($hints.Count -gt 0) { Write-Note ("detected developer tooling: " + ($hints -join ', ')) }
87
+ $ans = Confirm-Ui -Prompt 'Are you a developer on this machine?' -Default 'y' -NoAutoYes
88
+ $ws.Developer = $ans
89
+ $ws.DeveloperSource = 'answer'
90
+ $ws.Config.developer = $ans
91
+ $ws.Config.developerAskedAt = (Get-Date).ToString('yyyy-MM-dd')
92
+ Save-Config
93
+ if ($ans) { Write-Ok 'developer mode ON - saved to config.json (change with --forget-developer)' } else { Write-Ok 'developer mode OFF - saved to config.json (change with --forget-developer)' }
94
+ return
95
+ }
96
+ $ws.Developer = $true
97
+ $ws.DeveloperSource = 'default'
98
+ Write-Note 'developer question not asked (non-interactive run) - defaulting to developer mode ON, the conservative choice; pass --not-developer to override'
99
+ }
100
+
101
+ function Confirm-PurgeAllOnce {
102
+ <# .SYNOPSIS --purge-all from a console asks for a typed word once per run; --yes is the confirmation in batch runs. #>
103
+ $ws = $Script:WS
104
+ if (-not $ws.PurgeAll -or $ws.DryRun -or $ws.Yes) { return }
105
+ if (Confirm-Typed -Prompt 'FULL PURGE: --purge-all empties every cache target completely, including files you used yesterday.' -Word 'purge') { Write-Ok 'purge-all confirmed for this run'; return }
106
+ $ws.PurgeAll = $false
107
+ Write-Info 'purge-all declined - this run prunes by the idle window instead'
108
+ }
package/lib/constants.ps1 CHANGED
@@ -1,89 +1,103 @@
1
- # constants.ps1 - project metadata, section catalogue, profiles and gates.
2
- # Dot-sourced by windowsweep.ps1. ASCII only (PowerShell 5.1 reads BOM-less files as ANSI).
3
-
4
- # Single source of truth for the version is package.json: the Node launcher exports it as
5
- # WINDOWSWEEP_VERSION. A direct checkout falls back to this literal; the version:check script
6
- # asserts the two never drift.
7
- $Script:WS_VERSION_FALLBACK = '1.0.1'
8
-
9
- $Script:WS_NAME = 'windowsweep'
10
- $Script:WS_TAGLINE = 'Safe-by-default Windows cleanup CLI - developer-aware, dry-run first, zero install via npx.'
11
- $Script:WS_AUTHOR = 'Ahsan Mahmood'
12
- $Script:WS_EMAIL = 'aoneahsan@gmail.com'
13
- $Script:WS_WEB = 'https://aoneahsan.com'
14
- $Script:WS_LINKEDIN = 'https://linkedin.com/in/aoneahsan'
15
- $Script:WS_GITHUB = 'https://github.com/aoneahsan'
16
- $Script:WS_REPO = 'https://github.com/aoneahsan/windowsweep'
17
- $Script:WS_ISSUES = 'https://github.com/aoneahsan/windowsweep/issues'
18
- $Script:WS_NPM = 'https://www.npmjs.com/package/windowsweep'
19
- $Script:WS_DOCS = 'https://github.com/aoneahsan/windowsweep/blob/main/docs/README.md'
20
- $Script:WS_SUPPORT = 'https://aoneahsan.com/payment?project-id=windowsweep&project-identifier=windowsweep'
21
- $Script:WS_LICENSE = 'MIT License'
22
-
23
- # Exit codes (documented in docs/cli-reference.md).
24
- $Script:WS_EXIT_OK = 0
25
- $Script:WS_EXIT_FAIL = 1
26
- $Script:WS_EXIT_USAGE = 2
27
- $Script:WS_EXIT_REFUSED = 3
28
- $Script:WS_EXIT_INTERRUPT = 130
29
-
30
- # Section catalogue. Numbering is a public contract frozen at 1.0.0: a section may be retired
31
- # (kept as a no-op that says so) but a number is never reused for something else.
32
- # Tier: report | rebuilds | slow | recycle | permanent | config
33
- # Batch: safe (in --all) | optin (--only/--profile with --yes) | deep (needs --i-understand-deep)
34
- # | interactive (never batch; dry-run/report allowed)
35
- $Script:WS_SECTIONS = @(
36
- @{ Id = 0; Key = 'health'; Title = 'System health report'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section00' }
37
- @{ Id = 1; Key = 'pkg'; Title = 'Package-manager caches (npm, yarn, pnpm, bun, deno, pip, uv, Composer, NuGet, Cargo, Go, pub)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section01' }
38
- @{ Id = 2; Key = 'build'; Title = 'Build-tool caches (Gradle, Maven, Android, Unity, JetBrains)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section02' }
39
- @{ Id = 3; Key = 'runners'; Title = 'Test-runner browsers (Cypress, Playwright, Puppeteer) - keep newest'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section03' }
40
- @{ Id = 4; Key = 'avd'; Title = 'Android emulators (AVDs) idle N+ days'; Tier = 'slow'; Admin = $false; Batch = 'optin'; Dev = $true; Fn = 'Invoke-Section04' }
41
- @{ Id = 5; Key = 'docker'; Title = 'Docker: dangling images, build cache, unused images older than N days'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section05' }
42
- @{ Id = 6; Key = 'editors'; Title = 'Editor caches (VS Code, Cursor, Windsurf, Visual Studio) + superseded extensions'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section06' }
43
- @{ Id = 7; Key = 'browsers'; Title = 'Browser caches (Chrome, Edge, Brave, Vivaldi, Opera, Chromium, Firefox)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section07' }
44
- @{ Id = 8; Key = 'apps'; Title = 'Desktop app caches (Discord, Slack, Teams, Zoom, Spotify, Postman, Figma, ...)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section08' }
45
- @{ Id = 9; Key = 'wincaches'; Title = 'Windows user caches (INetCache, WER, crash dumps, shader caches, UWP temp)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section09' }
46
- @{ Id = 10; Key = 'temp'; Title = 'User temp files older than N days'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section10' }
47
- @{ Id = 11; Key = 'recycle'; Title = 'Empty the Recycle Bin - PERMANENT'; Tier = 'permanent'; Admin = $false; Batch = 'deep'; Dev = $false; Fn = 'Invoke-Section11' }
48
- @{ Id = 12; Key = 'wu'; Title = 'Windows Update + system temp (SoftwareDistribution, Delivery Optimization, Windows\Temp, CBS logs)'; Tier = 'rebuilds'; Admin = $true; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section12' }
49
- @{ Id = 13; Key = 'cleanmgr'; Title = 'Windows Disk Cleanup engine (cleanmgr, curated handlers)'; Tier = 'rebuilds'; Admin = $true; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section13' }
50
- @{ Id = 14; Key = 'dism'; Title = 'Component store cleanup (DISM StartComponentCleanup) - slow'; Tier = 'rebuilds'; Admin = $true; Batch = 'optin'; Dev = $false; Fn = 'Invoke-Section14' }
51
- @{ Id = 15; Key = 'hiberfil'; Title = 'Hibernation file (off / reduced)'; Tier = 'config'; Admin = $true; Batch = 'deep'; Dev = $false; Fn = 'Invoke-Section15' }
52
- @{ Id = 16; Key = 'eventlogs'; Title = 'Clear Windows Event Logs - PERMANENT'; Tier = 'permanent'; Admin = $true; Batch = 'deep'; Dev = $false; Fn = 'Invoke-Section16' }
53
- @{ Id = 17; Key = 'projects'; Title = 'Stale project build artefacts (node_modules, dist, .next, target, ...)'; Tier = 'rebuilds'; Admin = $false; Batch = 'interactive'; Dev = $true; Fn = 'Invoke-Section17' }
54
- @{ Id = 18; Key = 'partials'; Title = 'Partial / orphan downloads -> Recycle Bin'; Tier = 'recycle'; Admin = $false; Batch = 'interactive'; Dev = $false; Fn = 'Invoke-Section18' }
55
- @{ Id = 19; Key = 'large'; Title = 'Large stale personal files (Downloads) -> Recycle Bin'; Tier = 'recycle'; Admin = $false; Batch = 'interactive'; Dev = $false; Fn = 'Invoke-Section19' }
56
- @{ Id = 20; Key = 'vhdx'; Title = 'Docker Desktop / WSL disk image compaction (stops Docker + WSL)'; Tier = 'config'; Admin = $true; Batch = 'deep'; Dev = $true; Fn = 'Invoke-Section20' }
57
- @{ Id = 21; Key = 'diskusage'; Title = 'Disk usage report (largest entries, drives, disk images)'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section21' }
58
- )
59
-
60
- # The safe batch run by --all. Admin sections 12 and 13 join it only when already elevated.
61
- $Script:WS_SAFE_BATCH = @(0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 21)
62
- $Script:WS_SAFE_BATCH_ADMIN = @(12, 13)
63
-
64
- $Script:WS_PROFILES = @{
65
- 'dev' = @(1, 2, 3, 4, 5, 6, 17)
66
- 'minimal' = @(7, 8, 9, 10)
67
- 'cache-only' = @(1, 2, 3, 6, 7, 8, 9)
68
- 'system' = @(12, 13, 14)
69
- 'deep' = @(0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 13, 14, 21)
70
- 'audit' = @(0, 21)
71
- }
72
-
73
- # Walkthrough order (interactive default mode). Admin sections are inserted when elevated.
74
- $Script:WS_WALKTHROUGH = @(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 17, 18, 19)
75
- $Script:WS_WALKTHROUGH_ADMIN = @(12, 13, 14)
76
-
77
- function Get-Section {
78
- <# .SYNOPSIS Returns the catalogue entry for a section id, or $null. #>
79
- param([int] $Id)
80
- foreach ($s in $Script:WS_SECTIONS) { if ($s.Id -eq $Id) { return $s } }
81
- return $null
82
- }
83
-
84
- function Get-ToolVersion {
85
- <# .SYNOPSIS The running version: launcher-provided env wins over the checkout fallback. #>
86
- $v = $env:WINDOWSWEEP_VERSION
87
- if ([string]::IsNullOrWhiteSpace($v)) { return $Script:WS_VERSION_FALLBACK }
88
- return $v.Trim()
89
- }
1
+ # constants.ps1 - project metadata, section catalogue, profiles and gates.
2
+ # Dot-sourced by windowsweep.ps1. ASCII only (PowerShell 5.1 reads BOM-less files as ANSI).
3
+
4
+ # Single source of truth for the version is package.json: the Node launcher exports it as
5
+ # WINDOWSWEEP_VERSION. A direct checkout falls back to this literal; the version:check script
6
+ # asserts the two never drift.
7
+ $Script:WS_VERSION_FALLBACK = '1.2.0'
8
+
9
+ $Script:WS_NAME = 'windowsweep'
10
+ $Script:WS_TAGLINE = 'Developer-aware Windows cleanup CLI: dry-run first, personal folders refused, zero install via npx.'
11
+ $Script:WS_AUTHOR = 'Ahsan Mahmood'
12
+ $Script:WS_EMAIL = 'aoneahsan@gmail.com'
13
+ $Script:WS_WEB = 'https://aoneahsan.com'
14
+ $Script:WS_LINKEDIN = 'https://linkedin.com/in/aoneahsan'
15
+ $Script:WS_GITHUB = 'https://github.com/aoneahsan'
16
+ $Script:WS_REPO = 'https://github.com/aoneahsan/windowsweep'
17
+ $Script:WS_ISSUES = 'https://github.com/aoneahsan/windowsweep/issues'
18
+ $Script:WS_NPM = 'https://www.npmjs.com/package/windowsweep'
19
+ $Script:WS_DOCS = 'https://github.com/aoneahsan/windowsweep/blob/main/docs/README.md'
20
+ $Script:WS_SUPPORT = 'https://aoneahsan.com/payment?project-id=windowsweep&project-identifier=windowsweep'
21
+ $Script:WS_LICENSE = 'MIT License'
22
+
23
+ # Exit codes (documented in docs/cli-reference.md).
24
+ $Script:WS_EXIT_OK = 0
25
+ $Script:WS_EXIT_FAIL = 1
26
+ $Script:WS_EXIT_USAGE = 2
27
+ $Script:WS_EXIT_REFUSED = 3
28
+ $Script:WS_EXIT_INTERRUPT = 130
29
+
30
+ # The protected-path CATEGORIES, in the wording the tool shows a person. Both --list-targets and
31
+ # --list --json read this one list: the JSON is the same sentences, so a front end cannot show a user
32
+ # a narrower promise than the console does.
33
+ $Script:WS_PROTECT_CATEGORIES = @(
34
+ 'every drive root, Windows, System32, Program Files, ProgramData, the user profile root and AppData roots',
35
+ 'browser profile data (Local Storage, IndexedDB, cookies, history, bookmarks, extensions, PWA CacheStorage)',
36
+ 'editor user data (settings, globalStorage, local History); UWP LocalState; toolchains (nvm, npm globals, corepack, ...)',
37
+ 'NTUSER.DAT, UsrClass.dat, hiberfil/pagefile/swapfile (only powercfg touches hiberfil), Prefetch, Windows\Installer, WinSxS (DISM only)'
38
+ )
39
+
40
+ # Section catalogue. Numbering is a public contract frozen at 1.0.0: a section may be retired
41
+ # (kept as a no-op that says so) but a number is never reused for something else.
42
+ # Tier: report | rebuilds | slow | recycle | permanent | config
43
+ # Batch: safe (in --all) | optin (--only/--profile with --yes) | deep (needs --i-understand-deep)
44
+ # | interactive (never batch; dry-run/report allowed)
45
+ $Script:WS_SECTIONS = @(
46
+ @{ Id = 0; Key = 'health'; Title = 'System health report'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section00' }
47
+ @{ Id = 1; Key = 'pkg'; Title = 'Package-manager caches (npm, yarn, pnpm, bun, deno, pip, uv, Composer, NuGet, Cargo, Go, pub)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section01' }
48
+ @{ Id = 2; Key = 'build'; Title = 'Build-tool caches (Gradle, Maven, Android, Unity, JetBrains)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section02' }
49
+ @{ Id = 3; Key = 'runners'; Title = 'Test-runner browsers (Cypress, Playwright, Puppeteer) - keep newest'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section03' }
50
+ @{ Id = 4; Key = 'avd'; Title = 'Android emulators (AVDs) idle N+ days'; Tier = 'slow'; Admin = $false; Batch = 'optin'; Dev = $true; Fn = 'Invoke-Section04' }
51
+ @{ Id = 5; Key = 'docker'; Title = 'Docker: dangling images, build cache, unused images older than N days'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $true; Fn = 'Invoke-Section05' }
52
+ @{ Id = 6; Key = 'editors'; Title = 'Editor caches (VS Code, Cursor, Windsurf, Visual Studio) + superseded extensions'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section06' }
53
+ @{ Id = 7; Key = 'browsers'; Title = 'Browser caches (Chrome, Edge, Brave, Vivaldi, Opera, Chromium, Firefox)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section07' }
54
+ @{ Id = 8; Key = 'apps'; Title = 'Desktop app caches (Discord, Slack, Teams, Zoom, Spotify, Postman, Figma, ...)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section08' }
55
+ @{ Id = 9; Key = 'wincaches'; Title = 'Windows user caches (INetCache, WER, crash dumps, shader caches, UWP temp)'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section09' }
56
+ @{ Id = 10; Key = 'temp'; Title = 'User temp files older than N days'; Tier = 'rebuilds'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section10' }
57
+ @{ Id = 11; Key = 'recycle'; Title = 'Empty the Recycle Bin - PERMANENT'; Tier = 'permanent'; Admin = $false; Batch = 'deep'; Dev = $false; Fn = 'Invoke-Section11' }
58
+ @{ Id = 12; Key = 'wu'; Title = 'Windows Update + system temp (SoftwareDistribution, Delivery Optimization, Windows\Temp, CBS logs)'; Tier = 'rebuilds'; Admin = $true; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section12' }
59
+ @{ Id = 13; Key = 'cleanmgr'; Title = 'Windows Disk Cleanup engine (cleanmgr, curated handlers)'; Tier = 'rebuilds'; Admin = $true; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section13' }
60
+ @{ Id = 14; Key = 'dism'; Title = 'Component store cleanup (DISM StartComponentCleanup) - slow'; Tier = 'rebuilds'; Admin = $true; Batch = 'optin'; Dev = $false; Fn = 'Invoke-Section14' }
61
+ @{ Id = 15; Key = 'hiberfil'; Title = 'Hibernation file (off / reduced)'; Tier = 'config'; Admin = $true; Batch = 'deep'; Dev = $false; Fn = 'Invoke-Section15' }
62
+ @{ Id = 16; Key = 'eventlogs'; Title = 'Clear Windows Event Logs - PERMANENT'; Tier = 'permanent'; Admin = $true; Batch = 'deep'; Dev = $false; Fn = 'Invoke-Section16' }
63
+ @{ Id = 17; Key = 'projects'; Title = 'Stale project build artefacts (node_modules, dist, .next, target, ...)'; Tier = 'rebuilds'; Admin = $false; Batch = 'interactive'; Dev = $true; Fn = 'Invoke-Section17' }
64
+ @{ Id = 18; Key = 'partials'; Title = 'Partial / orphan downloads -> Recycle Bin'; Tier = 'recycle'; Admin = $false; Batch = 'interactive'; Dev = $false; Fn = 'Invoke-Section18' }
65
+ @{ Id = 19; Key = 'large'; Title = 'Large stale personal files (Downloads) -> Recycle Bin'; Tier = 'recycle'; Admin = $false; Batch = 'interactive'; Dev = $false; Fn = 'Invoke-Section19' }
66
+ @{ Id = 20; Key = 'vhdx'; Title = 'Docker Desktop / WSL disk image compaction (stops Docker + WSL)'; Tier = 'config'; Admin = $true; Batch = 'deep'; Dev = $true; Fn = 'Invoke-Section20' }
67
+ @{ Id = 21; Key = 'diskusage'; Title = 'Disk usage report (largest entries, drives, disk images)'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section21' }
68
+ @{ Id = 22; Key = 'globals'; Title = 'Globally installed packages audit (npm, pnpm, yarn, bun, deno) - report only'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section22' }
69
+ @{ Id = 23; Key = 'orphaned'; Title = 'Orphaned application data under AppData -> Recycle Bin'; Tier = 'recycle'; Admin = $false; Batch = 'interactive'; Dev = $false; Fn = 'Invoke-Section23' }
70
+ @{ Id = 24; Key = 'programs'; Title = 'Installed programs not modified for N+ days - report only'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section24' }
71
+ @{ Id = 25; Key = 'startup'; Title = 'Startup items audit (Run keys, Startup folders, logon tasks) - report only'; Tier = 'report'; Admin = $false; Batch = 'safe'; Dev = $false; Fn = 'Invoke-Section25' }
72
+ )
73
+
74
+ # The safe batch run by --all. Admin sections 12 and 13 join it only when already elevated.
75
+ $Script:WS_SAFE_BATCH = @(0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 21)
76
+ $Script:WS_SAFE_BATCH_ADMIN = @(12, 13)
77
+
78
+ $Script:WS_PROFILES = @{
79
+ 'dev' = @(1, 2, 3, 4, 5, 6, 17)
80
+ 'minimal' = @(7, 8, 9, 10)
81
+ 'cache-only' = @(1, 2, 3, 6, 7, 8, 9)
82
+ 'system' = @(12, 13, 14)
83
+ 'deep' = @(0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 13, 14, 21)
84
+ 'audit' = @(0, 21, 22, 24, 25)
85
+ }
86
+
87
+ # Walkthrough order (interactive default mode). Admin sections are inserted when elevated.
88
+ $Script:WS_WALKTHROUGH = @(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 17, 18, 19, 23)
89
+ $Script:WS_WALKTHROUGH_ADMIN = @(12, 13, 14)
90
+
91
+ function Get-Section {
92
+ <# .SYNOPSIS Returns the catalogue entry for a section id, or $null. #>
93
+ param([int] $Id)
94
+ foreach ($s in $Script:WS_SECTIONS) { if ($s.Id -eq $Id) { return $s } }
95
+ return $null
96
+ }
97
+
98
+ function Get-ToolVersion {
99
+ <# .SYNOPSIS The running version: launcher-provided env wins over the checkout fallback. #>
100
+ $v = $env:WINDOWSWEEP_VERSION
101
+ if ([string]::IsNullOrWhiteSpace($v)) { return $Script:WS_VERSION_FALLBACK }
102
+ return $v.Trim()
103
+ }