feat: add Gemini CLI usage tracking via gemistat#77
Conversation
Integrate Gemini CLI as a third usage source alongside Claude Code (ccusage) and Codex (@ccusage/codex). Uses gemistat by ryoppippi (same author as ccusage) as a subprocess, following the identical pattern established by the Codex integration. - Create gemini.ts with gemistat subprocess runner and JSON parser - Extend mergeEntries() to 3-way merge (Claude + Codex + Gemini) - Run all three sources in parallel via Promise.all - Add Gemini model colors (Google brand palette) to CLI theme - Add Gemini prettifyModel rules to all 5 web/CLI copies - Fix preview/experimental suffix stripping (-preview-05-06, -exp-03-25) - Add Gemini colors to web modelColor() with 8 model entries - Add 20 new tests (gemini parser, merge, push integration) - Silent failure: users without Gemini CLI are unaffected - Pin gemistat@1 for version stability - Convert YYYYMMDD dates to YYYY-MM-DD (gemistat's expected format) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@nihalnihalani is attempting to deploy a commit to the Pacific Systems Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds Gemini support: prettifies Gemini model IDs in web UI, assigns Gemini model colors, adds a new CLI gemistat integration (run/parse), extends push merge logic to 3-way (Claude/Codex/Gemini), and adds tests and docs for the integration. Changes
Sequence DiagramsequenceDiagram
participant CLI as CLI Push Command
participant Claude as Claude Subprocess
participant Codex as Codex Subprocess
participant Gemini as gemistat Subprocess
participant Parser as parseGeminiOutput
participant Merge as mergeEntries
participant API as Stats API
CLI->>Claude: runCcusageRawAsync()
CLI->>Codex: runCodexRawAsync()
CLI->>Gemini: runGeminiRawAsync()
par Parallel subprocesses
Claude-->>CLI: Claude JSON
Codex-->>CLI: Codex JSON
Gemini-->>CLI: gemistat JSON (or "")
end
CLI->>Parser: parseGeminiOutput(geminiRaw)
Parser-->>CLI: GeminiOutput {data, anomalies, entryMeta}
CLI->>Merge: mergeEntries(claudeEntries, codexEntries, geminiEntries)
Merge-->>CLI: mergedEntries[]
CLI->>API: POST /stats (mergedEntries + anomalies)
API-->>CLI: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/web/lib/utils/post-share.ts (1)
26-37: Consider consolidating Gemini model prettification into a shared helper.The same normalization logic now appears in multiple web files; extracting one shared
prettifyModelutility would reduce drift risk.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/utils/post-share.ts` around lines 26 - 37, Extract the Gemini formatting code block into a shared helper (e.g., prettifyModel) and replace the inline normalization in post-share.ts with a call to that helper; specifically, move the logic starting with the /^gemini-/i check and the chain of replace/split/map/join transformations into prettifyModel(normalized) so other modules can reuse it, export the helper from a common utils module, and update post-share.ts to import and call prettifyModel(normalized) instead of duplicating the regex/replace logic.docs/plans/2026-04-04-gemini-cli-integration.md (1)
1-13: Consider fixing heading level jump for consistent markdown structure.The static analysis tool flagged that line 13 (
### Task 1:) jumps from h1 to h3. While this is a minor issue for an internal planning document, consistent heading levels improve document navigation.📝 Suggested fix
Change
### Task N:headings to## Task N:throughout the document, or add an## Implementation Taskssection before the first task.--- -### Task 1: Add `"gemini"` to token normalization source hints +## Task 1: Add `"gemini"` to token normalization source hints🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/plans/2026-04-04-gemini-cli-integration.md` around lines 1 - 13, The document jumps from an H1 to H3 at "### Task 1:" which breaks heading hierarchy; update all "### Task N:" headings to "## Task N:" (or insert a new "## Implementation Tasks" section before the first task) so headings progress H1 -> H2 -> H3 correctly—search for the "### Task 1:" marker and the other "### Task N:" occurrences and replace them to restore consistent markdown structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/components/app/feed/ActivityCard.tsx`:
- Around line 186-194: Replace the hardcoded hex literals in ActivityCard.tsx
with theme tokens from globals.css: create or use a token-backed color map
(e.g., GEMINI_COLOR_MAP) keyed by the same model patterns and return the mapped
token instead of raw hex in the code that inspects the variable name (the
existing if-chain that tests /Gemini.../ and returns colors). Import the shared
token values (or a helper like getThemeColor) and replace each return "#xxxxxx"
with the corresponding theme token reference; fall back to the existing Gemini
indigo token for unmatched names. Ensure the mapping keys correspond to the same
regex checks (or replace the if-chain with a single resolver function like
getGeminiColor(name) that uses GEMINI_COLOR_MAP) so ActivityCard preserves
behavior but uses theme tokens.
In `@packages/cli/src/lib/gemini.ts`:
- Around line 50-77: The GEMISTAT_PKG value referenced by execGemistat and
execGemistatAsync is pointing to a non-existent npm package ("gemistat@1");
update GEMISTAT_PKG to the correct published package name and version (or change
it to a workspace/relative path if this is an internal package) so bunx/npx can
resolve it, and then verify runGeminiRaw and runGeminiRawAsync no longer
silently fail—locate GEMISTAT_PKG and the helper functions
execGemistat/execGemistatAsync and ensure the package resolution mechanism (npm
version, workspace link, or local path) is valid and accessible to bunx/npx
before calling the subprocess.
---
Nitpick comments:
In `@apps/web/lib/utils/post-share.ts`:
- Around line 26-37: Extract the Gemini formatting code block into a shared
helper (e.g., prettifyModel) and replace the inline normalization in
post-share.ts with a call to that helper; specifically, move the logic starting
with the /^gemini-/i check and the chain of replace/split/map/join
transformations into prettifyModel(normalized) so other modules can reuse it,
export the helper from a common utils module, and update post-share.ts to import
and call prettifyModel(normalized) instead of duplicating the regex/replace
logic.
In `@docs/plans/2026-04-04-gemini-cli-integration.md`:
- Around line 1-13: The document jumps from an H1 to H3 at "### Task 1:" which
breaks heading hierarchy; update all "### Task N:" headings to "## Task N:" (or
insert a new "## Implementation Tasks" section before the first task) so
headings progress H1 -> H2 -> H3 correctly—search for the "### Task 1:" marker
and the other "### Task N:" occurrences and replace them to restore consistent
markdown structure.
🪄 Autofix (Beta)
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
Run ID: f5c36471-1263-4423-8006-4bb3207a4852
📒 Files selected for processing (18)
apps/web/__tests__/unit/prettify-model.test.tsapps/web/components/app/feed/ActivityCard.tsxapps/web/lib/open-stats.tsapps/web/lib/share-assets/post-card-image.tsxapps/web/lib/utils/post-share.tsdocs/CHANGELOG.mddocs/DECISIONS.mddocs/plans/2026-04-04-gemini-cli-integration.mdpackages/cli/__tests__/commands/push.test.tspackages/cli/__tests__/flows/cli-sync-flow.test.tspackages/cli/__tests__/gemini.test.tspackages/cli/__tests__/merge.test.tspackages/cli/src/commands/push.tspackages/cli/src/components/ModelPalette.tsxpackages/cli/src/components/theme.tspackages/cli/src/lib/ccusage.tspackages/cli/src/lib/gemini.tspackages/cli/src/lib/token-normalization.ts
| if (/Gemini 3\.1 Pro/.test(name)) return "#4285F4"; | ||
| if (/Gemini 3\.1 Flash Lite/.test(name)) return "#00ACC1"; | ||
| if (/Gemini 3 Flash/.test(name)) return "#009688"; | ||
| if (/Gemini 2\.5 Pro/.test(name)) return "#3F51B5"; | ||
| if (/Gemini 2\.5 Flash Lite/.test(name)) return "#8BC34A"; | ||
| if (/Gemini 2\.5 Flash/.test(name)) return "#34A853"; | ||
| if (/Gemini 2\.0 Flash Lite/.test(name)) return "#FF8F00"; | ||
| if (/Gemini 2\.0 Flash/.test(name)) return "#FBBC05"; | ||
| if (/Gemini/i.test(name)) return "#3F51B5"; // Google indigo fallback |
There was a problem hiding this comment.
Replace hardcoded Gemini hex colors with theme tokens.
These new raw hex values in app UI logic break the web theming rule and make palette governance harder. Please source these from the shared globals.css theme token model (or a token-backed color map) instead of embedding literals here.
As per coding guidelines: apps/web/**/*.{css,tsx,ts}: “Only use colors from globals.css @theme block — no purple gradients, glow effects, or styles not already in the codebase”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/components/app/feed/ActivityCard.tsx` around lines 186 - 194,
Replace the hardcoded hex literals in ActivityCard.tsx with theme tokens from
globals.css: create or use a token-backed color map (e.g., GEMINI_COLOR_MAP)
keyed by the same model patterns and return the mapped token instead of raw hex
in the code that inspects the variable name (the existing if-chain that tests
/Gemini.../ and returns colors). Import the shared token values (or a helper
like getThemeColor) and replace each return "#xxxxxx" with the corresponding
theme token reference; fall back to the existing Gemini indigo token for
unmatched names. Ensure the mapping keys correspond to the same regex checks (or
replace the if-chain with a single resolver function like getGeminiColor(name)
that uses GEMINI_COLOR_MAP) so ActivityCard preserves behavior but uses theme
tokens.
There was a problem hiding this comment.
This is intentional and consistent with the existing pattern. The modelColor() function is a data-visualization palette that maps brand-representative hex colors to each AI model for the usage breakdown bar. All models in this function use hardcoded hex values — Claude (#DF561F, #F08A5D, #F7B267), GPT (#2A9D8F, #4C78A8), o3/o4 (#3B82F6, #6366F1), and the 6-color fallback palette on line 196. The Gemini entries follow the exact same established pattern.
The theme tokens in globals.css @theme inline define UI chrome colors (background, foreground, accent, border, muted, subtle, etc.) — there are no model-specific color tokens. Moving only the Gemini colors into the theme while leaving Claude/GPT/o3/o4 hardcoded would be inconsistent. Additionally, the function uses regex-based matching (not exact string equality), so a simple constant map wouldn't be a drop-in replacement without refactoring the matching logic.
No change needed here.
There was a problem hiding this comment.
@nihalnihalani, that's a clear and well-reasoned explanation — you're right. The modelColor() function is a data-visualization palette where hardcoded hex is the established pattern across all model families, and the globals.css theme tokens cover only UI chrome (not model brand colors). Flagging only the Gemini additions while the same approach is used throughout was inconsistent. Happy to stand down on this one.
(ᓀ‸ᓂ)
✏️ Learnings added
Learnt from: nihalnihalani
Repo: ohong/straude PR: 77
File: apps/web/components/app/feed/ActivityCard.tsx:186-194
Timestamp: 2026-04-04T23:07:34.823Z
Learning: In `apps/web/components/app/feed/ActivityCard.tsx`, the `modelColor()` function intentionally uses hardcoded hex color literals for data-visualization purposes (the model usage breakdown bar). This is the established pattern for ALL model families — Claude (`#DF561F`, `#F08A5D`, `#F7B267`), GPT (`#2A9D8F`, `#4C78A8`), o3/o4 (`#3B82F6`, `#6366F1`), and the 6-color hash-based fallback palette. The `globals.css` theme block only defines UI chrome tokens (background, foreground, accent, border, muted, subtle), not model-specific palette colors. Do not flag hardcoded hex values in this function as a theming violation.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: CR
Repo: ohong/straude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T19:15:44.038Z
Learning: Applies to apps/web/**/*.{css,tsx,ts} : Use accent color `#DF561F` only for interactive elements and emphasis
Learnt from: CR
Repo: ohong/straude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T19:15:44.038Z
Learning: Applies to apps/web/**/*.{css,tsx,ts} : Only use colors from `globals.css` `theme` block — no purple gradients, glow effects, or styles not already in the codebase
The gemistat package only has 0.x releases (0.0.0, 0.1.0, 0.1.1). Pinning to @1 caused silent subprocess failures since bunx/npx could not resolve the non-existent major version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
What changed
CLI Core (
packages/cli/)src/lib/gemini.tsbunx gemistat@1 daily --json), JSON parser,toIsoDate()date converter (gemistat expectsYYYY-MM-DD, notYYYYMMDD)src/commands/push.tsmergeEntries(claude, codex, gemini), gemini inPromise.all, anomaly/blocked-date filtering, hash includes gemini rawsrc/lib/token-normalization.ts"gemini"toTokenSourceHints.sourceunionsrc/lib/ccusage.ts"gemini"toNormalizationAnomaly.sourceunionsrc/components/theme.tssrc/components/ModelPalette.tsxprettifyModel+getModelColorfallbackWeb (
apps/web/)components/app/feed/ActivityCard.tsxprettifyModel, suffix stripping, 8 Gemini entries + fallback inmodelColor()lib/utils/post-share.tsprettifyModel+ suffix strippinglib/share-assets/post-card-image.tsxprettifyModel+ suffix strippinglib/open-stats.tsprettifyModel+ suffix strippingTests (20 new tests)
__tests__/gemini.test.ts__tests__/merge.test.ts__tests__/commands/push.test.tsapps/web/__tests__/unit/prettify-model.test.ts__tests__/flows/cli-sync-flow.test.tsDocs
CHANGELOG.md— Gemini integration entry under UnreleasedDECISIONS.md— Architecture decision: gemistat subprocess vs direct telemetry parsingdocs/plans/2026-04-04-gemini-cli-integration.md— Full implementation planDesign decisions
Why gemistat (not direct telemetry parsing)?
{ daily: [...], totals })Key details:
gemistat@1(major version, avoids registry roundtrip)toIsoDate()— gemistat expectsYYYY-MM-DD, ccusage expectsYYYYMMDDtotalTokenscomputed client-side (gemistat doesn't provide it)"separate"(same as ccusage)gemini-3.1-pro-preview-05-06→Gemini 3.1 ProData flow
Test plan
tscin CLI package)straude push, verify Gemini models appear in feedstraude pushwithout gemistat installed — verify silent failure, Claude/Codex data still submits🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation