Commit Graph

19 Commits

Author SHA1 Message Date
teernisse
5b9edc7702 feat: add live activity aggregation with today-hourly and last-hour bucketing
Add data layer support for real-time usage visualization:

- MinuteStats type: holds token counts for 5-minute buckets, enabling
  granular recent-activity views (12 buckets covering the last hour).

- AggregateTodayHourly(): computes 24 hourly token buckets for the
  current local day by filtering sessions to today's date boundary and
  slotting each into the correct hour index. Tracks prompts, sessions,
  and total tokens per hour.

- AggregateLastHour(): computes 12 five-minute token buckets for the
  last 60 minutes using reverse-offset bucketing (bucket 11 = most
  recent 5 minutes, bucket 0 = 55-60 minutes ago). Bounds-clamped to
  prevent off-by-one at the edges.

Both functions filter on StartTime locality and skip zero-time sessions,
consistent with existing aggregation patterns in the pipeline package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:36:38 -05:00
teernisse
35fae37ba4 feat: overhaul TUI dashboard with subscription data, new tabs, and setup wizard
Major rewrite of the Bubble Tea dashboard, adding live claude.ai
integration and splitting the monolithic app.go into focused tab modules.

App model (app.go):
- Integrate claudeai.Client for live subscription/rate-limit data
- Add SubDataMsg and async fetch with periodic refresh (every 5 min)
- Add spinner for loading states (charmbracelet/bubbles spinner)
- Integrate huh form library for in-TUI setup wizard
- Rework tab routing to dispatch to dedicated tab renderers
- Add compact layout detection for narrow terminals (<100 cols)

TUI setup wizard (setup.go):
- Full huh-based setup flow embedded in the TUI (not just CLI)
- Three-step form: credentials, preferences (time range + theme), confirm
- Pre-populates from existing config, validates session key prefix
- Returns to dashboard on completion with config auto-saved

New tab modules:
- tab_overview.go: summary cards (sessions, prompts, cost, time), daily
  activity sparkline, rate-limit progress bars from live subscription data
- tab_breakdown.go: per-model usage table with calls, input/output tokens,
  cost, and share percentage; compact mode for narrow terminals
- tab_costs.go: cost analysis with daily cost chart, model cost breakdown,
  cache efficiency metrics, and budget tracking with progress bar

Rewritten tabs:
- tab_sessions.go: paginated session browser with sort-by-cost/tokens/time,
  per-session detail view, model usage breakdown per session, improved
  navigation (j/k, enter/esc, n/p for pages)
- tab_settings.go: updated to work with new theme struct and config fields
2026-02-20 16:08:26 -05:00
teernisse
2be7b5e193 refactor: simplify TUI theme struct and extract chart/progress components
Theme simplification (theme/theme.go):
- Remove Purple and BorderHover fields from Theme struct — neither was
  referenced in the new tab renderers, reducing per-theme boilerplate
  from 14 to 12 color definitions

Component extraction (components/):
- Move Sparkline() and BarChart() from card.go to new chart.go, giving
  visualization components their own file as complexity grows
- card.go retains MetricCard, ContentCard, LayoutRow, and CardInnerWidth
  which are layout-focused
- New chart.go: Sparkline (unicode block chars) and BarChart (multi-row
  with anchored Y-axis, optional X-axis labels, dynamic height/width)
- New progress.go: ProgressBar component with customizable width, color,
  and percentage display — used by rate-limit and budget views

Status bar and tab bar updates:
- statusbar.go: adapt to simplified theme struct
- tabbar.go: adapt to simplified theme struct
2026-02-20 16:08:06 -05:00
teernisse
e241ee3966 feat: add CLI status command, rewrite setup wizard, and modernize table renderer
Three interconnected CLI improvements:

New status command (cmd/status.go):
- Fetches live claude.ai subscription data using the claudeai client
- Renders rate-limit windows (5-hour, 7-day all/Opus/Sonnet) with
  color-coded progress bars: green <50%, orange 50-80%, red >80%
