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
The PR #59 merge landed on main (ac31883) before the 0.8.7 bump
(cb44cc6), so the code shipped in 0.8.7 but the initial changelog entry
only credited MiniMax and the menubar fixes. Adds three entries under
Added for the OMP provider, the model-alias command, and the built-in
Anthropic proxy aliases. Adds one entry under Fixed (CLI) for the
prototype-pollution guard in resolveAlias. Updates the CLI parity note
so users on 0.8.5 know whether they can stay there.
Filed before 0.8.7 hits npm.
Skips 0.8.6 to match mac-v0.8.7 and keep CLI + menubar versions aligned.
CLI change: MiniMax-M2.7 and MiniMax-M2.7-highspeed pricing. macOS menubar
change: three stability fixes culminating in the App Nap opt-out that keeps
the refresh loop ticking while the icon is idle in the background.
Confirmed in the system log: while the menubar icon sits idle in the
background, macOS flips _kLSApplicationWouldBeTerminatedByTALKey to 1,
which is the Automatic Termination and App Nap subsystem declaring the
app a suspend candidate. Once that happens the 15s refresh Task's sleep
stretches arbitrarily, so the status bar label freezes until the user
clicks the icon (which triggers NSApp.activate and wakes everything up).
Hold a ProcessInfo.beginActivity handle for the life of the app with
.userInitiated + .automaticTerminationDisabled + .suddenTerminationDisabled
so macOS leaves the app alone. Released implicitly when the app exits.
Adds Oh My Pi support by parameterizing the Pi JSONL reader to accept a
providerName, so sessions at ~/.omp/agent/sessions/ are discovered and
tracked alongside Pi. Ships a model-alias CLI command plus five
built-in aliases for the anthropic--claude-X.Y-tier double-dash format
that some Anthropic-compatible proxies emit, so cost rows no longer
read $0.00 for those names.
Contributed by @cgrossde.
Second merge of main since the PR was opened. Main moved 30+ commits
(0.8.5 bump, plan tracking feature, MiniMax pricing, menubar
prefetchAll walk-back, aicrowd cache rewrite revert) so the branch
needed another reconciliation before merging to main.
Two new conflicts resolved. Took main's text in both cases per the
policy of favoring main when the feature work is neutral:
README.md Kept main's Node 20+ / better-sqlite3
Requirements wording and main's shorter src/
tree listing. Added OMP to the Requirements
line.
src/providers/pi.ts Main dropped the discovery-cache snapshot and
the rich source-metadata fields as part of the
aicrowd revert. Took main's simpler structure
and only kept the providerName parameter so
OMP sources still report the correct provider
in the session source and dedup key.
Earlier fixups carried forward from the prior merge commit:
- Object.hasOwn guards in resolveAlias against prototype-pollution
via a model literally named '__proto__'.
- source.provider in the dedup key prefix so OMP rows no longer
stamp 'pi:'.
- Combined pi.js imports in providers/index.ts.
- Trailing newline on pi.ts.
- Unknown-model fallback in cursor-agent.ts from yesterday's PR #117
fixup (preserved via main).
353 tests pass (count dropped from 378 because main deleted the
parse-progress / parser-cache / provider-colors / source-cache test
files alongside the cache-rewrite revert).
Feature work by @cgrossde.
Two independent causes for the stuck-label / only-refreshes-on-click
behaviour, both fixed here.
1. NSStatusItem button defers the status bar paint for accessory apps
that are not foreground, so after refreshStatusButton sets the new
attributed title the menu bar visually froze until the user opened
the popover (which triggers NSApp.activate and a forced redraw
cycle). Explicit needsDisplay + display() forces the paint every
cycle.
2. The codeburn subprocess inherited the accessory app's default QoS,
which macOS background-throttles. That could stretch a sub-1-second
parse into tens of seconds on large corpora and overrun the 15s
refresh cadence. Set .userInitiated so the CLI runs at the same
priority it does from a user-interactive terminal.
The detached prefetch spawned four codeburn subprocesses (week, 30days,
month, all) that competed with the main refresh loop's Today fetch for
disk and parser time. On large session corpora that starved the Today
refresh for minutes at a time, and the status label drifted stale until
the prefetch queue finally drained. Users perceived this as the menubar
"stopping" until they clicked a provider tab, which coincided with the
queue clearing.
Drop prefetchAll. Period tabs in the popover now fetch lazily on first
click (3-10 seconds once per app lifetime per period), same as they did
in 0.8.1. Today's label stays fresh because nothing else is hammering
the parser in the background.
Adds pr-number input and workflow_dispatch trigger so existing open PRs
can be scanned without waiting for a new push. Leave pr-number empty to
loop all open PRs; supply a number to rescan one.
Runs on every PR open/reopen/synchronize against getagentseal/firstlook
and fails the check when the author's score is 'unknown' (new or
untracked accounts). Skips bot accounts so dependabot and renovate pass
through.
This screens the same drive-by pattern that landed in PR #118 (octo-patch,
fresh automation account) without requiring a manual tier check on every
submission.
Adds FALLBACK_PRICING entries plus display names so MiniMax sessions
show up with the right cost and readable labels when users route MiniMax
through providers like OpenCode. Pricing verified against the live
MiniMax paygo page:
MiniMax-M2.7 input $0.3/M output $1.2/M cache-read $0.06/M cache-write $0.375/M
MiniMax-M2.7-highspeed input $0.6/M output $2.4/M cache-read $0.06/M cache-write $0.375/M
prefetchAll was awaited inside the refresh loop after the first cycle.
On large corpora the All Time parse takes 60+ seconds and kept the Today
refresh blocked for that whole window, so the status label got stuck for
minutes at a time while the CLI was returning fresh numbers.
Detach prefetchAll into its own @MainActor task. The popover still gets
the benefit of pre-warmed period caches, the refresh loop ticks every 15
seconds regardless.
Also adds a regression test for the midnight-straddle bucketing invariant
that was flagged by the pre-push review: if someone reverts the assistant-
timestamp bucketing back to user-timestamp, this test will catch it.
Reverts the persistent source cache added in 0.8.2 back to v0.8.1's
full-reparse path for Claude sessions; keeps the plan pill, pricing fix,
cursor-agent provider, and the Mac menubar prefetch+timezone work added
between 0.8.2 and 0.8.4.
A turn that straddles midnight (user typed at 23:58, assistant responded
at 00:30) was bucketed and filtered inconsistently across call sites.
parseSessionFile filtered entries by timestamp, producing orphan assistant
calls that groupIntoTurns pushed as turns with empty timestamp. Some
downstream code counted those (buildPeriodData summing project totals)
and other code dropped them (renderStatusBar's empty-timestamp skip).
The menubar showed today = $32 while the terminal status showed today = $27
for the same dataset; each was internally consistent but used a different
turn-bucket rule.
Fix both: parseSessionFile now builds all turns first, then filters each
turn by its first assistant call timestamp (the moment cost was incurred).
renderStatusBar buckets the same way. day-aggregator.ts already bucketed
on assistant time, so it is now consistent too.
Net effect: a turn is counted in the day the API call actually ran in.