Skip to content

feat: add Gemini CLI usage tracking via gemistat#77

Open
nihalnihalani wants to merge 2 commits into
ohong:mainfrom
nihalnihalani:feat/gemini-cli-integration
Open

feat: add Gemini CLI usage tracking via gemistat#77
nihalnihalani wants to merge 2 commits into
ohong:mainfrom
nihalnihalani:feat/gemini-cli-integration

Conversation

@nihalnihalani

@nihalnihalani nihalnihalani commented Apr 4, 2026

Copy link
Copy Markdown

Summary

  • Adds Gemini CLI as the third usage source alongside Claude Code (ccusage) and Codex (@ccusage/codex), using gemistat by ryoppippi (same author as ccusage)
  • Mirrors the existing Codex integration pattern exactly — subprocess call, JSON parsing, silent failure, 3-way date merge
  • Gemini models display with Google brand colors across CLI scorecard and web feed, with proper prettification of all model IDs

What changed

CLI Core (packages/cli/)

File Change
src/lib/gemini.ts New — gemistat subprocess runner (bunx gemistat@1 daily --json), JSON parser, toIsoDate() date converter (gemistat expects YYYY-MM-DD, not YYYYMMDD)
src/commands/push.ts 3-way mergeEntries(claude, codex, gemini), gemini in Promise.all, anomaly/blocked-date filtering, hash includes gemini raw
src/lib/token-normalization.ts Added "gemini" to TokenSourceHints.source union
src/lib/ccusage.ts Added "gemini" to NormalizationAnomaly.source union
src/components/theme.ts 8 Gemini model colors (Google Blue → Amber gradient by tier)
src/components/ModelPalette.tsx Gemini prettifyModel + getModelColor fallback

Web (apps/web/)

File Change
components/app/feed/ActivityCard.tsx Gemini prettifyModel, suffix stripping, 8 Gemini entries + fallback in modelColor()
lib/utils/post-share.ts Gemini prettifyModel + suffix stripping
lib/share-assets/post-card-image.tsx Gemini prettifyModel + suffix stripping
lib/open-stats.ts Gemini prettifyModel + suffix stripping

Tests (20 new tests)

File Tests
__tests__/gemini.test.ts New — 14 tests: parsing, date conversion, multi-model, silent failure, edge cases
__tests__/merge.test.ts New — 3 tests: 3-way merge, gemini-only, backward compat
__tests__/commands/push.test.ts 3 new: timeout forwarding, silent failure, gemini-only submission
apps/web/__tests__/unit/prettify-model.test.ts 8 new Gemini model prettification cases
__tests__/flows/cli-sync-flow.test.ts Updated for 3rd subprocess mock

Docs

  • CHANGELOG.md — Gemini integration entry under Unreleased
  • DECISIONS.md — Architecture decision: gemistat subprocess vs direct telemetry parsing
  • docs/plans/2026-04-04-gemini-cli-integration.md — Full implementation plan

Design decisions

Why gemistat (not direct telemetry parsing)?

  • Same author as ccusage, same output format ({ daily: [...], totals })
  • Handles OpenTelemetry parsing, LiteLLM pricing lookup, and daily aggregation
  • Keeps the CLI thin — no Gemini pricing table to maintain
  • Silent failure like Codex — existing users unaffected

