Public/Get-WingetPackageInfo.ps1

function Get-WingetPackageInfo {
    <#
    .SYNOPSIS
        Display rich, detailed information about a winget package.
 
    .DESCRIPTION
        Shows comprehensive package metadata in a beautiful terminal layout including:
        version info, publisher, license, pricing, install size, dependencies,
        available versions, and direct links. Similar to 'brew info' or 'apt show'.
 
        Fetches data from both the local COM API and the winget-pkgs GitHub repository
        for maximum detail.
 
    .PARAMETER Id
        The exact package identifier (e.g., "Git.Git", "Python.Python.3.12").
 
    .PARAMETER Query
        A search query to find packages (shows top matches with quick info).
 
    .PARAMETER ShowManifest
        Also display the raw winget manifest YAML from the GitHub repository.
 
    .PARAMETER ShowVersions
        List all available versions for the package.
 
    .EXAMPLE
        Get-WingetPackageInfo -Id "Git.Git"
        Shows full details for Git.
 
    .EXAMPLE
        Get-WingetPackageInfo -Query "visual studio code"
        Searches and shows info for matching packages.
 
    .EXAMPLE
        Get-WingetPackageInfo -Id "Python.Python.3.12" -ShowVersions
        Shows Python 3.12 info plus all available versions.
 
    .LINK
        https://github.com/thebubbsy/WingetBatch
    #>


    [CmdletBinding(DefaultParameterSetName = 'ById')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ById', Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Id,

        [Parameter(Mandatory, ParameterSetName = 'ByQuery', Position = 0)]
        [string]$Query,

        [Parameter()]
        [switch]$ShowManifest,

        [Parameter()]
        [switch]$ShowVersions
    )

    begin {
        if (-not (Get-Module -Name Microsoft.WinGet.Client)) {
            try { Import-Module Microsoft.WinGet.Client -ErrorAction Stop }
            catch {
                Write-Error "Microsoft.WinGet.Client module is required."
                return
            }
        }
        if (-not (Get-Module -Name PwshSpectreConsole)) {
            if (Get-Module -ListAvailable -Name PwshSpectreConsole) {
                Import-Module PwshSpectreConsole -ErrorAction SilentlyContinue
            }
        }
    }

    process {
        # Resolve package
        $package = $null

        if ($PSCmdlet.ParameterSetName -eq 'ByQuery') {
            Write-Host ""
            Write-Host " Searching for: " -ForegroundColor Cyan -NoNewline
            Write-Host $Query -ForegroundColor Yellow

            $results = Microsoft.WinGet.Client\Find-WinGetPackage -Query $Query -Count 10 -ErrorAction SilentlyContinue
            if (-not $results -or $results.Count -eq 0) {
                Write-Host " No packages found matching '$Query'" -ForegroundColor Red
                return
            }

            if ($results.Count -eq 1) {
                $package = $results[0]
            } else {
                # Show selection list
                Write-Host ""
                Write-Host " Multiple matches found:" -ForegroundColor White
                Write-Host ""

                $choices = [System.Collections.Generic.List[string]]::new()
                $choiceMap = @{}
                foreach ($r in $results) {
                    $ver = if ($r.Version) { $r.Version } else { '?' }
                    $display = "$($r.Name) ($($r.Id)) v$ver [$($r.Source)]"
                    $choices.Add($display)
                    $choiceMap[$display] = $r
                }

                if (Get-Module -Name PwshSpectreConsole) {
                    $selected = Read-SpectreSelection -Title "[cyan]Select a package[/]" -Choices $choices -Color "Green"
                    if ($selected) { $package = $choiceMap[$selected] }
                } else {
                    for ($i = 0; $i -lt $choices.Count; $i++) {
                        Write-Host " [$($i + 1)] " -ForegroundColor Cyan -NoNewline
                        Write-Host $choices[$i] -ForegroundColor White
                    }
                    $idx = Read-Host " Enter number (1-$($choices.Count))"
                    if ($idx -match '^\d+$' -and [int]$idx -ge 1 -and [int]$idx -le $choices.Count) {
                        $package = $choiceMap[$choices[[int]$idx - 1]]
                    }
                }

                if (-not $package) {
                    Write-Host " No selection made." -ForegroundColor Yellow
                    return
                }
            }
        } else {
            # Direct ID lookup
            $results = Microsoft.WinGet.Client\Find-WinGetPackage -Id $Id -Count 5 -ErrorAction SilentlyContinue
            if ($results) {
                $package = $results | Where-Object { $_.Id -eq $Id } | Select-Object -First 1
                if (-not $package) { $package = $results[0] }
            }
        }

        if (-not $package) {
            Write-Host " Package not found: $Id" -ForegroundColor Red
            return
        }

        # Also check if installed locally
        $localPkg = Get-WinGetPackage -Id $package.Id -ErrorAction SilentlyContinue

        # Display rich info
        Write-Host ""
        Write-Host " $('─' * 56)" -ForegroundColor DarkGray

        # Package name header
        $nameStr = " $($package.Name)"
        Write-Host $nameStr -ForegroundColor White -NoNewline
        if ($localPkg) {
            Write-Host " [Installed]" -ForegroundColor Green -NoNewline
        }
        Write-Host ""

        Write-Host " $('─' * 56)" -ForegroundColor DarkGray
        Write-Host ""

        # Core info table
        $infoItems = [ordered]@{
            'Id'            = $package.Id
            'Name'          = $package.Name
            'Version'       = if ($package.Version) { $package.Version } else { 'Unknown' }
            'Source'        = if ($package.Source) { $package.Source } else { 'Unknown' }
        }

        if ($localPkg) {
            $infoItems['Installed Ver'] = if ($localPkg.InstalledVersion) { $localPkg.InstalledVersion } else { 'Unknown' }
            $infoItems['Update Available'] = if ($localPkg.IsUpdateAvailable) { 'Yes' } else { 'No' }
            if ($localPkg.IsUpdateAvailable -and $localPkg.AvailableVersion) {
                $infoItems['Latest Version'] = $localPkg.AvailableVersion
            }
        }

        $infoItems['Publisher'] = if ($package.Publisher) { $package.Publisher } else { 'Unknown' }

        # Calculate max key width for alignment
        $maxKeyLen = ($infoItems.Keys | ForEach-Object { $_.Length } | Measure-Object -Maximum).Maximum

        foreach ($key in $infoItems.Keys) {
            $paddedKey = $key.PadRight($maxKeyLen + 2)
            $value = $infoItems[$key]
            $color = switch ($key) {
                'Update Available' { if ($value -eq 'Yes') { 'Yellow' } else { 'Green' } }
                'Id' { 'Cyan' }
                default { 'White' }
            }
            Write-Host " $paddedKey" -ForegroundColor Gray -NoNewline
            Write-Host $value -ForegroundColor $color
        }

        Write-Host ""

        # Fetch extended details from GitHub (manifest) if possible
        if ($ShowManifest -or $ShowVersions) {
            Write-Host " Fetching extended details from winget-pkgs..." -ForegroundColor DarkGray

            $token = $null
            try { $token = Get-WingetBatchGitHubToken } catch {}

            $headers = @{ 'User-Agent' = 'WingetBatch' }
            if ($token) { $headers['Authorization'] = "Bearer $token" }

            # Try to get the manifest from GitHub
            $packageIdPath = $package.Id -replace '\.', '/'
            $apiUrl = "https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/$($packageIdPath.Substring(0,1).ToLower())/$packageIdPath"

            try {
                $response = Invoke-RestMethod -Uri $apiUrl -Headers $headers -ErrorAction Stop
                if ($response -is [array]) {
                    # Find the installer manifest
                    $installerManifest = $response | Where-Object { $_.name -match '\.installer\.yaml$' } | Select-Object -First 1
                    $localeManifest = $response | Where-Object { $_.name -match '\.locale\.' } | Select-Object -First 1

                    if ($localeManifest) {
                        $localeContent = Invoke-RestMethod -Uri $localeManifest.download_url -Headers $headers -ErrorAction SilentlyContinue
                        if ($localeContent) {
                            # Parse useful fields
                            $lines = $localeContent -split "`n"
                            foreach ($line in $lines) {
                                if ($line -match '^License:\s*(.+)') {
                                    Write-Host " $('License'.PadRight($maxKeyLen + 2))" -ForegroundColor Gray -NoNewline
                                    Write-Host $Matches[1].Trim() -ForegroundColor White
                                }
                                if ($line -match '^LicenseUrl:\s*(.+)') {
                                    Write-Host " $('License URL'.PadRight($maxKeyLen + 2))" -ForegroundColor Gray -NoNewline
                                    Write-Host $Matches[1].Trim() -ForegroundColor Cyan
                                }
                                if ($line -match '^PackageUrl:\s*(.+)') {
                                    Write-Host " $('Homepage'.PadRight($maxKeyLen + 2))" -ForegroundColor Gray -NoNewline
                                    Write-Host $Matches[1].Trim() -ForegroundColor Cyan
                                }
                                if ($line -match '^ShortDescription:\s*(.+)') {
                                    Write-Host " $('Description'.PadRight($maxKeyLen + 2))" -ForegroundColor Gray -NoNewline
                                    Write-Host $Matches[1].Trim() -ForegroundColor White
                                }
                                if ($line -match '^Moniker:\s*(.+)') {
                                    Write-Host " $('Moniker'.PadRight($maxKeyLen + 2))" -ForegroundColor Gray -NoNewline
                                    Write-Host $Matches[1].Trim() -ForegroundColor DarkGray
                                }
                                if ($line -match '^- (\w+)$' -and $prevLine -match '^Tags:') {
                                    # Collect tags
                                }
                            }
                        }
                    }

                    if ($ShowVersions) {
                        Write-Host ""
                        Write-Host " Available Versions:" -ForegroundColor White
                        $versions = $response | Where-Object { $_.type -eq 'dir' } | Select-Object -ExpandProperty name | Sort-Object -Descending
                        if ($versions) {
                            $showCount = [Math]::Min($versions.Count, 15)
                            for ($i = 0; $i -lt $showCount; $i++) {
                                $marker = if ($localPkg -and $localPkg.InstalledVersion -eq $versions[$i]) { ' *' } else { '' }
                                Write-Host " - $($versions[$i])$marker" -ForegroundColor $(if ($marker) { 'Green' } else { 'Gray' })
                            }
                            if ($versions.Count -gt 15) {
                                Write-Host " ... and $($versions.Count - 15) more" -ForegroundColor DarkGray
                            }
                        }
                    }

                    if ($ShowManifest -and $installerManifest) {
                        Write-Host ""
                        Write-Host " Raw Installer Manifest:" -ForegroundColor White
                        Write-Host " $('─' * 50)" -ForegroundColor DarkGray
                        $manifestContent = Invoke-RestMethod -Uri $installerManifest.download_url -Headers $headers -ErrorAction SilentlyContinue
                        if ($manifestContent) {
                            $manifestLines = ($manifestContent -split "`n") | Select-Object -First 40
                            foreach ($line in $manifestLines) {
                                Write-Host " $line" -ForegroundColor DarkGray
                            }
                            if (($manifestContent -split "`n").Count -gt 40) {
                                Write-Host " ... (truncated)" -ForegroundColor DarkGray
                            }
                        }
                    }
                }
            } catch {
                Write-Verbose "Could not fetch extended details from GitHub: $_"
            }
        }

        Write-Host ""
        Write-Host " $('─' * 56)" -ForegroundColor DarkGray

        # Quick action hints
        Write-Host " Actions: " -ForegroundColor DarkGray -NoNewline
        if ($localPkg) {
            if ($localPkg.IsUpdateAvailable) {
                Write-Host "Update-WinGetPackage -Id '$($package.Id)'" -ForegroundColor Yellow -NoNewline
            } else {
                Write-Host "Up to date" -ForegroundColor Green -NoNewline
            }
            Write-Host " | Uninstall-WinGetPackage -Id '$($package.Id)'" -ForegroundColor DarkGray
        } else {
            Write-Host "Install-WingetAll -Id '$($package.Id)'" -ForegroundColor Green
        }
        Write-Host ""
    }
}