New command: codeburn yield --period <period>
Correlates AI sessions with git commits to categorize spend:
- Productive: sessions with nearby commits that made it to main
- Reverted: sessions with commits that were reverted
- Abandoned: sessions with no commits or not in main
Uses timestamp proximity heuristic (session time + 1 hour window).
Works across branches, squash merges, and rebases by checking
if commits are in main branch ancestry.
Closes#152
PR #136 (2e5e449) changed parseAllSessions to use periodInfo.range
(full period) instead of just today's range. When combined with
cached historical data, this caused days to be counted twice:
- Once from getDaysInRange(cache, ...)
- Again from parseAllSessions(periodInfo.range, ...)
Result: 7-day cost showed ~$402 instead of correct ~$209.
Fix: Parse only today's sessions when using cache path. Historical
data comes exclusively from cache, today's data from fresh parse.
The AgentTabStrip was using allProvidersToday for cost display, which
meant tabs always showed today's per-provider costs regardless of
which period was selected. This caused the hero to show e.g. $209 for
30 Days but the Claude tab to show $59 (today's Claude cost).
Fix: cost(for:) now reads from store.payload (selected period) instead
of allProvidersToday. Tab VISIBILITY still uses todayPayload so tabs
don't disappear when switching periods.
Bug existed since the original menubar app commit (495a254, Apr 17).
Bars now always show their assigned colors (blue for model A, green
for model B) instead of graying out the non-winner. Only the winner's
percentage value is highlighted in green.
Codex records file modifications as event_msg entries with type
patch_apply_end rather than function_call tool invocations. This fix
tracks those events and adds Edit to the tools list, enabling proper
calculation of edit turns, one-shot rates, and retry rates.
Fixes edit detection showing 0 edit turns despite actual file changes.
On macOS 26.4+, accessory apps may fail to render their status item if
the activation policy is set to .accessory before the status item is
created and populated. The status item draw code executes but nothing
appears in the menu bar.
Workaround:
1. Start as .regular app and call activate() to ensure window server
registration
2. Set a simple SF Symbol image immediately on the status button
3. Defer the attributed title setup to ensure initial render completes
4. Switch to .accessory policy after setup to hide from Dock
Fixes#146
Newer Cursor versions store conversation data in agentKv entries
instead of populating tokenCount in bubbleId entries. This adds
a fallback parser that reads agentKv blobs and estimates tokens
from content length.
Closes#114Closes#102
Fixes crash when switching timeframes or providers by handling
duplicate dates in history data gracefully.
Adds LaunchAgent that posts a distributed notification every 15
seconds to keep prices fresh even after long idle periods.
The menubar price would freeze after the app was idle for a while because macOS App Nap suspends Task.sleep even with beginActivity.
Replace Task.sleep with DispatchSourceTimer which is more robust for background execution. Also add observers for system wake and screen wake events to force a refresh when the Mac resumes from sleep.
The menubar showed stale prices because provider all used end:now while provider specific queries used end:endOfDay. Sessions with timestamps after now was captured were excluded from all providers but included in specific provider queries.
Use periodInfo.range consistently across all parseAllSessions calls in menubar json status.
Remove hardcoded "default" account allowlist from keychain credential
lookup. Claude Code 2.1.x writes the macOS login username, not
"default", so the filter silently dropped valid credentials on every
install.
Collapse the two-phase keychain enumeration into a single
SecItemCopyMatching call (one keychain prompt instead of four on
debug builds).
Harden App Nap opt-out: disable automaticTerminationSupport and
suddenTermination at the process level so AppKit cannot override
the beginActivity token.
Closes#115
UserDefaults key CodeBurnMenubarCompact toggles a tighter menubar
display: no decimals, no leading space, variable width that hugs
the rendered text instead of the fixed 130pt slot.
Closes#129
readViaStream (used for files ≥8 MB) reconstructs the full file as a
single string via chunks.join('\n'), giving the same peak allocation as
readFile. Callers then call content.split('\n'), creating a second copy.
With FILE_READ_CONCURRENCY=16 and files up to 128 MB this can exhaust
the V8 heap (~6 GB theoretical peak).
readSessionLines already exists as a proper async generator that yields
one line at a time. Switch both hot-path callers to iterate it directly
so the full file string is never held in memory.
Adds two tests: a spy test confirming readSessionLines is called (not
readSessionFile), and a 500-entry correctness test.
Fixes#131