core/public/Write-JaxUpdateNotice.ps1

function Write-JaxUpdateNotice {
    <#
    .SYNOPSIS
        Print a short "a newer Jax is available" hint, at most once per shell
        session.
    .DESCRIPTION
        Reads only the locally cached PowerShell Gallery result, so it never
        waits on the network. When that cache is stale it starts a detached
        background refresh and prints nothing on this run; the next run uses the
        fresh result. Every failure mode - no cache, no network, an unreadable
        state file, a read-only HOME - is silent.

        The hint is skipped when output is redirected (scripts, pipelines, and
        agent sessions), when CI is set, and when JAX_NO_UPDATE_NOTICE is set.
    .EXAMPLE
        Write-JaxUpdateNotice -RuntimeRoot $PSScriptRoot
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $RuntimeRoot,
        [string] $CachePath = (Join-Path $HOME '.jax/update-check.json'),
        [string] $StatePath = (Join-Path $HOME '.jax/update-notice.json'),
        [TimeSpan] $CacheTtl = [TimeSpan]::FromHours(24),
        [switch] $Force
    )

    try {
        if (-not $Force) {
            $optOut = [string]$env:JAX_NO_UPDATE_NOTICE
            if (-not [string]::IsNullOrWhiteSpace($optOut) -and $optOut -notin @('0', 'false', 'no')) {
                return
            }
            if (-not [string]::IsNullOrWhiteSpace([string]$env:CI)) {
                return
            }
            if ([Console]::IsOutputRedirected) {
                return
            }
        }

        $versionPath = Join-Path ([IO.Path]::GetFullPath($RuntimeRoot)) 'VERSION'
        if (-not (Test-Path -LiteralPath $versionPath -PathType Leaf)) {
            return
        }
        $currentVersion = $null
        if (-not [version]::TryParse((Get-Content -LiteralPath $versionPath -Raw).Trim(), [ref] $currentVersion)) {
            return
        }

        $cacheFile = [IO.Path]::GetFullPath($CachePath)
        $stateFile = [IO.Path]::GetFullPath($StatePath)
        $cache = Read-JaxJsonFile -Path $cacheFile
        $checkedAt = ConvertTo-JaxUtcDate -Value (Get-JaxJsonProperty -Object $cache -Name 'CheckedAtUtc')

        if ($null -eq $checkedAt -or ([DateTime]::UtcNow - $checkedAt) -ge $CacheTtl) {
            Start-JaxUpdateCheck -RuntimeRoot $RuntimeRoot -CachePath $cacheFile -StatePath $stateFile
            return
        }

        if ([string](Get-JaxJsonProperty -Object $cache -Name 'Status') -ne 'Available') {
            return
        }
        $latestVersion = $null
        $latestText = [string](Get-JaxJsonProperty -Object $cache -Name 'LatestVersion')
        if (-not [version]::TryParse($latestText, [ref] $latestVersion)) {
            return
        }
        if ($latestVersion -le $currentVersion) {
            return
        }

        $sessionKey = Get-JaxShellSessionKey
        $state = Read-JaxJsonFile -Path $stateFile
        foreach ($entry in @(Get-JaxJsonProperty -Object $state -Name 'NotifiedSessions')) {
            if ($null -eq $entry) { continue }
            if ([string](Get-JaxJsonProperty -Object $entry -Name 'Session') -ne $sessionKey) { continue }
            if ([string](Get-JaxJsonProperty -Object $entry -Name 'Version') -eq $latestVersion.ToString()) {
                return
            }
        }

        Save-JaxUpdateNoticeState -Path $stateFile -State $state `
            -NotifiedSession $sessionKey -NotifiedVersion $latestVersion.ToString()

        Write-Host ''
        Write-Host (" Jax {0} is available (you have {1})." -f $latestVersion, $currentVersion) -ForegroundColor Cyan
        Write-Host ' Update: Update-PSResource Jax -Scope CurrentUser -Repository PSGallery -TrustRepository' -ForegroundColor DarkGray
    } catch {
        # An update hint must never slow down, interrupt, or fail a Jax run.
    }
}