Guest User

Untitled

a guest
Mar 3rd, 2026
212
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.07 KB | None | 0 0
  1. # Claude Code Status Line - PowerShell Version for Windows
  2. # Inspired by https://github.com/daniel3303/ClaudeCodeStatusLine
  3. # Displays: Model | Directory@Branch(+adds -dels) | Tokens (used%) | Effort | 5h% | 7d%
  4.  
  5. $ErrorActionPreference = 'SilentlyContinue'
  6.  
  7. # ===== OAuth Token Resolution =====
  8. function Get-OAuthToken {
  9. # 1. Environment variable override
  10. if ($env:CLAUDE_CODE_OAUTH_TOKEN) {
  11. return $env:CLAUDE_CODE_OAUTH_TOKEN
  12. }
  13.  
  14. # 2. Windows Credential Manager
  15. try {
  16. $cred = Get-StoredCredential -Target "Claude Code-credentials" -ErrorAction SilentlyContinue
  17. if ($cred) {
  18. $blob = $cred.GetNetworkCredential().Password
  19. $credJson = $blob | ConvertFrom-Json
  20. if ($credJson.claudeAiOauth.accessToken) {
  21. return $credJson.claudeAiOauth.accessToken
  22. }
  23. }
  24. } catch { }
  25.  
  26. # 3. Credentials file fallback
  27. $credsFile = Join-Path $env:USERPROFILE ".claude\.credentials.json"
  28. if (Test-Path $credsFile) {
  29. try {
  30. $credJson = Get-Content $credsFile -Raw | ConvertFrom-Json
  31. if ($credJson.claudeAiOauth.accessToken) {
  32. return $credJson.claudeAiOauth.accessToken
  33. }
  34. } catch { }
  35. }
  36.  
  37. return $null
  38. }
  39.  
  40. # ===== Usage API with Caching =====
  41. function Get-UsageData {
  42. $cacheDir = Join-Path $env:TEMP "claude"
  43. $cacheFile = Join-Path $cacheDir "statusline-usage-cache.json"
  44. $cacheMaxAge = 120 # seconds
  45.  
  46. # Ensure cache directory exists
  47. if (-not (Test-Path $cacheDir)) {
  48. New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null
  49. }
  50.  
  51. # Check cache
  52. $needsRefresh = $true
  53. $usageData = $null
  54.  
  55. if (Test-Path $cacheFile) {
  56. $cacheInfo = Get-Item $cacheFile
  57. $cacheAge = (Get-Date) - $cacheInfo.LastWriteTime
  58. if ($cacheAge.TotalSeconds -lt $cacheMaxAge) {
  59. $needsRefresh = $false
  60. $usageData = Get-Content $cacheFile -Raw | ConvertFrom-Json
  61. }
  62. }
  63.  
  64. # Fetch fresh data if needed
  65. if ($needsRefresh) {
  66. $token = Get-OAuthToken
  67. if ($token) {
  68. try {
  69. $headers = @{
  70. "Accept" = "application/json"
  71. "Content-Type" = "application/json"
  72. "Authorization" = "Bearer $token"
  73. "anthropic-beta" = "oauth-2025-04-20"
  74. "User-Agent" = "claude-code/2.1.34"
  75. }
  76. $response = Invoke-RestMethod -Uri "https://api.anthropic.com/api/oauth/usage" `
  77. -Headers $headers -Method Get -TimeoutSec 10 -ErrorAction Stop
  78. $usageData = $response
  79. $response | ConvertTo-Json -Depth 10 | Set-Content $cacheFile -Force
  80. } catch {
  81. # Fall back to stale cache
  82. if (Test-Path $cacheFile) {
  83. $usageData = Get-Content $cacheFile -Raw | ConvertFrom-Json
  84. }
  85. }
  86. }
  87. }
  88.  
  89. return $usageData
  90. }
  91.  
  92. # ===== Format Reset Time =====
  93. function Format-ResetTime {
  94. param(
  95. [string]$isoString,
  96. [string]$style = "time"
  97. )
  98.  
  99. if ([string]::IsNullOrEmpty($isoString) -or $isoString -eq "null") {
  100. return $null
  101. }
  102.  
  103. try {
  104. $dt = [DateTime]::Parse($isoString).ToLocalTime()
  105. switch ($style) {
  106. "time" { return $dt.ToString("h:mmtt").ToLower() }
  107. "datetime" { return $dt.ToString("MMM d, h:mmtt").ToLower() }
  108. default { return $dt.ToString("MMM d").ToLower() }
  109. }
  110. } catch {
  111. return $null
  112. }
  113. }
  114.  
  115. # Read JSON input from stdin
  116. $input_data = [Console]::In.ReadToEnd()
  117.  
  118. if ([string]::IsNullOrWhiteSpace($input_data)) {
  119. Write-Host "Claude"
  120. exit 0
  121. }
  122.  
  123. try {
  124. $json = $input_data | ConvertFrom-Json
  125.  
  126. # ANSI escape character (compatible with PowerShell 5.1+)
  127. $esc = [char]27
  128.  
  129. # ANSI color codes
  130. $blue = "$esc[38;2;0;153;255m"
  131. $orange = "$esc[38;2;255;176;85m"
  132. $green = "$esc[38;2;0;160;0m"
  133. $cyan = "$esc[38;2;46;149;153m"
  134. $red = "$esc[38;2;255;85;85m"
  135. $yellow = "$esc[38;2;230;200;0m"
  136. $white = "$esc[38;2;220;220;220m"
  137. $dim = "$esc[2m"
  138. $reset = "$esc[0m"
  139.  
  140. # Helper function to format token counts
  141. function Format-Tokens {
  142. param([int64]$num)
  143. if ($num -ge 1000000) {
  144. return "{0:F1}m" -f ($num / 1000000)
  145. } elseif ($num -ge 1000) {
  146. return "{0:F0}k" -f ($num / 1000)
  147. } else {
  148. return "$num"
  149. }
  150. }
  151.  
  152. # Helper function to get color based on usage percentage
  153. function Get-UsageColor {
  154. param([int]$pct)
  155. if ($pct -ge 90) { return $red }
  156. elseif ($pct -ge 70) { return $orange }
  157. elseif ($pct -ge 50) { return $yellow }
  158. else { return $green }
  159. }
  160.  
  161. # Extract model name
  162. $model_name = if ($json.model.display_name) { $json.model.display_name } else { "Claude" }
  163.  
  164. # Build output
  165. $out = "${blue}${model_name}${reset}"
  166.  
  167. # Current directory with git branch
  168. $cwd = $json.cwd
  169. if ($cwd) {
  170. $display_dir = Split-Path -Leaf $cwd
  171. $out += " ${dim}|${reset} ${cyan}${display_dir}${reset}"
  172.  
  173. # Try to get git branch
  174. Push-Location $cwd
  175. $git_branch = git rev-parse --abbrev-ref HEAD 2>$null
  176. if ($git_branch) {
  177. $out += "${dim}@${reset}${green}${git_branch}${reset}"
  178.  
  179. # Get git diff stats
  180. $git_diff = git diff --numstat 2>$null
  181. if ($git_diff) {
  182. $additions = 0
  183. $deletions = 0
  184. foreach ($line in $git_diff) {
  185. $parts = $line -split '\s+'
  186. if ($parts[0] -match '^\d+$') { $additions += [int]$parts[0] }
  187. if ($parts[1] -match '^\d+$') { $deletions += [int]$parts[1] }
  188. }
  189. if ($additions + $deletions -gt 0) {
  190. $out += " ${dim}(${reset}${green}+${additions}${reset} ${red}-${deletions}${reset}${dim})${reset}"
  191. }
  192. }
  193. }
  194. Pop-Location
  195. }
  196.  
  197. # Token usage
  198. $context = $json.context_window
  199. if ($context) {
  200. $size = if ($context.context_window_size) { $context.context_window_size } else { 200000 }
  201.  
  202. # Calculate current token usage
  203. $input_tokens = if ($context.current_usage.input_tokens) { $context.current_usage.input_tokens } else { 0 }
  204. $cache_create = if ($context.current_usage.cache_creation_input_tokens) { $context.current_usage.cache_creation_input_tokens } else { 0 }
  205. $cache_read = if ($context.current_usage.cache_read_input_tokens) { $context.current_usage.cache_read_input_tokens } else { 0 }
  206. $current = $input_tokens + $cache_create + $cache_read
  207.  
  208. $used_tokens = Format-Tokens $current
  209. $total_tokens = Format-Tokens $size
  210.  
  211. $pct_used = if ($size -gt 0) { [int](($current * 100) / $size) } else { 0 }
  212. $pct_remain = 100 - $pct_used
  213.  
  214. $token_color = Get-UsageColor $pct_used
  215.  
  216. $out += " ${dim}|${reset} ${orange}${used_tokens}/${total_tokens}${reset} ${dim}(${reset}${token_color}${pct_used}%${reset}${dim})${reset}"
  217. }
  218.  
  219. # Effort level
  220. $effort_level = "high"
  221. if ($env:CLAUDE_CODE_EFFORT_LEVEL) {
  222. $effort_level = $env:CLAUDE_CODE_EFFORT_LEVEL
  223. } else {
  224. $settings_path = Join-Path $env:USERPROFILE ".claude\settings.json"
  225. if (Test-Path $settings_path) {
  226. $settings = Get-Content $settings_path | ConvertFrom-Json
  227. if ($settings.effortLevel) {
  228. $effort_level = $settings.effortLevel
  229. }
  230. }
  231. }
  232.  
  233. $out += " ${dim}|${reset} ${white}effort${reset} "
  234. switch ($effort_level) {
  235. "low" { $out += "${dim}low${reset}" }
  236. "medium" { $out += "${orange}med${reset}" }
  237. default { $out += "${green}high${reset}" }
  238. }
  239.  
  240. # ===== 5h and 7d Usage Limits =====
  241. $usageData = Get-UsageData
  242. if ($usageData) {
  243. # 5-hour usage
  244. if ($usageData.five_hour) {
  245. $fiveHourPct = [int]$usageData.five_hour.utilization
  246. $fiveHourColor = Get-UsageColor $fiveHourPct
  247. $fiveHourReset = Format-ResetTime $usageData.five_hour.resets_at "time"
  248.  
  249. $out += " ${dim}|${reset} ${white}5h${reset} ${fiveHourColor}${fiveHourPct}%${reset}"
  250. if ($fiveHourReset) {
  251. $out += " ${dim}@${fiveHourReset}${reset}"
  252. }
  253. }
  254.  
  255. # 7-day usage
  256. if ($usageData.seven_day) {
  257. $sevenDayPct = [int]$usageData.seven_day.utilization
  258. $sevenDayColor = Get-UsageColor $sevenDayPct
  259. $sevenDayReset = Format-ResetTime $usageData.seven_day.resets_at "datetime"
  260.  
  261. $out += " ${dim}|${reset} ${white}7d${reset} ${sevenDayColor}${sevenDayPct}%${reset}"
  262. if ($sevenDayReset) {
  263. $out += " ${dim}@${sevenDayReset}${reset}"
  264. }
  265. }
  266.  
  267. # Extra usage (if enabled)
  268. if ($usageData.extra_usage.is_enabled -eq $true) {
  269. $extraPct = [int]$usageData.extra_usage.utilization
  270. $extraUsed = "{0:F2}" -f ($usageData.extra_usage.used_credits / 100)
  271. $extraLimit = "{0:F2}" -f ($usageData.extra_usage.monthly_limit / 100)
  272. $extraColor = Get-UsageColor $extraPct
  273.  
  274. $out += " ${dim}|${reset} ${white}extra${reset} ${extraColor}`$${extraUsed}/`$${extraLimit}${reset}"
  275. }
  276. }
  277.  
  278. # Output mode style if available
  279. if ($json.output_style.name) {
  280. $style_name = $json.output_style.name
  281. $out += " ${dim}|${reset} ${white}style${reset} ${cyan}${style_name}${reset}"
  282. }
  283.  
  284. # Vim mode if enabled
  285. if ($json.vim.mode) {
  286. $vim_mode = $json.vim.mode
  287. $vim_color = if ($vim_mode -eq "INSERT") { $green } else { $blue }
  288. $out += " ${dim}|${reset} ${vim_color}${vim_mode}${reset}"
  289. }
  290.  
  291. Write-Host $out
  292.  
  293. } catch {
  294. Write-Host "Claude"
  295. }
  296.  
  297. exit 0
  298.  
Advertisement
Add Comment
Please, Sign In to add comment