Last active
April 23, 2026 10:44
-
-
Save kerray/b2e1ca86c55a592d076eb0367846da79 to your computer and use it in GitHub Desktop.
Claude Code statusline for Windows/PowerShell - shows model, folder, git branch, API usage limits, and context window. Adapted from https://gist.github.com/kerray/a65a95a0d4931e19bf83cf19ab040e64
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # fetch-usage.ps1 | |
| # Fetches Claude API usage stats and writes them to a cache file. | |
| # Line 1: five_hour.utilization (integer %) | |
| # Line 2: seven_day.utilization (integer %) | |
| # Line 3: five_hour.resets_at (raw ISO string) | |
| # Line 4: seven_day.resets_at (raw ISO string) | |
| # | |
| # Windows PowerShell version. | |
| # Based on: https://gist.github.com/kerray/a65a95a0d4931e19bf83cf19ab040e64 | |
| # Original bash version by kerray, inspired by: | |
| # https://www.reddit.com/r/ClaudeCode/comments/1rg3nmt/my_minimal_claude_code_statusline_config/ | |
| $ErrorActionPreference = 'SilentlyContinue' | |
| $CacheFile = Join-Path $env:TEMP ".claude_usage_cache" | |
| $CredsFile = Join-Path $env:USERPROFILE ".claude\.credentials.json" | |
| # Read OAuth token from credentials file | |
| if (-not (Test-Path $CredsFile)) { | |
| exit 0 | |
| } | |
| try { | |
| $creds = Get-Content $CredsFile -Raw | ConvertFrom-Json | |
| $token = $creds.claudeAiOauth.accessToken | |
| } catch { | |
| exit 0 | |
| } | |
| if (-not $token) { | |
| exit 0 | |
| } | |
| # Fetch usage from API | |
| try { | |
| $headers = @{ | |
| "accept" = "application/json" | |
| "anthropic-beta" = "oauth-2025-04-20" | |
| "authorization" = "Bearer $token" | |
| "user-agent" = "claude-code/2.1.11" | |
| } | |
| $response = Invoke-WebRequest -Uri "https://api.anthropic.com/oauth/usage" -Headers $headers -TimeoutSec 10 | |
| $usage = $response.Content | ConvertFrom-Json | |
| } catch { | |
| exit 0 | |
| } | |
| if (-not $usage) { | |
| exit 0 | |
| } | |
| # Extract values - convert DateTime back to ISO format if PowerShell auto-converted | |
| $fiveH = [math]::Round($usage.five_hour.utilization) | |
| $sevenD = [math]::Round($usage.seven_day.utilization) | |
| $fiveHReset = $usage.five_hour.resets_at | |
| $sevenDReset = $usage.seven_day.resets_at | |
| # If PowerShell converted to DateTime, format back to ISO | |
| if ($fiveHReset -is [DateTime]) { | |
| $fiveHReset = $fiveHReset.ToUniversalTime().ToString("o") | |
| } | |
| if ($sevenDReset -is [DateTime]) { | |
| $sevenDReset = $sevenDReset.ToUniversalTime().ToString("o") | |
| } | |
| # Write to cache file | |
| @($fiveH, $sevenD, $fiveHReset, $sevenDReset) | Out-File -FilePath $CacheFile -Encoding utf8 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # statusline-command.ps1 | |
| # Claude Code statusline command - reads JSON from stdin, renders two-line status. | |
| # | |
| # Line 1: Model | Folder . Branch | |
| # Line 2: 5h usage% (reset) . 7d usage% (reset) | ctx usage% (tokens) | |
| # | |
| # Windows PowerShell version. | |
| # Based on: https://gist.github.com/kerray/a65a95a0d4931e19bf83cf19ab040e64 | |
| # Original bash version by kerray, inspired by: | |
| # https://www.reddit.com/r/ClaudeCode/comments/1rg3nmt/my_minimal_claude_code_statusline_config/ | |
| $ErrorActionPreference = 'SilentlyContinue' | |
| # Read JSON from stdin | |
| $input_json = $input | Out-String | |
| if (-not $input_json) { | |
| exit 0 | |
| } | |
| try { | |
| $data = $input_json | ConvertFrom-Json | |
| } catch { | |
| exit 0 | |
| } | |
| # --- Model --- | |
| $model = $data.model.display_name | |
| # --- Folder --- | |
| $dir = if ($data.workspace.current_dir) { $data.workspace.current_dir } else { $data.cwd } | |
| $dirName = Split-Path $dir -Leaf | |
| # --- Git branch --- | |
| $branch = "" | |
| if (Test-Path (Join-Path $dir ".git")) { | |
| Push-Location $dir | |
| $branch = git symbolic-ref --short HEAD 2>$null | |
| if (-not $branch) { | |
| $branch = git rev-parse --short HEAD 2>$null | |
| } | |
| Pop-Location | |
| } | |
| # --- Usage stats from cache --- | |
| $CacheFile = Join-Path $env:TEMP ".claude_usage_cache" | |
| $fiveH = "" | |
| $sevenD = "" | |
| $fiveHReset = "" | |
| $sevenDReset = "" | |
| if (Test-Path $CacheFile) { | |
| $lines = Get-Content $CacheFile | |
| if ($lines.Count -ge 4) { | |
| $fiveH = $lines[0] | |
| $sevenD = $lines[1] | |
| $fiveHReset = $lines[2] | |
| $sevenDReset = $lines[3] | |
| } | |
| } else { | |
| # Trigger background fetch | |
| Start-Process -WindowStyle Hidden -FilePath "pwsh" -ArgumentList "-NoProfile", "-File", "$env:USERPROFILE\.claude\fetch-usage.ps1" | |
| } | |
| # --- Compute delta: human-readable time until reset --- | |
| function Get-TimeDelta { | |
| param([string]$isoTimestamp) | |
| if (-not $isoTimestamp) { return "" } | |
| try { | |
| $resetTime = [DateTimeOffset]::Parse($isoTimestamp) | |
| $now = [DateTimeOffset]::UtcNow | |
| $diff = $resetTime - $now | |
| if ($diff.TotalSeconds -le 0) { return "now" } | |
| $days = [math]::Floor($diff.TotalDays) | |
| $hours = $diff.Hours | |
| $minutes = $diff.Minutes | |
| if ($days -gt 0) { | |
| return "${days}d ${hours}h" | |
| } elseif ($hours -gt 0) { | |
| return "${hours}h ${minutes}m" | |
| } else { | |
| return "${minutes}m" | |
| } | |
| } catch { | |
| return "" | |
| } | |
| } | |
| # --- Context window --- | |
| $ctxStr = "" | |
| $ctxTokensStr = "" | |
| $used = $data.context_window.used_percentage | |
| if ($used) { | |
| $usedInt = [math]::Round($used) | |
| $ctxStr = "${usedInt}%" | |
| $cu = $data.context_window.current_usage | |
| if ($cu) { | |
| $ctxUsed = ($cu.cache_read_input_tokens + $cu.cache_creation_input_tokens + $cu.input_tokens + $cu.output_tokens) | |
| $ctxTotal = $data.context_window.context_window_size | |
| if ($ctxUsed -and $ctxTotal) { | |
| $ctxUsedK = [math]::Floor($ctxUsed / 1000) | |
| $ctxTotalK = [math]::Floor($ctxTotal / 1000) | |
| $ctxTokensStr = "${ctxUsedK}k/${ctxTotalK}k" | |
| } | |
| } | |
| } | |
| # --- ANSI color codes --- | |
| $esc = [char]27 | |
| $orange = "$esc[38;5;208m" | |
| $cyan = "$esc[38;2;76;208;222m" | |
| $purple = "$esc[38;2;192;103;222m" | |
| $gray = "$esc[38;2;156;162;175m" | |
| $dimGray = "$esc[2m$esc[38;2;156;162;175m" | |
| $sepGray = "$esc[90m" | |
| $bold = "$esc[1m" | |
| $reset = "$esc[0m" | |
| $sep = "${sepGray} . ${reset}" | |
| $pipe = "${sepGray} | ${reset}" | |
| # --- Line 1: Model | Folder . Branch --- | |
| $line1 = "${orange}${bold}${model}${reset}${pipe}${bold}${cyan}${dirName}${reset}" | |
| if ($branch) { | |
| $line1 += "${sep}${bold}${purple}${branch}${reset}" | |
| } | |
| # --- Line 2: Usage | Context --- | |
| $line2Parts = @() | |
| if ($fiveH) { | |
| $part = "${gray}5h ${fiveH}%${reset}" | |
| $delta = Get-TimeDelta $fiveHReset | |
| if ($delta) { | |
| $part += " ${dimGray}(${delta})${reset}" | |
| } | |
| $line2Parts += $part | |
| } | |
| if ($sevenD) { | |
| $part = "${gray}7d ${sevenD}%${reset}" | |
| $delta = Get-TimeDelta $sevenDReset | |
| if ($delta) { | |
| $part += " ${dimGray}(${delta})${reset}" | |
| } | |
| $line2Parts += $part | |
| } | |
| $line2 = $line2Parts -join $sep | |
| if ($ctxStr) { | |
| if ($line2) { | |
| $line2 += $pipe | |
| } | |
| $line2 += "${gray}ctx ${ctxStr}${reset}" | |
| if ($ctxTokensStr) { | |
| $line2 += " ${dimGray}(${ctxTokensStr})${reset}" | |
| } | |
| } | |
| # --- Output --- | |
| Write-Host $line1 | |
| Write-Host $line2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment