Public/Session.ps1
|
# --------------------------------------------------------------------------- # Session tracking # --------------------------------------------------------------------------- function Update-TTSession { <# .SYNOPSIS Called from the prompt hook to update the current session's state. .DESCRIPTION Records PID, working directory, window title, shell type, and timestamp for the current PowerShell process. Creates a new session record if one does not exist, or updates the existing record with current state. .PARAMETER Note Optional note to attach to the session. .EXAMPLE Update-TTSession Updates the current session's tracking data. .EXAMPLE Update-TTSession -Note 'Working on feature branch' Updates session and attaches a note. #> [CmdletBinding(SupportsShouldProcess)] param( [string]$Note ) Initialize-TTDataStore if (-not $PSCmdlet.ShouldProcess("Current session (PID $PID)", "Update")) { return } $pid_current = $PID $cwd = (Get-Location).Path $title = $Host.UI.RawUI.WindowTitle $shell = if ($PSVersionTable.PSEdition -eq 'Core') { "PowerShell $($PSVersionTable.PSVersion)" } elseif ($PSVersionTable.PSVersion.Major -le 5) { "Windows PowerShell $($PSVersionTable.PSVersion)" } else { "PowerShell" } $procName = (Get-Process -Id $pid_current -ErrorAction SilentlyContinue).ProcessName $now = [datetime]::UtcNow.ToString('o') $sessions = @(Read-JsonFile $script:SessionsFile) $existing = $sessions | Where-Object { $_.Pid -eq $pid_current } if ($existing) { # Keep last directory in history before updating if ($existing.WorkingDirectory -ne $cwd) { if (-not $existing.DirectoryHistory) { $existing | Add-Member -NotePropertyName DirectoryHistory -NotePropertyValue @() -Force } $histEntry = [PSCustomObject]@{ Directory = $existing.WorkingDirectory Timestamp = $existing.LastUpdated } $history = @($existing.DirectoryHistory) + @($histEntry) # Keep last 50 entries if ($history.Count -gt 50) { $history = $history[-50..-1] } $existing.DirectoryHistory = $history } $existing.WorkingDirectory = $cwd $existing.WindowTitle = $title $existing.LastUpdated = $now $existing.LastSeen = $now if ($Note) { $existing.Notes = $Note } } else { $newSession = [PSCustomObject]@{ Id = [guid]::NewGuid().ToString('N').Substring(0, 8) Pid = $pid_current ProcessName = $procName Shell = $shell WindowTitle = $title WorkingDirectory = $cwd StartTime = $now LastUpdated = $now LastSeen = $now Notes = if ($Note) { $Note } else { '' } Hidden = $false Tags = @() DirectoryHistory = @() } $sessions = @($sessions) + @($newSession) } Write-JsonFile $script:SessionsFile $sessions } # --------------------------------------------------------------------------- # Session queries # --------------------------------------------------------------------------- function Get-TTSession { <# .SYNOPSIS List tracked terminal sessions. .DESCRIPTION Returns terminal sessions currently tracked by TerminalTracker. Supports filtering by active/hidden status and retrieving specific sessions by ID. .PARAMETER Id Filter by session ID (prefix match supported). .PARAMETER Active Only show sessions whose process is still running. .PARAMETER IncludeHidden Include hidden sessions in output. .PARAMETER All Include all sessions regardless of hidden status. .PARAMETER Clean Archive and remove dead sessions before returning (default: true). .EXAMPLE Get-TTSession Returns all tracked sessions. .EXAMPLE Get-TTSession -Id 'abc12345' Returns the session with ID starting with abc12345. .EXAMPLE Get-TTSession -Active Returns only sessions whose process is still running. #> [CmdletBinding()] param( [string]$Id, [switch]$Active, [switch]$IncludeHidden, [switch]$All, [bool]$Clean = $true ) Initialize-TTDataStore $sessions = @(Read-JsonFile $script:SessionsFile) # Deduplicate by Id (not PID — PID reuse can suppress new sessions) $idSeen = @{} $unique = [System.Collections.Generic.List[object]]::new() foreach ($s in $sessions) { $idKey = $s.Id if (-not $idKey) { $unique.Add($s); continue } if ($idSeen.ContainsKey($idKey)) { # Keep the one with the more recent LastUpdated $existingIdx = $idSeen[$idKey] $existing = $unique[$existingIdx] if ($s.LastUpdated -gt $existing.LastUpdated) { $unique[$existingIdx] = $s } continue } $idSeen[$idKey] = $unique.Count $unique.Add($s) } $sessions = @($unique.ToArray()) # Auto-clean dead sessions on every read if ($Clean) { $alive = [System.Collections.Generic.List[object]]::new() $dirty = $false foreach ($s in $sessions) { $running = try { $null -ne (Get-Process -Id $s.Pid -ErrorAction Stop); $true } catch { $false } if ($running) { $alive.Add($s) } else { Add-TTArchiveEntry $s 'closed' $dirty = $true } } if ($dirty -or ($unique.Count -ne @($sessions).Count)) { Write-JsonFile $script:SessionsFile @($alive.ToArray()) $sessions = @($alive.ToArray()) } } if ($Id) { $sessions = @($sessions | Where-Object { $_.Id -like "$Id*" }) } if ($Active) { $sessions = @($sessions | Where-Object { try { $null -ne (Get-Process -Id $_.Pid -ErrorAction Stop); $true } catch { $false } }) } if (-not $IncludeHidden -and -not $All) { $sessions = @($sessions | Where-Object { $_.Hidden -ne $true }) } foreach ($s in $sessions) { $s | Add-Member -NotePropertyName 'IsAlive' -NotePropertyValue $true -Force } $sessions } function Set-TTNote { <# .SYNOPSIS Add or update resume notes for a session. .DESCRIPTION Sets or appends a text note on a tracked session. Notes are preserved through suspend/resume cycles and displayed in the dashboard. .PARAMETER Id Session ID (prefix match supported). .PARAMETER Note The note text. Use -Append to add to existing notes. .PARAMETER Append Append the note to existing notes instead of replacing. .EXAMPLE Set-TTNote -Id 'abc12345' -Note 'Debugging auth issue' Sets a note on the session. .EXAMPLE Set-TTNote -Id 'abc12345' -Note 'Also check logs' -Append Appends additional text to the existing note. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Id, [Parameter(Mandatory)][string]$Note, [switch]$Append ) Initialize-TTDataStore $sessions = @(Read-JsonFile $script:SessionsFile) $target = $sessions | Where-Object { $_.Id -like "$Id*" } if (-not $target) { Write-Warning "Session '$Id' not found."; return } if (@($target).Count -gt 1) { Write-Warning "Ambiguous ID '$Id' matches multiple sessions."; return } $target = @($target)[0] if (-not $PSCmdlet.ShouldProcess("Session $($target.Id)", "Set note")) { return } if ($Append -and $target.Notes) { $target.Notes = "$($target.Notes)`n$Note" } else { $target.Notes = $Note } $target.LastUpdated = [datetime]::UtcNow.ToString('o') Write-JsonFile $script:SessionsFile $sessions Write-Verbose "Note updated for session $($target.Id)." $target } function Set-TTTag { <# .SYNOPSIS Add or remove tags on a session. .DESCRIPTION Manages tags on a tracked session. Tags can be used for categorization and filtering. Multiple tags can be added or removed in a single call. .PARAMETER Id Session ID (prefix match supported). .PARAMETER Add Tags to add to the session. .PARAMETER Remove Tags to remove from the session. .EXAMPLE Set-TTTag -Id 'abc12345' -Add 'project-x', 'frontend' Adds two tags to the session. .EXAMPLE Set-TTTag -Id 'abc12345' -Remove 'temp' Removes the 'temp' tag from the session. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Id, [string[]]$Add, [string[]]$Remove ) Initialize-TTDataStore $sessions = @(Read-JsonFile $script:SessionsFile) $target = $sessions | Where-Object { $_.Id -like "$Id*" } if (-not $target -or @($target).Count -ne 1) { Write-Warning "Session not found or ambiguous."; return } $target = @($target)[0] if (-not $PSCmdlet.ShouldProcess("Session $($target.Id)", "Set tags")) { return } if (-not $target.Tags) { $target | Add-Member -NotePropertyName Tags -NotePropertyValue @() -Force } $tagArray = @($target.Tags | ForEach-Object { [string]$_ }) $tags = [System.Collections.Generic.List[string]]::new([string[]]$tagArray) foreach ($t in $Add) { if ($t -notin $tags) { $tags.Add($t) } } foreach ($t in $Remove) { $tags.Remove($t) | Out-Null } $target.Tags = @($tags) $target.LastUpdated = [datetime]::UtcNow.ToString('o') Write-JsonFile $script:SessionsFile $sessions $target } # --------------------------------------------------------------------------- # Session removal # --------------------------------------------------------------------------- function Remove-TTSession { <# .SYNOPSIS Remove a session from tracking (archives it first by default). .DESCRIPTION Removes a tracked session. By default, the session is archived before removal so it appears in the session history. Use -NoArchive to skip. .PARAMETER Id Session ID (prefix match supported). .PARAMETER NoArchive Skip archiving before removal. .EXAMPLE Remove-TTSession -Id 'abc12345' Archives and removes the session from active tracking. .EXAMPLE Remove-TTSession -Id 'abc12345' -NoArchive Removes the session without archiving. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Id, [switch]$NoArchive ) Initialize-TTDataStore $sessions = @(Read-JsonFile $script:SessionsFile) $target = $sessions | Where-Object { $_.Id -like "$Id*" } if (-not $target) { Write-Warning "Session '$Id' not found."; return } if (@($target).Count -gt 1) { Write-Warning "Ambiguous ID '$Id'."; return } $target = @($target)[0] if ($PSCmdlet.ShouldProcess("Session $($target.Id) at $($target.WorkingDirectory)", "Remove")) { if (-not $NoArchive) { Add-TTArchiveEntry $target 'manual-remove' } $sessions = @($sessions | Where-Object { $_.Id -ne $target.Id }) Write-JsonFile $script:SessionsFile $sessions Write-Verbose "Session $($target.Id) removed." } } |