costs

package
v1.9.13 Latest Latest
Warning

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

Go to latest
Published: May 17, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatUSD

func FormatUSD(microdollars int64) string

FormatUSD converts microdollars to a display string.

func Recompute added in v1.7.73

func Recompute(ctx context.Context, store *Store, pricer *Pricer, dryRun bool) (updated, skipped int, err error)

Recompute walks every cost_events row in `store`, recalculates cost_microdollars using `pricer`, and writes any differences back. It is idempotent: a second run with no pricing data changes returns updated=0.

Rows whose `model` is unknown to the pricer are left untouched and counted as skipped (writing 0 over an existing positive cost would lose data).

When dryRun is true no UPDATE is executed; the returned counts describe what would change.

func RenderCostLine added in v1.7.75

func RenderCostLine(template string, vars map[string]int64, hideWhenZero bool) string

RenderCostLine substitutes {name} placeholders in template with values drawn from vars and formatted as USD via FormatUSD. Recognized placeholders whose value is non-zero count toward "non-empty"; unknown placeholders pass through literally so typos are visible.

When hideWhenZero is true and zero recognized placeholders rendered to a non-zero value (either none were recognized at all, or all recognized values were zero), the empty string is returned. This preserves the "no events, no segment" behavior of the legacy hardcoded status-bar.

The walker is left-to-right and never iterates the vars map, so output is deterministic regardless of map iteration order.

Types

type BudgetAction

type BudgetAction int
const (
	BudgetActionNone BudgetAction = iota
	BudgetActionWarn
	BudgetActionStop
)

type BudgetChecker

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

func NewBudgetChecker

func NewBudgetChecker(cfg BudgetConfig, store *Store) *BudgetChecker

func (*BudgetChecker) Check

func (b *BudgetChecker) Check(sessionID, groupName string) BudgetResult

Check is a convenience for non-transactional checks (e.g., TUI display).

func (*BudgetChecker) CheckTx

func (b *BudgetChecker) CheckTx(tx *sql.Tx, sessionID, groupName string, groupSessionIDs []string) (BudgetResult, error)

CheckTx evaluates all budget limits within a transaction. This must be called within the same transaction as the cost event INSERT.

type BudgetConfig

type BudgetConfig struct {
	DailyLimit    int64
	WeeklyLimit   int64
	MonthlyLimit  int64
	GroupLimits   map[string]int64 // group name -> daily limit in microdollars
	SessionLimits map[string]int64 // session ID -> total lifetime limit in microdollars
	Timezone      *time.Location   // for determining day/week/month boundaries
}

BudgetConfig holds budget limits in microdollars.

type BudgetResult

type BudgetResult struct {
	Action     BudgetAction
	Reason     string
	UsedMicro  int64
	LimitMicro int64
	Percentage float64
}

type ClaudeHookParser

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

ClaudeHookParser parses Claude Code Stop hook JSON payloads.

func (*ClaudeHookParser) CanParse

func (p *ClaudeHookParser) CanParse(toolType string) bool

func (*ClaudeHookParser) Name

func (p *ClaudeHookParser) Name() string

func (*ClaudeHookParser) Parse

func (p *ClaudeHookParser) Parse(input string) ([]CostEvent, error)

type Collector

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

Collector routes tool output to the appropriate parser and applies pricing.

func NewCollector

func NewCollector(pricer *Pricer) *Collector

NewCollector creates a Collector with all registered parsers.

func (*Collector) Collect

func (c *Collector) Collect(toolType, sessionID, input string) ([]CostEvent, error)

Collect parses input using the appropriate parser, sets session ID and computes cost.

type CostEvent

type CostEvent struct {
	ID               string
	SessionID        string
	Timestamp        time.Time
	Model            string
	InputTokens      int64
	OutputTokens     int64
	CacheReadTokens  int64
	CacheWriteTokens int64
	CostMicrodollars int64 // 1 USD = 1,000,000 microdollars
}

CostEvent represents a single token usage and cost record.

type CostEventWatcher

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

CostEventWatcher watches a directory for new cost event JSON files.

func NewCostEventWatcher

func NewCostEventWatcher(dir string) (*CostEventWatcher, error)

NewCostEventWatcher creates a watcher for the given directory.

