Documentation
¶
Overview ¶
Package globalbudget manages a global (cross-project) daily token and cost budget stored in ~/.config/cloop/budget.yaml plus a global cost ledger at ~/.config/cloop/costs.jsonl.
Package globalbudget — SQLite mirror for the YAML global budget store (Task 20075).
The canonical store remains ~/.config/cloop/budget.yaml (already atomic- write hardened). This file adds a parallel SQLite mirror at ~/.config/cloop/global.db so the global budget config is queryable alongside the per-project state.db, and recoverable from YAML loss.
Failure mode: every SQLite mirror operation here is best-effort. If the SQLite write or read fails, callers fall back to YAML. The user-visible behaviour without SQLite is identical to the pre-Task-20075 behaviour; SQLite is purely additive.
Index ¶
- func AppendLedger(entry GlobalLedgerEntry) error
- func ConfigPath() (string, error)
- func EffectiveProjectTokenLimit(globalCfg GlobalBudgetConfig, pct float64) int
- func EffectiveProjectUSDLimit(globalCfg GlobalBudgetConfig, pct float64) float64
- func LedgerPath() (string, error)
- func Save(cfg GlobalBudgetConfig) error
- type DailyStats
- type GlobalBudgetConfig
- type GlobalLedgerEntry
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AppendLedger ¶
func AppendLedger(entry GlobalLedgerEntry) error
AppendLedger appends a global ledger entry to ~/.config/cloop/costs.jsonl. Concurrent in-process callers are serialised via ledgerMu so JSON object writes can't interleave on the shared file (json.Encoder issues separate Write calls for the JSON body and the trailing newline). Cross-process races aren't guarded — only one cloop daemon is expected per host.
func ConfigPath ¶
ConfigPath returns the path to ~/.config/cloop/budget.yaml.
func EffectiveProjectTokenLimit ¶
func EffectiveProjectTokenLimit(globalCfg GlobalBudgetConfig, pct float64) int
EffectiveProjectTokenLimit returns the project-level effective daily token cap derived from the global budget and the per-project percentage. Returns 0 when there is no global token limit or no percentage configured.
func EffectiveProjectUSDLimit ¶
func EffectiveProjectUSDLimit(globalCfg GlobalBudgetConfig, pct float64) float64
EffectiveProjectUSDLimit returns the project-level effective daily USD cap derived from the global budget and the per-project percentage. Returns 0 when there is no global USD limit or no percentage configured.
func LedgerPath ¶
LedgerPath returns the path to ~/.config/cloop/costs.jsonl.
func Save ¶
func Save(cfg GlobalBudgetConfig) error
Save writes the global budget config to ~/.config/cloop/budget.yaml, creating the directory if needed. The write is atomic (tmp → fsync → rename), so a crash, ENOSPC, or concurrent reader during the write can never leave the file half-written or empty — readers see either the old valid file or the new valid file. Concurrent in-process callers are serialised via saveMu so two updates can't drop each other.
Types ¶
type DailyStats ¶
DailyStats is the aggregated global usage for today.
func DailyUsage ¶
func DailyUsage() (DailyStats, error)
DailyUsage returns today's (UTC) aggregated usage across all projects from the global ledger.
type GlobalBudgetConfig ¶
type GlobalBudgetConfig struct {
// DailyUSDLimit is the maximum daily USD spend across all projects.
// 0 means no global USD limit.
DailyUSDLimit float64 `yaml:"daily_usd_limit,omitempty"`
// DailyTokenLimit is the maximum daily token count (input+output) across
// all projects. 0 means no global token limit.
DailyTokenLimit int `yaml:"daily_token_limit,omitempty"`
// AlertThresholdPct is the percentage at which alerts fire. Default 80.
AlertThresholdPct int `yaml:"alert_threshold_pct,omitempty"`
// BlockExtraUsage, when true (default), prevents cloop from running tasks
// when doing so would incur Claude Code extra usage (per-token billing).
BlockExtraUsage *bool `yaml:"block_extra_usage,omitempty"`
}
GlobalBudgetConfig holds the global (across all projects) daily budget limits.
func Load ¶
func Load() (GlobalBudgetConfig, error)
Load reads the global budget config. Returns an empty config (no limits) if the file does not exist.
On parse failure (zero-byte file from a torn pre-atomicfile write, schema drift, manual edit gone wrong) the corrupt budget.yaml is quarantined aside as budget.yaml.corrupt-<unix> and an empty config is returned. The pre-fix behaviour was that every cloop invocation across the host bailed out before doing real work because pre-task budget enforcement loaded this file and returned the parse error. Empty config means "no limits" which is the same as the file not existing — the worst case is the user has to re-run `cloop budget set` to restore their cap.
func (GlobalBudgetConfig) ShouldBlockExtraUsage ¶
func (g GlobalBudgetConfig) ShouldBlockExtraUsage() bool
ShouldBlockExtraUsage returns true if extra usage should be blocked. Defaults to true when not explicitly set.
type GlobalLedgerEntry ¶
type GlobalLedgerEntry struct {
Timestamp time.Time `json:"timestamp"`
ProjectPath string `json:"project_path,omitempty"`
TaskID int `json:"task_id"`
TaskTitle string `json:"task_title"`
Provider string `json:"provider"`
Model string `json:"model"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
ThinkingTokens int `json:"thinking_tokens,omitempty"`
EstimatedUSD float64 `json:"estimated_usd"`
// Identity attributes the spend to whoever initiated the run — an
// oidcauth OwnerKey, or "local" for an unauthenticated one (Task 20264).
//
// Carried here as well as in the per-project costs table because this
// ledger is the only one that spans projects, and "what did alice spend
// today" is a question about a person, not about a directory. Omitted
// when empty, so a line written before this field existed round-trips
// unchanged and reads back as unattributed.
Identity string `json:"identity,omitempty"`
}
GlobalLedgerEntry mirrors cost.LedgerEntry but adds a project path field so entries from different projects are distinguishable.
func ReadLedger ¶
func ReadLedger() ([]GlobalLedgerEntry, error)
ReadLedger reads all entries from the global ledger. Returns nil (no error) when the file does not exist.