Key details:

  • Pinned to gemistat@1 (major version, avoids registry roundtrip)
  • Dates converted via toIsoDate() — gemistat expects YYYY-MM-DD, ccusage expects YYYYMMDD
  • totalTokens computed client-side (gemistat doesn't provide it)
  • Cache semantics: "separate" (same as ccusage)
  • Preview/experimental suffixes stripped: gemini-3.1-pro-preview-05-06Gemini 3.1 Pro

Data flow

~/.gemini/telemetry.log
    ↓
gemistat (bunx gemistat@1 daily --json)
    ↓
gemini.ts: parseGeminiOutput() → CcusageDailyEntry[]
    ↓
push.ts: mergeEntries(claude, codex, gemini)
    ↓
POST /api/usage/submit (no server changes needed)

Test plan

  • CLI: 225 tests pass (16 files)
  • Web: 20 prettify-model tests pass
  • TypeScript: compiles cleanly (tsc in CLI package)
  • Manual: install gemistat, enable Gemini CLI telemetry, run straude push, verify Gemini models appear in feed
  • Manual: run straude push without gemistat installed — verify silent failure, Claude/Codex data still submits
  • Manual: verify Gemini model colors render correctly in CLI scorecard and web feed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Gemini usage tracking to the CLI and unified Gemini model display with human-readable names and dedicated palette across CLI and web.
  • Bug Fixes

    • Fixed inconsistent Gemini model name formatting and adjusted fallback messaging to reference “other sources” when local data is missing.
  • Tests

    • Added comprehensive tests for Gemini parsing, merging, subprocess behavior, and push workflows.
  • Documentation

    • Updated changelog, decisions, and integration plan for Gemini support.

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>
@vercel

vercel Bot commented Apr 4, 2026

Copy link
Copy Markdown

@nihalnihalani is attempting to deploy a commit to the Pacific Systems Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d10857c-c60c-43a0-bdf5-adf4676aea30

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd1491 and 925d96f.

📒 Files selected for processing (1)
  • packages/cli/src/lib/gemini.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/cli/src/lib/gemini.ts

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Web prettify & colors
apps/web/__tests__/unit/prettify-model.test.ts, apps/web/lib/open-stats.ts, apps/web/lib/share-assets/post-card-image.tsx, apps/web/lib/utils/post-share.ts, apps/web/components/app/feed/ActivityCard.tsx
Added Gemini-specific model normalization (detect gemini-, strip -preview/-exp suffixes, convert hyphens to spaces, title-case). Updated model color mapping in ActivityCard to recognize Gemini names and assign fixed hex colors. Tests updated to include Gemini cases and remove gemini-2.0-flash from unknown list.
CLI gemini integration module
packages/cli/src/lib/gemini.ts
New gemini module: sync/async subprocess runners (runGeminiRaw/runGeminiRawAsync) to call gemistat daily --json, toIsoDate helper, parseGeminiOutput that normalizes daily rows into project entry shape, computes totals, and returns anomalies/entryMeta/normalizationSummary. Handles timeouts, maxBuffer, and silent failures.
Push command & merge logic
packages/cli/src/commands/push.ts
Integrated Gemini fetch into parallel data collection, included Gemini raw output in hashing, added anomaly aggregation and blocking of unresolved Gemini dates, and extended mergeEntries signature to accept optional geminiEntries and perform 3-way date-based merges (union models, sum token/cost fields). Updated user-facing messaging for missing Claude data.
CLI model palette & theme
packages/cli/src/components/ModelPalette.tsx, packages/cli/src/components/theme.ts
Extended prettifyModel in ModelPalette to handle gemini- IDs; added multiple Gemini model-to-color mappings in modelColors and adjusted getModelColor() to prefer Gemini-specific mapping when present.
Types / normalization hints
packages/cli/src/lib/ccusage.ts, packages/cli/src/lib/token-normalization.ts
Extended union types to include "gemini": NormalizationAnomaly.source and TokenSourceHints.source now accept "gemini".
CLI tests (Vitest)
packages/cli/__tests__/gemini.test.ts, packages/cli/__tests__/merge.test.ts, packages/cli/__tests__/commands/push.test.ts, packages/cli/__tests__/flows/cli-sync-flow.test.ts
Added/updated tests: gemini parsing and run behavior, 3-way merge semantics, push command forwarding timeout to gemini subprocess, silent-failure cases, and updated sync-flow mocks/counts/messages to account for gemistat attempts.
Docs & plans
docs/CHANGELOG.md, docs/DECISIONS.md, docs/plans/2026-04-04-gemini-cli-integration.md
Documented Gemini prettify/color fixes in changelog, added a decision entry describing subprocess-based gemistat integration and silent-failure behavior, and added a detailed plan for the Gemini CLI integration tasks.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰
I hopped through code at break of dawn,
Merged three streams until worries were gone,
Claude, Codex, Gemini — a trio so bright,
Colors and names now all set right,
Carrots for tests, and moonlit delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: add Gemini CLI usage tracking via gemistat' accurately summarizes the main change: integrating Gemini as a third usage-tracking source via the gemistat tool.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 prettifyModel utility 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 Tasks section 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

📥 Commits

Reviewing files that changed from the base of the PR and between d435ab1 and 6dd1491.

📒 Files selected for processing (18)
  • apps/web/__tests__/unit/prettify-model.test.ts
  • apps/web/components/app/feed/ActivityCard.tsx
  • apps/web/lib/open-stats.ts
  • apps/web/lib/share-assets/post-card-image.tsx
  • apps/web/lib/utils/post-share.ts
  • docs/CHANGELOG.md
  • docs/DECISIONS.md
  • docs/plans/2026-04-04-gemini-cli-integration.md
  • packages/cli/__tests__/commands/push.test.ts
  • packages/cli/__tests__/flows/cli-sync-flow.test.ts
  • packages/cli/__tests__/gemini.test.ts
  • packages/cli/__tests__/merge.test.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/components/ModelPalette.tsx
  • packages/cli/src/components/theme.ts
  • packages/cli/src/lib/ccusage.ts
  • packages/cli/src/lib/gemini.ts
  • packages/cli/src/lib/token-normalization.ts

Comment on lines +186 to +194
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

@coderabbitai coderabbitai Bot Apr 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Comment thread packages/cli/src/lib/gemini.ts
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant