Set accessory activation policy in willFinishLaunching before the
focus chain forms. Debounce observation tracking to coalesce rapid
property changes into a single status bar refresh.
Goose: read token usage from ~/.local/share/goose/sessions/sessions.db.
Lazy-loaded, zero overhead for non-Goose users.
Codex: use sessionId instead of file path in dedup key so forked
sessions sharing the same session_id don't double-count tokens.
- Attach a no-op `stream.on('error', () => {})` so a late read-ahead
error that races with a successful first-line yield can never escape
as an unhandled 'error' event. Defense in depth: empirically the
destroy() in finally already swallows it on Node 18+, but the listener
removes any version-dependent surprise.
- Tighten the comment to say "up to FIRST_LINE_READ_CAP" instead of
"regardless of length"; the cap is real and worth being precise about.
- Stop tracking a separate streamError flag. createReadStream's default
64 KiB highWaterMark means the stream may already be reading chunk 2
when we break out of the loop after yielding the first line; if that
later chunk errors, the flag could reject an otherwise-valid line.
readline's async iterator already re-throws stream errors on Node 16+,
which the existing catch handles.
- Test: 120 KB session_meta line forces multi-chunk line assembly.
- Test: truncated mid-write first line is rejected, not parsed as half
an object.
- Cap createReadStream at 1 MiB so a malformed file with no newline
cannot make readline buffer indefinitely (real session_meta lines
are 22-27 KB).
- Capture stream errors explicitly; readline's async iterator does
not always re-throw underlying stream errors per Node docs.
- Test: assert project is extracted from the >16 KB session_meta to
prove the line was actually parsed, not just discovered.
- Test: session_meta line with no trailing newline is still accepted.
- Test: empty rollout file is silently skipped.
`readFirstLine` allocated a fixed 16 KB buffer, but Codex CLI 0.128+
embeds the entire base_instructions / system prompt in the
`session_meta` line, pushing it past 20 KB. When the buffer doesn't
catch a newline, `isValidCodexSession` rejects the session, so every
recent Codex session is silently excluded from totals.
Switch to a streaming readline read so the first line is captured
regardless of length, and add a regression test that creates a
40 KB session_meta payload.
Locally, this changes my 30-day Codex total from €267 (only ~half
of sessions parsed) to €878 (all sessions parsed).
- Fix refresh loop: proper while loop with 30s sleep and force:true
instead of single-fire Task that never repeated
- Fix loading overlay: counter-based isLoading so concurrent fetches
don't flicker the overlay on/off
- Fix rapid tab switching: cancel previous switchTask, check
Task.isCancelled after CLI returns to discard stale results
- Fix tab strip vs hero desync: fetch provider-specific and all-provider
data in parallel so costs arrive from same data snapshot
- Fix stale menubar icon after wake: forceRefresh now fetches today/all
in parallel alongside the current selection
- Fix accent color: ThemeState is now @Observable so color changes
propagate via observation, removing .id() view hierarchy teardown
- Fix currency flash: defer store.currency and symbol update until a
rate is available so symbol and rate apply atomically
- Fix export: terminationHandler instead of waitUntilExit (no UI freeze),
HHmmss in filename to prevent overwrite on double-export
- Fix CurrencyState: @MainActor isolation with proper Sendable
conformance, nonisolated on pure static functions
- Fix streak count: iterate calendar days instead of sparse history
entries so gaps are counted as streak-breakers
- Fix TrendBar identity: stable date-based id instead of UUID
- Add GPT-5.3 and DeepSeek model display names
Three fixes for issue #184:
1. Menubar Swift code used UTC instead of local timezone in two places:
computeHistoryStats hardcoded TimeZone("UTC") and
effectiveTokensInLast7Days used ISO8601DateFormatter (UTC default).
Both now use .current to match CLI-produced local date keys.
2. Add --timezone flag and CODEBURN_TZ env var to override the system
timezone for all date grouping. Sets process.env.TZ before any Date
operations so all existing local-timezone code works unchanged.
3. Replace MS_PER_DAY arithmetic with Date constructor day-of-month
math for yesterday/backfill computations. Subtracting 86400000ms
from midnight skips a day on DST spring-forward (23-hour day).
Fixes#184
Fixes#183. Users with large Codex session directories (45 GB, 10K+
files) experienced CPU pegging because every 30-second refresh re-parsed
all session files from scratch.
Three optimizations:
1. readFirstLine now reads 16 KB via fs.open() instead of loading the
entire file through readSessionFile. Cuts discovery I/O from ~45 GB
to ~160 MB for 10K files.
2. Per-file result cache (codex-results.json) with mtime+size
fingerprinting. Parsed results are cached on first run; subsequent
runs return cached data instantly for unchanged files.
3. Cache-accelerated discovery skips header validation for cached files,
pulling the project name directly from the cache manifest.
Cache safety: fingerprint captured before read (no TOCTOU), atomic
write via temp+fsync+rename, 0o600 permissions, Object.hasOwn for
prototype pollution defense, eviction of deleted files on flush,
try/finally ensures flush even on parse errors.
forceRefresh() was missing force:true, so the cache TTL guard
silently skipped every LaunchAgent and wake-triggered refresh.
Also adds right-click context menu and version label in footer.
Replace providers hero image with honeycomb design. Add compare screenshot
to 2x2 grid. Consolidate all documentation into single README with provider
table showing data locations, full feature reference, reading the dashboard
guide, and credits section. Add bunx as alternative install option.
- Wrap hydrateCache() in try/catch so disk errors don't crash commands
that previously never touched the cache
- Export MS_PER_DAY, BACKFILL_DAYS, toDateString from daily-cache.ts
and remove duplicates from cli.ts
- Remove double hydrateCache() call in report JSON path
- Persist migrated cache to disk so old-version files aren't
re-migrated on every run
- Export emptyCache() for use as fallback on hydration failure
- Extract ensureCacheHydrated() from menubar-json path into daily-cache.ts
- Call it from every command that parses sessions (report, status, today,
month, export, optimize, compare, yield) so CLI-only users also persist
historical data that survives source file deletion
- Replace strict version equality check with fill-defaults migration for
cache versions 2-4, preserving history across schema changes
- Back up old cache to .bak before discarding on unmigrateable versions
- Fix Copilot auto bucket display names in menubar (Copilot (Anthropic),
Copilot (OpenAI))
- Fix Roo Code / KiloCode provider key matching in menubar tab strip
- OpenClaw: JSONL parser with multi-path discovery, tool extraction
(toolCall + tool_use block types), model tracking via model_change
and custom model-snapshot events
- Roo Code + KiloCode: shared Cline-family parser extracts model from
<model> tags in api_conversation_history.json, strips provider
prefixes from model names
- Add cline-auto and openclaw-auto aliases and display names
- Add menubar provider filters and tab colors for all three
- Show cached data instantly instead of blocking on CLI refresh
Only show the loading overlay when no cached data exists for the
current period/provider. When stale data is available, display it
immediately while the background fetch runs. Also skip CLI calls
entirely when the cache is still fresh (within TTL), reducing
redundant subprocess invocations from the 30s LaunchAgent timer.
Cursor emits dot-version tier-last names like claude-4.6-sonnet
that don't match our pricing keys. Map them so individual models
show costs instead of $0.
Gemini CLI 0.39 switched from single JSON to JSONL with one object
per line and $set metadata lines. Parser now handles both formats.
Also updated --provider help text to list all providers.
Parse ~/.gemini/tmp/<project>/chats/session-*.json files from Gemini
CLI 0.38+. Uses real token counts (input, output, cached, thoughts)
embedded in each message instead of character estimation. Correctly
separates cached tokens from fresh input to avoid double-charging.
- Pricing for gemini-3.1-pro-preview, gemini-3-flash-preview,
gemini-2.5-pro, gemini-2.5-flash from official Google API rates
- Tool name normalization (ReadFile->Read, SearchText->Grep, etc.)
- Menubar tab with Google Blue color (#4485F4)
Closes#166
* Add Kiro provider and transparent auto-model naming
- Add Kiro IDE provider: parses .chat JSON files, estimates tokens,
normalizes dot-versioned model IDs for cost lookup
- Show "Cursor (auto)", "Copilot (auto)", "Kiro (auto)" in CLI
dashboard instead of pretending to know which model was used
- Route auto model names through BUILTIN_ALIASES for cost estimation
* Fix menubar tabs: add missing providers, show period-scoped costs
- Add Kiro, OMP to ProviderFilter enum so installed providers appear as tabs
- Merge Cursor + Cursor Agent into single Cursor tab
- Tab costs now reflect the selected period (7d/30d/month/all) instead
of always showing today
- Tab visibility still uses today's provider list so tabs don't
disappear when switching to periods with no data
* Add accent color picker to menubar with Apple system presets
- 9 presets using Apple's exact macOS dark-mode accent colors
(Ember, Blue, Purple, Pink, Red, Orange, Yellow, Green, Graphite)
- Color picker in header, persisted via UserDefaults
- "Burn" text stays fixed ember regardless of accent
- ThemeState is MainActor-isolated for thread safety
- Picker state lifted to AppStore so it survives .id() tree rebuild
- Accessibility labels on all color swatches
- Renamed brandAccentDark/brandEmberDeep/brandEmberGlow to match
their actual light/deep/glow semantics
* Fix review findings: case-sensitive cost lookup, Kiro timestamp guard, cache versioning
- Normalize provider dictionary keys to lowercase in tab cost lookup
so "Cursor Agent" (title-case from CLI) matches providerKeys
- Guard against missing/invalid/epoch startTime in Kiro parser to
prevent RangeError crash or 1970-01-01 ghost entries
- Bump DAILY_CACHE_VERSION to 4 so upgraded users get a clean
recompute with the new auto-model naming (cursor-auto vs default)
- Add version field to cursor-results.json cache to invalidate stale
entries that still use the old 'default' model name
The Copilot provider only looked in ~/.copilot/session-state/ which is
from an older CLI tool. VS Code Copilot agent stores transcripts in
~/Library/Application Support/Code/User/workspaceStorage/*/GitHub.copilot-chat/transcripts/.
The new transcript format has no outputTokens or model_change events,
so tokens are estimated from content length and the model is inferred
from tool call ID prefixes. Both legacy and VS Code paths are now
scanned in parallel.
Fixes#161
Cursor v3 stores zero token counts in bubbles, causing parseBubbles to
return empty results. The query also dropped rows with NULL createdAt
via the SQL comparison, hiding data from older Cursor versions too.
Changes:
- Remove inputTokens > 0 SQL filter, estimate tokens from text length
when token counts are zero (same 4 chars/token ratio as agentKv)
- Include NULL createdAt rows with OR IS NULL, fall back to current
timestamp when createdAt is missing
- Parse agentKv entries with plain string content instead of skipping
them (not all content is a JSON array)
- Always parse both bubbles and agentKv instead of agentKv-only fallback
- Discover subagent transcripts in subagents/ subdirectories
- Fix timezone-dependent test in day-aggregator
Fixes#159, #163