Skip to content

Commit 4f11382

Browse files
committed
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.
2 parents c2ab80d + d69aa34 commit 4f11382

38 files changed

Lines changed: 473 additions & 3324 deletions

.github/workflows/firstlook.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: firstlook
2+
on:
3+
pull_request:
4+
types: [opened, reopened, synchronize]
5+
workflow_dispatch:
6+
inputs:
7+
pr-number:
8+
description: 'PR number to scan (leave empty for all open PRs)'
9+
required: false
10+
type: string
11+
12+
jobs:
13+
assess:
14+
runs-on: ubuntu-latest
15+
permissions:
16+
pull-requests: write
17+
contents: read
18+
steps:
19+
- uses: getagentseal/firstlook@main
20+
with:
21+
pr-number: ${{ inputs.pr-number }}
22+
skip-users: 'dependabot[bot],renovate[bot]'
23+
fail-on: 'unknown'

CHANGELOG.md

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,18 @@
11
# Changelog
22

3-
## 0.8.4 - 2026-04-20
3+
## 0.8.5 - 2026-04-21
44

55
### Fixed
6-
- **Menubar hang on large session histories.** Menubar-json now uses the source cache instead of re-parsing all files on every poll.
6+
- **Stale Today totals after 0.8.2.** The persistent source cache introduced in 0.8.2 caused Today's cost to under-report and sometimes drop between polls during active Claude Code sessions. The cache keyed entries on `(mtime, size)` fingerprints that diverged from Claude's append-mostly JSONL model, producing empty or partial entries that were served on subsequent polls. Reverted the cache rewrite to the v0.8.1 full-reparse path for Claude sessions. Both the menubar and `codeburn status` now return consistent, monotonically-increasing Today totals.
7+
- **Menubar and terminal status disagreed on Today.** A turn that straddled midnight (user message in one day, response in the next) was bucketed by user timestamp in one code path and by assistant timestamp in another, producing different Today values in the two surfaces. Both paths now count a turn on the day its first assistant call ran.
8+
- **Kept from 0.8.2-0.8.4:** subscription plan tracking, pricing accuracy and CSV injection hardening, cursor-agent provider, menubar prefetch and timezone alignment. Only the cache rewrite and its follow-up patches were reverted.
79

