Repository navigation
feat(grok): add Grok Build CLI usage adapter - #1593
Conversation
Parse completed Grok Build turns, map token usage and LiteLLM pricing, and expose focused and unified reports. Discover data from non-empty GROK_HOME or ~/.grok, add schema and CLI integration, and document the source.
Session reports grouped by key then moved the group key into session_id, which left first_activity, last_activity and project_path unset. Accumulate with SessionAccumulator like qwen/opencode/antigravity, and include session metadata in the JSON report.
Add fixture-backed tests for path discovery edge cases, token/pricing mapping, timestamp and summary metadata, cross-session dedupe, corrupt file isolation, and session aggregation. Synthesize updates.jsonl rather than committing real session trees.
…usage-adapter # Conflicts: # apps/ccusage/README.md # docs/guide/index.md # docs/guide/source-support-qa.md # rust/crates/ccusage-adapter-all/src/loader.rs # rust/crates/ccusage-cli-parser/src/parser.rs # rust/crates/ccusage-cli-parser/src/snapshots/ccusage_cli_parser__tests__root_help.snap # rust/crates/ccusage-cli-parser/src/tests.rs # rust/crates/ccusage-config/src/config_schema.rs # rust/crates/ccusage-config/src/snapshots/ccusage_config__config_schema__tests__snapshots_schema_agent_specific_option_edges.snap # rust/crates/ccusage-core/src/lib.rs # rust/crates/ccusage/src/cli/last_window.rs
…tals Two corrections to the Grok adapter, both found by comparing its output against real ~/.grok logs. Cost: every completed turn records `costUsdTicks`, a fixed-point USD amount where one tick is 1e-10 USD. The adapter discarded it and recomputed from the pricing table instead. Grok bills each API request separately, but a `turn_completed` row only carries the sum over the requests in that turn, so a recomputation cannot place the long-context tier boundary where Grok did and lands on either side of the real figure. Across 85 turns of local data the recomputed total is $15.18 against an actual $17.40, and per-day it ranges from -46% to +12%. Feeding the ticks through `cost_usd` reproduces the invoice exactly in `display` and `auto`, while `calculate` keeps recomputing and `auto` still falls back for turns that recorded no ticks. Tokens: `reasoningTokens` were added to `extra_total_tokens`, which is the bucket for tokens *not* already covered by the usage sum. Grok reports `totalTokens == inputTokens + outputTokens` and its reasoning count never exceeds output, so reasoning is a subset of output and was being counted twice. That inflated the grand total by 113,556 tokens over the same 85 turns. The `turn_line` test helper moves its four token counts into a struct so it stays under the clippy argument limit.
Merging main brought the adapter count to 15 while the array annotation in the `agent_commands_are_exposed_by_independent_crates` test still said 14, so the test crate stopped compiling. Also runs treefmt over the two files the grok branch left unformatted, which the clippy and treefmt checks require.
Grok started writing `cacheCreationTokens` alongside `cachedReadTokens` around CLI 0.2.118 and still writes it in 1.0.0. The adapter hardcoded `cache_creation_input_tokens: 0`, so the field would be dropped the moment it went non-zero. Every turn observed so far reports it as zero, so there is no sample proving whether it sits inside `inputTokens` or beside it. `cachedReadTokens` is provably inside — session totals match `logs/unified.jsonl`, where `cached_prompt_tokens` is part of `prompt_tokens` — so the sibling field is carved out of the same total. That keeps the three parts summing back to `inputTokens`, which is the invariant the adapter already relies on. Also documents two limits that surfaced while checking real logs: a session killed mid-turn never writes `turn_completed`, so its usage cannot be reported at all, and `logs/unified.jsonl` cannot stand in for it because it records no per-request model id.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Grok Build CLI as a supported ChangesGrok support
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant CcusageCLI
participant GrokAdapter
participant GrokSessionFiles
participant UnifiedReports
User->>CcusageCLI: run ccusage grok daily/monthly/session
CcusageCLI->>GrokAdapter: dispatch Command::Grok
GrokAdapter->>GrokSessionFiles: discover updates.jsonl files
GrokSessionFiles-->>GrokAdapter: return session files
GrokAdapter->>GrokAdapter: parse completed turns and calculate costs
GrokAdapter-->>CcusageCLI: return formatted or JSON report
UnifiedReports->>GrokAdapter: load Grok entries
GrokAdapter-->>UnifiedReports: return summarized usage rows
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | 0a5ab84 | Commit Preview URL Branch Preview URL |
Aug 10 2026, 03:35 PM |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Grok adapter crate —
rust/adapters/grok/: token mapping (inputTokenssplit into uncached/cache-read/cache-creation),costUsdTicksinvoice-cost path withdisplay/auto/calculatemodes, and pricing candidate resolution (grok-4.5-build→xai/grok-4.5, etc.) - CLI wiring — standard agent pattern:
Command::Grokvariant intypes.rs,parse_basic_agent_commandinparser.rs, root-help and parse-shape snapshots,--lastand config dispatch - Adapter-all integration —
AgentLoadSpecat index 15,agent_label("grok") → "Grok", unified-row tests withGROK_HOMEfixture - Config schema —
GrokConfig/GrokCommandsConfigstructs, schema JSON regeneration, source-specific option isolation tests - Docs — new
docs/guide/grok/index.md, updated agent lists in 6 other doc pages,GROK_HOMEin env-variables table,source-support-qamoved Grok from unsupported to supported, env-only path discovery design doc
@v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
rust/adapters/grok/src/loader.rs (1)
74-76: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
has_datadoes not short-circuit.
discover_session_fileswalks the wholesessionstree, collects every.jsonlpath, filters the list, and sorts it.has_datathen only checks whether the result is non-empty. On a large Grok home this reads far more directory entries than needed.Add a detection helper in
rust/adapters/grok/src/paths.rsthat returns as soon as it finds the firstupdates.jsonl, and call it here.♻️ Proposed fix
// rust/adapters/grok/src/paths.rs /// Return `true` as soon as one `sessions/**/updates.jsonl` exists. pub(super) fn has_session_file() -> bool { let Some(root) = resolve_root() else { return false; }; let sessions = root.join("sessions"); // Walk lazily and stop at the first match instead of collecting every path. // ... }pub fn has_data() -> bool { - discover_session_files().is_ok_and(|files| !files.is_empty()) + super::paths::has_session_file() }As per coding guidelines: "Implement fast detection that short-circuits once a usable source file is found."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/adapters/grok/src/loader.rs` around lines 74 - 76, Replace the `discover_session_files` call in `has_data` with a new `has_session_file` helper from `paths.rs`. Implement `has_session_file` to resolve the root, walk `sessions` lazily, and return true immediately upon finding any `updates.jsonl`, returning false when the root is unavailable or no match exists.Source: Coding guidelines
rust/adapters/grok/src/parser.rs (1)
320-320: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the no-op read of
total_tokens.
let _ = model_usage.total_tokens;has no effect.model_usage_rowsalready populates the field fromusage.total_tokens, so the statement is not needed to suppress a dead-field warning. If the field is intentionally unused for accounting, state that in a comment instead.♻️ Proposed cleanup
- let _ = model_usage.total_tokens;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/adapters/grok/src/parser.rs` at line 320, Remove the no-op `model_usage.total_tokens` read near the model usage handling; `model_usage_rows` already populates this field, so no replacement is needed unless an intentional accounting decision must be documented with a comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/guide/environment-variables.md`:
- Line 26: Update the directory-variable guidance in
docs/guide/environment-variables.md:26-26 to explicitly exclude GROK_HOME from
comma-separated multi-root behavior. Also revise the custom-path guidance in
docs/guide/getting-started.md:200-200 so GROK_HOME is not included in the
multi-directory statement; preserve its single-root contract.
In `@rust/adapters/grok/README.md`:
- Around line 63-69: Update the “Public surface” list in the README to remove
report::report_from_rows, leaving only the publicly accessible symbols
loader::has_data, loader::load_entries, report::summarize_entries, and run.
In `@rust/adapters/grok/src/loader.rs`:
- Around line 48-69: Align the fallback deduplication key in the loader’s
entries.retain block with parse_session_files by using reasoning tokens as its
final discriminator instead of entry.extra_total_tokens. If reasoning is not
available on LoadedEntry, add and populate that field through the loader path,
then use it consistently in both keys while preserving the existing event-ID key
behavior.
In `@rust/adapters/grok/src/parser.rs`:
- Around line 498-516: Update url_decode_lightweight to accumulate decoded
percent bytes and raw UTF-8 bytes in a byte buffer, then construct the result
once with String::from_utf8_lossy (or reuse an established percent-decoding
crate) so UTF-8 project paths remain intact. Add a test covering a UTF-8 project
segment such as %C3%A9 and verify it produces é rather than mojibake.
In `@rust/adapters/grok/src/report.rs`:
- Around line 83-126: Update the Grok test helper entry to set
extra_total_tokens to zero, matching parse_session_files’ production contract
where reasoning tokens are included in outputTokens. Adjust the affected
daily-total and session assertions to expect 120 and 0 respectively, and prefer
fixture-backed parser/loader coverage for these Rust tests where applicable.
---
Nitpick comments:
In `@rust/adapters/grok/src/loader.rs`:
- Around line 74-76: Replace the `discover_session_files` call in `has_data`
with a new `has_session_file` helper from `paths.rs`. Implement
`has_session_file` to resolve the root, walk `sessions` lazily, and return true
immediately upon finding any `updates.jsonl`, returning false when the root is
unavailable or no match exists.
In `@rust/adapters/grok/src/parser.rs`:
- Line 320: Remove the no-op `model_usage.total_tokens` read near the model
usage handling; `model_usage_rows` already populates this field, so no
replacement is needed unless an intentional accounting decision must be
documented with a comment.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6a9d153e-c3e7-497c-8499-cdaae4f5ccb8
⛔ Files ignored due to path filters (4)
rust/Cargo.lockis excluded by!**/*.lockrust/crates/ccusage-cli-parser/src/snapshots/ccusage_cli_parser__tests__root_help.snapis excluded by!**/*.snaprust/crates/ccusage-cli-parser/src/snapshots/ccusage_cli_parser__tests__snapshots_representative_cli_parse_shapes.snapis excluded by!**/*.snaprust/crates/ccusage-config/src/snapshots/ccusage_config__config_schema__tests__snapshots_schema_agent_specific_option_edges.snapis excluded by!**/*.snap
📒 Files selected for processing (38)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/config-files.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/grok/index.mddocs/guide/index.mddocs/guide/source-support-qa.mddocs/superpowers/specs/2026-07-28-grok-env-only-path-design.mdrust/Cargo.tomlrust/adapters/grok/Cargo.tomlrust/adapters/grok/README.mdrust/adapters/grok/src/lib.rsrust/adapters/grok/src/loader.rsrust/adapters/grok/src/parser.rsrust/adapters/grok/src/paths.rsrust/adapters/grok/src/report.rsrust/crates/ccusage-adapter-all/Cargo.tomlrust/crates/ccusage-adapter-all/README.mdrust/crates/ccusage-adapter-all/src/lib.rsrust/crates/ccusage-adapter-all/src/loader.rsrust/crates/ccusage-adapter-all/src/report.rsrust/crates/ccusage-adapter-all/src/tests.rsrust/crates/ccusage-cli-parser/src/cli-commands.jsonrust/crates/ccusage-cli-parser/src/parser.rsrust/crates/ccusage-cli-parser/src/tests.rsrust/crates/ccusage-cli/README.mdrust/crates/ccusage-cli/src/types.rsrust/crates/ccusage-config/src/config.rsrust/crates/ccusage-config/src/config_schema.rsrust/crates/ccusage-core/src/lib.rsrust/crates/ccusage/Cargo.tomlrust/crates/ccusage/README.mdrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/cli/last_window.rsrust/crates/ccusage/src/main.rs
Co-authored-by: Codesmith <[email protected]>
DiagnosisThe check suite (ID FixCommit StatusThe fix is already pushed to the Task list (3/6 completed)
|
…root contract Decode percent triplets into bytes so multi-byte cwd segments survive, drop the always-zero discriminator from the loader dedupe key, match the report test helper to the parser's reasoning contract, and correct the README surface list plus the GROK_HOME multi-root docs. Co-authored-by: Codesmith <[email protected]>
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="rust/adapters/grok/src/parser.rs">
<violation number="1" location="rust/adapters/grok/src/parser.rs:441">
P2: Distinct no-`eventId` turns can be dropped when only `cacheCreationTokens` differs, undercounting token and cost reports. Include cache-creation tokens in this fallback key (and the matching cross-file fallback key in `loader.rs`).</violation>
<violation number="2" location="rust/adapters/grok/src/parser.rs:498">
P3: Fallback project paths are mojibake for non-ASCII CWDs when `summary.json` is absent. Decode percent escapes into bytes, then construct the UTF-8 string once.</violation>
</file>
<file name="docs/guide/environment-variables.md">
<violation number="1" location="docs/guide/environment-variables.md:26">
P3: The new `GROK_HOME` row is listed in the environment-variables table, but the intro right above it promises that 'Directory variables can be one directory or a comma-separated list of directories'. `GROK_HOME` is a single root — the Grok adapter (resolve_root) does not split on commas and instead treats the whole, non-directory value as missing and falls back to `~/.grok`, so a comma-separated `GROK_HOME` will silently not do what the intro implies. This same PR tightened the language in `guide/index.md` to 'variables that support multiple roots can contain comma-separated directories', which is the right framing. Consider applying the same clarification to the `environment-variables.md` intro (e.g. noting that most directory variables support comma-separated lists, with `GROK_HOME` being a single root) so readers don't configure it as a list.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| return format!("{event_id}|{model}"); | ||
| } | ||
| format!( | ||
| "{session_id}|{}|{model}|{}|{}|{}|{reasoning}", |
There was a problem hiding this comment.
P2: Distinct no-eventId turns can be dropped when only cacheCreationTokens differs, undercounting token and cost reports. Include cache-creation tokens in this fallback key (and the matching cross-file fallback key in loader.rs).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/adapters/grok/src/parser.rs, line 441:
<comment>Distinct no-`eventId` turns can be dropped when only `cacheCreationTokens` differs, undercounting token and cost reports. Include cache-creation tokens in this fallback key (and the matching cross-file fallback key in `loader.rs`).</comment>
<file context>
@@ -0,0 +1,1051 @@
+ return format!("{event_id}|{model}");
+ }
+ format!(
+ "{session_id}|{}|{model}|{}|{}|{}|{reasoning}",
+ timestamp.as_millis(),
+ usage.input_tokens,
</file context>
| ) | ||
| } | ||
|
|
||
| fn url_decode_lightweight(value: &str) -> String { |
There was a problem hiding this comment.
P3: Fallback project paths are mojibake for non-ASCII CWDs when summary.json is absent. Decode percent escapes into bytes, then construct the UTF-8 string once.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/adapters/grok/src/parser.rs, line 498:
<comment>Fallback project paths are mojibake for non-ASCII CWDs when `summary.json` is absent. Decode percent escapes into bytes, then construct the UTF-8 string once.</comment>
<file context>
@@ -0,0 +1,1051 @@
+ )
+}
+
+fn url_decode_lightweight(value: &str) -> String {
+ // Session parents are URL-encoded cwd paths (e.g. `D%3A%5Cproj`).
+ let bytes = value.as_bytes();
</file context>
| | `QWEN_DATA_DIR` | Qwen | `~/.qwen` | | ||
| | `COPILOT_OTEL_FILE_EXPORTER_PATH` | Copilot CLI | Explicit `.jsonl` file | | ||
| | `GEMINI_DATA_DIR` | Gemini CLI | `~/.gemini/tmp` | | ||
| | `GROK_HOME` | Grok Build CLI | `~/.grok` | |
There was a problem hiding this comment.
P3: The new GROK_HOME row is listed in the environment-variables table, but the intro right above it promises that 'Directory variables can be one directory or a comma-separated list of directories'. GROK_HOME is a single root — the Grok adapter (resolve_root) does not split on commas and instead treats the whole, non-directory value as missing and falls back to ~/.grok, so a comma-separated GROK_HOME will silently not do what the intro implies. This same PR tightened the language in guide/index.md to 'variables that support multiple roots can contain comma-separated directories', which is the right framing. Consider applying the same clarification to the environment-variables.md intro (e.g. noting that most directory variables support comma-separated lists, with GROK_HOME being a single root) so readers don't configure it as a list.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/guide/environment-variables.md, line 26:
<comment>The new `GROK_HOME` row is listed in the environment-variables table, but the intro right above it promises that 'Directory variables can be one directory or a comma-separated list of directories'. `GROK_HOME` is a single root — the Grok adapter (resolve_root) does not split on commas and instead treats the whole, non-directory value as missing and falls back to `~/.grok`, so a comma-separated `GROK_HOME` will silently not do what the intro implies. This same PR tightened the language in `guide/index.md` to 'variables that support multiple roots can contain comma-separated directories', which is the right framing. Consider applying the same clarification to the `environment-variables.md` intro (e.g. noting that most directory variables support comma-separated lists, with `GROK_HOME` being a single root) so readers don't configure it as a list.</comment>
<file context>
@@ -6,23 +6,24 @@ ccusage supports several environment variables for configuration and customizati
+| `QWEN_DATA_DIR` | Qwen | `~/.qwen` |
+| `COPILOT_OTEL_FILE_EXPORTER_PATH` | Copilot CLI | Explicit `.jsonl` file |
+| `GEMINI_DATA_DIR` | Gemini CLI | `~/.gemini/tmp` |
+| `GROK_HOME` | Grok Build CLI | `~/.grok` |
Example:
</file context>
Co-authored-by: Codesmith <[email protected]>
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-x64
commit: |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- UTF-8 project path decoding —
url_decode_lightweightnow accumulates percent-decoded bytes into aVec<u8>and constructs the result withString::from_utf8_lossy, fixing mojibake for multi-byte UTF-8 path segments like%C3%A9(é). A new testurl_decodes_multi_byte_project_segmentcovers this. - Dedupe key simplification — Removed
extra_total_tokensfrom the loader's fallback dedupe key since it is always 0 for Grok entries (reasoning stays insideoutput_tokens). The key now uses session, timestamp, model, input, output, and cache-read — consistent with the parser's key sans reasoning. GROK_HOMEsingle-root docs — Clarified indocs/guide/environment-variables.md,docs/guide/getting-started.md, anddocs/guide/grok/index.mdthatGROK_HOMEaccepts a single root only, unlike comma-separated agent directories. AddedGROK_HOMEto the env-var example and grep command.- Test assertion alignments — Updated report and parser tests to expect
extra_total_tokens: 0andtotalTokens: 120(reasoning was previously double-counted at 130), matching the production contract.
@v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
Co-authored-by: Codesmith <[email protected]>
The existing case decodes a two-byte scalar end-to-end. This adds a direct unit test for a three-triplet CJK segment, and pins the behaviour for a percent triplet that is not valid UTF-8: it degrades to the replacement character rather than panicking or dropping the rest of the path.
The global dedupe rebuilt a content key for entries without an `eventId`, but that key can only be built from what `LoadedEntry` carries, and reasoning tokens are not among them. `parse_session_files` deduped its own file using the full record including reasoning, so two eventId-less turns in the same second with identical input, output and cache counts but different reasoning were kept by the parser and then collapsed here, dropping one turn entirely. Cross-file dedupe is about the same server event appearing in more than one session export, which is what `eventId` identifies. Entries without one are now left alone rather than matched on token counts. Reported by an adversarial review pass. Every `turn_completed` in the logs checked so far carries an `eventId`, so this was not reachable in practice.
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Global cross-file dedupe narrowed to
eventIdonly — entries without an eventId are always kept. The single-file parser already deduped them with the full record (including reasoning tokens, whichLoadedEntrydoes not carry), so a coarser fallback key could only collapse distinct turns the parser deliberately kept apart. - Regression test
keeps_event_id_less_turns_that_differ_only_in_reasoning— validates that two eventId-less turns differing only in their reasoning count both survive the global dedupe. url_decode_lightweightedge-case coverage — new test exercises wide CJK scalars, Windows-style drive-letter paths, and invalid UTF-8 byte triples that must degrade to the replacement character without panicking or truncation.- Module-local helpers made private —
split_tokens,pricing_candidates, andresolve_rootchanged frompub(super)to plainfnfor hawk compliance.
@v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
Resolves the models.dev snapshot conflict by regenerating it from the merged pin rather than taking either side: main's copy was produced by the hourly job under the old selection rules, and this branch's predates the pin bump. The snapshot goes to 602 entries. Grok Build (#1593) landed on main meanwhile, which is what makes the three adapter test adjustments below necessary. `grok-4.5` is now in the snapshot, so `find("grok-4.5-build")` resolves on the grok adapter's *first* pricing candidate instead of falling through to the `xai/grok-4.5` form. The two cost-mode tests overrode the key the fallback used to land on, so they now override `grok-4.5`; the candidate-order test needs a model no table prices at all, or the first candidate resolves it and the ordering is never exercised. Both keys carry xAI's list rate ($2/$6 per Mtok) in every table, so reported costs are unchanged. What does change is that a user pricing override keyed `xai/grok-4.5` no longer reaches `grok-4.5-build`, while one keyed `grok-4.5` now does. Flagged on the PR rather than papered over: the adapter's candidate order is advisory once `PricingMap::find` matches fuzzily, and restoring the old precedence would mean an exact-first lookup in the adapter.

Summary
Adds the Grok Build CLI as a data source:
ccusage grok daily|monthly|session, plus Grok rows in the unified reports.This builds on @LeeShunEE's work in #1520 (branch kept intact in the history, then merged with
main) and corrects three things that only showed up when the output was checked against real~/.groklogs.Closes #1513. Supersedes #1520, #1447, #1202.
What changed on top of #1520
Cost now comes from Grok's own
costUsdTicks. Every completed turn records it as fixed-point USD, one tick being 1e-10 USD. The adapter discarded it and recomputed from the pricing table. Grok bills each API request separately, but aturn_completedrow only carries the sum over the requests in that turn, so recomputation cannot place the long-context tier boundary where Grok did — it lands on either side of the real figure depending on how the turn split:displayand the defaultautonow report the invoice figure.calculatestill recomputes, andautofalls back to the pricing table for turns that recorded no ticks (Grok only started writingusageon 2026-07-19).Reasoning tokens are no longer counted twice. They were added to
extra_total_tokens, the bucket for tokens not already covered by the usage sum. Grok reportstotalTokens == inputTokens + outputTokensand its reasoning count never exceeds output, so reasoning is a subset of output. Over 85 local turns this inflated the grand total by 113,556 tokens.cacheCreationTokensis read. Grok added the field around CLI 0.2.118 and still writes it in 1.0.0; the adapter hardcodedcache_creation_input_tokens: 0. Every observed turn reports zero, so there is no sample proving whether it sits insideinputTokensor beside it — it is carved out of the same total, matching the proven behaviour ofcachedReadTokensand keeping the parts summing back toinputTokens.Two smaller items: the merge with
mainleft the adapter-count array inagent_commands_are_exposed_by_independent_cratesat 14 while there are now 15, and theturn_linetest helper exceeded the clippy argument limit, so its token counts moved into a struct.Testing
cargo test --workspace— 573 passed, 0 failedcargo clippy --workspace --all-targets -- -D warnings— cleantreefmt— clean~/.grok(85 turns across Grok CLI 0.2.103 through 1.0.0):ccusage grok dailyThe tick scale was pinned on a single-request turn, where no tiering applies: 7,180 uncached x $2.00/M + 11,264 cached x $0.30/M + 130 output x $6.00/M = $0.0185192, against a recorded 185,192,000 ticks. All 58 turns from CLI 1.0.0 reproduce the xAI list price for
xai/grok-4.5exactly, which also confirms thatgrok-4.5-buildbills atgrok-4.5rates rather than the separately-listedgrok-build-0.1.Known limits, documented rather than fixed
turn_completed, so its usage cannot be reported.logs/unified.jsonlrecords the underlying requests but carries no per-request model id, so it cannot be priced or attributed and is not used as a source.usageat all.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is enabled.Summary by cubic
Adds Grok Build CLI as a first-class data source with daily, monthly, and session reports in unified views. Uses Grok’s recorded
costUsdTicksfor invoice-accurate costs and decodes UTF‑8 project paths.New Features
ccusage-adapter-grokparsingsessions/**/updates.jsonlunderGROK_HOMEor~/.grok(env-only, no path flag).ccusage grok daily,monthly,session; Grok also appears in unifieddaily|monthly|session.costUsdTicks;autofalls back when ticks are missing;calculatestill recomputes.cacheCreationTokensand keeps parts summing to input tokens.grokconfig schema, and docs added.Bug Fixes
eventId; entries without one are left as-is so distinct turns aren’t collapsed.GROK_HOMEis a single root (no comma-separated paths).Written for commit 0a5ab84. Summary will update on new commits.
Summary by CodeRabbit
New Features
GROK_HOMEor the default data directory.Documentation