func (*CostEventWatcher) EventCh

func (w *CostEventWatcher) EventCh() <-chan RawCostEvent

EventCh returns the channel that emits parsed cost events.

func (*CostEventWatcher) Start

func (w *CostEventWatcher) Start()

Start begins watching for file events. Blocks until stopped.

func (*CostEventWatcher) Stop

func (w *CostEventWatcher) Stop()

Stop cancels the watcher and closes resources.

type CostPoller

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

CostPoller polls tmux capture-pane for non-hook tools and extracts cost events.

func NewCostPoller

func NewCostPoller(collector *Collector) *CostPoller

NewCostPoller creates a poller with deduplication.

func (*CostPoller) Poll

func (p *CostPoller) Poll(toolType, sessionID, capturedOutput string) ([]CostEvent, error)

Poll processes captured tmux output. Returns new cost events or nil if already seen.

type CostSummary

type CostSummary struct {
	TotalCostMicrodollars int64
	TotalInputTokens      int64
	TotalOutputTokens     int64
	TotalCacheReadTokens  int64
	TotalCacheWriteTokens int64
	EventCount            int
}

CostSummary aggregates cost data.

type DailyCost

type DailyCost struct {
	Date             time.Time
	CostMicrodollars int64
	Group            string
}

DailyCost represents cost for a single day.

type Fetcher

type Fetcher struct {
	CachePath string
	Pricer    *Pricer
	Logger    *slog.Logger
}

func (*Fetcher) CacheAge

func (f *Fetcher) CacheAge() time.Duration

func (*Fetcher) FetchAndCache

func (f *Fetcher) FetchAndCache() error

FetchAndCache writes current known prices to cache. Real HTML scraping is deferred — for now, writes hardcoded defaults.

func (*Fetcher) StartDaily

func (f *Fetcher) StartDaily(ctx context.Context)

StartDaily runs the fetch loop. Blocks until context is cancelled.

type GeminiOutputParser

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

GeminiOutputParser parses Gemini CLI token usage output.

func (*GeminiOutputParser) CanParse

func (p *GeminiOutputParser) CanParse(toolType string) bool

func (*GeminiOutputParser) Name

func (p *GeminiOutputParser) Name() string

func (*GeminiOutputParser) Parse

func (p *GeminiOutputParser) Parse(input string) ([]CostEvent, error)

type MiniMaxOutputParser added in v1.3.1

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

MiniMaxOutputParser parses MiniMax CLI/tool token usage output.

func (*MiniMaxOutputParser) CanParse added in v1.3.1

func (p *MiniMaxOutputParser) CanParse(toolType string) bool

func (*MiniMaxOutputParser) Name added in v1.3.1

func (p *MiniMaxOutputParser) Name() string

func (*MiniMaxOutputParser) Parse added in v1.3.1

func (p *MiniMaxOutputParser) Parse(input string) ([]CostEvent, error)

type ModelPrice

type ModelPrice struct {
	InputPerMtokMicro      int64
	OutputPerMtokMicro     int64
	CacheReadPerMtokMicro  int64
	CacheWritePerMtokMicro int64
}

ModelPrice holds per-model pricing in microdollars per million tokens.

type OpenAIOutputParser

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

OpenAIOutputParser parses OpenAI/Codex CLI token usage output.

func (*OpenAIOutputParser) CanParse

func (p *OpenAIOutputParser) CanParse(toolType string) bool

func (*OpenAIOutputParser) Name

func (p *OpenAIOutputParser) Name() string

func (*OpenAIOutputParser) Parse

func (p *OpenAIOutputParser) Parse(input string) ([]CostEvent, error)

type Parser

type Parser interface {
	Name() string
	CanParse(toolType string) bool
	Parse(input string) ([]CostEvent, error)
}

Parser parses tool output into cost events.

type PriceOverride

type PriceOverride struct {
	InputPerMtok      float64 `toml:"input_per_mtok"`
	OutputPerMtok     float64 `toml:"output_per_mtok"`
	CacheReadPerMtok  float64 `toml:"cache_read_per_mtok"`
	CacheWritePerMtok float64 `toml:"cache_write_per_mtok"`
}