- Shows overage spend limits and usage percentage
- Handles partial data gracefully (renders what's available)
- Clear onboarding guidance when no session key is configured

Setup wizard rewrite (cmd/setup.go):
- Replace raw bufio.Reader prompts with charmbracelet/huh multi-step form
- Three form groups: welcome screen, credentials (session key + admin key
  with password echo mode), and preferences (time range + theme select)
- Pre-populates from existing config, preserves existing keys on empty input
- Dracula theme for the form UI
- Graceful Ctrl+C handling via huh.ErrUserAborted

Table renderer modernization (internal/cli/render.go):
- Replace 120-line manual box-drawing table renderer with lipgloss/table
- Automatic column width calculation, rounded borders, right-aligned
  numeric columns (all except first)
- Filter out "---" separator sentinels (not supported by lipgloss/table)
- Remove unused style variables (valueStyle, costStyle, tokenStyle,
  warnStyle) and Table.Widths field

Config display update (cmd/config_cmd.go):
- Show claude.ai session key and org ID in config output

Dependencies (go.mod):
- Add charmbracelet/huh v0.8.0 for form-based TUI wizards
- Upgrade golang.org/x/text v0.3.8 -> v0.23.0
- Add transitive deps: catppuccin/go, harmonica, hashstructure, etc.
2026-02-20 16:07:55 -05:00
teernisse
547d402578 feat: add claude.ai API client and session key configuration
Introduce a client for fetching subscription data from the claude.ai web
API, enabling rate-limit monitoring and overage tracking in the dashboard.

New package internal/claudeai:
- Client authenticates via session cookie (sk-ant-sid... prefix validated)
- FetchAll() retrieves orgs, usage windows, and overage in one call,
  returning partial data when individual requests fail
- FetchOrganizations/FetchUsage/FetchOverageLimit for granular access
- Defensive utilization parsing handles polymorphic API responses: int
  (75), float (0.75 or 75.0), and string ("75%" or "0.75"), normalizing
  all to 0.0-1.0 range
- 10s request timeout, 1MB body limit, proper status code handling
  (401/403 -> ErrUnauthorized, 429 -> ErrRateLimited)

Types (claudeai/types.go):
- Organization, UsageResponse, UsageWindow (raw), OverageLimit
- SubscriptionData (TUI-ready aggregate), ParsedUsage, ParsedWindow

Config changes (config/config.go):
- Add ClaudeAIConfig struct with session_key and org_id fields
- Add GetSessionKey() with CLAUDE_SESSION_KEY env var fallback
- Fix directory permissions 0o755 -> 0o750 (gosec G301)
- Fix Save() to propagate encoder errors before closing file
2026-02-20 16:07:40 -05:00
teernisse
892f578565 fix: linter compliance and code quality improvements across codebase
Address golangci-lint findings and improve error handling throughout:

Package doc comments:
- Add canonical "// Package X ..." comments to source, model, config,
  pipeline, cli, store, and main packages for godoc compliance.

Security & correctness:
- Fix directory permissions 0o755 -> 0o750 in store/cache.go Open()
  (gosec G301: restrict group write on cache directory)
- Fix config.Save() to check encoder error before closing file, preventing
  silent data loss on encode failure
- Add //nolint:gosec annotations with justifications on intentional
  patterns (constructed file paths, manual bounds checking, config fields)
- Add //nolint:nilerr on intentional error-swallowing in scanner WalkDir
- Add //nolint:revive on stuttering type names (ModelStats, ModelUsage)
  that would break too many call sites to rename

Performance (perfsprint):
- Replace fmt.Sprintf("%d", n) with strconv.FormatInt(n, 10) in format.go
  FormatTokens() and FormatNumber() hot paths
- Clean up redundant fmt.Sprintf patterns in FormatCost and FormatDelta

Code cleanup:
- Convert if-else chain to switch in parser.go skipJSONString() for clarity
- Remove unused indexedResult struct from pipeline/loader.go
- Add deferred cache.Close() in pipeline/bench_test.go to prevent leaks
- Add deferred cache.Close() in cmd/root.go data loading path
- Fix doc comment alignment in scanner.go decodeProjectName
- Remove trailing blank line in cmd/costs.go
- Fix duplicate "/day" suffix in cmd/summary.go cost-per-day formatting
- Rename shadowed variable 'max' -> 'maxVal' in cli/render.go Sparkline
2026-02-20 16:07:26 -05:00
teernisse
c65335bd21 test: add comprehensive parser unit tests and fuzz test
Cover the core JSONL byte-level parser with targeted tests and a fuzz
harness to prevent regressions and catch panics on malformed input:

Unit tests:
- TestParseFile_UserMessages: verifies user message counting and
  ProjectPath extraction from cwd field
- TestParseFile_AssistantDedup: confirms message-ID-based deduplication
  where the last entry wins (handles edits/retries)
- TestParseFile_TimeRange: validates StartTime/EndTime tracking across
  out-of-order timestamps
- TestParseFile_SystemDuration: tests turn_duration aggregation from
  system entries (durationMs -> DurationSecs conversion)
- TestParseFile_EmptyFile: ensures zero stats without errors on empty input
- TestParseFile_MalformedLines: confirms graceful skip of unparseable
  lines without aborting the entire file
- TestParseFile_CacheTokens: validates extraction of cache_read,
  cache_creation_5m, and cache_creation_1h token fields
- TestExtractTopLevelType: table-driven tests for the byte-level type
  extractor covering user, assistant, system, nested-type-ignored,
  unknown, no-type, and empty cases

Fuzz test:
- FuzzExtractTopLevelType: seeds with realistic patterns plus edge cases
  (unterminated strings, non-string type values, empty input). Asserts
  the parser never panics and only returns known type strings or empty.

Uses a writeSession helper that creates temp JSONL files for each test,
keeping tests isolated and cleanup automatic via t.TempDir().
2026-02-20 16:07:09 -05:00
teernisse
a04a0065a0 feat: add root TUI app model with 10-tab dashboard and async data loading
Add the core App Bubble Tea model that orchestrates the entire TUI
dashboard. The model manages:

- 10 navigable tabs: Dashboard, Costs, Sessions, Models, Projects,
  Trends, Efficiency, Activity, Budget, and Settings. Each tab is
  accessible via single-key shortcuts (d/c/s/m/p/t/e/a/b/x) or
  left/right arrows for sequential navigation.

- Async data pipeline: launches the JSONL loader in a goroutine and
  receives progress updates via a channel subscription, displaying
  an animated loading screen with a spinner and file count. Data
  loads once on startup and recomputes aggregates when filters change.

- Filter state: supports configurable time range (7/30/90 days),
  project filter, and model filter. Changing any filter triggers
  recomputation of all derived stats (summary, daily, model, project
  breakdowns) including a comparison period for delta calculations.

- First-run detection: if no config file exists when data finishes
  loading, automatically enters the setup wizard flow before showing
  the dashboard.

- Tab-specific rendering: Dashboard shows metric cards with period-
  over-period deltas, daily token/cost bar charts, model pie-style
  breakdown, and top projects. Costs shows a per-model cost table.
  Trends renders daily tokens, cost, and session bar charts.
  Efficiency shows cache hit rate and savings metrics. Activity
  renders an hourly heatmap. Budget tracks spend against plan limits
  with a burn-rate projection chart.

- Help overlay: toggleable help panel listing all keyboard shortcuts,
  rendered as a centered overlay above the active tab content.
2026-02-19 15:38:43 -05:00
teernisse
b69b24c107 feat: add TUI feature modules for setup wizard, session browser, and settings
Add three self-contained feature modules that plug into the root App
model via shared state structs and render methods.

setup.go -- First-run wizard with a 5-step flow: welcome screen, API
key entry (password-masked text input), default time range selector
(radio-style j/k navigation over 7/30/90 day options), theme picker
(radio-style over all registered themes), and a completion screen
that persists choices to ~/.config/cburn/config.toml. Gracefully
handles save failures by noting the settings apply for the current
session only.

tab_sessions.go -- Session browser with two view modes: split view
(1/3 condensed list + 2/3 detail pane, scrollable with offset
tracking) and full-screen detail. The detail body shows duration,
prompt/API-call ratio, per-type token breakdown with cache cost
attribution, per-model API call table, and subagent indicator.

tab_settings.go -- Runtime settings editor with 4 configurable
fields (API key, theme, default days, monthly budget). Supports
inline text input editing with Enter/Esc save/cancel flow, immediate
config persistence, flash "Saved!" confirmation, and error display
on save failure. Theme changes apply instantly without restart.
2026-02-19 15:01:47 -05:00
teernisse
04abdafa9a feat: zero-fill missing days in aggregator for continuous chart data
The daily aggregation now iterates from the since date through the
until date and inserts a zero-valued DailyStats entry for any day
not already present in the day map. This ensures sparklines and bar
charts render a continuous time axis with explicit zeros for idle
days, rather than connecting adjacent data points across gaps.

Also switch config file creation to os.OpenFile with explicit 0600
permissions and O_WRONLY|O_CREATE|O_TRUNC flags, matching the intent
of the original os.Create call while making the restricted permission
bits explicit for security.
2026-02-19 15:01:34 -05:00
teernisse
4d46977328 feat: expand component library with bar chart, content cards, and layout helpers
Add BarChart component that renders multi-row Unicode bar charts with
anchored Y-axis labels, automatic tick-step computation, sub-sampling
for narrow terminals, and optional X-axis date labels. The chart
gracefully degrades to a sparkline when width/height is too small.

Add ContentCard, CardRow, and CardInnerWidth utilities for consistent
bordered card layout across all dashboard tabs. ContentCard renders
a lipgloss-bordered card with optional bold title; CardRow joins
pre-rendered cards horizontally; CardInnerWidth computes the usable
text width after accounting for border and padding.

Add LayoutRow helper that distributes a total width into n integer
widths that sum exactly, absorbing the integer-division remainder
into the first items -- eliminates off-by-one pixel drift in multi-
column layouts.

Refactor MetricCard to accept an outerWidth parameter and derive the
content width internally by subtracting border, replacing the old
raw-width parameter that required callers to do the subtraction.
MetricCardRow now uses LayoutRow for exact width distribution.

Refine TabBar to render all tabs on a single row when they fit within
the terminal width, falling back to the two-row layout only when
they overflow.

Simplify StatusBar by removing the unused filterInfo append that was
cluttering the left section.
2026-02-19 15:01:26 -05:00
teernisse
79ab17488e feat: add TUI reusable components: metric cards, sparklines, tab bar, and status bar
Implement shared UI components used across the dashboard tabs:

- tui/components/card.go: Three components:

  * MetricCard: bordered card with label (muted), value (bold), and
    optional delta (dim) — used for the dashboard's top-level KPIs.

  * MetricCardRow: renders N cards side-by-side using lipgloss
    horizontal join, auto-calculating card width from available space.

  * Sparkline: theme-colored Unicode block sparkline (8-level,
    auto-scaled to series max). Used in Dashboard and Trends tabs.

  * ProgressBar: filled/empty bar (accent + dim) with percentage
    label. Used in the Budget tab for plan-relative spend.

- tui/components/statusbar.go: Bottom status bar with left-aligned
  keybinding hints ([f]ilter [?]help [q]uit), current filter info,
  and right-aligned data age indicator. Padding auto-fills to
  terminal width.

- tui/components/tabbar.go: 10-tab navigation bar split across two
  rows (Dashboard/Costs/Sessions/Models/Projects on row 1,
  Trends/Efficiency/Activity/Budget/Settings on row 2). Each
  inactive tab highlights its keyboard shortcut letter with [bracket]
  notation. Active tab renders in accent color. Settings uses 'x'
  as its shortcut (not present in the name, so appended). TabIdxByKey
  maps key presses to tab indices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:59 -05:00
teernisse
253776ffd5 feat: add TUI theme system with four color palettes
Implement the theming layer for the interactive dashboard:

- tui/theme/theme.go: Theme struct with 14 semantic color roles
  (Background, Surface, Border, BorderHover, TextDim, TextMuted,
  TextPrimary, Accent, Green, Orange, Red, Blue, Purple, Yellow).
  Four built-in themes:

  * Flexoki Dark (default): warm, low-contrast palette designed for
    extended reading. Teal accent (#3AA99F) on near-black background.

  * Catppuccin Mocha: pastel warm tones with blue accent (#89B4FA)
    on dark surface (#1E1E2E).

  * Tokyo Night: cool blue/purple palette with blue accent (#7AA2F7)
    on deep navy (#1A1B26).

  * Terminal: ANSI 16-color only for maximum compatibility — maps
    all roles to standard terminal colors (0-15), works correctly
    in any terminal emulator regardless of true-color support.

  Global Active variable holds the current theme. SetActive/ByName
  enable runtime theme switching from the settings tab and setup
  wizard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:45 -05:00
teernisse
f7d8fea140 feat: add CLI formatting utilities and table renderer
Implement the presentation layer for terminal output:

- cli/format.go: Human-readable formatting functions — FormatTokens
  (K/M/B suffixes), FormatCost (adaptive precision: $X.XX under $10,
  $X.X under $100, $X over $100, comma-separated over $1000),
  FormatDuration (Xh Ym / Xm / Xs), FormatNumber (comma-separated
  integers), FormatPercent (0-1 -> "XX.X%"), FormatDelta (signed
  cost delta with +/- prefix), FormatDayOfWeek (weekday number to
  3-letter abbreviation).

- cli/render.go: lipgloss-styled output components using the Flexoki
  Dark color palette:

  * RenderTitle: centered title in a rounded border box.

  * RenderTable: full-featured bordered table with auto-calculated
    column widths, right-aligned numeric columns (all except first),
    separator rows (triggered by "---" sentinel), and proper Unicode
    box-drawing characters. Supports optional title header and
    explicit column widths.

  * RenderProgressBar: bracketed fill bar with current/total counts.

  * RenderSparkline: Unicode block sparkline (8-level: ▁ through █)
    from arbitrary float64 series, auto-scaled to max.

  * RenderHorizontalBar: simple horizontal bar chart entry for
    inline comparisons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:13 -05:00
teernisse
24454247a3 feat: add data pipeline with parallel loading, aggregation, and cache integration
Implement the pipeline layer that orchestrates discovery, parsing,
caching, and aggregation:

- pipeline/loader.go: Load() discovers session files via ScanDir,
  optionally filters out subagent files, then parses all files in
  parallel using a bounded worker pool sized to GOMAXPROCS. Workers
  read from a pre-filled channel (no contention on dispatch) and
  report progress via an atomic counter and callback. LoadResult
  tracks total files, parsed files, parse errors, and file errors.

- pipeline/aggregator.go: Five aggregation functions, all operating
  on time-filtered session slices:

  * Aggregate: computes SummaryStats across all sessions — total
    tokens (5 types), estimated cost, cache savings (summed per-model
    via config.CalculateCacheSavings), cache hit rate, and per-active-
    day rates (cost, tokens, sessions, prompts, minutes).

  * AggregateDays: groups sessions by local calendar date, sorted
    most-recent-first.

  * AggregateModels: groups by normalized model name with share
    percentages, sorted by cost descending.

  * AggregateProjects: groups by project name, sorted by cost.

  * AggregateHourly: distributes prompt/session/token counts across
    24 hour buckets (attributed to session start hour).

  Also provides FilterByTime, FilterByProject, FilterByModel with
  case-insensitive substring matching.

- pipeline/incremental.go: LoadWithCache() implements the incremental
  loading strategy — compares discovered files against the cache's
  file_tracker (mtime_ns + size), loads unchanged sessions from
  SQLite, and only reparses files that changed. Reparsed results
  are immediately saved back to cache. CacheDir/CachePath follow
  XDG_CACHE_HOME convention (~/.cache/cburn/metrics.db).

- pipeline/bench_test.go: Benchmarks for ScanDir, ParseFile (worst-
  case largest file), full Load, and LoadWithCache to measure the
  incremental cache speedup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:01:56 -05:00
teernisse
0e9091f56e feat: add SQLite cache store for incremental session persistence
Implement the caching layer that enables fast subsequent runs by
persisting parsed session data in SQLite:

- store/schema.go: DDL for three tables — sessions (primary metrics
  with file_mtime_ns/file_size for change detection), session_models
  (per-model breakdown, FK cascade on delete), and file_tracker
  (path -> mtime+size mapping for cache invalidation). Indexes on
  start_time and project for efficient time-range and filter queries.

- store/cache.go: Cache struct wrapping database/sql with WAL mode
  and synchronous=normal for concurrent read safety and write
  performance. Key operations:

  * Open: creates the cache directory, opens/creates the database,
    and ensures the schema is applied (idempotent via IF NOT EXISTS).

  * GetTrackedFiles: returns the mtime/size map used by the pipeline
    to determine which files need reparsing.

  * SaveSession: transactional upsert of session stats + model
    breakdown + file tracker entry. Uses INSERT OR REPLACE to handle
    both new files and files that changed since last parse.

  * LoadAllSessions: batch-loads all cached sessions with a two-pass
    strategy — first loads session rows, then batch-loads model data
    with an index map for O(1) join, avoiding N+1 queries.

  Uses modernc.org/sqlite (pure-Go, no CGO) for zero-dependency
  cross-platform builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:01:40 -05:00
teernisse
ad484a2a6f feat: add JSONL source layer with directory scanner and byte-level parser
Implement the bottom of the data pipeline — discovery and parsing of
Claude Code session files:

- source/types.go: Raw JSON deserialization types (RawEntry,
  RawMessage, RawUsage, CacheCreation) matching the Claude Code
  JSONL schema. DiscoveredFile carries file metadata including
  decoded project name, session ID, and subagent relationship info.

- source/scanner.go: ScanDir walks ~/.claude/projects/ to discover
  all .jsonl session files. Detects subagent files by the
  <project>/<session>/subagents/agent-<id>.jsonl path pattern and
  links them to parent sessions. decodeProjectName reverses Claude
  Code's path-encoding convention (/-delimited path segments joined
  with hyphens) by scanning for known parent markers (projects,
  repos, src, code, workspace, dev) and extracting the project name
  after the last marker.

- source/parser.go: ParseFile processes a single JSONL session file.
  Uses a hybrid parsing strategy for performance:

  * "user" and "system" entries: byte-level field extraction for
    timestamps, cwd, and turn_duration (avoids JSON allocation).
    extractTopLevelType tracks brace depth and string boundaries to
    find only the top-level "type" field, early-exiting ~400 bytes
    in for O(1) per line cost regardless of line length.

  * "assistant" entries: full JSON unmarshal to extract token usage,
    model name, and cost data.

  Deduplicates API calls by message.id (keeping the last entry per
  ID, which holds the final billed usage). Computes per-model cost
  breakdown using config.CalculateCost and aggregates cache hit rate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:01:27 -05:00
teernisse
8984d5062d feat: add configuration system with pricing tables and plan detection
Implement the configuration layer that supports the entire cost
estimation pipeline:

- config/config.go: TOML-based config at ~/.config/cburn/config.toml
  (XDG-compliant) with sections for general preferences, Admin API
  credentials, budget tracking, appearance, and per-model pricing
  overrides. Supports Load/Save with sensible defaults (30-day
  window, subagents included, Flexoki Dark theme). Admin API key
  resolution checks ANTHROPIC_ADMIN_KEY env var first, falling back
  to the config file.

- config/pricing.go: Hardcoded pricing table for 8 Claude model
  variants (Opus 4/4.1/4.5/4.6, Sonnet 4/4.5/4.6, Haiku 3.5/4.5)
  with per-million-token rates across 5 billing dimensions: input,
  output, cache_write_5m, cache_write_1h, cache_read, plus long-
  context overrides (>200K tokens). NormalizeModelName strips date
  suffixes (e.g., "claude-opus-4-5-20251101" -> "claude-opus-4-5").
  CalculateCost and CalculateCacheSavings compute per-call USD costs
  by multiplying token counts against the pricing table.

- config/plan.go: DetectPlan reads ~/.claude/.claude.json to
  determine the billing plan type. Maps "stripe_subscription" to
  the Max plan ($200/mo ceiling), everything else to Pro ($100/mo).
  Used by the budget tab for plan-relative spend visualization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:01:11 -05:00
teernisse
cfbbb9d6db feat: add domain model types for session metrics and statistics
Define the core data structures that flow through the entire pipeline:

- model/session.go: SessionStats (per-session aggregates including
  token counts across 5 categories — input, output, cache_write_5m,
  cache_write_1h, cache_read), APICall (deduplicated by message.id,
  keyed to the final billed usage), and ModelUsage (per-model
  breakdown within a session). Tracks subagent relationships via
  IsSubagent/ParentSession fields.

- model/metrics.go: Higher-order aggregate types — SummaryStats
  (top-level totals with per-active-day rates for cost, tokens,
  sessions, and minutes), DailyStats/HourlyStats/WeeklyStats
  (time-bucketed views), ModelStats (cross-session model comparison
  with share percentages), ProjectStats (per-project ranking), and
  PeriodComparison (current vs previous period for delta display).

- model/budget.go: BudgetStats with plan ceiling, custom budget,
  burn rate, and projected monthly spend for the budget tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:00:57 -05:00