Private/Get-FylgyrOwnerContext.ps1

function Get-FylgyrOwnerContext {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-zA-Z0-9._-]+$')]
        [string]$Owner,

        [Parameter(Mandatory)]
        [string]$Token
    )

    if (-not ($script:FylgyrOwnerContextCache -is [hashtable])) {
        $script:FylgyrOwnerContextCache = @{}
    }

    $tokenHashBytes = [System.Security.Cryptography.SHA256]::HashData([System.Text.Encoding]::UTF8.GetBytes($Token))
    $tokenHash = ([System.BitConverter]::ToString($tokenHashBytes)).Replace('-', '')
    $cacheKey = "$Owner::$tokenHash"

    if ($script:FylgyrOwnerContextCache.ContainsKey($cacheKey)) {
        return $script:FylgyrOwnerContextCache[$cacheKey]
    }

    $normalizePlan = {
        param([object]$Name)
        $raw = [string]$Name
        if (-not $raw) {
            return 'unknown'
        }

        $lower = $raw.ToLowerInvariant()
        if ($lower -match '^enterprise') { return 'enterprise' }
        if ($lower -match '^team')       { return 'team' }
        if ($lower -match '^pro')        { return 'pro' }
        if ($lower -match '^free')       { return 'free' }
        return 'unknown'
    }

    $context = [PSCustomObject]@{
        Type             = 'Unknown'
        Login            = $Owner
        PlanName         = 'unknown'
        TokenOwner       = 'unknown'
        TokenMatchesOwner = $false
    }

    $ownerInfo = $null
    $authedUser = $null

    try {
        $ownerInfo = Invoke-GitHubApi -Endpoint "users/$Owner" -Token $Token

        if ($ownerInfo -and $ownerInfo.PSObject.Properties['type'] -and
            $ownerInfo.type -in @('User', 'Organization')) {
            $context.Type = $ownerInfo.type
        }

        if ($ownerInfo -and $ownerInfo.PSObject.Properties['login'] -and $ownerInfo.login) {
            $context.Login = [string]$ownerInfo.login
        }
    }
    catch {
        $msg = $_.Exception.Message
        if ($msg -notmatch '403' -and $msg -notmatch '404') {
            Write-Debug "Owner context fallback for '$Owner': $msg"
        }

        $script:FylgyrOwnerContextCache[$cacheKey] = $context
        return $context
    }

    try {
        $authedUser = Invoke-GitHubApi -Endpoint 'user' -Token $Token
        if ($authedUser -and $authedUser.PSObject.Properties['login'] -and $authedUser.login) {
            $context.TokenOwner = [string]$authedUser.login
        }
    }
    catch {
        Write-Debug "Could not resolve token owner for '$Owner': $($_.Exception.Message)"
    }

    if ($context.TokenOwner -and $context.TokenOwner -ne 'unknown' -and $context.Login) {
        $context.TokenMatchesOwner = $context.TokenOwner.Equals($context.Login, [System.StringComparison]::OrdinalIgnoreCase)
    }

    if ($context.Type -eq 'User') {
        if ($authedUser -and $context.TokenMatchesOwner -and $authedUser.PSObject.Properties['plan']) {
            $context.PlanName = & $normalizePlan $authedUser.plan.name
        }
        elseif ($ownerInfo -and $ownerInfo.PSObject.Properties['plan']) {
            $context.PlanName = & $normalizePlan $ownerInfo.plan.name
        }
    }
    elseif ($context.Type -eq 'Organization') {
        try {
            $orgInfo = Invoke-GitHubApi -Endpoint "orgs/$Owner" -Token $Token
            if ($orgInfo -and $orgInfo.PSObject.Properties['plan']) {
                $context.PlanName = & $normalizePlan $orgInfo.plan.name
            }
        }
        catch {
            $msg = $_.Exception.Message
            if ($msg -notmatch '403' -and $msg -notmatch '404') {
                Write-Debug "Could not resolve organization plan for '$Owner': $msg"
            }
        }
    }

    $script:FylgyrOwnerContextCache[$cacheKey] = $context
    return $context
}