* Add IBM Bob provider
* Add workspace extraction for Cline-family providers
Extract project name from workspace directory in api_conversation_history.json
so sessions show actual folder names instead of the provider display name.
Thread projectPath through ParsedProviderCall to avoid unsanitizePath mangling
hyphenated folder names.
---------
Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
Co-authored-by: iamtoruk <hello@agentseal.org>
Cursor's chat history showed as a single row labeled 'cursor' in
the dashboard because the global state.vscdb has no workspace
field on individual bubbles. The fix joins through Cursor's
per-workspace storage:
1. Walk ~/Library/Application Support/Cursor/User/workspaceStorage/*
2. For each hash dir, read workspace.json -> folder URI
3. Open that dir's state.vscdb, read
ItemTable['composer.composerData'] -> allComposers list
4. Build Map<composerId, folder URI>
5. emit one SessionSource per workspace plus a catch-all 'cursor'
source for composers that did not register against any
workspace (multi-root workspaces, no-folder-open windows,
deleted workspaces with surviving global rows)
The parser decodes source.path's #cursor-ws= tag, filters the
parsed bubbles to the composerIds that belong to this workspace,
and yields only those. The orphan-tag source negates the filter so
it captures every composer not in any workspace.
In passing, fix a real bug in the old code: parseBubbles set
`sessionId: row.conversation_id ?? 'unknown'`, but the JSON
`conversationId` field is empty in current Cursor builds, so every
call shipped with `sessionId: 'unknown'`. We now derive the
composer id from the row key (`bubbleId:<composerId>:<bubbleUuid>`)
which is what the workspace map joins on. The old behavior masked
the bug because every call went into a single 'cursor' project
anyway; with per-workspace bucketing the bug becomes load-bearing.
Cache version bumped 2 -> 3 to invalidate caches that still record
'unknown' as the session id.
Live-tested against my real 1.9 GB Cursor DB: the single 'cursor'
row with 1904 calls / $4.08 now breaks into 5 workspaces plus an
orphan bucket, totals reconcile exactly. 8 fixture-based tests
cover multi-workspace routing, orphan filtering, legacy bare DB
path backwards compat, multi-root workspace skip, vscode-remote
URI slugification, and total reconciliation across all sources.
Full suite: 46 files, 653 tests passing.
Adds an OS-delimited list env var so a user with more than one
Claude account or profile can scan all of them in a single run.
Sessions across every configured dir merge into one ProjectSummary
per project, matching the option-1 design agreed on the issue
thread (no per-account splitting in the data model or the UI).
Format: `CLAUDE_CONFIG_DIRS=~/.claude-work:~/.claude-personal`
on POSIX, `;`-separated on Windows. Precedence is
CLAUDE_CONFIG_DIRS > CLAUDE_CONFIG_DIR > ~/.claude. Empty entries
in the list are skipped, duplicates are deduped on resolved path,
and a missing or unreadable dir does not abort the scan of the
others. If the user explicitly set CLAUDE_CONFIG_DIRS but every
listed entry is unreadable, a one-line stderr hint identifies the
attempted paths and the platform's expected delimiter, so a
Windows user typing the POSIX `:` does not get a silent zero-row
result. `~` is now also expanded in CLAUDE_CONFIG_DIR for
consistency.
Implementation is intentionally narrow: only `claude.ts` changes,
plus a small parser-cache key update so a stale cache from one
config does not bleed into a run with a different config (matters
for the macOS menubar and GNOME extension which run as long-lived
processes). The merge happens for free in
`src/parser.ts:scanProjectDirs`, which keys ProjectSummary entries
by canonical cwd (or the sanitized slug as a fallback). Two
SessionSource entries with the same `project` field land under the
same key and combine their sessions, regardless of which dir they
came from. No new fields on SessionSource / SessionSummary /
ProjectSummary, and no UI changes.
Tests: 12 fixture-based cases covering the unset path (default
~/.claude), single-dir override via CLAUDE_CONFIG_DIR, multi-dir
override via CLAUDE_CONFIG_DIRS, ~ expansion, dedup of repeated
entries, leading/trailing/doubled delimiters, missing dir
tolerated, file-not-directory entry tolerated, empty
CLAUDE_CONFIG_DIRS falls back to single-dir env, and two
parser-level integration tests asserting (a) two sessions from
two dirs sharing one cwd produce one ProjectSummary with combined
totals and no `account`/`accountPath` fields anywhere, and (b)
two sessions sharing a slug but with different canonical cwds
still merge by slug at the project-rollup layer (option 1
behavior pinned so a future refactor cannot quietly swap to
cwd-aware merging without an explicit opt-in).
Supersedes the alternative implementation in #227, which builds
per-account attribution (option 2) instead.
Closes#278.
Adds Charmbracelet Crush as a lazy-loaded provider:
- src/providers/crush.ts: walks ~/.local/share/crush/projects.json
(XDG_DATA_HOME and CRUSH_GLOBAL_DATA aware), opens each project's
crush.db read-only, queries root sessions where parent_session_id
IS NULL. Emits one ParsedProviderCall per session with real
prompt_tokens, completion_tokens, cost (dollars), and the
dominant model resolved from messages.model.
- src/providers/index.ts: register crush alongside cursor, goose,
opencode, antigravity, cursor-agent in the lazy import path.
- tests/providers/crush.test.ts: 10 fixture-based tests covering
discovery, parsing, missing-registry, malformed JSON, missing db,
child session exclusion, dominant model selection, dedup, and
array-shaped legacy registry.
Schema source: charmbracelet/crush@v0.66.1
internal/db/migrations/20250424200609_initial.sql, verified by
spawning a research agent against upstream. The schema *comments*
in that migration claim millisecond timestamps but every actual
INSERT/UPDATE uses strftime('%s', 'now') which returns Unix
seconds; the parser treats values as seconds. Tokscale's
parser (junhoyeo/tokscale#346) gets this wrong and is off by
1000x, plus its parser misses the prompt_tokens/completion_tokens
columns that exist in Crush's schema. Our integration uses both,
so Crush sessions get real per-model attribution.
Menubar:
- mac/Sources/CodeBurnMenubar/AppStore.swift: add .crush case to
ProviderFilter and its cliArg switch.
- mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift: add
Crush color to the per-tab color extension. The visibleFilters
computed property already filters by detected providers, so the
Crush tab appears automatically when a user has Crush data.
README:
- Replace the provider table with an icon-led layout. Icons live
under assets/providers/<name>.<ext>. 14 icons sourced from
junhoyeo/tokscale (MIT) under nominative fair use, 4 sourced
separately: codex (OpenAI org avatar), cursor-agent (reuses the
Cursor icon), kiro (kiro.dev favicon, ico->png via sips), omp
(can1357/oh-my-pi icon.svg, MIT). Attribution line added.
- Add Crush row.
Docs:
- docs/providers/crush.md: full per-provider doc with verified
schema excerpt, the seconds-vs-milliseconds quirk, and a
"when fixing a bug here" checklist.
- docs/architecture.md: provider count 17 -> 18, test count
41 -> 42, and crush in the lazy list.
- docs/providers/README.md: add Crush row to the lazy index.
- CONTRIBUTING.md: bump test count to 568 (was 558).
All 568 tests pass locally; swift build clean.
Two passes of validators across CLI accuracy, dashboard UX, menubar Swift,
performance, security, and end-to-end smoke tests on real session data.
Data-correctness fixes:
- parseLocalDate rejects month/day overflow. JS Date silently rolled
Feb 31 to Mar 3, so --from 2026-02-31 --to 2026-03-15 quietly dropped
sessions on Feb 28 - Mar 2. Now throws "Invalid date" with a clear
reason. Leap-day case covered (2024-02-29 valid, 2025-02-29 rejected).
- CSV/JSON exports use the active currency's natural decimal places. The
previous round2 helper produced ¥412.37 in CSV while the dashboard
rendered ¥412 — finance teams comparing the two surfaces saw a
discrepancy. New roundForActiveCurrency consults Intl.NumberFormat for
the right precision (0 for JPY/KRW/CLP, 2 for USD/EUR, etc).
- Copilot toolRequests is Array.isArray-guarded in both modern and legacy
event branches. Previously a corrupt session with toolRequests=null or
a string aborted the whole file's parse loop and silently dropped every
legitimate call after it.
- Codex token_count dedup uses a null sentinel for prevCumulativeTotal so
the first event is never confused with a duplicate. Sessions that emit
only last_token_usage (no total_token_usage) report cumulativeTotal=0
on every event; with the previous 0-initialized prev, the first event
matched the dedup guard and was dropped.
- LiteLLM pricing values are clamped to [0, 1] per token via safePerTokenRate.
Defense in depth against a tampered upstream JSON shipping negative or
absurdly large per-token costs that would otherwise propagate into all
cost totals.
Performance:
- Cursor SQLite parse no longer pegs at minutes on multi-GB DBs. Two
changes: per-conversation user-message buffer uses an index pointer
instead of Array.shift() (which was O(n) per call); and a real ROWID
cutoff via subquery limits the scan to the most recent 250k bubbles
with a stderr warning so power users get a partial report rather than
a stalled CLI.
- Spawned codeburn CLI subprocesses are terminated when the calling Task
is cancelled. Without this, rapid period/provider tab clicks in the
menubar cancelled the Task but left the subprocess running to
completion, piling up zombie processes.
UX:
- Dashboard period switch flips to loading and clears projects
synchronously before reloadData runs, eliminating the frame where the
new period label rendered over the old period's projects.
- Optimize findings tab paginates 3-at-a-time with j/k scroll. With 4
new detectors plus 7 originals, 8-10 findings * 6 lines was scrolling
the StatusBar off the alt buffer top.
- Custom --from/--to ranges hide the period tab strip and disable the
1-5 / arrow keys so a stray period press no longer abandons the user's
explicit range. A "Custom range: X to Y" banner replaces the tab strip.
- OpenCode storage-format warning is per-table-set, rate-limited to once
per process, and points the user at OpenCode's migration step or the
issue tracker. The previous all-or-nothing check fired the generic
"format not recognized" string for any schema mismatch.
Menubar / OAuth:
- Both Claude and Codex bootstrap (Reconnect button) now honour the
usageBlockedUntil 429 backoff that refreshIfBootstrapped respects.
Spamming Reconnect during sustained rate-limit windows previously
hammered the upstream endpoint on every click.
- Codex Retry-After HTTP header is parsed (delta-seconds plus IMF-fixdate
fallback) so we don't over-back-off when ChatGPT tells us a shorter
window than our 5-minute floor.
- Both credential cache files are written via SafeFile.write
(O_CREAT | O_EXCL | O_NOFOLLOW with explicit 0600) so there is no race
window where the temp file briefly exists at default umask, and a
symlink at the destination cannot redirect the write. Reads now route
through SafeFile.read with a 64 KiB cap, closing the symlink-follow gap
on Data(contentsOf:).
CI signal:
- TypeScript strict typecheck (tsc --noEmit) is now zero errors. The
six errors in src/providers/copilot.ts came from a discriminated-union
catch-all branch whose `data: Record<string, unknown>` shape TS picked
over the specific event branches when narrowing on `type`. Removed the
catch-all; runtime falls through unknown event types via the existing
if/else chain.
Tests added: 16 new (now 555 total)
- date-range-filter: month/day/year overflow rejection, leap-day correctness
- currency-rounding: convertCost no-rounding contract, roundForActiveCurrency
for USD/JPY/KRW/EUR
- providers/copilot: malformed toolRequests does not abort the parse
- providers/cursor-bubble-dedup: re-parse after token mutation does not
double-count, single parse yields one call per bubble
- providers/codex: first event with cumulativeTotal=0 not dropped,
consecutive zero-cumulative duplicates still deduped
- Stop tracking a separate streamError flag. createReadStream's default
64 KiB highWaterMark means the stream may already be reading chunk 2
when we break out of the loop after yielding the first line; if that
later chunk errors, the flag could reject an otherwise-valid line.
readline's async iterator already re-throws stream errors on Node 16+,
which the existing catch handles.
- Test: 120 KB session_meta line forces multi-chunk line assembly.
- Test: truncated mid-write first line is rejected, not parsed as half
an object.
- 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.
`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).
- 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
* 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
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
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.
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.
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.
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
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.
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.
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
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.
- Add OMP provider reading from ~/.omp/agent/sessions (same JSONL
format as Pi, shared parser)
- Parameterize discoverSessionsInDir with provider name so sessions
carry correct provider field
- Add BUILTIN_ALIASES for proxy model name variants (anthropic--claude-*
double-dash format) that don't match LiteLLM keys
- Add model-alias CLI command for user-defined name mappings
- Wire setModelAliases into preAction after config load
- Add modelAliases field to CodeburnConfig
- Update README: OMP in provider table, model-alias section
- init currentModel to '' and skip assistant messages before first
session.model_change to avoid silent misattribution
- add comment documenting why inputTokens is always 0
- fix delete_file tool mapping ('Edit' -> 'Delete')
- add schema doc comment to ToolRequest optional fields
- remove catch-all from CopilotEvent union for proper TS narrowing
- add tests: pre-model-change skip, workspace.yaml quote/comment strip,
longest-prefix model display name match
Maps Pi's lowercase tool names (bash, read, edit, write...) to
the capitalized form used by every other provider, so the
dashboard shows consistent tool names across providers and the
activity classifier works without extra lowercase entries.
Reverts the lowercase additions to classifier.ts since the
provider now normalizes tool names at the source.
- Adds support for Pi (pi.ai) as a new session provider.
- Pi sessions are stored as JSONL files under `~/.pi/agent/sessions/<project-dir>/` and use OpenAI-compatible model IDs (gpt-5, gpt-5.4, gpt-4o, etc.).
- `src/providers/pi.ts` (new): Pi provider - discovers JSONL session files, parses assistant turns, extracts token counts, tool calls, and bash commands, deduplicates via response ID with line-index fallback
- `src/providers/types.ts`: added bashCommands field to `ParsedProviderCall` so all providers carry extracted bash command lists
- `src/providers/index.ts`: registered Pi as a core provider alongside Claude and Codex
- `src/providers/codex.ts`, `cursor.ts`: added `bashCommands: []` to satisfy the new required field on `ParsedProviderCall`
- `src/parser.ts`: fixed bug where `providerCallToTurn` always emitted an empty bashCommands array instead of passing through the parsed commands
- `src/classifier.ts`: added lowercase tool name variants (bash, edit, read, write) to match Pi's tool naming convention in JSONL output
- `src/bash-utils.ts`: exclude `true`, `false`, and shell variable assignments from extracted commands; scan past leading `NAME=val` tokens so `FOO=bar ls` correctly records `ls` rather than being dropped
- `package.json`: added pi to keywords
- `tests/providers/pi.test.ts` (new): 16 unit tests covering session discovery, multi-turn parsing, tool/bash extraction, deduplication, zero-token filtering, and display name mapping
- `tests/provider-registry.test.ts`: updated core provider list to include pi
- [X] Unit tests pass (`npx vitest run`, 56 tests across 6 files);
- [X] Manually verified via `npx tsx src/cli.ts` report and showing Pi sessions alongside Claude and Codex in the dashboard.
Codex Desktop on Windows uses "Codex Desktop" as the originator
string instead of "codex_cli" or "codex_vscode". The startsWith
check was case-sensitive, rejecting these sessions silently.
Fixes#1 (comment by @JiglioNero).
Reads session data from OpenCode's SQLite databases at
~/.local/share/opencode/. Reuses the existing better-sqlite3
adapter (same as Cursor), lazy-loaded so users without OpenCode
see no difference. Adds bashCommands to the provider interface
so shell command breakdowns work across all providers.
31 tests, schema validation, diagnostic stderr on failures.
Also fixes a pre-existing tsc error in currency.ts.
First run parses the 21GB DB (slow, ~40-80s). Writes parsed
results to ~/.cache/codeburn/cursor-results.json. Subsequent
runs check DB mtime+size -- if unchanged, load from cache
(instant). Cache auto-invalidates when Cursor modifies the DB.
Cursor module (sqlite.ts, better-sqlite3) now only loads when
cursor provider is actually requested. Claude/Codex startup
is unaffected -- cursor import never happens unless needed.
Reads token usage from Cursor's local state.vscdb database.
Supports per-request input/output tokens, model tracking,
and incremental caching for large databases.
- better-sqlite3 as optionalDependency (lazy-loaded, no impact on Claude/Codex)
- Parameterized SQL queries, read-only mode, per-row error handling
- Schema detection with clear error on format changes
- Cache layer with timestamp watermark for incremental reads
- Provider colors and [p] key cycling in dashboard
- 39 tests passing, zero regressions
- Remove agent-*.jsonl exclusion filter that was dropping ~46% of API calls
- Scan subagents/ directories for subagent session files
- Normalize Codex token semantics: OpenAI includes cached tokens inside
input_tokens, subtract them to match Anthropic's separate reporting
- Fixes cost double-counting and 100% cache hit display for Codex users