Skip to content

Instantly share code, notes, and snippets.

@joeskeen
Forked from m0wer/copilot-quota.sh
Last active April 2, 2026 17:03
Show Gist options
  • Select an option

  • Save joeskeen/ba9d090d58d93f1522f6439fd800f1cd to your computer and use it in GitHub Desktop.

Select an option

Save joeskeen/ba9d090d58d93f1522f6439fd800f1cd to your computer and use it in GitHub Desktop.
Github Copilot subscription quota tracker
<#
.SYNOPSIS
Retrieves GitHub Copilot premium usage and pace details via OpenCode-authenticated APIs.
.DESCRIPTION
This file contains only the function definition. To use it as a command, save it to its
own `.ps1` file and dot-source that file from your `$PROFILE`.
This command depends on OpenCode authentication state. Install OpenCode, then run
`opencode auth` and complete GitHub Copilot authentication before using this command.
By default, the command writes a formatted console report. Pass `-AsObject` to return
the raw object so you can pipe it or further manipulate the data in scripts.
.EXAMPLE
Get-CopilotUsage
Displays the formatted Copilot premium usage and pacing report.
.EXAMPLE
Get-CopilotUsage -AsObject | Select-Object Used, Remaining, PercentUsed
Returns the raw usage object for scripting and custom output.
#>
function Get-CopilotUsage {
[CmdletBinding()]
[OutputType([pscustomobject])]
param(
[Parameter()]
[string]$AuthFile = (Join-Path $HOME ".local/share/opencode/auth.json"),
[Parameter()]
[string]$CopilotVersion = "0.35.0",
[Parameter()]
[string]$EditorVersion = "vscode/1.107.0",
[Parameter()]
[int]$BarWidth = 34,
[Parameter()]
[switch]$AsObject
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[Console]::OutputEncoding = $utf8NoBom
$OutputEncoding = $utf8NoBom
function Invoke-CopilotApi {
param(
[Parameter(Mandatory)]
[string]$Uri,
[Parameter(Mandatory)]
[hashtable]$Headers
)
$response = Invoke-WebRequest -Uri $Uri -Method Get -Headers $Headers -SkipHttpErrorCheck
[pscustomobject]@{
StatusCode = [int]$response.StatusCode
Body = [string]($response.Content ?? "")
}
}
function New-UsageBar {
param(
[Parameter(Mandatory)]
[double]$FilledPercent,
[Parameter()]
[double]$SecondaryPercent,
[Parameter(Mandatory)]
[int]$Width
)
$filledPct = [Math]::Max(0, [Math]::Min(100, $FilledPercent))
$filledCount = [Math]::Round(($filledPct / 100) * $Width)
$filledCount = [Math]::Max(0, [Math]::Min($Width, $filledCount))
if ($PSBoundParameters.ContainsKey('SecondaryPercent')) {
$secondaryPct = [Math]::Max(0, [Math]::Min(100, $SecondaryPercent))
$secondaryCount = [Math]::Round(($secondaryPct / 100) * $Width)
$secondaryCount = [Math]::Max($filledCount, [Math]::Min($Width, $secondaryCount))
}
else {
$secondaryCount = $filledCount
}
"[{0}{1}{2}]" -f ("█" * $filledCount), ("▒" * ($secondaryCount - $filledCount)), ("░" * ($Width - $secondaryCount))
}
if (-not (Test-Path -Path $AuthFile -PathType Leaf)) {
throw "opencode auth file not found at $AuthFile"
}
$authJson = Get-Content -Path $AuthFile -Raw | ConvertFrom-Json
$ghAuth = $authJson.'github-copilot'
if ($null -eq $ghAuth) {
throw "No 'github-copilot' section found in auth.json"
}
$oauthToken = $ghAuth.refresh
if ([string]::IsNullOrWhiteSpace($oauthToken)) {
$oauthToken = $ghAuth.access
}
if ([string]::IsNullOrWhiteSpace($oauthToken)) {
throw "No GitHub Copilot token found in auth.json"
}
$commonHeaders = @{
"Accept" = "application/json"
"User-Agent" = "GitHubCopilotChat/$CopilotVersion"
"Editor-Version" = $EditorVersion
"Editor-Plugin-Version" = "copilot-chat/$CopilotVersion"
"Copilot-Integration-Id" = "vscode-chat"
}
$exchangeHeaders = $commonHeaders.Clone()
$exchangeHeaders["Authorization"] = "Bearer $oauthToken"
$copilotToken = $null
$exchange = Invoke-CopilotApi -Uri "https://api.github.com/copilot_internal/v2/token" -Headers $exchangeHeaders
if ($exchange.StatusCode -eq 200) {
try {
$copilotToken = ($exchange.Body | ConvertFrom-Json).token
}
catch {
$copilotToken = $null
}
}
$quotaHeaders = $commonHeaders.Clone()
$quotaHeaders["Content-Type"] = "application/json"
$quotaHeaders["Authorization"] = if ([string]::IsNullOrWhiteSpace($copilotToken)) { "token $oauthToken" } else { "Bearer $copilotToken" }
$quota = Invoke-CopilotApi -Uri "https://api.github.com/copilot_internal/user" -Headers $quotaHeaders
if ($quota.StatusCode -ne 200 -and -not [string]::IsNullOrWhiteSpace($copilotToken)) {
$fallbackHeaders = $commonHeaders.Clone()
$fallbackHeaders["Content-Type"] = "application/json"
$fallbackHeaders["Authorization"] = "Bearer $oauthToken"
$quota = Invoke-CopilotApi -Uri "https://api.github.com/copilot_internal/user" -Headers $fallbackHeaders
}
if ($quota.StatusCode -ne 200) {
throw "Copilot API call failed (HTTP $($quota.StatusCode)): $($quota.Body)"
}
try {
$data = $quota.Body | ConvertFrom-Json
}
catch {
throw "Failed to parse Copilot API response"
}
$premium = $data.quota_snapshots.premium_interactions
if ($null -eq $premium) {
throw "No premium_interactions quota data found"
}
if ($premium.unlimited -eq $true) {
$unlimited = [pscustomobject]@{
Entitlement = "Unlimited"
}
if ($AsObject) {
return $unlimited
}
Write-Output "── Copilot Premium Requests ─────────────────"
Write-Output " Entitlement : Unlimited"
return $unlimited
}
$entitlement = [int]$premium.entitlement
$remaining = [int]$premium.remaining
$used = $entitlement - $remaining
$pctUsed = if ($entitlement -gt 0) { ($used / $entitlement) * 100 } else { 0 }
try {
$resetDate = [datetimeoffset]::Parse($data.quota_reset_date).Date
$prior = $resetDate.AddMonths(-1)
$cycleDay = [Math]::Min($resetDate.Day, [datetime]::DaysInMonth($prior.Year, $prior.Month))
$cycleStart = Get-Date -Year $prior.Year -Month $prior.Month -Day $cycleDay -Hour 0 -Minute 0 -Second 0
$today = (Get-Date).Date
$daysElapsed = [int](($today - $cycleStart).TotalDays) + 1
$daysInMonth = [int](($resetDate - $cycleStart).TotalDays)
$daysRemaining = $daysInMonth - $daysElapsed
}
catch {
$today = (Get-Date).Date
$cycleStart = $today
$resetDate = $today.AddDays(30)
$daysElapsed = 1
$daysInMonth = 30
$daysRemaining = 29
}
if ($daysElapsed -lt 1) { $daysElapsed = 1 }
$pctMonthElapsed = if ($daysInMonth -gt 0) { ($daysElapsed / $daysInMonth) * 100 } else { 0 }
$dailyBudget = if ($daysInMonth -gt 0) { $entitlement / $daysInMonth } else { 0 }
$actualDailyRate = if ($daysElapsed -gt 0) { $used / $daysElapsed } else { 0 }
$expectedUsed = if ($daysInMonth -gt 0) { [Math]::Floor($entitlement * $daysElapsed / $daysInMonth) } else { 0 }
$paceDiff = $expectedUsed - $used
$daysDelta = if ($dailyBudget -gt 0) { [Math]::Abs($paceDiff) / $dailyBudget } else { 0 }
$projectedEom = [Math]::Round($actualDailyRate * $daysInMonth)
$daysUntilEmpty = if ($actualDailyRate -gt 0) { $remaining / $actualDailyRate } else { [double]::PositiveInfinity }
$result = [pscustomobject]@{
Entitlement = $entitlement
Used = $used
Remaining = $remaining
PercentUsed = [Math]::Round($pctUsed, 1)
CycleStart = $cycleStart
ResetDate = $resetDate
DaysElapsed = $daysElapsed
DaysInCycle = $daysInMonth
PercentMonthElapsed = [Math]::Round($pctMonthElapsed, 1)
DailyBudget = [Math]::Round($dailyBudget, 2)
ActualDailyRate = [Math]::Round($actualDailyRate, 2)
ExpectedUsed = [int]$expectedUsed
PaceDiff = [int]$paceDiff
DaysDelta = [Math]::Round($daysDelta, 1)
ProjectedEndOfMonth = [int]$projectedEom
DaysUntilEmpty = if ([double]::IsInfinity($daysUntilEmpty)) { "Infinity" } else { [Math]::Round($daysUntilEmpty, 1) }
}
if ($AsObject) {
return $result
}
$usageBar = New-UsageBar -FilledPercent $pctUsed -SecondaryPercent $pctMonthElapsed -Width $BarWidth
$monthBar = New-UsageBar -FilledPercent $pctMonthElapsed -Width $BarWidth
$pacePct = if ($entitlement -gt 0) { [Math]::Round(([Math]::Abs($paceDiff) / $entitlement) * 100, 1) } else { 0 }
$projPct = if ($entitlement -gt 0) { ($projectedEom / $entitlement) * 100 } else { 0 }
Write-Output "── Copilot Premium Requests ─────────────────────────"
Write-Output (" Entitlement : {0} / month ({1:N1} / day)" -f $entitlement, $dailyBudget)
Write-Output (" Used : {0} ({1:N1}%)" -f $used, $pctUsed)
Write-Output (" Remaining : {0}" -f $remaining)
Write-Output ""
Write-Output "── Usage vs Month ───────────────────────────────────"
Write-Output (" Usage {0} {1:N1}%" -f $usageBar, $pctUsed)
Write-Output (" Month {0} {1:N1}% (day {2}/{3})" -f $monthBar, $pctMonthElapsed, $daysElapsed, $daysInMonth)
Write-Output ""
Write-Output "── Pace Check ───────────────────────────────────────"
Write-Output (" Burn rate : {0:N1} req/day (budget {1:N1}/day)" -f $actualDailyRate, $dailyBudget)
Write-Output (" Expected used: {0}" -f $expectedUsed)
Write-Output (" Actual used : {0}" -f $used)
if ($paceDiff -ge 0) {
Write-Output (" Status : ✅ UNDER pace by {0} req ({1:N1}%) · {2:N1} days ahead" -f $paceDiff, $pacePct, $daysDelta)
}
else {
$over = -1 * $paceDiff
Write-Output (" Status : ❌ OVER pace by {0} req ({1:N1}%) · {2:N1} days behind" -f $over, $pacePct, $daysDelta)
}
Write-Output ""
Write-Output "── Forecast ─────────────────────────────────────────"
Write-Output (" Projected EOM: {0} req ({1:N1}% of entitlement)" -f $projectedEom, $projPct)
if ([double]::IsInfinity($daysUntilEmpty)) {
Write-Output " Quota runs out: never at current rate"
}
elseif ($daysUntilEmpty -gt $daysRemaining) {
$surplus = $remaining - [Math]::Round($dailyBudget * $daysRemaining)
Write-Output (" Quota runs out: after reset (~{0:+#;-#;0} req surplus)" -f $surplus)
}
else {
$exhaustionDate = $today.AddDays($daysUntilEmpty)
Write-Output (" Quota runs out: ~{0} ({1:N1} days at current rate)" -f $exhaustionDate.ToString("MMM dd"), $daysUntilEmpty)
}
}
@joeskeen

joeskeen commented Apr 2, 2026

Copy link
Copy Markdown
Author

Example output:

PS $> Get-CopilotUsage
── Copilot Premium Requests ─────────────────────────
  Entitlement : 300 / month  (10.0 / day)
  Used        : 31  (10.3%)
  Remaining   : 269

── Usage vs Month ───────────────────────────────────
  Usage  [███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 10.3%
  Month  [██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 6.7%  (day 2/30)

── Pace Check ───────────────────────────────────────
  Burn rate    : 15.5 req/day  (budget 10.0/day)
  Expected used: 20
  Actual used  : 31
  Status       : ❌ OVER pace by 11 req (3.7%) · 1.1 days behind

── Forecast ─────────────────────────────────────────
  Projected EOM: 465 req (155.0% of entitlement)
  Quota runs out: ~Apr 19 (17.4 days at current rate)

PS $> Get-CopilotUsage -AsObject | Select-Object Used, Remaining, PercentUsed

Used Remaining PercentUsed
---- --------- -----------
  31       269       10.30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment