Public/Update-KritPax8McpToken.ps1

function Update-KritPax8McpToken {
    <#
    .SYNOPSIS
        Rotate the Pax8 MCP token: prompt for the new value (SecureString, no echo),
        back up the old token, write the new one to the secrets folder, re-wire every
        currently-wired agent, re-probe live.

    .DESCRIPTION
        Use after the operator mints a new token from app.pax8.com (Settings >
        Integrations > MCP server > Connect > Claude > Option 2 Pax8 Token).

        Token is read via Read-Host -AsSecureString so it never appears in the
        host buffer / transcript / clipboard echo.

    .EXAMPLE
        Update-KritPax8McpToken
        Prompts; rotates; re-wires every detected agent.

    .EXAMPLE
        Update-KritPax8McpToken -NewToken (Get-Content C:\drop\new-token.txt -Raw)
        Non-interactive rotation (CI / Hermes / supervisor).

    .NOTES
        Author: Joshua Finley - Kritical Pty Ltd
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([pscustomobject])]
    param(
        [string] $NewToken,
        [string] $SecretsDir,
        [string] $TokenFileName,
        [string[]] $Agent,
        [switch] $NoBanner,
        [switch] $SkipReinstall
    )
    if (-not $NoBanner.IsPresent) { Write-KritPax8Banner -Title 'Rotate Pax8 MCP Token' }

    $tokenPath = Get-KritPax8TokenPath -SecretsDir $SecretsDir -TokenFileName $TokenFileName

    if (-not $NewToken) {
        Write-Host ''
        Write-Host 'Mint a fresh Pax8 MCP token via:' -ForegroundColor Yellow
        Write-Host ' https://app.pax8.com -> Settings -> Integrations -> MCP server -> Connect -> Claude -> Option 2 Pax8 Token'
        $sec = Read-Host 'Paste new Pax8 MCP token (input not echoed)' -AsSecureString
        $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
        try {
            $NewToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
        } finally {
            [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) | Out-Null
        }
    }
    $NewToken = $NewToken.Trim()
    if ([string]::IsNullOrWhiteSpace($NewToken)) { throw 'No token entered.' }
    if (-not (Test-KritPax8TokenSane -Token $NewToken)) {
        throw ("New token rejected — failed sanity check (length=$($NewToken.Length), whitespace=" + ($NewToken -match '\s') + ").")
    }

    # Backup existing token
    $bak = $null
    if (Test-Path -LiteralPath $tokenPath) {
        $utc = (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmssZ')
        $bak = "$tokenPath.bak.$utc"
        if ($PSCmdlet.ShouldProcess($tokenPath, "Back up existing token to $bak")) {
            Move-Item -LiteralPath $tokenPath -Destination $bak -Force
        }
    }
    if ($PSCmdlet.ShouldProcess($tokenPath, 'Write new token')) {
        [System.IO.File]::WriteAllText($tokenPath, $NewToken, [System.Text.UTF8Encoding]::new($false))
    }
    Write-Host ('Token written to: ' + $tokenPath) -ForegroundColor Green

    $installResult = $null
    if (-not $SkipReinstall.IsPresent) {
        Write-Host 'Re-wiring agents with new token...' -ForegroundColor DarkCyan
        $installResult = Install-KritPax8Mcp -Agent $Agent -SecretsDir $SecretsDir -TokenFileName $TokenFileName -NoBanner -SkipProbe
    }

    Write-Host 'Probing mcp.pax8.com with new token...' -ForegroundColor DarkCyan
    $probe = Invoke-KritPax8McpInitialize -Token $NewToken
    if ($probe.Ok) {
        $tools = Get-KritPax8McpToolList -Token $NewToken
        Write-Host (" Server: " + $probe.ServerName + " v" + $probe.ServerVersion + " | tools: " + $tools.ToolCount) -ForegroundColor Green
    } else {
        Write-Host (' [FAIL] ' + ($probe.Error ?? "HTTP $($probe.StatusCode)")) -ForegroundColor Red
    }

    [pscustomobject]@{
        TokenPath       = $tokenPath
        Backup          = $bak
        ProbeOk         = $probe.Ok
        ProbeServer     = $probe.ServerName
        InstallResult   = $installResult
        RestartRequired = $true
    }
}