PriceOverride holds user-configured pricing in USD per million tokens.

type Pricer

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

Pricer resolves model pricing with fallback: override > cache > hardcoded.

func NewPricer

func NewPricer(cfg PricerConfig) *Pricer

NewPricer creates a Pricer with hardcoded defaults and optional overrides.

func (*Pricer) CacheAge

func (p *Pricer) CacheAge() time.Duration

CacheAge returns the age of the cache, or -1 if no cache is loaded.

func (*Pricer) ComputeCost

func (p *Pricer) ComputeCost(model string, input, output, cacheRead, cacheWrite int64) int64

ComputeCost calculates cost in microdollars for token usage on a model.

func (*Pricer) GetPrice

func (p *Pricer) GetPrice(model string) (ModelPrice, bool)

GetPrice returns the price for a model with fallback: override > cache > hardcoded.

func (*Pricer) LoadCache

func (p *Pricer) LoadCache() error

LoadCache reads pricing.json from CachePath.

func (*Pricer) SaveCache

func (p *Pricer) SaveCache(models map[string]pricingCacheModel) error

SaveCache writes pricing.json to CachePath.

type PricerConfig

type PricerConfig struct {
	CachePath string
	Overrides map[string]PriceOverride
}

PricerConfig configures the Pricer.

type RawCostEvent

type RawCostEvent struct {
	InstanceID       string `json:"instance_id"`
	Model            string `json:"model"`
	InputTokens      int64  `json:"input_tokens"`
	OutputTokens     int64  `json:"output_tokens"`
	CacheReadTokens  int64  `json:"cache_read_tokens"`
	CacheWriteTokens int64  `json:"cache_write_tokens"`
	Timestamp        int64  `json:"ts"`
}

RawCostEvent is the JSON structure written by hook_handler.

type SessionCost

type SessionCost struct {
	SessionID        string
	SessionTitle     string
	Group            string
	CostMicrodollars int64
	EventCount       int
}

SessionCost represents per-session cost totals.

type Store

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

Store persists and queries cost events in SQLite.

func NewStore

func NewStore(db *sql.DB) *Store

NewStore creates a Store using an existing database connection.

func (*Store) ApplyCostUpdates added in v1.7.73

func (s *Store) ApplyCostUpdates(ctx context.Context, updates map[string]int64) error

ApplyCostUpdates writes a batch of cost_microdollars updates within a single transaction. The map key is cost_event id. Returns an error and rolls back on any failure; on success commits and returns nil.

func (*Store) CostByModel

func (s *Store) CostByModel() (map[string]int64, error)

CostByModel returns total cost per model.

func (*Store) CostByModelForSession

func (s *Store) CostByModelForSession(sessionID string) (map[string]int64, error)

CostByModelForSession returns cost per model for a specific session.

func (*Store) DB

func (s *Store) DB() *sql.DB

DB returns the underlying database for transactional operations.

func (*Store) DailyBySession

func (s *Store) DailyBySession(sessionID string, from, to time.Time) ([]DailyCost, error)

DailyBySession returns daily costs for a specific session.

func (*Store) GlobalRunningTotal

func (s *Store) GlobalRunningTotal(tx *sql.Tx, since time.Time) (int64, error)

GlobalRunningTotal returns the sum of all costs within a time window (for use in a transaction).

func (*Store) GroupRunningTotal

func (s *Store) GroupRunningTotal(tx *sql.Tx, sessionIDs []string, since time.Time) (int64, error)

GroupRunningTotal returns the sum of costs for a set of sessions within a time window.

func (*Store) PageEventsAfter added in v1.7.73

func (s *Store) PageEventsAfter(afterRowID int64, limit int) ([]CostEvent, int64, error)

PageEventsAfter returns up to `limit` cost_events with rowid > afterRowID, ordered by rowid ascending, plus the rowid of the last returned row (or afterRowID itself if no rows were returned). Use 0 as the initial afterRowID. Cursor-based pagination is stable under concurrent inserts.

func (*Store) ProjectedMonthly

func (s *Store) ProjectedMonthly() (int64, error)

ProjectedMonthly estimates monthly spend based on rolling 7-day average.

func (*Store) PurgeOlderThan

func (s *Store) PurgeOlderThan(days int) (int64, error)

