globalbudget

package
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 13 Imported by: 0

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

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

func ConfigPath() (string, error)

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

func LedgerPath() (string, error)

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

type DailyStats struct {
	TotalTokens int
	TotalUSD    float64
	EntryCount  int
}

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.

Jump to

Keyboard shortcuts

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