Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-1083GitHub issuePublicDecided· Out of scope

Add Gemini collector to the Agents panel

Reported by acf1210 · filed Sep 1, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 days ago.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
AreaOutside the audited area· Decided by agents · verified
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

Add Gemini collector to the Agents panelrevision 1 · captured 2026-09-28 21:24 UTC via poll

Context

Gemini CLI is a supported default agent in Omarchy (omarchy-default-agent gemini) but has no usage collector, so daily Gemini usage never appears in the omarchy.agents bar panel.

Current State

  • Gemini CLI 0.57.0 installed, but local session storage is empty on the machine this spec was written on — local-stats parsing is unverified against a real file.
  • Documented (not yet locally verified against a real file) session storage: ~/.gemini/tmp/<project-hash>/chats/logs.json, one hashed directory per project root (gemini-cli session-management and checkpointing docs).
  • A live quota mechanism exists and was verified live end-to-end (2026-09-01), using a Google OAuth token from the same account (obtained via the separately-authenticated Antigravity CLI, which shares the same Code Assist backend cloudcode-pa.googleapis.com). The mechanism is not a single retrieveUserQuota call (an earlier web search pointed at that method name; it does not appear anywhere in a real, working, open-source client and was not verified) — it is two calls, confirmed by reading the antigravity-usage npm package's own source (src/google/cloudcode.ts) and reproducing both requests live:
  1. POST /v1internal:loadCodeAssist, body {"metadata": {"ideType": "ANTIGRAVITY", "platform": "PLATFORM_UNSPECIFIED", "pluginType": "GEMINI"}} → 200, returns cloudaicompanionProject (a project id, e.g. isometric-bonfire-pqb1d) plus currentTier/allowedTiers.
  2. POST /v1internal:fetchAvailableModels, body {"project": "<projectId from step 1>"} → 200, returns models as a dict keyed by model id (not an array), each entry shaped:
     {"displayName": "Gemini 3.6 Flash (Low)", "quotaInfo": {"remainingFraction": 0.9911605, "resetTime": "2026-09-02T01:45:13Z"},
      "model": "MODEL_PLACEHOLDER_M73", "apiProvider": "API_PROVIDER_GOOGLE_GEMINI", "modelProvider": "MODEL_PROVIDER_GOOGLE", ...}

Both headers: Authorization: Bearer <token>, Content-Type: application/json, User-Agent: antigravity. My first attempt at this endpoint, sent without the metadata object, returned 403 SUBSCRIPTION_REQUIRED — that was a malformed-request rejection, not a real licensing gate; with the correct body it succeeded on this same (personal, unlicensed-for-Enterprise) account.
- Open question, not yet resolved: this was verified using Antigravity's own client identity (ideType: "ANTIGRAVITY", pluginType: "GEMINI"). Gemini CLI is a separate client against the same backend and may (or may not) need a different ideType/pluginType to identify itself, and may return the same account-wide quota or a differently-scoped one. Confirm by reading google-gemini/gemini-cli's own request code, or by testing once Gemini CLI is actually logged in somewhere, before hard-coding the metadata values into the collector.
- Gemini CLI's own OAuth credential lives at ~/.gemini/oauth_creds.json (standard location per third-party tooling that reads it) — absent on the machine this spec was written on, since Gemini CLI was never logged in there. The collector must degrade gracefully exactly like omarchy-agent-usage-claude does when .credentials.json is missing.

Proposed Change

