feat(crush): add initial session-level local usage support#346
Conversation
|
@IvGolovach is attempting to deploy a commit to the Inevitable Team on Vercel. A member of the Team first needs to authorize it. |
|
@IvGolovach OMG you rock! |
|
@IvGolovach is there anything we need to do to merge this or just someone that's a maintainer need to do it? |
|
@Jelloeater From my side, I think this PR is in good shape now. The PR is mergeable, the code checks are green, and the remaining red Vercel status looks like the usual external authorization noise rather than a code blocker. Since @junhoyeo is the repo owner, I think the next step is just maintainer review/merge from their side, or from any collaborator with merge permissions if applicable. |
…m host filesystem After rebasing onto main (which added Kilo at index 14), Crush moved to index 15 bumping the total client count. The all-clients filter test asserted 16 but now needs 17 (16 ClientId variants + synthetic). The crush scanner test picked up a real .crush/crush.db from the host machine because find_current_workspace_crush_db() walks from CWD. On assertion failure the manual env var restore was skipped, leaking XDG_DATA_HOME and breaking 4 downstream source-cache tests. Fixed by changing CWD to an isolated temp directory during the test.
6b483d0 to
6ad18ca
Compare
Maintainer rebase + analysisI've rebased this branch onto current Rebase resolutionPR #353 added
Test fixThe Crush scanner test VerificationAfter rebase:
Integration completenessCrush is wired into every required integration point — matching the pattern of Mux and other clients:
Data quality noteCrush provides session-level cost only — no per-message tokens, no model attribution. This is an honest v1 that imports what Crush reliably exposes. The Remaining gapREADME.md is not updated — Crush is missing from the client support table, filtering examples, data sources section, and Windows paths table. This could be addressed in a follow-up. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Both clients were missing from documentation across all 4 locales. Added to: client support table, multi-platform feature list, filtering examples, frontend source filtering, Windows paths, and data sources sections.
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
⚠️ 1 issue in files not directly in the diff
⚠️ CACHE_SCHEMA_VERSION not bumped after adding message_count to bincode-serialized UnifiedMessage (crates/tokscale-core/src/message_cache.rs:14)
The PR adds a new message_count: i32 field to UnifiedMessage (crates/tokscale-core/src/sessions/mod.rs:37), which is serialized into the on-disk message cache via bincode (crates/tokscale-core/src/message_cache.rs:401). Bincode is a positional binary format that does not honour #[serde(default)] for missing fields — so any cache written by the previous release (schema version 4, without message_count) will fail to deserialize under the new struct layout. The CACHE_SCHEMA_VERSION at crates/tokscale-core/src/message_cache.rs:14 remains 4 instead of being bumped to 5. Because deserialize_from(...).ok()? gracefully returns None on failure (crates/tokscale-core/src/message_cache.rs:434-437), the stale cache is silently discarded and data is re-parsed — so there is no crash or data loss. However, the schema-version mechanism exists precisely to short-circuit this: without the bump the code reads and attempts to parse the entire (potentially large) cache file before giving up, instead of rejecting it immediately at the version check.
View 10 additional findings in Devin Review.
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
🐛 1 issue in files not directly in the diff
🐛 KiloCode parser outputs client = "kilo" instead of "kilocode", breaking the KiloCode/Kilo CLI split (crates/tokscale-core/src/sessions/kilocode.rs:10)
After splitting the old combined Kilo client into KiloCode (VS Code extension, id "kilocode") and Kilo (CLI, id "kilo"), the parse_kilocode_file function at crates/tokscale-core/src/sessions/kilocode.rs:10 still passes "kilo" as the source/client string to parse_roo_kilo_file. This means all VS Code KiloCode messages will have client = "kilo" — identical to Kilo CLI messages — instead of client = "kilocode".
This causes three failures:
--kilocodefilter returns nothing: The filter sends"kilocode"toretain_for_requested_clients(crates/tokscale-core/src/lib.rs:63) which checksrequested.contains(client). Since KiloCode messages haveclient = "kilo", they won't match.- KiloCode data mixed into Kilo CLI:
--kilowill return both Kilo CLI AND KiloCode VS Code data since both haveclient = "kilo". - Wrong display names:
client_display_name("kilo")returns"Kilo CLI"(crates/tokscale-cli/src/commands/wrapped.rs:1363), so VS Code extension usage will be mislabeled as "Kilo CLI" in wrapped images and reports.
View 14 additional findings in Devin Review.
…pport # Conflicts: # crates/tokscale-cli/src/main.rs # crates/tokscale-core/src/clients.rs # crates/tokscale-core/src/scanner.rs
Resolve new PR junhoyeo#346 review findings by removing legacy kilocode->kilo normalization, propagating use_env_roots to Crush DB discovery, and including Kilo/Mux in --test-data diagnostics.
|
🚀 Released in |
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.
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.
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.
Summary
Why
Crush is a good fit for Tokscale, but its storage model differs from existing clients. It stores usage in per-project SQLite databases and exposes reliable session-level totals, not clean per-message token accounting. This PR adds a narrow, honest v1 integration that imports only the data Crush stores reliably instead of fabricating model-level attribution.
Diff scope
Test proof
Verification-pack proof
Not applicable - core and CLI local parsing changes only.
Migration notes
Not applicable - no schema or data migration.
CI context confirmation
CI context names unchanged.
Rollback plan
Known residual risks
Related