Skip to content

feat(windows): add Windows support Phase 1 - core infrastructure#275

Merged
tw93 merged 15 commits intotw93:devfrom
bhadraagada:dev
Jan 9, 2026
Merged

feat(windows): add Windows support Phase 1 - core infrastructure#275
tw93 merged 15 commits intotw93:devfrom
bhadraagada:dev

Conversation

@bhadraagada
Copy link
Contributor

Adds Windows support for Mole as proposed in #273. All Windows code is isolated in the windows/ directory with zero changes to existing macOS code.

This PR implements Phase 1: Core Infrastructure.

Changes
New Files in windows/

File Purpose
mole.ps1 Main CLI entry point with menu system
install.ps1 Windows installer (PATH + optional shortcut)
README.md Windows-specific documentation
go.mod / go.sum Go module for upcoming TUI tools
lib/core/base.ps1 Core definitions, colors, icons, constants
lib/core/common.ps1 Common functions loader, Initialize-Mole
lib/core/file_ops.ps1 Safe file operations with protection checks
lib/core/log.ps1 Logging functions (Write-Info, Write-Success, etc.)
lib/core/ui.ps1 Interactive UI (menus, confirmations, banners)

Architecture

windows/
├── mole.ps1            # CLI entry point
├── install.ps1         # Installer
├── README.md           # Docs
├── go.mod / go.sum     # Go module
└── lib/core/           # Core libraries
    ├── base.ps1        # Constants, colors
    ├── common.ps1      # Initialization
    ├── file_ops.ps1    # Safe file ops
    ├── log.ps1         # Logging
    └── ui.ps1          # UI components

Add isolated Windows support in windows/ directory with zero changes to existing macOS code.

Phase 1 includes:
- install.ps1: Windows installer with PATH and shortcut support
- mole.ps1: Main CLI entry point with menu system
- lib/core/base.ps1: Core definitions, colors, icons, constants
- lib/core/common.ps1: Common functions loader
- lib/core/file_ops.ps1: Safe file operations with protection checks
- lib/core/log.ps1: Logging functions with colors
- lib/core/ui.ps1: Interactive UI components (menus, confirmations)
- go.mod/go.sum: Go module for future TUI tools
- README.md: Windows-specific documentation

Closes tw93#273
- Simplified banner in install.ps1 and ui.ps1 to avoid Unicode box-drawing
  characters that caused parsing errors on some Windows systems
- Fixed PowerShell 5.1 compatibility for ANSI color codes using [char]27
- Renamed Show-Help to Show-InstallerHelp to avoid potential conflicts
- Changed -Help parameter to -ShowHelp for consistency with mole.ps1
Copilot AI review requested due to automatic review settings January 8, 2026 09:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Windows support for Mole as Phase 1 of a multi-phase implementation. The changes add a complete PowerShell-based infrastructure for Windows system maintenance, isolated entirely in a new windows/ directory with zero impact on existing macOS code.

Key Changes:

  • Complete PowerShell-based CLI and library infrastructure
  • Windows installer with PATH integration and shortcut creation
  • Core utility libraries for safe file operations, logging, and interactive UI
  • Go module setup for planned TUI tools in future phases

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
windows/README.md Documentation covering installation, usage, directory structure, and development roadmap
windows/install.ps1 Windows installer supporting custom paths, PATH integration, shortcuts, and uninstallation
windows/mole.ps1 Main CLI entry point with command routing, interactive menu, and help system
windows/go.mod Go module definition for future TUI tool dependencies
windows/go.sum Dependency checksums for Go modules
windows/lib/core/base.ps1 Core definitions including colors, icons, constants, path utilities, and formatting functions
windows/lib/core/common.ps1 Module loader and initialization logic
windows/lib/core/log.ps1 Logging infrastructure with multiple log levels and progress indicators
windows/lib/core/ui.ps1 Interactive UI components including menus, confirmations, and banners
windows/lib/core/file_ops.ps1 Safe file operations with protection checks, whitelisting, and cleanup statistics