Add bin/omarchy-agent-usage-gemini: local stats from ~/.gemini/tmp/*/chats/logs.json across every project hash (mirroring how the Claude collector scans ~/.claude/projects recursively), plus a best-effort live limits probe against the Code Assist quota mechanism using ~/.gemini/oauth_creds.json, degrading to stats-only with a clear usageStatusText when unauthenticated or when the two-call probe fails.

Implementation Details

New file: `bin/omarchy-agent-usage-gemini`

  • Local scan: ~/.gemini/tmp/*/chats/logs.json (glob every project hash). Research task, not yet resolved by this spec: confirm the exact per-message JSON shape (role, model, token usage field names) against a real file before writing the parser — either generate one locally (a throwaway gemini -p call) or read the authoritative field names from the open-source google-gemini/gemini-cli parser. Do not guess field names.
  • Live limits: read access_token + expiry from ~/.gemini/oauth_creds.json (same shape verified live in this spec: {access_token, token_type, refresh_token, expiry}); if missing or expired, skip the probe (no refresh-token exchange in v1 — matches Claude's own collector, which also never refreshes, only reports "sign-in expired").
  • Two-call probe against https://cloudcode-pa.googleapis.com, headers Authorization: Bearer <access_token>, Content-Type: application/json, User-Agent: <TBD — confirm Gemini CLI's own value, see open question above>:
  1. POST /v1internal:loadCodeAssist with the correct metadata object (exact ideType/pluginType for Gemini CLI: open question above) → extract cloudaicompanionProject.
  2. POST /v1internal:fetchAvailableModels with {"project": "<id from step 1>"} → iterate the models dict, keep entries where apiProvider == "API_PROVIDER_GOOGLE_GEMINI", map each quotaInfo.remainingFraction/quotaInfo.resetTime into the standard limits: [{label, percent, resetsAt}] shape (percent = 1 - remainingFraction, matching the "percent used" convention every other collector already uses; label = displayName).
  • Any error at either step (401/403/429/5xx/transport failure): same graceful pattern as probe_limits() in the Claude collector — usageStatusText set to a short, honest reason, limits: [], local stats still shown, collector never crashes.
  • Same wrapper record shape as every other collector: schemaVersion: 1, id: "gemini", name: "Gemini", updatedAt, ready, hasLocalStats, tierLabel, usageStatusText, authHelpText, limits merged with the stats bucket.

`shell/plugins/agents/manifest.json`: add "gemini": {"enabled": true} to defaults.providers.

`shell/plugins/agents/assets/gemini.svg` (+ -light if needed): Google's public Gemini mark.

`shell/plugins/agents/README.md`: add a row to the collector table documenting the two-call quota mechanism (loadCodeAssist + fetchAvailableModels) and the local log path.

Acceptance Criteria

  1. On a machine with real ~/.gemini/tmp/*/chats/logs.json files, the collector prints a valid record with correct totalPrompts/token aggregates — must be verified against a real file before merge, since the local format is unconfirmed as of this spec.
  2. On a machine with no Gemini session data, the collector exits cleanly with ready: false, no crash.
  3. With the correct metadata body (once Gemini CLI's own ideType/pluginType is confirmed), limits contains at least one entry with a percent between 0 and 1 and a valid resetsAt — reproducing the live result already verified in this spec (0.9911605 remainingFraction on a real account, via the same backend under Antigravity's client identity).
  4. On any error at either probe step (401/403/429/5xx/transport), usageStatusText carries a short honest reason, limits: [], local stats still shown — no exception, no stack trace.
  5. omarchy agent usage-update gemini writes only gemini.json, leaving every sibling untouched.
  6. No changes to Claude, Codex, Fireworks, or OpenCode collector behavior.

Testing Plan

LayerWhatCount
Unitlogs.json aggregation from a fixture (once the real shape is confirmed)+4
UnitQuota probe: loadCodeAssist+fetchAvailableModels 200/200 → limits populated (fixture from the real verified response shape); either call failing → graceful stats-only+3
Integrationomarchy-agent-usage-update gemini writes only its own file+1
ManualLive panel shows a Gemini tab once a real session exists on a test machine+1

Rollback Plan

Delete bin/omarchy-agent-usage-gemini, remove gemini from manifest.json:defaults.providers, delete stale ~/.local/state/omarchy/agents/usage/gemini.json if present.

Effort Estimate

~6h — 1h confirm the real logs.json shape + Gemini CLI's own ideType/pluginType (open research items), 2h collector script, 1h quota-probe error handling, 1h manifest/assets/README, 1h tests.

Open Research Items

  1. The exact ~/.gemini/tmp/*/chats/logs.json per-message field names are sourced from documentation, not verified against a real file (none existed on the machine this spec was written on). Before merge, either generate one real session or read the field names directly from the open-source google-gemini/gemini-cli parser.
  2. This spec verified the loadCodeAssist+fetchAvailableModels quota mechanism live, but under Antigravity's own client identity (ideType: "ANTIGRAVITY", pluginType: "GEMINI"), not Gemini CLI's. Confirm Gemini CLI's own values (likely visible in google-gemini/gemini-cli's request code) before hard-coding them into the collector — using the wrong identity may return the wrong quota scope, or none at all.

