Commit graph

183 commits

Author SHA1 Message Date
Resham Joshi
8c2fc4ffe8
Merge pull request #145 from getagentseal/fix-cursor-provider-agentKv
Fix Cursor provider for newer versions
2026-04-24 11:06:49 -07:00
AgentSeal
ec10ad1b67 Fix Codex model extraction from turn_context entries 2026-04-24 19:42:21 +02:00
AgentSeal
e1609e495c Add GPT-5.2 Low model display name for Cursor 2026-04-24 19:23:54 +02:00
AgentSeal
681afdbece Fix Cursor provider for newer versions that store data in agentKv
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 #114
Closes #102
2026-04-24 18:11:25 +02:00
AgentSeal
25a71bf016 Add claude-max-5x preset for $100/month tier
Closes #141
2026-04-24 15:47:59 +02:00
AgentSeal
2e5e449ad1 fix: use consistent date range for menubar status queries
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.
2026-04-22 21:28:12 +02:00
Łukasz Majcher
5e49f17e64 fix: switch scanJsonlFile and parseSessionFile to readSessionLines to prevent OOM
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
2026-04-22 10:11:13 +00:00
iamtoruk
4f1138290e Merge main into feat/omp-support-model-aliases
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.
2026-04-21 11:51:20 -07:00
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
c2ab80d6e2 Merge main into feat/omp-support-model-aliases
Brings the PR branch up to the current main so the OMP provider and the
model-alias command can land cleanly. Resolves six merge conflicts and
applies a handful of small fixups alongside the resolution so the
feature matches the conventions set by the cursor-agent merge earlier
today.

Conflict resolutions:

  README.md               Combine cursor-agent and OMP rows in provider
                          list, Requirements, and data-location table;
                          take main's Node 22+ and node:sqlite text.
  src/cli.ts              Keep both new commands: model-alias and plan.
  src/config.ts           Add modelAliases alongside plan on the config
                          type.
  src/providers/index.ts  Keep the cursor-agent lazy-loader from main
                          and add omp to coreProviders. Fold the two
                          pi-module imports into one statement.
  src/providers/pi.ts     Keep the discovery-cache snapshot path from
                          main and the providerName parameterization
                          from the PR. Propagate providerName through
                          saveDiscoveryCache, loadDiscoveryCache, the
                          parserVersion tag, and the dedup key prefix
                          so OMP sources no longer stamp 'pi:' inside
                          their cache entries or dedup keys.
  tests/models.test.ts    Keep main's pricing-and-short-name tests and
                          add the PR's alias tests alongside, sharing a
                          single loadPricing setup and an afterEach
                          alias reset.

Fixups in the same commit:

  src/models.ts           Replace ?? chain in resolveAlias with
                          Object.hasOwn checks. The previous form
                          returned Object.prototype for a model named
                          '__proto__' and broke downstream
                          canonical.startsWith calls. Caught by the
                          existing prototype-pollution test suite.
  src/providers/pi.ts     Use source.provider in the dedup key prefix
                          and add a trailing newline to the file.
  tests/providers/omp.test.ts  Expect 'omp:' in the dedup key for OMP
                          sources, matching the fix above.

Feature work by @cgrossde.
2026-04-21 03:16:28 -07:00
iamtoruk
28c825ff15 Merge PR #117: cursor-agent provider
Some checks are pending
CI / semgrep (push) Waiting to run
Adds a cursor-agent provider that reads sessions from
~/.cursor/projects/*/agent-transcripts/*.txt and joins against the
ai-code-tracking.db for model attribution, closing issue #55. Token
counts are estimated from transcript character length since neither the
transcript nor the attribution DB carries real counts, and every row
carries an (est.) label to surface that.

Contributed by @mvanhorn.
2026-04-20 19:26:32 -07:00
iamtoruk
ed5512144a 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-20 19:20:15 -07:00
iamtoruk
bf17d20476 fix: remove noCache override for menubar-json format
Source cache fixes (empty-session guards, date range reordering) make
the cache safe for menubar use. Forcing noCache on every 15s poll was
re-parsing 5800+ files each time, causing the menubar to hang.
2026-04-20 18:25:43 -07:00
iamtoruk
b7ad5c5505 fix: source cache empty-session poisoning, TUI refresh, menubar stale data
Source cache entries with zero sessions now treated as cache misses instead
of serving stale empty data. Date range skip moved after fingerprint check
so changed files are never incorrectly excluded. TUI refresh timer bypasses
in-memory CachedWindow cache. Menubar-json forces noCache. Swift menubar
adds explicit refreshStatusButton calls to avoid observation race.
2026-04-20 18:07:37 -07:00
Matt Van Horn
554036d2a7
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-20 17:49:45 -07:00
iamtoruk
e18a1ef2f7 fix: tighten types, remove dead exports, prevent FD leak 2026-04-20 15:52:18 -07:00
Sharada Mohanty
7594fa0254 feat: optimize parse caching across providers 2026-04-21 00:07:07 +02:00
Sharada Mohanty
563f9c4f1b refactor: share provider presentation metadata 2026-04-21 00:04:29 +02:00
Sharada Mohanty
ff442c71f2 perf: cache provider discovery metadata 2026-04-21 00:03:49 +02:00
Sharada Mohanty
2a9daec0ea feat: add cache rebuild flag and progress 2026-04-21 00:03:49 +02:00
Sharada Mohanty
1b8e0f8289 fix: harden Claude append cache refresh 2026-04-21 00:01:46 +02:00
Sharada Mohanty
ad5366472a feat: cache Claude sources by session file 2026-04-21 00:01:46 +02:00
Sharada Mohanty
862be251e5 refactor: move providers onto shared cache metadata 2026-04-21 00:01:46 +02:00
Sharada Mohanty
303a9256c5 feat: reuse cached parsed sources 2026-04-21 00:01:46 +02:00
Sharada Mohanty
a2593ceb1e fix: harden source cache validation 2026-04-21 00:01:46 +02:00
Sharada Mohanty
ac5dd8c3e9 fix: tighten source cache validation 2026-04-21 00:01:46 +02:00
Sharada Mohanty
0d4d103627 fix: tighten source cache validation 2026-04-21 00:01:46 +02:00
Sharada Mohanty
a0bad07c19 feat: add persistent source cache storage 2026-04-21 00:01:46 +02:00
iamtoruk
e25922030d fix(config): restore catch-all in readConfig to prevent CLI crash on malformed config 2026-04-20 15:00:03 -07:00
Trevin Chow
c0d24cc191 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-20 14:55:07 -07:00
Trevin Chow
1af4d73da4 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-20 14:55:07 -07:00
Trevin Chow
553cf2d706 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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:55:07 -07:00
iamtoruk
a4d261a536 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-20 14:49:32 -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