mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-17 03:56:45 +00:00
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.
4.7 KiB
4.7 KiB
Crush
Charmbracelet's Crush TUI coding agent.
- Source:
src/providers/crush.ts - Loading: lazy (
src/providers/index.ts). Lazy because Crush ships per-project SQLite databases and we usenode:sqliteto read them. - Test:
tests/providers/crush.test.ts(10 tests, fixture-based)
Where it reads from
Crush keeps a global registry that lists every project it has touched, and a separate SQLite database per project.
| File | Path |
|---|---|
| Registry (project list) | $CRUSH_GLOBAL_DATA/projects.json, otherwise $XDG_DATA_HOME/crush/projects.json, otherwise ~/.local/share/crush/projects.json (Linux/macOS) or %LOCALAPPDATA%/crush/projects.json (Windows). |
| Per-project db | <project.path>/<project.data_dir>/crush.db where data_dir defaults to .crush. |
The registry shape is an object keyed by project id (modern Crush) or an array (older builds and tokscale's sample fixtures). The parser accepts both.
Storage format
SQLite. Schema verified against charmbracelet/crush v0.66.1 (internal/db/migrations/20250424200609_initial.sql plus subsequent additive migrations).
Two tables matter for codeburn:
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
parent_session_id TEXT,
title TEXT NOT NULL,
message_count INTEGER NOT NULL DEFAULT 0,
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
cost REAL NOT NULL DEFAULT 0.0,
updated_at INTEGER NOT NULL,
created_at INTEGER NOT NULL,
...
);
CREATE TABLE messages (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
parts TEXT NOT NULL DEFAULT '[]',
model TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
...
);
Caching
None at the provider level.
Deduplication
Per crush:<sessionId> (crush.ts).
What we extract
| codeburn field | Crush source |
|---|---|
inputTokens |
sessions.prompt_tokens |
outputTokens |
sessions.completion_tokens |
costUSD |
sessions.cost (already in dollars) |
model |
dominant value of messages.model for the session, picked by GROUP BY model ORDER BY COUNT(*) DESC LIMIT 1. Falls back to unknown. |
timestamp |
sessions.updated_at if set, otherwise created_at |
Cache tokens, reasoning tokens, web-search counts, tools, and bash commands are all left as zero / empty. Crush does not record per-message token data, so per-turn attribution is not available.
Quirks worth knowing
- Timestamps are seconds, not milliseconds. The Crush schema comments in the upstream migration claim millisecond timestamps, but every actual
INSERT/UPDATEininternal/db/sql/{sessions,messages}.sqlusesstrftime('%s', 'now'), which returns Unix seconds. The parser multiplies by 1000 before constructing aDate. Tokscale's parser (junhoyeo/tokscale#346) gets this wrong and is off by 1000x. Confirmed against Crush v0.66.1. - Cost is stored in dollars as a
REAL. No conversion needed. - Child sessions are skipped. Only rows with
parent_session_id IS NULLare surfaced. Crush sub-agents inherit cost into the parent. - Zero-spend rows are filtered. Discovery skips sessions with
cost = 0 AND prompt_tokens = 0 AND completion_tokens = 0. - Optimize detectors that depend on tools (
detectJunkReads,detectDuplicateReads,detectLowReadEditRatio) will not flag Crush sessions. That is correct: Crush does not log per-tool calls in a way we can read today. detectLowWorthSessionsmay flag Crush sessions because it looks for cost without edits. That is a known false positive; if it becomes noisy, we can branch the detector on provider.
When fixing a bug here
- Confirm the issue against a real Crush install (
brew install charmbracelet/tap/crush) before assuming the schema has changed. Migrations in the last six months have only added columns tosessions/messages, never removed any of the ones we read. - If the bug is "Crush sessions show timestamps from 1970-something", check whether someone "fixed" the seconds-vs-milliseconds handling by removing the
* 1000. The schema comment is wrong; the data is in seconds. - If the bug is "Crush model column shows
unknown", the session has no messages with a non-nullmodel. Some early Crush builds did not record provider on every message; addLIKEmatching againstproviderif you want a stronger fallback. - If the bug is "no sessions discovered", the registry path probably has not been verified for the user's setup. Print
getRegistryPath()and have them confirm the file exists at that location. - New fixtures go under the inline schema in
tests/providers/crush.test.ts; keep theCREATE TABLEliteral and synchronized with the upstream migration.