quota

package
v0.0.0-...-7d0a1c9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package quota is Core/Quota.cs: how much of each AI tool's plan is used. None of them publishes a quota API, so each is read the way the tool itself exposes it, read-only, and nothing leaves the PC except each tool's own usage request with its own sign-in:

Kiro CLI     `kiro-cli chat --no-interactive /usage`, the CLI's printed report.
Codex        the rate-limit snapshot Codex writes into its session logs.
Cursor       cursor.com/api/usage-summary, with the token Cursor keeps locally.
Claude Code  api.anthropic.com/api/oauth/usage, with Claude Code's own sign-in.

The parsers here take text and a clock, so they are tested on their own. The readers that touch the disk, the network and kiro-cli are in read.go, and OwlApp's five-minute refresh is schedule.go.

Index

Constants

View Source
const (
	ItemKiro   = "kiro"
	ItemCodex  = "codex"
	ItemCursor = "cursor"
	ItemClaude = "claude"
)

Notch items: what the resting notch can show, the AI quotas (Core/Layout.cs NotchItem).

View Source
const ClaudeKeychainMissing = "Claude Code credentials are unavailable. Sign in and allow Hover to read the Claude Code Keychain entry."

ClaudeKeychainMissing is what the quota says on a Mac when neither the Keychain nor the file gives a sign-in.

View Source
const ClaudeKeychainService = "Claude Code-credentials"

ClaudeKeychainService is the Keychain item Claude Code keeps its sign-in in on a Mac: a generic password of this service, whose value is the text .credentials.json holds elsewhere.

View Source
const ClaudeURL = "https://api.anthropic.com/api/oauth/usage"
View Source
const CreditDays = 30

