Repository navigation
internals usage dashboard
User guide: Subscription usage dashboard.
internal/quota imports only the standard library, internal/fsatomic (store writes), and golang.org/x/sys/unix (file locking). Callers pass in fetchers and observations. Provider fetchers are in internal/provider/quota_*.go, with BuildQuotaAccounts in quota_registry.go. The overlay is in internal/tui/usage_overlay*.go.
In cmd/steiner, runtime_build.go builds the service and store, and primeQuota registers configured accounts at startup. MarkUsed is called from the provider factory closure (runtime_build_provider.go) and from cliRunner (runner_run.go) before cache routing, so cached Codex WebSocket providers are marked too.
-
Fetcher.Fetchreturns aSnapshot. ATimeoutFetcheroverrides the 10-second default timeout.FetchOptions.AllowSpawnis true forRefreshAlland false forRefreshUsed; only the Claude fetcher reads it. -
Viewsreturns one row per registered account, sorted by display alias, which is the alphabetically first alias. Stored snapshots without a registration are never shown, but stay in the store.Latestis in-memory state; projections read the store, so they can lag the newest observation until the writer drains. -
commitdoes not take the store lock or write. It queues apersistJobfor the writer goroutine, which appends snapshots one at a time in commit order (internal/quota/service_persist.go); the only store read on this path is the history load, which each commit retries until one succeeds. The queue holds 32 jobs; when it is full the oldest pending job is dropped and logged, so the writer always ends on the newest state. Persist failures and drops are logged after the service lock is released, and the in-memory state still updates. A successful append notifiesUpdatesso projections rebuild from the new history. -
Drain(ctx)waits for the queue and any in-flight append to finish, or returns thectxerror.closeRuntimeincmd/steinercalls it withquotaDrainTimeoutafter the claude_subscription pool stops, thenClose, so interactive, exec, and oneshot runs all flush before exit. Code that reads the store in the same process right after an observation must drain first. A blockedStore.Appendcannot be cancelled: when the deadline expires the writer may stay blocked on the lock until the process exits, the queued snapshots are lost, and teardown reports asession_healthwarning. Observations arriving after a drain starts are still queued.Drainis safe to call repeatedly and concurrently. -
Closestops the writer goroutine. It is idempotent; jobs still queued are dropped and logged, an append in flight still finishes, and a laterDrainreturns without waiting. A nil service is a no-op, so teardown needs no guard. -
commitskips persistence only when the new state repeats the last persisted snapshot within an hour of that append. The baseline starts as the stored snapshot loaded on first use, restarts included, and moves on every write or file-level dedupe skip to the snapshot the store now holds, so a chain of small drifts cannot hide a real change.sameStatecompares reset times withinresetsAtTolerance(five seconds): the Claude CLI repeats a reset with sub-second drift, and Codex derivesresets_atfromresets_in_secondsplus now, so latency and whole-second rounding add a second or two. A real reset moves reset times by hours. - The in-process baseline is authoritative inside the dedupe window: if another process reverts an account to an older state, this process keeps skipping that observation until the hour since its last baseline write expires, so the reversion reaches the store late.
-
claudeSubQuotaObservedGetUsagewraps the Claude usage call.parseCodexRateLimitsFrameparses Codex rate-limit frames. -
Observestamps a zeroFetchedAtwith the service clock.mergePassiveoverwrites windows with the same label and keeps the others. It keeps the existing provider type, plan, and credits when the observation has none, and replaces budgets when it has any. It takes the observation's time and source. - A successful fetch replaces the latest snapshot and clears the error. A failed fetch keeps the snapshot and stores the error, truncated to 80 runes. Passive observations do not clear that error, so it can show beside newer data until the next successful fetch.
-
Appendcreates the directory with mode0o700, opens<path>.lock(mode0o600, tightened on each append), and takes a blocking exclusiveflock. - Under the lock it reads the file, merges, prunes old snapshots, and writes through
fsatomic.WriteFile: a synced temp file renamed over the data file. It skips the write when the newest stored snapshot for the account has the same state and was fetched less than an hour earlier, using the samesameStateandresetsAtTolerancecomparison ascommit. Because every append re-reads the file, identical snapshots queued before the writer ran collapse into one stored entry. -
Latestand the history read inViewstake no lock. Each sees the old or the new file, never a partial one. - Locking is unix-only. Elsewhere
lockFilereturns an error, soAppendwrites nothing. - A file that fails to decode makes
Appendfail, so history is never overwritten.Viewstreats an unreadable history as empty and logs the error.
ProjectWindow and ProjectBudget extrapolate linearly to capacity from the oldest history point for the same account, label, and reset period (reset times within one minute). Times come from the current snapshot's FetchedAt, not the wall clock. A window at or above full usage has no remaining capacity, so a projection that is OK has EmptyAt equal to the snapshot's FetchedAt; without a projection, EmptyAt is zero.
usageAccountLines in internal/tui/usage_overlay_render.go renders a trend row under each window through appendWindowTrend. When usageAtLimit reports that the window's displayed whole percentage is 100 or more, the row reads trend: limit reached in the error style, whether or not a projection exists. usageAtLimit rounds half to even, the rounding the row's %3.0f text uses, so a window showing 100% always reads as exhausted. Otherwise appendWindowTrend drops an empty by row whose EmptyAt is not after usageFrame.now, because a past time is not a forecast, and defers to appendTrend for the remaining rows. Budgets render through appendTrend alone.
Service loads the latest snapshot per account from the store once, on first use. Afterwards the displayed value changes only through this process's fetches and observations. Other processes' writes reach the overlay only through projection history, so a value can stay stale until this process fetches that account.
-
openUsageOverlayshows cached views and startsRefreshAllunder a 30-second context. Views are read only in commands (loadUsageViewsCmd,usageRunFinishedCmd).handleQuotaUpdateMsgcallsUpdates(), a capacity-one channel whose notifications coalesce, and checks overlay state. -
needsTickingincludesIsOpen() && refreshing().handleTickMsgadvances the spinner only while open and refreshing. Closing keys start no ticker. -
withViewscachesbodyCountso scroll keys never render the body.Viewclamps the storedscrollOffsetto the body it renders: an empty-by forecast that expires drops a row, so the cached count can exceed the body until the next views load. -
usageRunFinishedCmdruns on run-finished events. It returns nil when there is no service or the interval is not positive. Otherwise it callsRefreshUsedwith that interval as the minimum snapshot age.RefreshUsedfetches only accounts whose alias the run marked throughMarkUsed, and skips those whose latest snapshot is newer than the interval (internal/quota/service_fetch.go). -
tui.quota_refresh_interval(default5m) is validated byvalidateTUIConfig, which rejects negative values and accepts zero.quotaTUIConfigpasses it to the TUI. Opening the dashboard and pressingrdo not read it.
Get started
Using Steiner
- How to work with steiner
- Interactive mode
- CLI reference
- Skills
- Sub-agent delegation
- Oneshot
- Context management
- Advisor
- Security audit
Tools and integrations
Safety
Usage and cost
Reference
Internals and maintenance