Two pre-existing type errors surfaced during the rebase against main:
1. JsonPlanSummary.id was hardcoded to four plan ids, but PlanId now
includes 'none' (PLAN_IDS was extended when 'codeburn plan clear'
was added). toJsonPlanSummary only runs for active plans at runtime,
but the static type still had to be widened. Use PlanId directly
instead of the hand-rolled union.
2. isActivePlan used Boolean(plan) as the nullish guard, which doesn't
narrow plan's type in TypeScript. Switch to an explicit
'plan !== undefined' so the subsequent .id and .monthlyUsd accesses
type-check.
npx tsc --noEmit is now clean; all 285 tests still pass.
Address review feedback on #74:
1. TUI plan row previously used the active tab's filtered projects as
plan spend, so 'Today' showed today's cost as plan spent. Switch
renderDashboard and reloadData to getPlanUsageOrNull(), which uses
the plan's own billing period regardless of tab.
2. Plan row rendered via a local formatUsd that hardcoded USD. Replace
every call with formatCost so 'codeburn currency EUR' flows through.
Removes the adjacent '$3,425.52' vs '$32.07' style mismatch.
3. renderPlanBar capped filled width at 100%, so 105% and 1700% looked
identical. Past 100%, render a full bar plus chevron tail sized by
order of magnitude (log10): 1.05x -> 1 chevron, 17x -> 2, 170x -> 3.
4. 'running on API overage pricing' is wrong for Claude Pro/Max (rate
limited, not charged overage). Drop that claim; keep the Nx-over
multiplier and match the under/near projection line structure.
5. Spell out 'equiv' as 'API-equivalent' in the plan label.
Dead code cleanup: getPlanUsageOrNullForProjects is now unused; remove
it. getPlanUsageFromProjects stays (unit tests still use it).
Adds `codeburn plan set <id>` to configure a subscription plan (Claude Pro,
Claude Max, Cursor Pro, or custom). When set, the Overview panel renders
an API-equivalent progress bar against subscription price with a
projected month-end cost.
Closes the loudest demand signal on the repo: issue #11 ("Subscription
vs API Use") from two independent voices, plus the routing-decision use
case raised in #12.
- src/config.ts: extends CodeburnConfig with Plan, adds readPlan/savePlan/clearPlan
- src/plans.ts: presets (claude-pro $20, claude-max $200, cursor-pro $20)
- src/plan-usage.ts: getPlanUsage, resetDay-aware period math (1-28),
median-of-7-day-trailing projection
- src/cli.ts: `codeburn plan [show|set|reset]` subcommand, plan wired
into JSON outputs for report/today/month/status (only when active)
- src/dashboard.tsx: Plan row in Overview, color-coded (green under 80%,
orange near, red over), with days-until-reset
- README.md: Plans section with honest framing (API-equivalent vs
subscription price, not token allowance)
- tests/plan-usage.test.ts, tests/plans.test.ts, tests/cli-plan.test.ts:
period math, presets, CLI round-trip
Resets respect resetDay across month boundaries. Uses median daily spend
(not mean) so one huge day doesn't distort the month-end projection.
Fixes#11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove bidirectional fuzzy match in getModelCosts that could return
wrong pricing when a short canonical name prefix-matched a longer key
- Use explicit undefined check in parseLiteLLMEntry so free models with
zero cost are not silently dropped from the LiteLLM pricing database
- Destroy read stream in finally block of readSessionLines to prevent
file descriptor leaks when the generator is abandoned early
- Extend CSV injection escaping to cover tab and carriage-return prefixes
- Add optional chaining fallback for empty periods in exportCsv/exportJson
- Add regression tests for all fixes (models, export, fs-utils)
Period switching no longer resets optimize or compare views back to
the dashboard. Auto-refresh keeps the current screen. Arrow keys
now work in all views. Added period switch hints to compare status bar.
Closes#107
reloadData() was clearing optimizeResult before fetching, which
caused the optimize screen to flash back to the dashboard on every
30s refresh cycle. Let the projects useEffect re-scan naturally.
Menubar: reduce cache TTL from 300s to 30s, background refresh from
60s to 15s, always fetch fresh data on tab switch instead of serving
stale cache. TUI: default auto-refresh to 30s (--refresh 0 to disable).
Closes#107
5-section compare view: Performance (one-shot, retry, self-correction),
Efficiency (cost/call, cost/edit, output/call, cache hit), Category
Head-to-Head bar charts, Working Style, and Context.
Planning rate now detects TaskCreate/TaskUpdate/TodoWrite instead of
only EnterPlanMode (which was never used, showing 0% for all models).
Validated against raw JSONL with zero false positives.
Responsive side-by-side layout at 90+ cols. Self-correction scanner
with compact file skipping and model+timestamp dedup. 274 tests.
Remove high-false-positive patterns (I'm sorry, I should have, sorry for).
Add precise patterns (you're right I, that was incorrect, let me correct).
Skip compact JSONL files that replay compressed context.
Deduplicate by model+timestamp to prevent double-counting.
Fix test timestamps to work with deduplication.
Period changes (arrows, 1-5) now update comparison results in place
instead of returning to dashboard. Status bar hidden in compare view
to reduce clutter.
Self-correction scanner was only reading JSONL files inside session
subdirectories, missing the main session transcripts stored at the
project level. Also adds bordered box to results and widens winner
column for readability.
Adds scanSelfCorrections() which reads raw .jsonl session files (including subagent dirs) and counts per-model self-correction patterns for use in the model comparison metrics.
The gapStart date was constructed with T00:00:00.000Z (UTC midnight),
causing it to land hours before local midnight. In PDT this meant
the gap fill re-parsed a partial slice of the previous day, and the
upsert replaced the full day with that partial data, losing cost.
Bump DAILY_CACHE_VERSION to 3 to force cache rebuild.
Timestamps in session files are UTC ISO strings. Several code paths
extracted the date via .slice(0, 10) which gives the UTC date, while
date range filtering uses local-time boundaries. This caused turns
between UTC midnight and local midnight to be bucketed under the wrong
day -- the menubar showed lower today cost than the TUI because those
turns were attributed to tomorrow (UTC) but filtered as today (local).
format.ts already had a localDateString fix; this applies the same
pattern everywhere via dateKey() in day-aggregator.ts.
The daily cache never re-processed yesterday once cached, so a mid-day
run would freeze partial cost/call data permanently. The "All" provider
path in menubar-json relied on this cache, causing the menubar to show
wildly incorrect numbers while per-provider views (which parse fresh)
were correct. Now yesterday is evicted and recomputed on every run, and
addNewDays upserts instead of skipping duplicates as defense-in-depth.
Replace eval-based require with createRequire(import.meta.url) so the SQLite driver loads correctly when the CLI runs as ESM.
This restores OpenCode and Cursor session discovery instead of returning empty results when require is unavailable.
Add package.json repository/bugs/homepage fields. Swap hardcoded
AgentSeal/codeburn URLs to getagentseal/codeburn across README,
mac README, macOS menubar star banner, and the menubar installer's
release-API endpoint. 301 redirects keep old URLs working, but
canonical links now point at the current org.
Co-authored-by: AgentSeal <hello@agentseal.org>
npm was warning on every install that prebuild-install@7.1.3 is no
longer maintained. prebuild-install ships as a transitive dependency
of better-sqlite3 and upstream PR #1446 to replace it is still open,
so we switch to Node's built-in node:sqlite module (stable in Node 24,
experimental in Node 22/23) and remove the better-sqlite3 dep entirely.
- src/sqlite.ts: uses DatabaseSync from node:sqlite. The one-shot
ExperimentalWarning about SQLite on Node 22/23 is silenced for that
specific warning; other warnings pass through unchanged.
- package.json: engines.node bumped to >=22 (Node 20 EOL 2026-04-30),
better-sqlite3 and @types/better-sqlite3 removed, @types/node added
(it was coming in transitively via @types/better-sqlite3).
- tests/providers/opencode.test.ts: fixture DB creation switched to
node:sqlite (API parity for the CREATE TABLE + INSERT + prepare
path we use).
End-user install footprint shrinks from 167 to 40 packages and prints
zero deprecation warnings.
Credit: @primeminister for the report.
renderStatusBar computed `today` via `new Date().toISOString().slice(0,10)`,
which is the UTC date. Session timestamps are also UTC ISO strings, but
the user's expectation of "today" is their wall-clock day. During the
window between local midnight and UTC midnight (e.g. 17:00 PDT on
2026-04-17, which is already 00:00 UTC on 2026-04-18), every session
bucketed under local April 17 missed the UTC-April-18 filter and the
status bar read `Today $0.00 0 calls` even while `--format json`
and the menubar app correctly showed the spend.
Both sides of the comparison now use the local date of each session
timestamp, so the terminal status and the JSON / menubar paths agree.
Verified at UTC midnight (the regression moment that surfaced the bug):
Before: Today $0.0000 0 calls
After: Today $339.87 1839 calls
Caught during the fresh-clone review of the menubar PR.