* Add CodeBurn Pro Mac App Store app
SwiftUI MenuBarExtra with litellm-snapshot pricing, Claude/Codex/Copilot
parsers, session discovery, auto-refresh timer, and dashboard UI matching
the real menubar design.
* Add appstore/ to .gitignore
Private Mac App Store build, not for the public repo.
* Fix one-shot rate detection for Gemini, Vibe, Kiro, and Goose
Gemini and Mistral Vibe now emit per-assistant-message calls grouped
by user turn via turnId, so the classifier sees Edit->Bash->Edit as
retries instead of independent one-shot turns. Kiro and Goose record
per-message tool ordering via toolSequence for the same effect on
aggregated sessions.
Vibe now prefers meta.json.stats.session_cost over price-derived
estimates. Session cache bumped to v2. Insight pill switcher scrolls
horizontally instead of wrapping.
Closes#351.
* Remove dead geminiOrdinal variable and add toolSequence cache validation
* Restore geminiOrdinal for idx fallback dedup key
* Add CodeBurn Pro Mac App Store app
SwiftUI MenuBarExtra with litellm-snapshot pricing, Claude/Codex/Copilot
parsers, session discovery, auto-refresh timer, and dashboard UI matching
the real menubar design.
* Add appstore/ to .gitignore
Private Mac App Store build, not for the public repo.
* Add Optimize tab, token display modes, daily budget alerts, and project drill-down
- New Optimize insight tab with Retry Tax and Routing Waste computations
(pure math from session data, no LLM required)
- Retry tax: shows money wasted on failed edit retries, per-model breakdown
- Routing waste: counterfactual savings vs cheapest reliable model, per-model
- Token display modes: Cost ($), Tokens (up/down split), Total Tokens
- Daily budget alert: configurable threshold, flame turns yellow when exceeded
- Project drill-down: click project rows to see per-session cost, tokens, models
- Period-aware top sessions: 30-day view now shows 30-day costliest sessions
- Friendly project names: show directory name instead of full path, Home for ~
- Session cache TTL bumped to 180s to prevent re-parsing on non-today periods
- Audit fixes: array mutation, percentage rounding, ForEach ID collision,
baseline minimum threshold, editTurns scoping, first-of-month edge case
* Fix model badge ForEach ID collision and budget warning provider filter
- SessionDetailsList model badges used id: \.name which silently drops
duplicate model entries. Switched to enumerated offset-based ID.
- Hero budget warning compared against provider-filtered payload instead
of todayPayload (all providers). Now matches the menubar flame tint.
* Add CodeBurn Pro Mac App Store app
SwiftUI MenuBarExtra with litellm-snapshot pricing, Claude/Codex/Copilot
parsers, session discovery, auto-refresh timer, and dashboard UI matching
the real menubar design.
* Add appstore/ to .gitignore
Private Mac App Store build, not for the public repo.
* Fix OpenCode/Goose returning 0 sessions on fresh install
SQLite-based providers use compound paths (db.path:sessionId) which
caused fingerprintFile to fail stat() and silently skip all sessions.
Fall back to stat on the DB file when the full compound path fails.
Fixes#346
The fallback daily history path (used when rangeStartStr = todayStr)
was missing cache-based historical data, showing only today's bar in
the trend chart. Now includes allCacheDays filtered by provider.
Per-provider menubar calls now use loadDailyCache() instead of hydrateCache(),
splitting history into cache-based and fallback paths. Fixes session cache wipe
when scanProjectDirs/parseProviderSources receive empty dirs. Strips _dirty flag
before session cache serialization to prevent unnecessary 132MB rewrites. Removes
dead keychain code from both credential stores.
Credential cache: switched from keychain to file-based storage under
Application Support. Ad-hoc signed builds invalidate keychain ACLs on
every rebuild, causing repeated macOS password prompts. Existing
keychain entries are migrated to file on first read, then deleted.
Per-provider menubar: the Codex/Claude/etc tabs previously re-parsed
all sessions from scratch (22s). Now parses only today with the
provider filter and uses the daily cache for historical days, matching
the fast path the All tab already uses.
Daily cache bumped to v7 to force a clean rebuild after pricing and
provider changes since v6.
When the user clicks Load Quota from dormant state after a clean
reinstall, the cached keychain item may not exist. Using the refresh
path triggered a macOS keychain prompt because it assumed the cache
was present. Now calls bootstrapSubscription/bootstrapCodex which
properly re-reads credentials from source.
Adds a `dormant` state to SubscriptionLoadState so the menubar never
prompts for keychain permission on launch. Users must navigate to the
plan tab and click "Load Quota" / "Connect" to trigger credential access.
Also fixes a flaky TZ-boundary test (cli-status-menubar) by widening the
time offset to avoid generating timestamps in the future at UTC hour 0.
GUI-launched apps inherit a minimal PATH that misses user-managed Node
directories. Scan ~/.volta/bin, ~/.npm-global/bin, ~/.asdf/shims, and
the newest nvm Node version at launch. All candidates pass isSafe()
validation. Closes#331.
Cache normalized turns/calls to ~/.cache/codeburn/session-cache.json so
the CLI skips re-parsing unchanged JSONL files on subsequent runs.
File reconciliation uses dev+ino+mtime+size fingerprinting; cost,
classification, and summaries are recomputed at query time. Atomic
writes via temp+fsync+rename, deep structural validation on load,
per-provider env fingerprinting, and best-effort save so cache failures
never break the CLI. ~6x speedup on warm cache.
Three-layer fix for V8 heap exhaustion when parsing heavy session data:
1. Buffer-based readSessionLines (fs-utils.ts): Replace readline with raw
Buffer streaming using Buffer.indexOf(0x0a). Eliminates ConsString trees
that caused OOM when regex-flattening 100MB+ lines. Two-state machine
(ACCUMULATING/SCANNING) skips old lines at ~2KB cost instead of 200MB.
2. Large-line streaming parser (parser.ts): Hand-written JSON scanner for
lines >32KB extracts only cost/token/tool fields without JSON.parse,
avoiding full object graph allocation. Dual string/Buffer paths.
3. Dashboard memory management (dashboard.tsx): Disable auto-refresh for
heavy periods (30d/month/all), clear old dataset before reload via
nextTick to allow GC, prevent overlapping reloads with mutex, lazy
optimize scanning on keypress instead of useEffect.
Also fixes three race conditions in dashboard reload deduplication:
- Early return after nextTick bypassing finally block (permanent mutex lock)
- A->B->A period switching dropping final reload (stale pending)
- Stale pendingReloadRef not cleared when in-flight matches request
package-lock.json was stale at 0.9.7 with engines >=22; now matches
package.json 0.9.9 / >=22.13.0.
The menubar-json CLI test used hardcoded 10:00/11:00 UTC timestamps
which fall in the "future" when the test runs before those hours,
causing the menubar's todayRange (start..now) to exclude them.
Use timestamps relative to now instead.
All Unreleased items (IBM Bob, cache write pricing, OpenCode MCP,
project names, Cursor bubbles) are already on main, so they belong
under 0.9.9. Replaced the narrow status-only hydrateCache bullet
with the broader 9-command removal.
Only `status --format menubar-json` uses getDaysInRange from the hydrated
cache. The other 9 call sites (report, today, month, export, optimize,
compare, models, yield) parse their own date ranges directly via
parseAllSessions. Removing hydrateCache from these paths avoids a 365-day
backfill parse that was the primary OOM multiplier on large session dirs.
Strip heavy fields from JournalEntry immediately after JSON.parse in the
JSONL hot loop. Keeps only what downstream consumers need: type, timestamp,
sessionId, cwd, compacted user text (2000 char total cap), assistant
model/usage/id, tool_use names with Skill and Bash inputs, and MCP
inventory attachments. Text, thinking, and tool_result blocks are dropped.
Also removes redundant hydrateCache() from status --format json and
terminal status paths, and clears the session cache between period
parses to avoid pinning both today and month result sets.
This is a mitigation, not a full fix. Very large month ranges still
materialize full ProjectSummary.turns arrays. The real fix is the
streaming single-pass parser refactor.
Two related bugs in the macOS menubar `AgentTabStrip`:
1. With more detected providers than fit at the default 360pt popover
width (~7+), the off-screen provider chips were unreachable. SwiftUI's
horizontal `ScrollView` does not scroll from click-drag, and there
was no other affordance to reveal the hidden tabs.
2. Independent mouse wheels could not scroll the horizontal strip.
Standard wheels emit only vertical `deltaY` with
`hasPreciseScrollingDeltas == false`, and a horizontal SwiftUI
`ScrollView` ignores vertical-only deltas. Trackpads (which emit
horizontal deltas natively) already worked.
Fix:
- Wrap the strip in `ScrollViewReader` and add overflow-aware
left/right chevron buttons that programmatically scroll to the
next/previous visible provider via `proxy.scrollTo(_, anchor: .center)`.
Buttons only appear when `stripContentWidth > stripViewportWidth - 30`
and disable at the ends.
- Install an `NSEvent.addLocalMonitorForEvents(matching: .scrollWheel)`
in `.onAppear` (removed in `.onDisappear`). When the cursor is over
the strip and the event is non-precise (`!hasPreciseScrollingDeltas`)
with `deltaX≈0` and `deltaY≠0`, copy the `CGEvent` and transpose
`scrollWheelEventDeltaAxis1` / `PointDeltaAxis1` / `FixedPtDeltaAxis1`
onto axis 2 so the underlying NSScrollView receives a real horizontal
delta.
- Track strip hover via a `@MainActor` singleton
`AgentTabStripScrollState` so the local-monitor closure can read the
latest hover status without capturing stale SwiftUI state.
Trackpad events are passed through untouched, so vertical scrolling
elsewhere in the popover is unaffected.
Co-authored-by: Cursor <cursoragent@cursor.com>
Timestamps with Z are interpreted as UTC, causing date bucketing tests
to fail in non-UTC timezones (e.g. UTC+12 shifts Apr 9 10:00Z to Apr 8).
Local timestamps without Z are interpreted in the runtime timezone,
matching how the aggregator actually buckets dates.
Based on #112 by @lfl1337, extended to cover all affected timestamps.
Bubble rows without createdAt were defaulting to new Date(), which
misattributed historical or undated usage to Today and inflated the
daily chart. Now filtered at the SQL level and skipped in application
code.
Based on the bubble-side fix from #262 by @darthrevanyunka.
* Fix mangled project paths in By Project and Top Sessions panels
shortProject() decoded Claude Code slugs by splitting on '-', which
broke directory names containing dashes ('foo-bar' became 'foo/bar').
Switch the dashboard to consume ProjectSummary.projectPath (the
canonical cwd already extracted by parser.ts) and rewrite shortProject
to operate on a real absolute path.
* shortProject: cache homedir, normalize Windows backslashes, fix stale test helper
---------
Co-authored-by: Abdallah Meghraoui <abdallah.meghraoui@outlook.com>
Split CLI into a tiny launcher (src/cli.ts) that checks for Node >= 22.13.0
before dynamically importing the full CLI (src/main.ts). Users on Node 18
now get a clear upgrade message instead of a cryptic regex parse error from
string-width. Closes#232.