PurgeOlderThan deletes events older than the given number of days. Returns count deleted.

func (*Store) RunningTotal

func (s *Store) RunningTotal(tx *sql.Tx, sessionID string, since time.Time) (int64, error)

RunningTotal returns the sum of costs for a session within a time window (for use in a transaction).

func (*Store) SetClock added in v1.9.6

func (s *Store) SetClock(now func() time.Time)

SetClock overrides the clock used for time-windowed queries. Tests use this to pin "now" to a deterministic instant (e.g. a Monday UTC boundary).

func (*Store) TopSessionsByCost

func (s *Store) TopSessionsByCost(limit int) ([]SessionCost, error)

TopSessionsByCost returns the top N sessions by total cost. Joins with instances table to get session titles and groups.

func (*Store) TotalByDateRange

func (s *Store) TotalByDateRange(from, to time.Time) ([]DailyCost, error)

TotalByDateRange returns daily costs within a date range.

func (*Store) TotalBySession

func (s *Store) TotalBySession(sessionID string) (CostSummary, error)

TotalBySession returns aggregated costs for a session.

func (*Store) TotalLastMonth added in v1.7.75

func (s *Store) TotalLastMonth() (CostSummary, error)

TotalLastMonth returns the prior calendar month's total costs.

func (*Store) TotalLastWeek added in v1.7.75

func (s *Store) TotalLastWeek() (CostSummary, error)

TotalLastWeek returns the prior ISO-week's total costs (Monday start). Boundaries are computed in Go from the injected clock so the result is stable across the Monday UTC tick — SQLite's `date('now', 'weekday 1')` is a no-op on Monday and shifts the window by 7 days, producing the week-before-last instead of last week (#932).

func (*Store) TotalThisMonth

func (s *Store) TotalThisMonth() (CostSummary, error)

TotalThisMonth returns this month's total costs.

func (*Store) TotalThisWeek

func (s *Store) TotalThisWeek() (CostSummary, error)

TotalThisWeek returns this week's total costs (Monday start).

func (*Store) TotalToday

func (s *Store) TotalToday() (CostSummary, error)

TotalToday returns today's total costs.

func (*Store) TotalYesterday added in v1.7.75

func (s *Store) TotalYesterday() (CostSummary, error)

TotalYesterday returns the prior day's total costs (00:00:00 UTC of yesterday inclusive to 00:00:00 UTC of today exclusive).

func (*Store) WriteCostEvent

func (s *Store) WriteCostEvent(ev CostEvent) error

WriteCostEvent inserts a cost event.

func (*Store) WriteCostEventTx

func (s *Store) WriteCostEventTx(tx *sql.Tx, ev CostEvent) error

WriteCostEventTx inserts a cost event within a transaction.

type SyncResult

type SyncResult struct {
	SessionsScanned int
	EventsImported  int
	EventsSkipped   int
	Errors          []string
}

SyncResult holds the result of a historical sync operation.

func SyncFromTranscripts

func SyncFromTranscripts(store *Store, pricer *Pricer, sessions []SyncSession) SyncResult

SyncFromTranscripts reads historical usage from Claude transcript files and backfills cost_events for managed sessions.

type SyncSession

type SyncSession struct {
	InstanceID      string
	ClaudeSessionID string
	ProjectPath     string
	Tool            string
}

SyncSession holds the info needed to locate a session's transcript.

type TranscriptEntry

type TranscriptEntry struct {
	Type    string `json:"type"`
	UUID    string `json:"uuid"`
	Message struct {
		Model string `json:"model"`
		Usage struct {
			InputTokens              int64 `json:"input_tokens"`
			OutputTokens             int64 `json:"output_tokens"`
			CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
			CacheReadInputTokens     int64 `json:"cache_read_input_tokens"`
		} `json:"usage"`
	} `json:"message"`
	// Progress entries nest usage inside data.message.message
	Data      *progressData `json:"data,omitempty"`
	Timestamp string        `json:"timestamp"` // ISO 8601
}

TranscriptEntry represents one line of a Claude transcript JSONL file. Handles both "assistant" entries (direct usage) and "progress" entries (subagent usage).

Jump to

Keyboard shortcuts

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