public/Uninstall-AITool.ps1

function Uninstall-AITool {
    <#
    .SYNOPSIS
        Uninstalls the specified AI CLI tool.
 
    .DESCRIPTION
        Uninstalls AI CLI tools (Claude Code, Aider, Gemini CLI, GitHub Copilot CLI, or OpenAI Codex CLI)
        with cross-platform support for Windows, Linux, and MacOS.
 
    .PARAMETER Name
        The name of the AI tool to uninstall. Valid values: ClaudeCode, Aider, Gemini, GitHubCopilot, Codex
 
    .PARAMETER Force
        Force uninstallation without confirmation prompts.
 
    .EXAMPLE
        Uninstall-AITool -Name ClaudeCode
        Uninstalls Claude Code after confirmation.
 
    .EXAMPLE
        Uninstall-AITool -Name Aider -Force
        Uninstalls Aider without confirmation.
 
    .OUTPUTS
        AITools.UninstallResult
        An object containing Tool name, Result (Success/Failed), and Uninstaller command used.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [Alias('Tool')]
        [string]$Name,

        [Parameter()]
        [switch]$Force
    )

    begin {
        Write-PSFMessage -Level Verbose -Message "Starting uninstallation of $Name"
    }

    process {
        Write-Progress -Activity "Uninstalling $Name" -Status "Preparing" -PercentComplete 5
        Write-PSFMessage -Level Verbose -Message "Retrieving tool definition for $Name"
        $tool = $script:ToolDefinitions[$Name]
        $os = Get-OperatingSystem

        if (-not $tool) {
            Write-Progress -Activity "Uninstalling $Name" -Completed
            Stop-PSFFunction -Message "Unknown tool: $Name" -EnableException $true
            return
        }

        Write-Progress -Activity "Uninstalling $Name" -Status "Checking installation" -PercentComplete 10
        Write-PSFMessage -Level Verbose -Message "Checking if $Name is installed"

        if (-not (Test-Command -Command $tool.Command)) {
            Write-Progress -Activity "Uninstalling $Name" -Completed
            Write-PSFMessage -Level Warning -Message "$Name is not currently installed."

            [PSCustomObject]@{
                PSTypeName  = 'AITools.UninstallResult'
                Tool        = $Name
                Result      = 'Failed'
                Uninstaller = 'N/A'
            }
            return
        }

        Write-Progress -Activity "Uninstalling $Name" -Status "Detecting installation method" -PercentComplete 15

        # Detect installation method by checking where the command is located
        $commandInfo = Get-Command $tool.Command -ErrorAction SilentlyContinue
        $commandPath = $commandInfo.Source
        if (-not $commandPath) {
            $commandPath = $commandInfo.Path
        }

        Write-PSFMessage -Level Verbose -Message "Command location: $commandPath"

        # Determine the appropriate uninstall command based on installation location
        $uninstallCmd = $tool.UninstallCommands[$os]

        # On Windows, detect if tool was installed via npm vs winget
        if ($os -eq 'Windows' -and $commandPath -match '\\npm\\') {
            Write-PSFMessage -Level Verbose -Message "Detected npm installation (path contains npm directory)"
            # Override with npm uninstall command if available
            $npmUninstallCmd = $tool.UninstallCommands['Linux']  # Linux/MacOS use npm
            if ($npmUninstallCmd -and $npmUninstallCmd -match '^npm') {
                Write-PSFMessage -Level Verbose -Message "Using npm uninstall command instead of default Windows command"
                $uninstallCmd = $npmUninstallCmd
            }
        }

        Write-PSFMessage -Level Verbose -Message "Getting uninstall command for $os"

        if (-not $uninstallCmd) {
            Write-Progress -Activity "Uninstalling $Name" -Completed
            Stop-PSFFunction -Message "No uninstall command defined for $Name on $os" -EnableException $true
            return
        }

        # Confirm unless Force is specified
        if (-not $Force -and -not $PSCmdlet.ShouldProcess($Name, "Uninstall AI tool")) {
            Write-Progress -Activity "Uninstalling $Name" -Completed
            Write-PSFMessage -Level Verbose -Message "Uninstallation cancelled by user"
            return
        }

        Write-Progress -Activity "Uninstalling $Name" -Status "Uninstalling" -PercentComplete 30
        Write-PSFMessage -Level Verbose -Message "Uninstalling $Name on $os..."
        Write-PSFMessage -Level Verbose -Message "Command: $uninstallCmd"
        Write-PSFMessage -Level Verbose -Message "Executing uninstall command"

        try {
            # Use Start-Process to reliably capture both stdout and stderr
            # Split the command into executable and arguments
            $cmdParts = $uninstallCmd -split ' ', 2
            $executable = $cmdParts[0]
            $arguments = if ($cmdParts.Count -gt 1) { $cmdParts[1] } else { '' }

            Write-PSFMessage -Level Verbose -Message "Executable: $executable"
            Write-PSFMessage -Level Verbose -Message "Arguments: $arguments"

            # Resolve the full path to the executable to avoid PATH issues with UseShellExecute = $false
            $executablePath = (Get-Command $executable -ErrorAction SilentlyContinue).Source
            if (-not $executablePath) {
                $executablePath = (Get-Command $executable -ErrorAction SilentlyContinue).Path
            }
            if (-not $executablePath) {
                # If we still can't find it, use the executable as-is and hope for the best
                $executablePath = $executable
            }

            Write-PSFMessage -Level Verbose -Message "Resolved path: $executablePath"

            # If the resolved path is a .ps1 or .cmd file, we need to invoke it through the shell
            # On Windows, npm resolves to npm.ps1 or npm.cmd which can't be directly executed
            $psi = New-Object System.Diagnostics.ProcessStartInfo
            if ($executablePath -match '\.(ps1|cmd)$') {
                Write-PSFMessage -Level Verbose -Message "Detected shell script, using cmd.exe wrapper"
                $psi.FileName = "cmd.exe"
                $psi.Arguments = "/c `"$executable $arguments`""
            } else {
                $psi.FileName = $executablePath
                $psi.Arguments = $arguments
            }
            $psi.RedirectStandardOutput = $true
            $psi.RedirectStandardError = $true
            $psi.UseShellExecute = $false
            $psi.CreateNoWindow = $true

            $process = New-Object System.Diagnostics.Process
            $process.StartInfo = $psi
            $process.Start() | Out-Null

            $stdout = $process.StandardOutput.ReadToEnd()
            $stderr = $process.StandardError.ReadToEnd()
            $process.WaitForExit()

            $exitCode = $process.ExitCode
            $outputText = "$stdout`n$stderr"

            # Send output to verbose
            if ($stdout) {
                $stdout -split "`n" | ForEach-Object { Write-PSFMessage -Level Verbose -Message $_ }
            }
            if ($stderr) {
                $stderr -split "`n" | ForEach-Object { Write-PSFMessage -Level Verbose -Message $_ }
            }

            Write-PSFMessage -Level Verbose -Message "Uninstall command completed with exit code: $exitCode"

            # Check if the uninstallation command failed
            if ($exitCode -ne 0) {
                Write-Progress -Activity "Uninstalling $Name" -Completed

                # Provide helpful error messages for common scenarios
                $nl = [Environment]::NewLine
                $errorMessage = "Uninstall command failed with exit code ${exitCode}.${nl}${nl}Command: $uninstallCmd"

                # Debug: Show what we captured
                Write-PSFMessage -Level Verbose -Message "Output text length: $($outputText.Length)"
                if ($outputText.Length -gt 0) {
                    Write-PSFMessage -Level Verbose -Message "Output text preview: $($outputText.Substring(0, [Math]::Min(200, $outputText.Length)))"
                }

                # Provide context-specific help based on the error
                if ($outputText -match "not found" -or $outputText -match "No package found") {
                    $errorMessage += "${nl}${nl}The package may have already been uninstalled or was installed using a different method."
                } elseif ($outputText -match "permission denied" -or $outputText -match "PermissionError") {
                    $errorMessage += "${nl}${nl}Permission denied. Try running with appropriate permissions (sudo on Linux/MacOS, or as Administrator on Windows)."
                } elseif ($outputText -match "pipx") {
                    $errorMessage += "${nl}${nl}pipx may not be installed or configured properly. Ensure pipx is available in your PATH."
                }

                Stop-PSFFunction -Message $errorMessage -EnableException $true
                return
            }

            Write-Progress -Activity "Uninstalling $Name" -Status "Verifying removal" -PercentComplete 85
            Write-PSFMessage -Level Verbose -Message "Verifying uninstallation"

            # Give the system a moment to update the PATH
            Start-Sleep -Milliseconds 500

            if (-not (Test-Command -Command $tool.Command)) {
                Write-PSFMessage -Level Verbose -Message "$Name uninstalled successfully!"
                Write-Progress -Activity "Uninstalling $Name" -Status "Complete" -PercentComplete 100
                Write-Progress -Activity "Uninstalling $Name" -Completed

                # Output directly to pipeline
                [PSCustomObject]@{
                    PSTypeName  = 'AITools.UninstallResult'
                    Tool        = $Name
                    Result      = 'Success'
                    Uninstaller = $uninstallCmd
                }
            } else {
                Write-Progress -Activity "Uninstalling $Name" -Completed
                Write-PSFMessage -Level Warning -Message "$Name uninstall command completed but command is still available. You may need to restart your shell or manually remove it."

                [PSCustomObject]@{
                    PSTypeName  = 'AITools.UninstallResult'
                    Tool        = $Name
                    Result      = 'Failed'
                    Uninstaller = $uninstallCmd
                }
            }
        } catch {
            Write-Progress -Activity "Uninstalling $Name" -Completed
            Stop-PSFFunction -Message "Failed to uninstall $Name : $_" -EnableException $true
        }
    }
}