CreditDays is the days in the view (the longer of Settings' two ranges).

View Source
const CursorURL = "https://cursor.com/api/usage-summary"
View Source
const Every = 5 * time.Minute
View Source
const Tick = 30 * time.Second

Tick is OwlApp's DispatcherTimer.

Variables

Functions

func ClaudeHome

func ClaudeHome() string

ClaudeHome is CLAUDE_CONFIG_DIR, else ~/.claude (the same on Windows, Linux and macOS).

func CleanToken

func CleanToken(s string) (string, bool)

CleanToken is trimmed of white space, then of quotes; false when nothing is left.

func CodexHome

func CodexHome() string

CodexHome is CODEX_HOME, else ~/.codex (the same on Windows, Linux and macOS).

func CursorCookie

func CursorCookie(token string, utcNow time.Time) (string, bool)

CursorCookie: Cursor's web session cookie is "user id::token"; the user id is the last part of the token's subject. False when the token is malformed or about to expire.

func CursorDB

func CursorDB() string

CursorDB is where Cursor (an Electron app) keeps its state: %APPDATA%\Cursor on Windows, ~/Library/Application Support/Cursor on a Mac, $XDG_CONFIG_HOME/Cursor (~/.config/Cursor) on Linux. "" when there is no home to look in.

func CursorDBUnder

func CursorDBUnder(base string) string

CursorDBUnder is Cursor's state database under the folder its Electron shell keeps settings in.

func CursorToken

func CursorToken(db string) (*string, error)

CursorToken is the token Cursor keeps in its database, as CursorToken reads it: text, or a blob in UTF-16 when its second byte is zero, else UTF-8. Nil for none.

func Custom

func Custom(v float64, hashes int) string

Custom is a double through a custom format of "0" and up to hashes "#" decimals ("0", "0.##"). .NET first takes the value to 15 significant digits (the precision it uses for custom formats), then rounds that decimal half away from zero: so 36.5 is "37" and 2.675 is "2.68", where Go's own formatting gives "36" and "2.67".

func DailyPath

func DailyPath() string

func FormatLocal

func FormatLocal(t time.Time, pattern string) string

FormatLocal is a .NET date pattern made of d, MMM, HH and mm, in local time. MMM is English (C# uses the current culture's names).

func ItemShort

func ItemShort(id string) string

ItemShort is the name beside a quota on the notch.

func ItemTitle

func ItemTitle(id string) string

func KiroRead

func KiroRead() (Reading, *KiroUsage)

KiroRead is the reading and the raw credits behind it, made from the one report (it takes eight seconds to print, so the daily credits must not ask twice).

func NextReset

func NextReset(text string, today core.Day) (core.Day, bool)

NextReset is the next reset as kiro-cli prints it: "2026-10-01", or "10/01" (this year's, else next year's once it has passed).

func ParseTime

func ParseTime(s string) (time.Time, bool)

ParseTime is DateTimeOffset.TryParse for the ISO 8601 stamps these tools write: an offset or Z is kept, none means local time (as .NET assumes). The instant, in UTC.

func RecordDay

func RecordDay(path string, u KiroUsage, now time.Time)

RecordDay saves a good Kiro reading as today's. Blocks on the disk: called on the poll's goroutine, never the UI's. Written to a temporary file and then renamed over the old one.

func RunsOut

func RunsOut(u KiroUsage, today core.Day) *core.Day

RunsOut is the day the month's credits run out at the month's pace so far (used over the days since the last reset, today counted), if that is before the next reset. Nil with too few days of the month to tell, or no reset date to measure them from.

func SpentBy

func SpentBy(days []UsageDay) map[core.Day]Spent

SpentBy is each recorded day's credits. Normally the day's last reading less the previous day's. After a monthly reset that is the reading itself. With no previous day, or one that isn't the day before (Hover wasn't running), only the day's own change is known: last less first, and partial. Never negative.

Types

type Book

type Book struct {
	// contains filtered or unexported fields
}

Book is the readings and which reads are under way. Pure bookkeeping, so it is tested with a clock of its own.

func NewBook

func NewBook() *Book

func (*Book) Busy

func (b *Book) Busy(id string) bool

func (*Book) Finished

func (b *Book) Finished(id string, r Reading, stillOn bool, now time.Time) bool

Finished: a read finished; it is kept only while the quota is still switched on. True when the reading was kept (the views redraw).

func (*Book) Refresh

func (b *Book) Refresh(on []string, force bool, now time.Time) (bool, []string)

Refresh is RefreshQuotas: it drops what is off (true when that changed anything) and marks busy the ids to read now, in the order given.

type CreditDay

type CreditDay struct {
	Date           core.Day
	Hover          float64
	Total, Outside *float64
	Partial        bool
}

CreditDay is one day. Total is nil when there is no reading to tell it from (Hover wasn't running, or the quota is off), and then so is Outside.

type Credits

type Credits struct {
	// contains filtered or unexported fields
}

Credits is the latest view, kept up to date on a goroutine of its own. The UI goroutine only reads it.

func NewCredits

func NewCredits(history *core.AgentHistory, file string, changed func()) *Credits

NewCredits makes the view; changed is called on the goroutine, when a new view differs from the last. The goroutine starts at the first View: the background service, which nobody looks at Settings in, never decrypts sessions to count credits.

func (*Credits) Close

func (c *Credits) Close()

Close stops the goroutine.

func (*Credits) OnUsage

func (c *Credits) OnUsage(u KiroUsage)

OnUsage: a good Kiro reading (on the poll's goroutine): kept as today's, then counted.

func (*Credits) Pin

func (c *Credits) Pin(v CreditsView)

Pin shows this view from now on, whatever the history and the readings say: the screenshots' made-up days.

func (*Credits) Poke

func (c *Credits) Poke()

Poke: the history changed, or Kiro was read: make the view again.

func (*Credits) View

func (c *Credits) View() *CreditsView

View is the latest view; nil until the first is made (the first call starts making it).

type CreditsView

type CreditsView struct {
	// Days are the last 30 days, oldest first, those with nothing included.
	Days  []CreditDay
	Today CreditDay
	// WeekTotal is the last 7 days' totals (the days with a total), WeekHover Hover's
	// share of the 7.
	WeekTotal *float64
	WeekHover float64
	// PerDay7 is the week's total over the days it is known for.
	PerDay7 *float64
	// Month is the reading of today, i.e. this month so far.
	Month   *KiroUsage
	RunsOut *core.Day
	// TopToday is today's dearest sessions in Hover, three at most.
	TopToday []core.SessionCredits
}

func Combine

func Combine(a map[core.Day]*core.DayA, days []UsageDay, today core.Day) CreditsView

type KiroUsage

type KiroUsage struct {
	Used, Limit float64
	Plan, Reset *string
}

KiroUsage is the raw numbers of kiro-cli's report, which ParseKiro boils down to a percent: what the daily credits are made from. Reset is the text as printed ("10/01" or "2026-10-01"), the next reset.

func ParseKiroUsage

func ParseKiroUsage(output string) (KiroUsage, bool)

ParseKiroUsage is the credit line, the plan and the reset of the same report, or false when there is no "X of Y covered" (a release that drops it, a failure) or the plan has no credits.

type Poller

type Poller struct {
	// contains filtered or unexported fields
}

Poller is the book, the reader and a callback for changes: what OwlApp's quota half is.

func NewPoller

func NewPoller(read func(string) Reading, isOn func(string) bool, changed func()) *Poller

func SystemPoller

func SystemPoller(isOn func(string) bool, changed func()) *Poller

SystemPoller has the real readers.

func SystemPollerWith

func SystemPollerWith(isOn func(string) bool, changed func(), onUsage func(KiroUsage)) *Poller

SystemPollerWith has the real readers, and Kiro's raw credits handed on as well, told on the poll's goroutine for every good Kiro reading. They don't ride on Reading: a field there would be carried by the other tools too, who never fill it. The Kiro reader sees them where it parses the report, and the other tools are read as before.

func (*Poller) Reading

func (p *Poller) Reading(id string) (Reading, bool)

func (*Poller) Refresh

func (p *Poller) Refresh(force bool)

type Reading

type Reading struct {
	Used   *float64
	Detail string
}

Reading is one reading of how much of a plan is used. Used is nil when there is nothing to show; Detail then says why ("kiro-cli not found", "Sign in to Cursor").

func ByID

func ByID(id string) Reading

ByID is one quota by its notch id.

func Claude

func Claude(now time.Time) Reading

Claude: on a Mac Claude Code keeps its sign-in in the Keychain, and the file is the fallback; elsewhere it is the file.

func ClaudeAt

func ClaudeAt(file, url string, now time.Time) Reading

ClaudeAt is Claude Code's plan limits, as its /usage shows them, asked of api.anthropic.com with its own sign-in, read-only: the sign-in is never refreshed, which would rotate Claude Code's tokens underneath it.

func ClaudeWith

func ClaudeWith(credentials, url string, now time.Time) Reading

ClaudeWith is ClaudeAt, with the sign-in's JSON already in hand (from the file or the Keychain).

func Codex

func Codex(now time.Time) Reading

func CodexIn

func CodexIn(home string, now time.Time) Reading

func Cursor

func Cursor(now time.Time) Reading

func CursorAt

func CursorAt(db, url string, now time.Time) Reading

func Fail

func Fail(why string) Reading

func Kiro

func Kiro() Reading

Kiro is kiro-cli's own report: `kiro-cli chat --no-interactive /usage`. One deadline of 25 s for the exit and both pipes: a grandchild that inherits stdout can hold it open after kiro-cli itself has gone. On a timeout the whole tree is killed.

func KiroWith

func KiroWith(exe string, args []string, limit time.Duration) Reading

func ParseClaudeUsage

func ParseClaudeUsage(text string, plan *string, now time.Time) Reading

ParseClaudeUsage reads the usage answer: five_hour and seven_day, each a utilization in percent and an ISO resets_at (null while the window hasn't begun). The notch shows whichever is closer to the limit, as it does for Codex.

func ParseCodexLine

func ParseCodexLine(line string, now time.Time) (Reading, bool)

ParseCodexLine reads one token_count event from a Codex session log. Its rate_limits carry the five-hour window (primary) and the weekly one (secondary). The notch shows whichever is closer to the limit; a window whose reset has passed since the snapshot counts as empty again.

func ParseCursorSummary

func ParseCursorSummary(text string) Reading

ParseCursorSummary reads usage-summary: the plan's percentages are already in percent. Older and team accounts report cents used against a limit instead.

func ParseKiro

func ParseKiro(output string) Reading

ParseKiro reads the report, a box: "████ 42% (resets on 10/01)" and "(21.00 of 50 covered in plan)". The bar's percentage is the share used; the credit line is the fallback when a release drops the bar.

func (Reading) OK

func (r Reading) OK() bool

type Scalar

type Scalar struct {
	Kind ScalarKind
	Text string
	Blob []byte
}

type ScalarKind

type ScalarKind int

ScalarKind is what ExecuteScalar hands back, as far as Quota.CursorToken looks at it.

const (
	ScalarOther ScalarKind = iota
	ScalarText
	ScalarBlob
)

type SignIn

type SignIn struct {
	Token   *string
	Expires *time.Time
	Plan    *string
}

SignIn is Claude Code's sign-in from .credentials.json: {"claudeAiOauth": {"accessToken", "expiresAt" (ms), "subscriptionType"}}.

func ClaudeSignIn

func ClaudeSignIn(text string) SignIn

type Spent

type Spent struct {
	Credits float64
	Partial bool
}

Spent is what Kiro's account spent on a day, across every client. Partial: only the part of the day Hover saw (the first day, or the one after a day Hover wasn't running).

type UsageDay

type UsageDay struct {
	Date        core.Day
	First, Used float64
	Limit       float64
	Reset, Plan *string
	// At is when the latest reading was taken (ISO 8601, local offset).
	At string
}

UsageDay is a day's readings: the first (where the day began, as far as Hover saw) and the latest. Reset is kiro-cli's printed next reset ("10/01" or "2026-10-01").

func AddReading

func AddReading(days []UsageDay, u KiroUsage, now time.Time) []UsageDay

AddReading adds a reading to today's row (or starts it). The first reading of a day stays its First, except after a reset within the day: the counter restarted from nothing, so what it held before doesn't count toward today.

func LoadDays

func LoadDays(path string) []UsageDay

LoadDays is the days on file; none when there is no file. One that can't be read is set aside, not written over (the history does the same with its index).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL