Commit graph

149 commits

Author SHA1 Message Date
iamtoruk
81b5cda173 feat: add MiniMax-M2.7 and MiniMax-M2.7-highspeed model pricing
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
2026-04-21 05:50:52 -07:00
iamtoruk
b491a1f590 fix: bucket turns by assistant timestamp, filter at turn level
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.
2026-04-21 04:40:44 -07:00
iamtoruk
68e9c63088 fix(cursor-agent): drop unused SessionSource fields reintroduced by revert
cursor-agent was authored on top of the Sharada cache rewrite and referenced
fingerprintPath, cacheStrategy, progressLabel, and parserVersion. With the
persistent source cache reverted, these fields no longer exist on SessionSource.
Strip the references; cursor-agent continues to work on the v0.8.1 discover +
parse path like every other provider.
2026-04-21 04:23:20 -07:00
iamtoruk
0725fe2fbb fix(cursor-agent): preserve raw model name for unknown Cursor models
The fallback path in modelDisplayName returned "Auto (Sonnet est.) (est.)"
for any model not listed in modelDisplayNames, double-tagging the est.
suffix and hiding the real model ID. New Cursor model IDs now surface as
their raw name with a single (est.) suffix until the display map is
updated. Adds a regression test.
2026-04-21 04:21:06 -07:00
Matt Van Horn
620ca32219 feat(cursor-agent): add provider for cursor-agent CLI sessions
Discovers transcripts at ~/.cursor/projects/*/agent-transcripts/*.txt
and joins against ~/.cursor/ai-tracking/ai-code-tracking.db for model
attribution. Token counts are estimated from transcript character
length since the attribution DB does not carry them; the model label
surfaces the estimation with an (est.) suffix on every row.

Deduplication keys prefix cursor-agent: to stay disjoint from the
existing cursor: prefix so the two providers do not cross-dedupe
on shared conversationId namespaces.

Tests cover: empty ~/.cursor/projects/, single transcript, multiple
projects, missing ai-code-tracking.db, unrecognized transcript format
skip, non-UUID filename fallback, and sqlite metadata join.

Closes #55
2026-04-21 04:21:01 -07:00
iamtoruk
0803005083 fix(config): restore catch-all in readConfig to prevent CLI crash on malformed config 2026-04-21 04:20:55 -07:00
Trevin Chow
2c43ec1ad0 fix(plan): resolve type errors in plan summary and isActivePlan guard
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.
2026-04-21 04:20:55 -07:00
Trevin Chow
cb4c3ee305 fix(plan): scope TUI plan row to billing period, use currency-aware formatting
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).
2026-04-21 04:20:54 -07:00
Trevin Chow
3f7470d29b feat(plan): subscription plan tracking with usage progress bar
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
2026-04-21 04:20:50 -07:00
iamtoruk
8e39a89fe0 fix: pricing accuracy, stream leak, CSV injection hardening
- 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)
2026-04-21 04:20:46 -07:00
iamtoruk
95bcd60aba fix: preserve view on period switch and auto-refresh
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
2026-04-19 13:34:30 -07:00
iamtoruk
19b4513400 fix: auto-refresh no longer resets optimize view
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.
2026-04-19 13:16:51 -07:00
iamtoruk
fc576f44ba fix: real-time refresh for menubar and TUI dashboard
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
2026-04-19 08:55:48 -07:00
iamtoruk
bd43b15342 feat(compare): model comparison with planning rate fix
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.
2026-04-19 08:34:49 -07:00
iamtoruk
fb24eea186 fix(compare): refine self-correction patterns, skip compact files, deduplicate
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.
2026-04-19 07:14:02 -07:00
iamtoruk
d04159a056 fix(compare): remove winner column, green highlight is sufficient 2026-04-19 06:52:50 -07:00
iamtoruk
27a3ddd7f8 fix(compare): strip date suffixes from model names for cleaner display 2026-04-19 06:51:26 -07:00
iamtoruk
2a9ecab05c feat(compare): show self-correction counts in context section 2026-04-19 06:47:43 -07:00
iamtoruk
e0d8ecddd9 fix(compare): show compare-styled loading screen during period switch 2026-04-19 06:45:47 -07:00
iamtoruk
73ae1c3786 feat(compare): period switching in compare view, hide status bar
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.
2026-04-19 06:43:27 -07:00
iamtoruk
f43ef70922 fix(compare): hide provider indicator and shortcut in compare view 2026-04-19 06:29:58 -07:00
iamtoruk
b285320063 fix(compare): wrap all screens in bordered boxes to match dashboard UI 2026-04-19 06:28:55 -07:00
iamtoruk
d52a55afb4 fix(compare): scan project-level JSONL files, improve results layout
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.
2026-04-19 06:25:47 -07:00
iamtoruk
d3864914a9 fix(compare): extract magic numbers, fix React state mutation 2026-04-19 05:46:11 -07:00
iamtoruk
a303fc7174 feat(compare): integrate into dashboard with c shortcut 2026-04-19 05:41:10 -07:00
iamtoruk
e89706b549 feat(compare): add codeburn compare command 2026-04-19 05:37:34 -07:00
iamtoruk
f67cdd2e45 feat(compare): add ModelSelector, ComparisonResults, and CompareView components 2026-04-19 05:31:44 -07:00
iamtoruk
3cb9a7a7bc feat(compare): add self-correction JSONL scanner
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.
2026-04-19 05:25:31 -07:00
iamtoruk
ac9afffed5 feat(compare): add computeComparison with normalized metrics 2026-04-19 05:22:34 -07:00
iamtoruk
9d119bfe40 feat(compare): add ModelStats type and aggregateModelStats 2026-04-19 05:20:37 -07:00
iamtoruk
e3395d241f Fix daily cache gap fill using UTC instead of local time
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.
2026-04-19 04:23:17 -07:00
iamtoruk
72ccf34a5a fix: use local timezone for daily date bucketing instead of UTC
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.
2026-04-19 03:18:38 -07:00
iamtoruk
888030fce3 fix: recompute yesterday in daily cache to prevent stale menubar data
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.
2026-04-19 03:07:54 -07:00
AgentSeal
11b3de89e4
fix(sqlite): load node:sqlite in ESM runtime
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.
2026-04-19 05:27:05 +00:00
Ninym
c634b10560
feat(report): add --from/--to date range filtering and avgCostPerSession (#80)
* test(cli): failing tests for parseDateRangeFlags helper

* feat(cli): add parseDateRangeFlags helper with local-time dates

* feat(report): add --from/--to date range filtering

* feat(report): add avgCostPerSession to JSON report and CSV/JSON export
2026-04-18 15:11:33 -07:00
Ninym
5932a273a1
chore(ci): add semgrep guard against prototype pollution regressions in provider hot paths (#78)
* chore(ci): add semgrep rule no-bracket-assign-on-literal-object-map

* chore(ci): add workflow running semgrep bracket-assign guard on push/PR

* fix(parser): use Object.create(null) for categoryBreakdown map

* chore(ci): expand semgrep rule to cover ||, ??=, and if-guard variants

* chore(ci): limit push trigger to main and add semgrep --strict

* chore(ci): use jq to enforce finding count (--error unreliable in semgrep 1.x)
2026-04-18 15:10:24 -07:00
AgentSeal
a031c8d32d
chore: point repo URLs at getagentseal org (#97)
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>
2026-04-18 14:55:44 -07:00
AgentSeal
7aefd674fc fix: drop better-sqlite3 to remove deprecated prebuild-install (#75)
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.
2026-04-18 01:26:23 -07:00
Resham Joshi
03f12ce81f fix(status): bucket Today/Month by local date, not UTC
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.
2026-04-17 17:05:08 -07:00
Resham Joshi
495a254338 feat(mac): native Swift menubar app + one-command install
Introduces mac/ with a native SwiftUI menubar app that replaces the
previous SwiftBar plugin entirely. Install via `npx codeburn menubar`,
which downloads the .app from GitHub Releases, strips Gatekeeper
quarantine, and drops it into ~/Applications.

Highlights

- mac/ SwiftUI app: agent tabs, Today/7/30/Month/All period switcher,
  Trend/Forecast/Pulse/Stats/Plan insights, activity + model
  breakdowns, optimize findings, CSV/JSON export, Star-on-GitHub
  banner, live 60s refresh, instant currency switching with offline FX
  cache.
- Security: CodeburnCLI argv-based spawn (no shell interpretation),
  SafeFile symlink guards + O_NOFOLLOW writes, FX rate clamping to
  [0.0001, 1_000_000], keychain filtered to account == "default",
  removed byte-window credential log, in-flight refresh guard, POSIX
  flock on config.json writes, TerminalLauncher validates argv before
  AppleScript interpolation.
- Performance: shared static NumberFormatter (thousands of allocations
  per popover redraw eliminated), concurrent pipe drain with 20 MB cap
  + 60s timeout in DataClient, Observation-tracked reactive UI, 5-min
  payload cache keyed on (period, provider).
- CLI: new `codeburn menubar` subcommand that downloads + installs +
  launches the .app (no clone, no build). New `status --format
  menubar-json` payload builder. `export` rewritten to produce a
  folder of one-table-per-file CSVs with a `.codeburn-export` marker
  so arbitrary -o paths cannot be silently deleted.
- Removed: src/menubar.ts (SwiftBar plugin generator),
  install-menubar / uninstall-menubar subcommands, `status --format
  menubar` directive output, tests/menubar.test.ts,
  tests/security/menubar-injection.test.ts.
- Release: .github/workflows/release-menubar.yml builds universal
  binary, assembles .app, ad-hoc signs, zips, uploads on mac-v* tag
  push. Runs on the free macos-latest runner.

Tests

- 230 TypeScript tests pass
- 10 Swift CapacityEstimator tests pass
- TypeScript typecheck clean
- Swift release build clean
2026-04-17 16:55:56 -07:00
AgentSeal
41c84b1e51
Merge pull request #66 from jeisaacs/fix/menubar-node-version-path
fix: prepend install-time node bin dir to menubar plugin PATH
2026-04-17 14:21:58 +02:00
AgentSeal
77257bcb89
Merge pull request #68 from lfl1337/fix/remove-claudeignore-references
docs(optimize): remove references to .claudeignore (#61)
2026-04-17 14:20:50 +02:00
Ninym
8f5927153e feat(cli): add --verbose flag for stderr warnings
Sets CODEBURN_VERBOSE=1 via commander preAction, which the fs-utils
helpers check before emitting stderr lines on skipped or failed reads.
Closes LOW-1 from the 2026-04-16 audit.
2026-04-17 08:32:20 +02:00
Ninym
646635c262 fix(menubar): sanitize SwiftBar labels via allowlist
Replaces any character outside [A-Za-z0-9 ._/-] with ? in model and
category labels and truncates to 14 chars before padEnd. Closes the
MEDIUM-2 finding from the 2026-04-16 audit: an attacker-controlled
JSONL with a crafted model name no longer injects SwiftBar directives
or ANSI escapes.
2026-04-17 08:32:20 +02:00
Ninym
216782391a fix(optimize): use bounded read helpers
All four read paths in the optimizer (async session scan + three sync
config/import/profile scans) now pass through the 128 MB-capped
helpers. JSON.parse in readJsonFile stays wrapped in try/catch.
MEDIUM-1 coverage for the optimize module.
2026-04-17 08:32:20 +02:00
Ninym
1bdbac4927 fix(context-budget): use bounded readSessionFile helper
Config JSON, CLAUDE.md scans, and session-discovery reads now pass
through the 128 MB-capped helper. JSON.parse remains wrapped in
try/catch to preserve the previous 'null on malformed JSON' contract.
MEDIUM-1 coverage for the context-budget module.
2026-04-17 08:32:19 +02:00
Ninym
716e080cb3 fix(pi): use bounded readSessionFile helper
Both Pi session read paths (first-entry meta and full-session parse)
now pass through the 128 MB-capped helper. MEDIUM-1 coverage for the
Pi provider.
2026-04-17 08:32:19 +02:00
Ninym
9f6827d528 fix(copilot): use bounded readSessionFile helper
Events JSONL and workspace.yaml reads now pass through the 128 MB-capped
helper. The workspace.yaml path stays non-fatal: a null read skips cwd
derivation but still pushes the session with sessionId as the fallback
project label. MEDIUM-1 coverage for the Copilot provider.
2026-04-17 08:32:19 +02:00
Ninym
1de0baf329 fix(codex): use bounded readSessionFile helper
Both Codex session read paths (first-line meta and full-session parse)
now pass through the 128 MB-capped helper. MEDIUM-1 coverage for the
Codex provider.
2026-04-17 08:32:19 +02:00
Ninym
ee738a1b26 fix(parser): use bounded readSessionFile helper
Replaces the unbounded readFile in parseSessionFile with the 128 MB-capped
helper from src/fs-utils. Addresses MEDIUM-1 for the Claude provider
hot path.

Verbose-mode stderr output replaces the previous silent catch,
closing LOW-1 as a side effect.
2026-04-17 08:32:19 +02:00