Files
atm-backtesting/scripts/codex_statusline.ps1
2026-05-21 01:08:00 +03:00

165 lines
6.1 KiB
PowerShell

param(
[string]$SessionFile = "",
[string]$Workdir = "",
[string]$CodexHome = ""
)
$ErrorActionPreference = "SilentlyContinue"
function Get-DefaultCodexHome {
if ($env:CODEX_HOME) { return $env:CODEX_HOME }
return (Join-Path $HOME ".codex")
}
function Format-Tokens([Int64]$Value) {
if ($Value -ge 1000000) { return ("{0:0.#}M" -f ($Value / 1000000.0)) }
if ($Value -ge 1000) { return ("{0:0.#}K" -f ($Value / 1000.0)) }
return "$Value"
}
function Format-ResetTime($EpochSeconds) {
if (-not $EpochSeconds) { return "--" }
try {
$dto = [DateTimeOffset]::FromUnixTimeSeconds([Int64]$EpochSeconds).ToLocalTime()
return $dto.ToString("ddd HH:mm")
} catch {
return "--"
}
}
function Format-Remaining($EpochSeconds) {
if (-not $EpochSeconds) { return "--" }
try {
$remaining = [DateTimeOffset]::FromUnixTimeSeconds([Int64]$EpochSeconds) - [DateTimeOffset]::Now
if ($remaining.TotalSeconds -le 0) { return "resetting" }
if ($remaining.TotalDays -ge 1) { return ("{0}d {1}h" -f [int]$remaining.TotalDays, $remaining.Hours) }
if ($remaining.TotalHours -ge 1) { return ("{0}h {1}m" -f [int]$remaining.TotalHours, $remaining.Minutes) }
return ("{0}m" -f [Math]::Max(1, [int]$remaining.TotalMinutes))
} catch {
return "--"
}
}
function Get-Number($Value, [Int64]$Default = 0) {
if ($null -eq $Value) { return $Default }
try { return [Int64]$Value } catch { return $Default }
}
function Read-JsonLine([string]$Line) {
if ([string]::IsNullOrWhiteSpace($Line)) { return $null }
try { return ($Line | ConvertFrom-Json) } catch { return $null }
}
function Get-TokenEventFromObject($Obj) {
if (-not $Obj) { return $null }
if ($Obj.type -eq "event_msg" -and $Obj.payload.type -eq "token_count") { return $Obj.payload }
if ($Obj.type -eq "token_count") { return $Obj }
if ($Obj.info -and $Obj.rate_limits) { return $Obj }
return $null
}
function Get-LatestSessionFile([string]$HomeDir) {
$sessionsDir = Join-Path $HomeDir "sessions"
if (-not (Test-Path $sessionsDir)) { return $null }
return Get-ChildItem -Path $sessionsDir -Recurse -Filter "*.jsonl" -File |
Where-Object { $_.Length -gt 0 } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
}
function Get-LatestTokenEvent([string]$Path) {
if (-not $Path -or -not (Test-Path $Path)) { return $null }
$lines = Get-Content -LiteralPath $Path -Tail 500
for ($i = $lines.Count - 1; $i -ge 0; $i--) {
$obj = Read-JsonLine $lines[$i]
$event = Get-TokenEventFromObject $obj
if ($event) { return $event }
}
return $null
}
function Get-SessionMeta([string]$Path) {
if (-not $Path -or -not (Test-Path $Path)) { return $null }
foreach ($line in (Get-Content -LiteralPath $Path -TotalCount 100)) {
$obj = Read-JsonLine $line
if ($obj.type -eq "session_meta") { return $obj.payload }
}
return $null
}
function Get-ConfigModel([string]$HomeDir) {
$config = Join-Path $HomeDir "config.toml"
if (-not (Test-Path $config)) { return "codex" }
$line = Get-Content -LiteralPath $config | Where-Object { $_ -match '^\s*model\s*=' } | Select-Object -First 1
if ($line -match '=\s*"([^"]+)"') { return $Matches[1] }
return "codex"
}
if (-not $CodexHome) { $CodexHome = Get-DefaultCodexHome }
$stdinText = @($input) -join "`n"
$stdinObj = Read-JsonLine $stdinText
$event = Get-TokenEventFromObject $stdinObj
if (-not $SessionFile) {
$latest = Get-LatestSessionFile $CodexHome
if ($latest) { $SessionFile = $latest.FullName }
}
if (-not $event) { $event = Get-LatestTokenEvent $SessionFile }
$meta = Get-SessionMeta $SessionFile
if (-not $Workdir) {
if ($stdinObj.workspace.current_dir) { $Workdir = $stdinObj.workspace.current_dir }
elseif ($stdinObj.cwd) { $Workdir = $stdinObj.cwd }
elseif ($meta.cwd) { $Workdir = $meta.cwd }
else { $Workdir = (Get-Location).Path }
}
$model = if ($stdinObj.model.display_name) { $stdinObj.model.display_name } elseif ($stdinObj.model) { $stdinObj.model } elseif ($meta.model) { $meta.model } else { Get-ConfigModel $CodexHome }
$project = Split-Path -Path $Workdir -Leaf
if (-not $project) { $project = "?" }
$branch = "-"
if (Test-Path $Workdir) {
$branchRaw = git -C $Workdir -c gc.auto=0 rev-parse --abbrev-ref HEAD 2>$null
if ($LASTEXITCODE -eq 0 -and $branchRaw) {
$branch = $branchRaw.Trim()
$dirty = git -C $Workdir -c gc.auto=0 status --porcelain 2>$null | Select-Object -First 1
if ($dirty) { $branch = "$branch*" }
}
}
$last = $event.info.last_token_usage
$window = Get-Number $event.info.model_context_window
$ctxUsed = (Get-Number $last.input_tokens) + (Get-Number $last.output_tokens)
$ctxPct = if ($window -gt 0) { [Math]::Round(($ctxUsed * 100.0) / $window) } else { 0 }
$total = $event.info.total_token_usage
$sessionTokens = Get-Number $total.total_tokens
$primary = $event.rate_limits.primary
$secondary = $event.rate_limits.secondary
$limitName = $event.rate_limits.limit_id
if (-not $limitName) { $limitName = "codex" }
$primaryPct = if ($primary.used_percent -ne $null) { [Math]::Round([double]$primary.used_percent) } else { 0 }
$primaryReset = Format-ResetTime $primary.resets_at
$primaryRemain = Format-Remaining $primary.resets_at
$primaryWindow = if ($primary.window_minutes) { "{0}h" -f [Math]::Round([double]$primary.window_minutes / 60.0) } else { "window" }
$line1 = "{0} | ctx {1}/{2} {3}% | session {4} | {5} ({6})" -f `
$model, (Format-Tokens $ctxUsed), (Format-Tokens $window), $ctxPct, (Format-Tokens $sessionTokens), $project, $branch
$line2 = "limits {0} {1}%/{2} reset {3} ({4})" -f $limitName, $primaryPct, $primaryWindow, $primaryRemain, $primaryReset
if ($secondary) {
$secondaryPct = if ($secondary.used_percent -ne $null) { [Math]::Round([double]$secondary.used_percent) } else { 0 }
$line2 += " | secondary {0}% reset {1}" -f $secondaryPct, (Format-Remaining $secondary.resets_at)
}
if ($event.rate_limits.plan_type) {
$line2 += " | plan {0}" -f $event.rate_limits.plan_type
}
Write-Output $line1
Write-Output $line2