Public/Show-FlightRecorder.ps1
|
# Copyright (c) 2026 Jeffrey Snover. All rights reserved. # Licensed under the MIT License. See LICENSE file in the project root. function Show-FlightRecorder { <# .SYNOPSIS Opens a flight recorder dump file in the interactive HTML viewer. .DESCRIPTION Launches the flight recorder viewer (tools/flight-recorder-viewer.html) in the default browser with the specified dump file. The viewer supports filtering, search, timeline visualization, and event detail inspection. .PARAMETER Path Path to a .jsonl flight recorder dump file. Can be piped from Get-FlightRecorderDump. .PARAMETER Last Shortcut: open the Nth most recent dump file. Default: 1 (latest). .EXAMPLE # Open the most recent dump Show-FlightRecorder -Last 1 .EXAMPLE # Open a specific dump file Show-FlightRecorder -Path ~/Library/Application\ Support/taxonomy-editor/flight-recorder/flight-recorder-2026-05-10.jsonl .EXAMPLE # Pipe from Get-FlightRecorderDump Get-FlightRecorderDump -Last 1 | Show-FlightRecorder #> [CmdletBinding(DefaultParameterSetName = 'ByPath')] param( [Parameter(ParameterSetName = 'ByPath', ValueFromPipelineByPropertyName, Position = 0)] [Alias('FullName')] [string]$Path, [Parameter(ParameterSetName = 'ByLast')] [int]$Last = 0 ) process { # Resolve the dump file if ($PSCmdlet.ParameterSetName -eq 'ByLast' -or (-not $Path -and $Last -eq 0)) { if ($Last -le 0) { $Last = 1 } $dump = Get-FlightRecorderDump -Last $Last | Select-Object -First 1 if (-not $dump) { Write-Error "No flight recorder dump files found." return } $Path = $dump.FullName } if (-not (Test-Path $Path)) { Write-Error "Dump file not found: $Path" return } # Find the viewer HTML $viewerPath = $null $candidates = @( (Join-Path $PSScriptRoot '../../tools/flight-recorder-viewer.html') # From module (Join-Path $PSScriptRoot '../../../tools/flight-recorder-viewer.html') # From source ) # Also check relative to repo root via .aitriad.json $repoRoot = $PSScriptRoot while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot '.aitriad.json'))) { $repoRoot = Split-Path $repoRoot -Parent if (-not $repoRoot -or $repoRoot -eq (Split-Path $repoRoot -Parent)) { $repoRoot = $null; break } } if ($repoRoot) { $candidates += Join-Path $repoRoot 'tools/flight-recorder-viewer.html' } foreach ($c in $candidates) { $resolved = Resolve-Path $c -ErrorAction SilentlyContinue if ($resolved) { $viewerPath = $resolved.Path; break } } if (-not $viewerPath) { Write-Error "Flight recorder viewer HTML not found. Expected at tools/flight-recorder-viewer.html" return } # Read the dump file content $dumpContent = Get-Content -Raw -Path $Path # Create a temporary HTML file that auto-loads the dump data $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) 'flight-recorder-viewer' New-Item -ItemType Directory -Path $tempDir -Force | Out-Null $viewerHtml = Get-Content -Raw -Path $viewerPath # Escape the NDJSON content for embedding in JavaScript $escapedContent = $dumpContent.Replace('\', '\\').Replace('`', '\`').Replace('$', '\$').Replace("'", "\'") # Inject auto-load script before closing </body> $autoLoadScript = @" <script> document.addEventListener('DOMContentLoaded', function() { document.getElementById('fileName').textContent = '$([System.IO.Path]::GetFileName($Path))'; parseNdjson(``$escapedContent``); }); </script> "@ $outputHtml = $viewerHtml.Replace('</body>', "$autoLoadScript`n</body>") $tempFile = Join-Path $tempDir "viewer-$(Get-Date -Format 'yyyyMMdd-HHmmss').html" Set-Content -Path $tempFile -Value $outputHtml -Encoding UTF8 Write-Host "Opening flight recorder viewer..." -ForegroundColor Cyan Write-Host " Dump: $Path" -ForegroundColor Gray Write-Host " Events: $((Select-String -Path $Path -Pattern '"_type"\s*:\s*"event"' -AllMatches).Matches.Count)" -ForegroundColor Gray # Open in default browser if ($IsMacOS) { & open $tempFile } elseif ($IsWindows) { & start $tempFile } else { & xdg-open $tempFile } # Return the temp file path for reference [PSCustomObject]@{ ViewerPath = $tempFile DumpPath = $Path DumpSize = (Get-Item $Path).Length } } } |