8-
## 0.8.3 - 2026-04-20
9-
10-
### Fixed
11-
- **Source cache empty-session poisoning.** Cache entries with zero sessions are now treated as cache misses, forcing a fresh re-parse instead of silently dropping the session data.
12-
- **Date range skip on changed files.** The date range exclusion now runs only after the fingerprint matches, so files that have grown with new data are never incorrectly skipped.
13-
- **TUI auto-refresh not updating.** The 30-second refresh timer now bypasses the in-memory CachedWindow, which was permanently stale because the date range end is always end-of-day.
14-
- **Menubar showing stale or decreasing costs.** Fixed cache invalidation so the menubar receives correct, up-to-date cost data.
15-
- **Swift menubar observation race.** Explicit UI refresh calls after each data fetch prevent missed updates from the one-shot observation callback.
16-
17-
## 0.8.2 - 2026-04-20
18-
19-
### Added
20-
- **Persistent parse cache for all providers.** Repeated CLI runs now reuse parsed source summaries across fresh processes instead of reparsing raw logs every time. Cache lives at `~/.cache/codeburn/source-cache-v1/` with atomic writes and 0600 file permissions. Credit: @spMohanty (PR #116).
21-
- **`--no-cache` on parse-backed commands.** `report`, `today`, `month`, `status`, `export`, `optimize`, and `compare` can bypass cached entries for that run and rebuild them from raw logs. Credit: @spMohanty (PR #116).
22-
- **`Updating cache` stderr progress.** Non-JSON cold or partial cache rebuilds now show progress while CodeBurn refreshes changed sources. Credit: @spMohanty (PR #116).
23-
- **`codeburn plan` subscription tracking.** Set your plan (`claude-pro`, `claude-max`, `cursor-pro`, or custom) to see a usage progress bar in the dashboard. Includes 7-day trailing median projection and billing-cycle-aware period math. Credit: @tmchow (PR #74).
24-
25-
### Changed
26-
- **Cursor now uses the shared parse cache.** The provider-specific Cursor cache path is gone; SQLite-backed provider data now flows through the same persistent cache layer as the other providers. Credit: @spMohanty (PR #116).
10+
### Removed
11+
- `--no-cache` flag on `report`, `today`, `month`, `status`, `export`, `optimize`, and `compare`. The flag existed to bypass the persistent source cache which no longer exists. If your scripts pass `--no-cache`, drop it; the parse runs fresh every time now.
2712

28-
### Fixed
29-
- **Model pricing: removed bidirectional fuzzy match.** `canonical.startsWith(key) || key.startsWith(canonical)` could match unrelated models. Now uses one-directional prefix only. Credit: @hobostay (PR #77).
30-
- **Zero-cost models incorrectly filtered.** `!entry.input_cost_per_token` treated `0` as missing. Now checks `=== undefined` so free-tier models retain their pricing entry. Credit: @hobostay (PR #77).
31-
- **File descriptor leak in `readSessionLines`.** Generator now calls `stream.destroy()` in a `finally` block so early abandonment does not leak open handles. Credit: @hobostay (PR #77).
32-
- **CSV injection guard extended.** Tab and carriage return characters at cell start are now escaped alongside `=`, `+`, `-`, `@`. Credit: @hobostay (PR #77).
33-
- **Crash on empty export periods.** Optional chaining prevents `undefined` access when a period has no projects. Credit: @hobostay (PR #77).
34-
- **Config read crash on malformed JSON.** Restored catch-all error handling in `readConfig` so a corrupt `config.json` returns defaults instead of crashing.
13+
### Notes
14+
- 0.8.2, 0.8.3, and 0.8.4 on npm contain the buggy cache. Upgrade with `npm i -g codeburn@latest` or `npm i -g codeburn@0.8.5`.
15+
- This release uses a full reparse on every invocation, matching v0.8.1 behavior. On large corpora (5,000+ session files) expect 3 to 10 seconds per invocation. An incremental refresh design that preserves correctness is planned for a follow-up release.
3516

3617
## 0.8.0 - 2026-04-19
3718

README.md

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<a href="https://www.npmjs.com/package/codeburn"><img src="https://img.shields.io/npm/v/codeburn.svg" alt="npm version" /></a>
1111
<a href="https://www.npmjs.com/package/codeburn"><img src="https://img.shields.io/npm/dt/codeburn.svg" alt="total downloads" /></a>
1212
<a href="https://github.com/getagentseal/codeburn/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/codeburn.svg" alt="license" /></a>
13-
<a href="https://github.com/getagentseal/codeburn"><img src="https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg" alt="node version" /></a>
13+
<a href="https://github.com/getagentseal/codeburn"><img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg" alt="node version" /></a>
1414
</p>
1515

1616
<p align="center">
@@ -35,9 +35,9 @@ npx codeburn
3535

3636
### Requirements
3737

38-
- Node.js 22+
38+
- Node.js 20+
3939
- Claude Code (`~/.claude/projects/`), Codex (`~/.codex/sessions/`), Cursor, OpenCode, Pi (`~/.pi/agent/sessions/`), OMP (`~/.omp/agent/sessions/`), and/or GitHub Copilot (`~/.copilot/session-state/`)
40-
- For Cursor/OpenCode support: uses Node's built-in `node:sqlite` (Node 22+)
40+
- For Cursor/OpenCode support: `better-sqlite3` is installed automatically as an optional dependency
4141

4242
## Usage
4343

@@ -80,25 +80,6 @@ codeburn today --format json | jq '.overview.cost'
8080

8181
For the lighter `status --format json` (today + month totals only) or file-based exports (`export -f json`), see above.
8282

83-
## Cache behavior
84-
85-
CodeBurn now keeps a persistent parse cache under `~/.cache/codeburn/source-cache-v1/`.
86-
It applies to every provider. Unchanged sources load from cache across fresh CLI runs,
87-
while changed sources are refreshed on demand so rolling windows like `today` stay current
88-
as new log entries land.
89-
90-
Use `--no-cache` on any command that reads session data to ignore cached entries for that
91-
run and rebuild them from raw logs:
92-
93-
```bash
94-
codeburn today --no-cache
95-
codeburn report --period all --no-cache
96-
codeburn export --no-cache
97-
```
98-
99-
When a non-JSON command needs to rebuild part of the cache, CodeBurn shows an
100-
`Updating cache` progress bar on stderr. JSON output stays clean on stdout.
101-
10283
## Providers
10384

10485
CodeBurn auto-detects which AI coding tools you use. If multiple providers have session data on disk, press `p` in the dashboard to toggle between them.
@@ -162,7 +143,7 @@ Either flag alone is valid. Inverted or malformed dates exit with a clear error.
162143

163144
Codex tool names are normalized to match Claude's conventions (`exec_command` shows as `Bash`, `read_file` as `Read`, etc.) so the activity classifier and tool breakdown work across providers.
164145

165-
Cursor reads token usage from its local SQLite database via Node's built-in `node:sqlite`. Since Cursor's "Auto" mode hides the actual model used, costs are estimated using Sonnet pricing (labeled "Auto (Sonnet est.)" in the dashboard). The Cursor view shows a **Languages** panel (extracted from code blocks) instead of Core Tools/Shell/MCP panels, since Cursor does not log individual tool calls. Parsed results are cached through the shared persistent cache layer.
146+
Cursor reads token usage from its local SQLite database. Since Cursor's "Auto" mode hides the actual model used, costs are estimated using Sonnet pricing (labeled "Auto (Sonnet est.)" in the dashboard). The Cursor view shows a **Languages** panel (extracted from code blocks) instead of Core Tools/Shell/MCP panels, since Cursor does not log individual tool calls. First run on a large Cursor database may take up to a minute; results are cached and subsequent runs are instant.
166147

167148
GitHub Copilot only logs output tokens in its session state, so Copilot cost rows sit below actual API cost. The model is tracked via `session.model_change` events; messages before the first model change are skipped to avoid silent misattribution.
168149

@@ -358,36 +339,28 @@ CodeBurn reads these files, deduplicates messages (by API message ID for Claude,
358339

359340
```
360341
src/
361-
cli.ts Commander.js entry point
362-
dashboard.tsx Ink TUI (React for terminals)
363-
parser.ts JSONL reader, dedup, date filter, provider orchestration
364-
models.ts LiteLLM pricing, cost calculation
365-
classifier.ts 13-category task classifier
366-
compare-stats.ts Model comparison engine (metrics, category breakdown, working style)
367-
types.ts Type definitions
368-
format.ts Text rendering (status bar)
369-
menubar-json.ts Payload builder consumed by the native macOS menubar app in mac/
370-
export.ts CSV/JSON multi-period export
371-
config.ts Config file management (~/.config/codeburn/)
372-
currency.ts Currency conversion, exchange rates, Intl formatting
373-
sqlite.ts SQLite adapter (node:sqlite)
374-
source-cache.ts Persistent parse cache (manifest + per-source entries)
375-
discovery-cache.ts Provider directory scan caching
376-
parse-progress.ts Stderr progress bar for cache rebuilds
377-
provider-colors.ts Provider color and label constants
378-
plans.ts Subscription plan presets and validators
379-
plan-usage.ts Billing period math and usage projection
380-
fs-utils.ts Bounded file readers with stream support
342+
cli.ts Commander.js entry point
343+
dashboard.tsx Ink TUI (React for terminals)
344+
parser.ts JSONL reader, dedup, date filter, provider orchestration
345+
models.ts LiteLLM pricing, cost calculation
346+
classifier.ts 13-category task classifier
347+
compare-stats.ts Model comparison engine (metrics, category breakdown, working style)
348+
types.ts Type definitions
349+
format.ts Text rendering (status bar)
350+
menubar-json.ts Payload builder consumed by the native macOS menubar app in mac/
351+
export.ts CSV/JSON multi-period export
352+
config.ts Config file management (~/.config/codeburn/)
353+
currency.ts Currency conversion, exchange rates, Intl formatting
354+
sqlite.ts SQLite adapter (lazy-loads better-sqlite3)
355+
cursor-cache.ts Cursor result cache (file-based, auto-invalidating)
381356
providers/
382-
types.ts Provider interface definitions
383-
index.ts Provider registry (lazy-loads Cursor, cursor-agent, OpenCode)
384-
claude.ts Claude Code session discovery
385-
codex.ts Codex session discovery and JSONL parsing
386-
copilot.ts GitHub Copilot session state parsing
387-
cursor.ts Cursor SQLite parsing, language extraction
388-
cursor-agent.ts cursor-agent CLI transcript parsing
389-
opencode.ts OpenCode SQLite session discovery and parsing
390-
pi.ts Pi/OMP agent JSONL session discovery and parsing
357+
types.ts Provider interface definitions
358+
index.ts Provider registry (lazy-loads Cursor, OpenCode)
359+
claude.ts Claude Code session discovery
360+
codex.ts Codex session discovery and JSONL parsing
361+
cursor.ts Cursor SQLite parsing, language extraction
362+
opencode.ts OpenCode SQLite session discovery and parsing
363+
pi.ts Pi/OMP agent JSONL session discovery and parsing
391364
```
392365

393366
## Star History

bin/codeburn

Lines changed: 0 additions & 20 deletions
This file was deleted.

mac/Sources/CodeBurnMenubar/AppStore.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ final class AppStore {
8989
}
9090
}
9191

92-
/// Prefetch all periods so tab switching is instant. Skips any period already cached.
93-
func prefetchAll() async {
94-
for period in Period.allCases {
95-
let key = PayloadCacheKey(period: period, provider: .all)
96-
if cache[key] != nil { continue }
97-
await refreshQuietly(period: period)
98-
}
99-
}
100-
10192
/// Background refresh for a period other than the visible one (e.g. keeping today fresh for the menubar badge).
10293
/// Does not toggle isLoading, so the popover's loading overlay is unaffected.
10394
/// Always uses the .all provider since the menubar badge shows total spend.

mac/Sources/CodeBurnMenubar/CodeBurnApp.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
7272
private func startRefreshLoop() {
7373
refreshTask = Task { [weak self] in
7474
guard let s = self else { return }
75-
// First cycle: fetch current view, then prefetch all periods in background
75+
// First cycle: fetch today so the status icon has a number within seconds of launch.
7676
await s.store.refreshQuietly(period: .today)
7777
s.refreshStatusButton()
7878
await s.store.refresh(includeOptimize: true)
7979
s.refreshStatusButton()
80-
await s.store.prefetchAll()
8180

8281
while !Task.isCancelled {
8382
try? await Task.sleep(nanoseconds: refreshIntervalNanos)
@@ -88,6 +87,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
8887
s.refreshStatusButton()
8988
}
9089
}
90+
91+
// Period tabs are fetched lazily when the user first clicks them in the popover.
92+
// An earlier version prefetched every period on launch to make tab switching instant,
93+
// but on large session corpora that spawned four concurrent codeburn subprocesses
94+
// competing with the main refresh loop for disk and parser time, and the status label
95+
// drifted stale for minutes. A per-tab first-click cost of a few seconds is the better
96+
// tradeoff on user machines that track thousands of sessions.
9197
}
9298

9399
private func observeStore() {

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
{
22
"name": "codeburn",
3-
"version": "0.8.4",
3+
"version": "0.8.5",
44
"description": "See where your AI coding tokens go - by task, tool, model, and project",
55
"type": "module",
66
"main": "./dist/cli.js",
77
"bin": {
8-
"codeburn": "bin/codeburn"
8+
"codeburn": "dist/cli.js"
99
},
1010
"files": [
11-
"dist",
12-
"bin"
11+
"dist"
1312
],
1413
"scripts": {
1514
"build": "tsup",

0 commit comments

Comments
 (0)