Commit graph

263 commits

Author SHA1 Message Date
ozymandiashh
98bbe5b678 review: cap first-line read size and add edge-case tests
- Cap createReadStream at 1 MiB so a malformed file with no newline
  cannot make readline buffer indefinitely (real session_meta lines
  are 22-27 KB).
- Capture stream errors explicitly; readline's async iterator does
  not always re-throw underlying stream errors per Node docs.
- Test: assert project is extracted from the >16 KB session_meta to
  prove the line was actually parsed, not just discovered.
- Test: session_meta line with no trailing newline is still accepted.
- Test: empty rollout file is silently skipped.
2026-05-02 02:30:17 +03:00
ozymandiashh
945da9f0ba fix(codex): read full first line for session validation
`readFirstLine` allocated a fixed 16 KB buffer, but Codex CLI 0.128+
embeds the entire base_instructions / system prompt in the
`session_meta` line, pushing it past 20 KB. When the buffer doesn't
catch a newline, `isValidCodexSession` rejects the session, so every
recent Codex session is silently excluded from totals.

Switch to a streaming readline read so the first line is captured
regardless of length, and add a regression test that creates a
40 KB session_meta payload.

Locally, this changes my 30-day Codex total from €267 (only ~half
of sessions parsed) to €878 (all sessions parsed).
2026-05-02 02:17:53 +03:00
Resham Joshi
ffc0e486d3
Merge pull request #188 from getagentseal/feat/menubar-hardening
Harden menubar: refresh loop, concurrency, data sync, edge cases
2026-05-01 08:03:03 -07:00
iamtoruk
39fc05595c Harden menubar: fix refresh loop, concurrency, data sync, and edge cases
- Fix refresh loop: proper while loop with 30s sleep and force:true
  instead of single-fire Task that never repeated
- Fix loading overlay: counter-based isLoading so concurrent fetches
  don't flicker the overlay on/off
- Fix rapid tab switching: cancel previous switchTask, check
  Task.isCancelled after CLI returns to discard stale results
- Fix tab strip vs hero desync: fetch provider-specific and all-provider
  data in parallel so costs arrive from same data snapshot
- Fix stale menubar icon after wake: forceRefresh now fetches today/all
  in parallel alongside the current selection
- Fix accent color: ThemeState is now @Observable so color changes
  propagate via observation, removing .id() view hierarchy teardown
- Fix currency flash: defer store.currency and symbol update until a
  rate is available so symbol and rate apply atomically
- Fix export: terminationHandler instead of waitUntilExit (no UI freeze),
  HHmmss in filename to prevent overwrite on double-export
- Fix CurrencyState: @MainActor isolation with proper Sendable
  conformance, nonisolated on pure static functions
- Fix streak count: iterate calendar days instead of sparse history
  entries so gaps are counted as streak-breakers
- Fix TrendBar identity: stable date-based id instead of UUID
- Add GPT-5.3 and DeepSeek model display names
2026-05-01 08:01:25 -07:00
Resham Joshi
bbe99fa298
Merge pull request #180 from josteinaj/locate-workspaceStorage-in-vscode-dev-container
Some checks are pending
CI / semgrep (push) Waiting to run
VSCode: correctly locate workspaceStorage when working inside a dev container
2026-04-30 18:41:20 -07:00
Resham Joshi
78ad04c77f
Merge pull request #186 from getagentseal/fix/menubar-timezone-184
Fix timezone handling for non-UTC users
2026-04-30 17:33:27 -07:00
iamtoruk
68c6f2c710 Fix timezone handling: menubar UTC bugs, --timezone flag, DST-safe dates
Three fixes for issue #184:

1. Menubar Swift code used UTC instead of local timezone in two places:
   computeHistoryStats hardcoded TimeZone("UTC") and
   effectiveTokensInLast7Days used ISO8601DateFormatter (UTC default).
   Both now use .current to match CLI-produced local date keys.

2. Add --timezone flag and CODEBURN_TZ env var to override the system
   timezone for all date grouping. Sets process.env.TZ before any Date
   operations so all existing local-timezone code works unchanged.

3. Replace MS_PER_DAY arithmetic with Date constructor day-of-month
   math for yesterday/backfill computations. Subtracting 86400000ms
   from midnight skips a day on DST spring-forward (23-hour day).

Fixes #184
2026-04-30 17:33:02 -07:00
iamtoruk
8ab9ea916b Add per-file result cache for Codex provider
Fixes #183. Users with large Codex session directories (45 GB, 10K+
files) experienced CPU pegging because every 30-second refresh re-parsed
all session files from scratch.

Three optimizations:

1. readFirstLine now reads 16 KB via fs.open() instead of loading the
   entire file through readSessionFile. Cuts discovery I/O from ~45 GB
   to ~160 MB for 10K files.

2. Per-file result cache (codex-results.json) with mtime+size
   fingerprinting. Parsed results are cached on first run; subsequent
   runs return cached data instantly for unchanged files.

3. Cache-accelerated discovery skips header validation for cached files,
   pulling the project name directly from the cache manifest.

Cache safety: fingerprint captured before read (no TOCTOU), atomic
write via temp+fsync+rename, 0o600 permissions, Object.hasOwn for
prototype pollution defense, eviction of deleted files on flush,
try/finally ensures flush even on parse errors.
2026-04-30 16:43:41 -07:00
Jostein Austvik Jacobsen
2de8909823
locate workspaceStorage in vscode dev container 2026-04-30 22:51:52 +02:00
David
116388427e fix(copilot): discover VS Code Insiders transcripts 2026-04-30 16:27:22 +02:00
AgentSeal
7b8f4df856 Fix review findings: error handling, constant dedup, migration persist
- Wrap hydrateCache() in try/catch so disk errors don't crash commands
  that previously never touched the cache
- Export MS_PER_DAY, BACKFILL_DAYS, toDateString from daily-cache.ts
  and remove duplicates from cli.ts
- Remove double hydrateCache() call in report JSON path
- Persist migrated cache to disk so old-version files aren't
  re-migrated on every run
- Export emptyCache() for use as fallback on hydration failure
2026-04-28 22:56:47 +02:00
AgentSeal
888f592bd2 Make daily cache durable: hydrate from all commands, migrate instead of nuke
- Extract ensureCacheHydrated() from menubar-json path into daily-cache.ts
- Call it from every command that parses sessions (report, status, today,
  month, export, optimize, compare, yield) so CLI-only users also persist
  historical data that survives source file deletion
- Replace strict version equality check with fill-defaults migration for
  cache versions 2-4, preserving history across schema changes
- Back up old cache to .bak before discarding on unmigrateable versions
- Fix Copilot auto bucket display names in menubar (Copilot (Anthropic),
  Copilot (OpenAI))
- Fix Roo Code / KiloCode provider key matching in menubar tab strip
2026-04-28 22:41:01 +02:00
Resham Joshi
fbb2c4e69c
Merge pull request #171 from ksp2000/feature/copilot-auto-model-buckets
refactor(copilot): use auto model buckets for transcript inference
2026-04-28 12:17:50 -07:00
Dunccan de Weerdt
26ebe75aa1 Add Droid CLI provider
Discovers and parses sessions from ~/.factory/sessions/, reading JSONL
message logs and companion settings.json files for token usage tracking.

- Discovers sessions by scanning per-cwd subdirectories
- Skips internal .factory housekeeping sessions
- Extracts tools, bash commands, and user messages from JSONL
- Distributes session-level cumulative token counts across calls
- Normalizes Droid model wrappers before existing pricing lookup
- Derives clean project names from cwd paths
- Adds menubar provider filtering for Droid
2026-04-28 20:16:45 +02:00
AgentSeal
d043795855 Add Qwen provider and replace hardcoded pricing with LiteLLM snapshot
- Add Qwen CLI provider (discovers sessions from ~/.qwen/projects/)
- Replace FALLBACK_PRICING (40 hand-maintained entries) with auto-generated
  LiteLLM snapshot (3595 models including Azure, OpenRouter pricing)
- Build script fetches and bundles LiteLLM data before tsup
- Provider-prefixed lookups (azure/, openrouter/) resolve to correct pricing
- Add display names for all GPT-5.x model variants
- Add Qwen to menubar provider filter and tab strip
2026-04-28 19:49:14 +02:00
Resham Joshi
ec2de6a642
Add OpenClaw, Roo Code, and KiloCode providers (#175)
- OpenClaw: JSONL parser with multi-path discovery, tool extraction
  (toolCall + tool_use block types), model tracking via model_change
  and custom model-snapshot events
- Roo Code + KiloCode: shared Cline-family parser extracts model from
  <model> tags in api_conversation_history.json, strips provider
  prefixes from model names
- Add cline-auto and openclaw-auto aliases and display names
- Add menubar provider filters and tab colors for all three
- Show cached data instantly instead of blocking on CLI refresh
2026-04-28 09:24:14 -07:00
saipraneeth.konda
74c1c4b4c1 refactor(copilot): use auto model buckets for transcript inference 2026-04-28 19:32:03 +05:30
AgentSeal
ce78ac52c1 Add builtin aliases for Cursor model names
Some checks are pending
CI / semgrep (push) Waiting to run
Cursor emits dot-version tier-last names like claude-4.6-sonnet
that don't match our pricing keys. Map them so individual models
show costs instead of $0.
2026-04-28 15:09:54 +02:00
AgentSeal
64259c929c Fix Gemini provider for JSONL format (CLI 0.39+)
Gemini CLI 0.39 switched from single JSON to JSONL with one object
per line and $set metadata lines. Parser now handles both formats.
Also updated --provider help text to list all providers.
2026-04-28 15:03:39 +02:00
Resham Joshi
383df90c93
Fix Cursor provider dropping data older than 35 days (#169)
Increase lookback from 35 to 180 days so longer period views
(30-day, month, all) show accurate totals.

Fixes #159, fixes #163
2026-04-27 20:02:06 -07:00
Resham Joshi
6d15ea43a5
Add Gemini CLI provider for session tracking (#168)
Parse ~/.gemini/tmp/<project>/chats/session-*.json files from Gemini
CLI 0.38+. Uses real token counts (input, output, cached, thoughts)
embedded in each message instead of character estimation. Correctly
separates cached tokens from fresh input to avoid double-charging.

- Pricing for gemini-3.1-pro-preview, gemini-3-flash-preview,
  gemini-2.5-pro, gemini-2.5-flash from official Google API rates
- Tool name normalization (ReadFile->Read, SearchText->Grep, etc.)
- Menubar tab with Google Blue color (#4485F4)

Closes #166
2026-04-27 19:48:25 -07:00
Resham Joshi
f7f64a01ab
Add new providers, fix menubar tabs, accent color picker (#167)
* Add Kiro provider and transparent auto-model naming

- Add Kiro IDE provider: parses .chat JSON files, estimates tokens,
  normalizes dot-versioned model IDs for cost lookup
- Show "Cursor (auto)", "Copilot (auto)", "Kiro (auto)" in CLI
  dashboard instead of pretending to know which model was used
- Route auto model names through BUILTIN_ALIASES for cost estimation

* Fix menubar tabs: add missing providers, show period-scoped costs

- Add Kiro, OMP to ProviderFilter enum so installed providers appear as tabs
- Merge Cursor + Cursor Agent into single Cursor tab
- Tab costs now reflect the selected period (7d/30d/month/all) instead
  of always showing today
- Tab visibility still uses today's provider list so tabs don't
  disappear when switching to periods with no data

* Add accent color picker to menubar with Apple system presets

- 9 presets using Apple's exact macOS dark-mode accent colors
  (Ember, Blue, Purple, Pink, Red, Orange, Yellow, Green, Graphite)
- Color picker in header, persisted via UserDefaults
- "Burn" text stays fixed ember regardless of accent
- ThemeState is MainActor-isolated for thread safety
- Picker state lifted to AppStore so it survives .id() tree rebuild
- Accessibility labels on all color swatches
- Renamed brandAccentDark/brandEmberDeep/brandEmberGlow to match
  their actual light/deep/glow semantics

* Fix review findings: case-sensitive cost lookup, Kiro timestamp guard, cache versioning

- Normalize provider dictionary keys to lowercase in tab cost lookup
  so "Cursor Agent" (title-case from CLI) matches providerKeys
- Guard against missing/invalid/epoch startTime in Kiro parser to
  prevent RangeError crash or 1970-01-01 ghost entries
- Bump DAILY_CACHE_VERSION to 4 so upgraded users get a clean
  recompute with the new auto-model naming (cursor-auto vs default)
- Add version field to cursor-results.json cache to invalidate stale
  entries that still use the old 'default' model name
2026-04-27 19:46:30 -07:00
Resham Joshi
5d1b335c0a
Fix Copilot provider to read VS Code workspace transcripts (#165)
The Copilot provider only looked in ~/.copilot/session-state/ which is
from an older CLI tool. VS Code Copilot agent stores transcripts in
~/Library/Application Support/Code/User/workspaceStorage/*/GitHub.copilot-chat/transcripts/.

The new transcript format has no outputTokens or model_change events,
so tokens are estimated from content length and the model is inferred
from tool call ID prefixes. Both legacy and VS Code paths are now
scanned in parallel.

Fixes #161
2026-04-27 19:44:35 -07:00
AgentSeal
410fad9495 Fix Cursor provider reporting $0 for v3 bubble format and NULL createdAt rows
Cursor v3 stores zero token counts in bubbles, causing parseBubbles to
return empty results. The query also dropped rows with NULL createdAt
via the SQL comparison, hiding data from older Cursor versions too.

Changes:
- Remove inputTokens > 0 SQL filter, estimate tokens from text length
  when token counts are zero (same 4 chars/token ratio as agentKv)
- Include NULL createdAt rows with OR IS NULL, fall back to current
  timestamp when createdAt is missing
- Parse agentKv entries with plain string content instead of skipping
  them (not all content is a JSON array)
- Always parse both bubbles and agentKv instead of agentKv-only fallback
- Discover subagent transcripts in subagents/ subdirectories
- Fix timezone-dependent test in day-aggregator

Fixes #159, #163
2026-04-28 00:35:51 +02:00
AgentSeal
6eb9ba0a3b chore: clean up yield.ts - remove redundant comments and dead code 2026-04-25 14:04:16 +02:00
iamtoruk
1c5ca45bd8 Add experimental yield command to track productive vs wasted spend
New command: codeburn yield --period <period>

Correlates AI sessions with git commits to categorize spend:
- Productive: sessions with nearby commits that made it to main
- Reverted: sessions with commits that were reverted
- Abandoned: sessions with no commits or not in main

Uses timestamp proximity heuristic (session time + 1 hour window).
Works across branches, squash merges, and rebases by checking
if commits are in main branch ancestry.

Closes #152
2026-04-25 13:47:07 +02:00
iamtoruk
8ffbffcd7f Fix double-counting in menubar-json period data
PR #136 (2e5e449) changed parseAllSessions to use periodInfo.range
(full period) instead of just today's range. When combined with
cached historical data, this caused days to be counted twice:
- Once from getDaysInRange(cache, ...)
- Again from parseAllSessions(periodInfo.range, ...)

Result: 7-day cost showed ~$402 instead of correct ~$209.

Fix: Parse only today's sessions when using cache path. Historical
data comes exclusively from cache, today's data from fresh parse.
2026-04-25 01:44:02 +02:00
AgentSeal
ed7d76567b Release 0.9.0: Cursor Composer 2 support and provider fixes
- Fix cursor-agent provider to detect Composer 2 JSONL sessions (#142)
- Bump version to 0.9.0
- Update changelog with all 0.9.0 changes
2026-04-24 20:24:49 +02:00
AgentSeal
752aaf04c1 Fix compare chart bar colors to always match legend
Bars now always show their assigned colors (blue for model A, green
for model B) instead of graying out the non-winner. Only the winner's
percentage value is highlighted in green.
2026-04-24 20:18:33 +02:00
AgentSeal
396c8b542c Fix Codex provider to detect file edits from patch_apply_end events
Codex records file modifications as event_msg entries with type
patch_apply_end rather than function_call tool invocations. This fix
tracks those events and adds Edit to the tools list, enabling proper
calculation of edit turns, one-shot rates, and retry rates.

Fixes edit detection showing 0 edit turns despite actual file changes.
2026-04-24 20:07:13 +02:00
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