Files Reference

FileChange
bin/omarchy-agent-usage-geminiNew collector
shell/plugins/agents/manifest.jsonAdd gemini to defaults.providers
shell/plugins/agents/assets/gemini.svgNew mark
shell/plugins/agents/README.mdDocument the new collector row
bin/omarchy-agent-usage-claude (reference only)oauth_login() / probe_limits() / collect_limits() are the pattern to mirror for graceful auth degradation

Out of Scope

  • Refresh-token exchange (v1 reports "sign-in expired" rather than silently refreshing, matching Claude's own collector).
  • Any non-Code-Assist quota source (e.g., AI Studio API key usage) — Gemini CLI's primary OAuth login is the only auth path covered.

Related

  • Parent epic: #9685 — Add Gemini and OpenCode monitoring to the Agents panel
  • Sibling: #9683 — Add OpenCode collector to the Agents panel
  • Quota mechanism (loadCodeAssist + fetchAvailableModels) verified live end-to-end 2026-09-01 via a Google account OAuth token (Antigravity CLI's own login, same Code Assist backend), including real per-model remainingFraction/resetTime values. Reference implementation read: antigravity-usage npm package (src/google/cloudcode.ts), installed locally.
View raw text
## Context

Gemini CLI is a supported default agent in Omarchy (`omarchy-default-agent gemini`) but has no usage collector, so daily Gemini usage never appears in the `omarchy.agents` bar panel.

## Current State

- Gemini CLI 0.57.0 installed, but local session storage is empty on the machine this spec was written on — local-stats parsing is unverified against a real file.
- Documented (not yet locally verified against a real file) session storage: `~/.gemini/tmp/<project-hash>/chats/logs.json`, one hashed directory per project root (gemini-cli session-management and checkpointing docs).
- A live quota mechanism exists and was **verified live end-to-end** (2026-09-01), using a Google OAuth token from the same account (obtained via the separately-authenticated Antigravity CLI, which shares the same Code Assist backend `cloudcode-pa.googleapis.com`). The mechanism is **not** a single `retrieveUserQuota` call (an earlier web search pointed at that method name; it does not appear anywhere in a real, working, open-source client and was not verified) — it is two calls, confirmed by reading the `antigravity-usage` npm package's own source (`src/google/cloudcode.ts`) and reproducing both requests live:
  1. `POST /v1internal:loadCodeAssist`, body `{"metadata": {"ideType": "ANTIGRAVITY", "platform": "PLATFORM_UNSPECIFIED", "pluginType": "GEMINI"}}` → `200`, returns `cloudaicompanionProject` (a project id, e.g. `isometric-bonfire-pqb1d`) plus `currentTier`/`allowedTiers`.
  2. `POST /v1internal:fetchAvailableModels`, body `{"project": "<projectId from step 1>"}` → `200`, returns `models` as a **dict keyed by model id** (not an array), each entry shaped:
     ```json
     {"displayName": "Gemini 3.6 Flash (Low)", "quotaInfo": {"remainingFraction": 0.9911605, "resetTime": "2026-09-02T01:45:13Z"},
      "model": "MODEL_PLACEHOLDER_M73", "apiProvider": "API_PROVIDER_GOOGLE_GEMINI", "modelProvider": "MODEL_PROVIDER_GOOGLE", ...}
     ```
  Both headers: `Authorization: Bearer <token>`, `Content-Type: application/json`, `User-Agent: antigravity`. My first attempt at this endpoint, sent without the `metadata` object, returned `403 SUBSCRIPTION_REQUIRED` — that was a malformed-request rejection, not a real licensing gate; with the correct body it succeeded on this same (personal, unlicensed-for-Enterprise) account.
- **Open question, not yet resolved**: this was verified using Antigravity's own client identity (`ideType: "ANTIGRAVITY", pluginType: "GEMINI"`). Gemini CLI is a separate client against the same backend and may (or may not) need a different `ideType`/`pluginType` to identify itself, and may return the same account-wide quota or a differently-scoped one. Confirm by reading `google-gemini/gemini-cli`'s own request code, or by testing once Gemini CLI is actually logged in somewhere, before hard-coding the metadata values into the collector.
- Gemini CLI's own OAuth credential lives at `~/.gemini/oauth_creds.json` (standard location per third-party tooling that reads it) — absent on the machine this spec was written on, since Gemini CLI was never logged in there. The collector must degrade gracefully exactly like `omarchy-agent-usage-claude` does when `.credentials.json` is missing.

## Proposed Change

Add `bin/omarchy-agent-usage-gemini`: local stats from `~/.gemini/tmp/*/chats/logs.json` across every project hash (mirroring how the Claude collector scans `~/.claude/projects` recursively), plus a best-effort live limits probe against the Code Assist quota mechanism using `~/.gemini/oauth_creds.json`, degrading to stats-only with a clear `usageStatusText` when unauthenticated or when the two-call probe fails.

### Implementation Details

**New file: `bin/omarchy-agent-usage-gemini`**

- Local scan: `~/.gemini/tmp/*/chats/logs.json` (glob every project hash). **Research task, not yet resolved by this spec**: confirm the exact per-message JSON shape (role, model, token usage field names) against a real file before writing the parser — either generate one locally (a throwaway `gemini -p` call) or read the authoritative field names from the open-source `google-gemini/gemini-cli` parser. Do not guess field names.
- Live limits: read `access_token` + `expiry` from `~/.gemini/oauth_creds.json` (same shape verified live in this spec: `{access_token, token_type, refresh_token, expiry}`); if missing or expired, skip the probe (no refresh-token exchange in v1 — matches Claude's own collector, which also never refreshes, only reports "sign-in expired").
- Two-call probe against `https://cloudcode-pa.googleapis.com`, headers `Authorization: Bearer <access_token>`, `Content-Type: application/json`, `User-Agent: <TBD — confirm Gemini CLI's own value, see open question above>`:
  1. `POST /v1internal:loadCodeAssist` with the correct `metadata` object (exact `ideType`/`pluginType` for Gemini CLI: open question above) → extract `cloudaicompanionProject`.
  2. `POST /v1internal:fetchAvailableModels` with `{"project": "<id from step 1>"}` → iterate the `models` dict, keep entries where `apiProvider == "API_PROVIDER_GOOGLE_GEMINI"`, map each `quotaInfo.remainingFraction`/`quotaInfo.resetTime` into the standard `limits: [{label, percent, resetsAt}]` shape (`percent = 1 - remainingFraction`, matching the "percent used" convention every other collector already uses; `label` = `displayName`).
  - Any error at either step (401/403/429/5xx/transport failure): same graceful pattern as `probe_limits()` in the Claude collector — `usageStatusText` set to a short, honest reason, `limits: []`, local stats still shown, collector never crashes.
- Same wrapper record shape as every other collector: `schemaVersion: 1, id: "gemini", name: "Gemini", updatedAt, ready, hasLocalStats, tierLabel, usageStatusText, authHelpText, limits` merged with the stats bucket.

**`shell/plugins/agents/manifest.json`**: add `"gemini": {"enabled": true}` to `defaults.providers`.

**`shell/plugins/agents/assets/gemini.svg`** (+ `-light` if needed): Google's public Gemini mark.

**`shell/plugins/agents/README.md`**: add a row to the collector table documenting the two-call quota mechanism (`loadCodeAssist` + `fetchAvailableModels`) and the local log path.

## Acceptance Criteria

1. On a machine with real `~/.gemini/tmp/*/chats/logs.json` files, the collector prints a valid record with correct `totalPrompts`/token aggregates — must be verified against a real file before merge, since the local format is unconfirmed as of this spec.
2. On a machine with no Gemini session data, the collector exits cleanly with `ready: false`, no crash.
3. With the correct `metadata` body (once Gemini CLI's own `ideType`/`pluginType` is confirmed), `limits` contains at least one entry with a `percent` between 0 and 1 and a valid `resetsAt` — reproducing the live result already verified in this spec (0.9911605 `remainingFraction` on a real account, via the same backend under Antigravity's client identity).
4. On any error at either probe step (401/403/429/5xx/transport), `usageStatusText` carries a short honest reason, `limits: []`, local stats still shown — no exception, no stack trace.
5. `omarchy agent usage-update gemini` writes only `gemini.json`, leaving every sibling untouched.
6. No changes to Claude, Codex, Fireworks, or OpenCode collector behavior.

## Testing Plan

| Layer | What | Count |
|-------|------|-------|
| Unit | `logs.json` aggregation from a fixture (once the real shape is confirmed) | +4 |
| Unit | Quota probe: `loadCodeAssist`+`fetchAvailableModels` 200/200 → limits populated (fixture from the real verified response shape); either call failing → graceful stats-only | +3 |
| Integration | `omarchy-agent-usage-update gemini` writes only its own file | +1 |
| Manual | Live panel shows a Gemini tab once a real session exists on a test machine | +1 |

## Rollback Plan

Delete `bin/omarchy-agent-usage-gemini`, remove `gemini` from `manifest.json:defaults.providers`, delete stale `~/.local/state/omarchy/agents/usage/gemini.json` if present.

## Effort Estimate

~6h — 1h confirm the real `logs.json` shape + Gemini CLI's own `ideType`/`pluginType` (open research items), 2h collector script, 1h quota-probe error handling, 1h manifest/assets/README, 1h tests.

## Open Research Items

1. The exact `~/.gemini/tmp/*/chats/logs.json` per-message field names are sourced from documentation, not verified against a real file (none existed on the machine this spec was written on). Before merge, either generate one real session or read the field names directly from the open-source `google-gemini/gemini-cli` parser.
2. This spec verified the `loadCodeAssist`+`fetchAvailableModels` quota mechanism live, but under Antigravity's own client identity (`ideType: "ANTIGRAVITY", pluginType: "GEMINI"`), not Gemini CLI's. Confirm Gemini CLI's own values (likely visible in `google-gemini/gemini-cli`'s request code) before hard-coding them into the collector — using the wrong identity may return the wrong quota scope, or none at all.

## Files Reference

| File | Change |
|------|--------|
| `bin/omarchy-agent-usage-gemini` | New collector |
| `shell/plugins/agents/manifest.json` | Add `gemini` to `defaults.providers` |
| `shell/plugins/agents/assets/gemini.svg` | New mark |
| `shell/plugins/agents/README.md` | Document the new collector row |
| `bin/omarchy-agent-usage-claude` (reference only) | `oauth_login()` / `probe_limits()` / `collect_limits()` are the pattern to mirror for graceful auth degradation |

## Out of Scope

- Refresh-token exchange (v1 reports "sign-in expired" rather than silently refreshing, matching Claude's own collector).
- Any non-Code-Assist quota source (e.g., AI Studio API key usage) — Gemini CLI's primary OAuth login is the only auth path covered.

## Related

- Parent epic: #9685 — Add Gemini and OpenCode monitoring to the Agents panel
- Sibling: #9683 — Add OpenCode collector to the Agents panel
- Quota mechanism (`loadCodeAssist` + `fetchAvailableModels`) verified live end-to-end 2026-09-01 via a Google account OAuth token (Antigravity CLI's own login, same Code Assist backend), including real per-model `remainingFraction`/`resetTime` values. Reference implementation read: `antigravity-usage` npm package (`src/google/cloudcode.ts`), installed locally.

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

Not linked to any known issue, other report or change yet.

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

  • Out of scopeDecided by agents · verified · 2026-10-09 10:29 UTC