Comment on lines +64 to +68
function Write-Error {
param([string]$Message)
$c = $script:Colors
Write-Host " $($c.Red)ERROR$($c.NC) $Message"
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function redefines PowerShell's built-in "Write-Error" cmdlet, which is a fundamental part of PowerShell's error handling. This can cause significant conflicts and break error handling throughout the script and other modules. Consider renaming to "Write-ErrorMessage" or "Write-InstallerError" to avoid shadowing the built-in cmdlet.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +90
function Write-Error {
<#
.SYNOPSIS
Write an error message
#>
param([string]$Message)
Write-LogMessage -Message $Message -Level "ERROR" -Color "Red" -Icon $script:Icons.Error
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function redefines PowerShell's built-in "Write-Error" cmdlet, which can cause conflicts and unexpected behavior throughout the script. This shadows the standard error handling mechanism in PowerShell. Consider renaming to "Write-ErrorMessage" or "Write-MoleError" to avoid conflicts with the built-in cmdlet.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +81
function Write-Warning {
<#
.SYNOPSIS
Write a warning message
#>
param([string]$Message)
Write-LogMessage -Message $Message -Level "WARN" -Color "Yellow" -Icon $script:Icons.Warning
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function redefines PowerShell's built-in "Write-Warning" cmdlet, which can cause conflicts and unexpected behavior. This shadows the standard warning mechanism in PowerShell. Consider renaming to "Write-WarningMessage" or "Write-MoleWarning" to avoid conflicts with the built-in cmdlet.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +104
function Write-Debug {
<#
.SYNOPSIS
Write a debug message (only if debug mode is enabled)
#>
param([string]$Message)

if ($script:LogConfig.DebugEnabled) {
$gray = $script:Colors.Gray
$nc = $script:Colors.NC
Write-Host " ${gray}[DEBUG] $Message${nc}"
}
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function redefines PowerShell's built-in "Write-Debug" cmdlet, which can cause conflicts with PowerShell's native debug output functionality. Consider renaming to "Write-DebugMessage" or "Write-MoleDebug" to avoid shadowing the built-in cmdlet.

Copilot uses AI. Check for mistakes.
Comment on lines +215 to +236
function Write-Progress {
<#
.SYNOPSIS
Write a progress bar
#>
param(
[int]$Current,
[int]$Total,
[string]$Message = "",
[int]$Width = 30
)

$percent = if ($Total -gt 0) { [Math]::Round(($Current / $Total) * 100) } else { 0 }
$filled = [Math]::Round(($Width * $Current) / [Math]::Max($Total, 1))
$empty = $Width - $filled

$bar = ("[" + ("=" * $filled) + (" " * $empty) + "]")
$cyan = $script:Colors.Cyan
$nc = $script:Colors.NC

Write-Host -NoNewline "`r ${cyan}$bar${nc} ${percent}% $Message "
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function redefines PowerShell's built-in "Write-Progress" cmdlet, which is widely used for progress bars throughout PowerShell. This can cause conflicts with other modules and scripts that rely on the standard Write-Progress cmdlet. Consider renaming to "Write-ProgressBar" or "Write-MoleProgress" to avoid shadowing the built-in cmdlet.

Copilot uses AI. Check for mistakes.
if (-not (Test-Path $scriptPath)) {
Write-Error "Unknown command: $CommandName"
Write-Host ""
Write-Host "Run 'mole -ShowHelp' for available commands"
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message incorrectly states to use "mole -ShowHelp" when the correct syntax is "mole.ps1 -ShowHelp" (since this is within a context where the command may not be in PATH yet). The message should be consistent with how users would actually invoke the command at this point in the code.

Suggested change
Write-Host "Run 'mole -ShowHelp' for available commands"
Write-Host "Run 'mole.ps1 -ShowHelp' for available commands"

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +100
if (Read-Confirmation -Prompt "Restart with admin privileges?" -Default $true) {
$scriptPath = $MyInvocation.PSCommandPath
if ($scriptPath) {
Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File `"$scriptPath`"" -Verb RunAs
exit
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function uses "$MyInvocation.PSCommandPath" which may be null in certain execution contexts (like when dot-sourcing). This could cause the restart to fail silently. Consider adding a fallback or validation check to ensure the script path is available before attempting to restart.

Suggested change
if (Read-Confirmation -Prompt "Restart with admin privileges?" -Default $true) {
$scriptPath = $MyInvocation.PSCommandPath
if ($scriptPath) {
Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File `"$scriptPath`"" -Verb RunAs
exit
}
if (Read-Confirmation -Prompt "Restart with admin privileges?" -Default $true) {
# Try to get the current script path from PSCommandPath; this may be null in some contexts
$scriptPath = $MyInvocation.PSCommandPath
# Fallback to the automatic $PSCommandPath variable if available
if (-not $scriptPath -and $PSCommandPath) {
$scriptPath = $PSCommandPath
}
if ($scriptPath) {
Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File `"$scriptPath`"" -Verb RunAs
exit
}
else {
Write-Error "Cannot restart with admin privileges because the script path is not available in this execution context."
}

Copilot uses AI. Check for mistakes.
.SYNOPSIS
Get number of CPU cores
#>
return (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Get-WmiObject is deprecated in PowerShell. Consider using Get-CimInstance instead, which is the modern replacement and performs better. For example, replace "Get-WmiObject Win32_Processor" with "Get-CimInstance Win32_Processor".

Copilot uses AI. Check for mistakes.
"C:\ProgramData\Microsoft\Windows Defender"
"$env:SYSTEMROOT"
"$env:WINDIR"
)
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The protected paths list includes environment variables that may not be set in all Windows configurations, which could lead to null or empty paths being added to the protection list. Consider adding validation to ensure these environment variables are defined before adding them to the protected paths array.

Suggested change
)
) | Where-Object { $_ -and $_.ToString().Trim() }

Copilot uses AI. Check for mistakes.
Comment on lines +291 to +293
foreach ($key in $selected.Keys) {
if ($selected[$key]) {
$result += $Items[$key]
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name "$key" shadows the loop variable in the outer foreach loop (line 291). This creates confusion and potential bugs. Consider renaming the inner variable to something like "$selectedKey" or "$index" to avoid shadowing.

Suggested change
foreach ($key in $selected.Keys) {
if ($selected[$key]) {
$result += $Items[$key]
foreach ($selectedKey in $selected.Keys) {
if ($selected[$selectedKey]) {
$result += $Items[$selectedKey]

Copilot uses AI. Check for mistakes.
Add cleanup modules and command scripts for Windows:
- lib/clean: user, caches, dev, apps, system cleanup modules
- bin/clean: deep cleanup orchestrator with dry-run and whitelist
- bin/uninstall: interactive app uninstaller
- bin/optimize: system optimization and health checks
- bin/purge: project artifact cleanup

All scripts support dry-run mode and follow safe deletion practices.
@bhadraagada
Copy link
Contributor Author

  • Adds Windows support in isolated `windows/` directory
  • Phase 1: Core infrastructure (mole.ps1, install.ps1, lib/core/)
  • Phase 2: Cleanup features (bin/clean, uninstall, optimize, purge)
  • Zero changes to existing macOS code

Add Go-based TUI tools for Windows:
- cmd/analyze: Interactive disk space analyzer with Bubble Tea
- cmd/status: Real-time system health monitor using gopsutil/WMI
- bin/analyze.ps1: Wrapper script for analyze tool
- bin/status.ps1: Wrapper script for status tool
- Makefile: Build automation for Go tools
- Updated README.md with Phase 3 documentation
- Pester test suite for PowerShell scripts (Core, Clean, Commands)
- Go tests for TUI tools (analyze, status)
- GitHub Actions workflow for Windows CI
- Build and test automation scripts (test.ps1, build.ps1)
- Fix mole.cmd batch launcher to properly pass switch arguments
- Store %~dp0 before parse loop to avoid expansion issues
- Use PowerShell splatting in Invoke-MoleCommand for proper switch handling
- Rename $script:DryRun to $script:MoleDryRunMode in file_ops.ps1 to avoid
  variable shadowing when dot-sourcing
- Handle switches passed as strings (e.g., '-ShowHelp') in mole.ps1 Main()

Fixes issue where 'mole clean -DryRun' would run cleanup instead of preview.
- Fix analyze: Add timeout and depth limits to calculateDirSize() to prevent
  indefinite scanning on large directories like user profile
- Fix purge: Add null checks for .Count property access under StrictMode
- Fix uninstall: Wrap registry property access in try/catch for items without
  DisplayName, and add null checks throughout
- Fix mole.ps1: Add null check for Arguments.Count
- Add try/catch around CursorVisible calls for non-interactive terminals

All 73 Pester tests and all Go tests pass.
- Reduce timeout from 5s to 500ms per directory
- Limit scan depth to 3 levels for quick size estimation
- Reduce max files scanned from 50k to 10k
- Results now appear in 1-2 seconds instead of minutes
@JackPhallen
Copy link
Contributor

We may want to put this in a separate branch until the complete Windows implementation is complete

- Remove existing directories before copying to prevent nested folder structures (e.g., bin/bin)
- This fixes 'Unknown command' errors after installation
- Fix openInExplorer to actually execute explorer.exe (was a no-op)
- Load System.Windows.Forms assembly before using Clipboard
- Use Remove-SafeItem in purge for consistent safety checks
- Fix Dropbox typo (was DroplboxCache)
- Fix Test-IsProtectedPath -> Test-ProtectedPath in CI workflow
- Fix purge.ps1 to handle boolean return from Remove-SafeItem
- Add C:\Program Files and C:\Program Files (x86) to ProtectedPaths
- Add protection checks to Go analyze tool before delete operations
- Use try/finally to ensure Windows Update service restarts after cleanup
- Don't count interactive uninstall as automatic success
- Tighten orphaned app detection with stricter prefix matching
Some terminals send escape sequences (ESC [ A/B) instead of VirtualKeyCode
for arrow keys. Now handles both methods for better terminal compatibility.
@makimask-jx
Copy link

Mac app
Look inside
Add Windows support

image

@tw93 tw93 merged commit ee2c798 into tw93:dev Jan 9, 2026
@tw93
Copy link
Owner

tw93 commented Jan 9, 2026

@NetChirp Thank you for your feedback. I will discuss with the contributors whether having a separate Windows branch would be better to avoid inconvenience for everyone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants