Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ follows [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added

- Add an explicit Antigravity Desktop connection in Electron setup and Settings.
Read account allowance from one running, signed-in desktop language server
without requiring `agy`. Keep CLI selection explicit and local activity
separate. Synthetic fixtures cover identity changes, unavailable quota,
transport limits and cancellation. Native and live-provider acceptance remain
pending; the published beta is unchanged.

### Changed

- Shorten heatmap hover/focus totals to values such as `42.4B tokens` and
Expand Down
31 changes: 31 additions & 0 deletions docs/DESKTOP-CONNECTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ The **Connect using** selector offers the methods supported by each provider.
Selecting a method does not start sign-in or change collection. Use the button
below it to apply the choice. Existing profiles keep their current source.

- Antigravity: choose **Antigravity Desktop**, then **Connect Antigravity Desktop**.
Keep one desktop account open and signed in. The candidate reads account
allowance from its running local service without requiring `agy`. Separate
language servers are ambiguous, even if they belong to the same account;
close extra instances before connecting. **Antigravity CLI** remains an
explicit alternative for the existing local integration. Desktop allowance
includes no CLI-local history. Native and live-provider checks are pending.
- Codex: choose **Sign in to Codex** and complete browser sign-in. The candidate
finds the helper in an existing Codex app in Applications, with existing
command installations as a compatibility path. Users do not open a terminal
Expand Down Expand Up @@ -237,3 +244,27 @@ confirm allowance is removed. Test denied access and account/organization
switches only with the account owner's approval. Never run the Keychain helper
directly or capture its output. See
[ADR 0016](adr/0016-claude-live-test-profile.md).

## Antigravity Desktop source boundary

The source adapter discovers one same-user Antigravity desktop language server
and its PID-owned IPv4 loopback listeners. It reads only the candidate process
arguments for its current CSRF token. Fixed `GetUserStatus` and
`RetrieveUserQuotaSummary` requests use HTTPS first; HTTP is limited to the
declared, owned extension-server port. TLS certificate exemption is local to
that verified loopback request. Redirects are rejected. Each reply is limited
to 1 MiB, each request to three seconds, and collection to fifteen seconds.

Account identity must match before and after allowance collection. Only an
opaque fingerprint enters preferences. CSRF credentials, account email and raw
replies stay in main-process memory, outside settings, diagnostics and logs.
The integration neither writes provider stores nor refreshes credentials.
Changed identity, provider closure, expired buckets, missing fractions and
empty summaries leave allowance unavailable and clear prior account quota.
An empty modern summary never falls back to legacy data. Legacy model quota
is read only when the modern RPC explicitly reports unsupported; it cannot
establish weekly allowance. Disconnect and cancellation reject late results.

This is source and synthetic-fixture coverage. It is absent from the published
build 54 beta. Native discovery, local transport and live allowance comparison
remain unverified. See [ADR 0026](adr/0026-antigravity-desktop-local-quota.md).
18 changes: 15 additions & 3 deletions docs/KB.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,26 @@ Release documentation refresh: 2026-10-16; build 54 source review is unchanged.

## Product and source facts

- Unreleased Antigravity Desktop setup reuses the existing connection controls,
preferences, IPC and account-bound source. It requires one running same-user
desktop language server with a CSRF token and PID-owned IPv4 loopback port.
Fixed quota/status RPCs use request-scoped TLS handling, redirect rejection,
1 MiB response limits and bounded time. Account fingerprints are opaque;
credentials stay in main-process memory. Empty modern quota and missing
fractions remain unavailable. Local CLI usage stays detached. Synthetic
fixtures cover account switches, closure, transport failures and connection
races. Native/live-provider proof and release inclusion remain pending.
See [ADR 0026](adr/0026-antigravity-desktop-local-quota.md).

- Heatmap hover/focus readouts use compact token and session totals, including
local, account, session-only, daily, weekly and cumulative grids. Exact grouped
counts remain in tooltips and accessible labels. Summary counts compact from
1,000. This follow-up is separate from the published build 54 artifact.

- Onboarding and Settings offer provider-specific source choices: Codex browser
sign-in or CLI, Claude Desktop or Claude Code CLI, and Grok CLI only. Choosing
an option does not change collection until its action is used. Use CLI waits
sign-in or CLI, Claude Desktop or Claude Code CLI, Antigravity Desktop or
Antigravity CLI, and Grok CLI only. Choosing an option does not change
collection until its action is used. Use CLI waits
for MeterUsage-owned connection cleanup before selecting existing local
collection. Ordinary Disconnect still prevents automatic local fallback.
CLI data covers this Mac; Claude CLI allowance needs a companion usage file.
Expand Down Expand Up @@ -149,7 +161,7 @@ Release documentation refresh: 2026-10-16; build 54 source review is unchanged.
- The published app is Swift Package Manager based, targets macOS 13 or later, and includes an executable target and a test target. `Package.swift` is the source for this package structure. Its AppKit and SwiftUI imports require macOS for package build and test checks; Swift itself supports other platforms. `AGENTS.md` describes a generic macOS SSH route for Linux development without private host aliases.
- `README.md` links directly to the prebuilt v0.2.41 app ZIP as the default installation path. Installing and running it does not require Swift, Xcode, Command Line Tools, or Git; its Swift runtime ships with macOS. `Scripts/make-app.sh` also downloads that pinned release by default, checks its SHA-256 and code signature, and prepares `dist/MeterUsage.app` without compiling or installing developer tools. It copies and verifies the replacement in staging on the destination filesystem before replacing existing output, so copy or verification failures preserve the previous generated app. It rejects unsupported platforms and never falls back to a source build. Source builds require `--build-from-source` and are documented in `CONTRIBUTING.md`. The v0.2.41 asset targets Apple silicon and macOS 13 or later, is ad-hoc signed, and is not notarized. GitHub's source archives contain source and scripts, not an app bundle. ADR 0009 records the script's default.
- Swift provider data sources are implemented under `Sources/MeterUsage/Services/`; the candidate adapters live under `src/main/providers/` and `src/main/composition.ts`. `docs/PRIVACY.md` describes the boundary for local files, provider CLIs, documented network endpoints, and data reduction before display.
- Antigravity quota and usage share a bounded Docker/Podman runtime resolver. It prefers a healthy runtime and can start only an already-existing `podman-machine-default` after an inspect check; it never creates a machine or pulls an image. Concurrent recovery calls are serialized; failed starts have a 60-second cooldown. Internet or authentication failures are not repaired by restarting a healthy runtime.
- Antigravity CLI quota and usage share a bounded Docker/Podman runtime resolver. It prefers a healthy runtime and can start only an already-existing `podman-machine-default` after an inspect check; it never creates a machine or pulls an image. Concurrent recovery calls are serialized; failed starts have a 60-second cooldown. Internet or authentication failures are not repaired by restarting a healthy runtime.
- Demo mode uses synthetic data. `README.md` and `docs/DEMO.md` describe it as the path for screenshots and local UI inspection without provider accounts.
- `Resources/Info.plist` and `CHANGELOG.md` record version 0.2.41, build 46, dated 2026-10-05. This release collects fresh quota observations after an accepted manual Codex reset, derives pacing and ETA from later usage growth, and removes the redeemed reset credit from archived quota data. The previous release was 0.2.40, which added accent colour themes, Codex cost estimates, and refreshed model prices. Repository version and release-note entries do not prove that a GitHub Release or its downloadable asset has been published.
- `CONTRIBUTING.md` requires focused changes, synthetic fixtures, and `swift build`, `swift test`, and `Scripts/make-app.sh --build-from-source` before a code pull request. `Tests/Scripts/make-app-tests.sh` covers default preparation, explicit source selection, and download, verification, argument, and platform failures without network or compiler access.
Expand Down
28 changes: 24 additions & 4 deletions docs/PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,31 @@ Collection timeout also clears account-bound allowance when the identity check
may not have completed. Disconnect removes consent metadata and prevents the
post-request credential reread without changing Claude Desktop.

Connected Codex/Claude sources do not attach local CLI activity to the connected
account. Quota archives are cleared on connection changes and candidate launch;
An explicit **Connect Antigravity Desktop** action permits discovery of one
same-user desktop language server and reading only its candidate process
arguments for the current CSRF credential. Multiple matching servers are
rejected. MeterUsage verifies PID-owned IPv4 loopback listeners before each
fixed `GetUserStatus` or `RetrieveUserQuotaSummary` request. HTTPS is preferred;
a certificate exemption applies only to that request to `127.0.0.1`. HTTP is
allowed only on the declared, PID-owned extension-server port. Redirects are
rejected, responses are limited to 1 MiB, each request to three seconds and
collection to fifteen seconds. No cloud credential file is opened or copied.

The Antigravity account email is checked before and after quota collection.
Only a provider-scoped SHA-256 fingerprint is saved in MeterUsage preferences,
excluded from settings publication and diagnostics. CSRF credentials and raw
responses stay in main-process memory and are not logged, cached or persisted.
No provider store is changed and no credential is refreshed. Connection
changes, cancellation and disconnect invalidate old reads. Missing or expired
quota stays unavailable. Empty modern summaries cannot select stale legacy
quota; legacy model fractions are allowed only for an explicitly unsupported
modern RPC. See [ADR 0026](adr/0026-antigravity-desktop-local-quota.md).

Connected Codex/Claude/Antigravity sources do not attach local CLI activity to
the connected account. Quota archives are cleared on connection changes and candidate launch;
authentication or absent-data failures clear the connected archive. Tokens,
passwords, raw caches, account identifiers and responses are not logged or
persisted by MeterUsage. In-memory strings remain subject to garbage collection.
passwords, raw caches, plaintext account identifiers and responses are not
logged or persisted by MeterUsage. In-memory strings remain subject to garbage collection.
Demo mode blocks live connection code before helper or key discovery. Grok has
no new consumer connection in this candidate. [ADR 0011](adr/0011-desktop-account-connections.md)
amends the candidate boundary; it does not change the published Swift release.
Expand Down
52 changes: 52 additions & 0 deletions docs/adr/0026-antigravity-desktop-local-quota.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ADR 0026: Read Antigravity Desktop quota through its local service

- Status: Source implementation; native and provider acceptance pending
- Date: 2026-10-16
- Amends: [ADR 0011](0011-desktop-account-connections.md), for opt-in Antigravity Desktop allowance

## Decision

Offer **Connect Antigravity Desktop** beside the existing explicit CLI choice.
Use the running desktop language server's fixed `GetUserStatus` and
`RetrieveUserQuotaSummary` RPCs. Do not require or start `agy`, download a helper,
copy OAuth credentials, refresh tokens or write to provider stores.

Discover executable names before reading arguments for one matching same-user
PID. Require its CSRF token and its own IPv4 loopback listener. Multiple
matching desktop servers are ambiguous, including multiple windows that start
separate servers. Reject them instead of choosing an account. Recheck process,
listener and token ownership before each request.

Prefer HTTPS. Limit certificate verification exemption to an individual request
to the verified `127.0.0.1` listener. HTTP is allowed only on the declared,
PID-owned extension-server port. Use fixed RPC paths and bodies, reject
redirects, limit responses to 1 MiB, requests to three seconds and a full read
to fifteen seconds. Never expose provider errors or process output.

Require `GetUserStatus.userStatus.email` before and after quota collection.
Retain only its provider-scoped SHA-256 fingerprint in MeterUsage preferences;
exclude it from renderer settings and diagnostics. This extends ADR 0011's
opt-in identity exception to Antigravity. Keep CSRF credentials and raw replies
in main-process memory only. Reject changed or missing identity, cancellation,
disconnect and results from an earlier connection, including reconnection to
the same account.

A successful modern response is authoritative. Empty summaries and missing,
invalid, disabled or expired buckets stay unavailable. Use legacy
`GetUserStatus` model fractions only after an explicit unsupported response
(HTTP 404/501 or RPC code 12/`unimplemented`). Do not infer weekly quota or
turn a missing fraction into depleted allowance.

Connected allowance has no CLI-local activity attached. Disconnect disables
collection without selecting CLI. **Use CLI** explicitly restores the existing
local source. Clear account quota on source changes, failed identity/data reads
and launch, using the existing connection/coordinator flow.

## Acceptance

Synthetic fixtures establish discovery filtering, transport bounds, quota
projection, account changes, archive clearing and cancellation/save races.
They do not establish native process discovery, installed-app compatibility,
local TLS behavior or live allowance accuracy. Those checks remain required
before claiming Antigravity Desktop support in a released artifact. The
published beta and legacy Swift release are unchanged.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ compatibility and acceptance requirements remain in force. The
| [0022](0022-codex-account-token-history.md) | Read provider account daily tokens separately from local activity through the managed ChatGPT helper. | Accepted implementation direction; independent review, native and live acceptance pending |
| [0023](0023-onboarding-source-choice.md) | Offer supported CLI and account methods in onboarding, with explicit cleanup before switching to local collection. | Accepted implementation direction; native interaction acceptance pending |
| [0025](0025-card-settings-diagnostic-reports.md) | Keep Settings in usage cards and restore explicit sanitized support reports in Diagnostics. | Accepted implementation direction; native and live delivery acceptance separate |
| [0026](0026-antigravity-desktop-local-quota.md) | Opt-in Antigravity Desktop quota through its owned local service. | Source implementation; native/provider acceptance pending |
18 changes: 15 additions & 3 deletions docs/electron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ to local collection. See [desktop connections](../DESKTOP-CONNECTIONS.md) and
### Existing provider inputs

All nine adapters remain wired in [composition](../../src/main/composition.ts).
Antigravity and OpenCode onboarding guidance/actions remain requested work;
their collection exists without a connection row.
OpenCode onboarding guidance/actions remain requested work; its collection
exists without a connection row. Antigravity Desktop setup is implemented only
in unreleased source, as noted below.

- Antigravity quota uses existing authenticated `agy` container state, Docker or
- Antigravity CLI quota uses existing authenticated `agy` container state, Docker or
Podman, image `antigravity-cli:local` and volumes `antigravity-config` and
`antigravity-bin`. Native database/history usage is independent of that quota
runtime. No separate MeterUsage browser login or API-key form exists.
Expand Down Expand Up @@ -220,3 +221,14 @@ source retirement and stable cutover. This opt-in beta does not close those chec
Desktop account research for Grok/Cursor, Antigravity and OpenCode Go is recorded
in [DESKTOP-LOGIN-RESEARCH.md](../DESKTOP-LOGIN-RESEARCH.md). It separates
documented sign-in from implemented quota collection.

### Unreleased Antigravity Desktop setup

Current source adds **Connect Antigravity Desktop** to the existing source
selector. One running, signed-in desktop language server supplies account
allowance without `agy`; local CLI history is excluded. Multiple matching
servers and missing or expired data remain unavailable. **Use CLI** selects
the existing local integration explicitly. This adapter has synthetic-fixture
coverage only and is absent from published build 54. Native process discovery,
local transport and live quota comparison remain acceptance gaps. See
[desktop connections](../DESKTOP-CONNECTIONS.md#antigravity-desktop-source-boundary).
9 changes: 6 additions & 3 deletions src/main/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export function compose(launch: Launch, prefs: Preferences, options: { command?:
}
codex.status = async signal => parseStatus("codex", await http(endpoints.codexStatus, "codex", undefined, signal), now());
claude.status = async signal => parseStatus("claude", await http(endpoints.claudeStatus, "claude", undefined, signal), now());
const antigravityIdentity = String(prefs.values.desktopAntigravityIdentity);
const antigravity: Source = antigravityIdentity !== "" ? {
slot: primary("antigravity"), accountBound: true,
quota: signal => options.connections ? options.connections.quota("antigravity", signal, antigravityIdentity) : Promise.reject(new Unavailable("notSignedIn", "Antigravity Desktop")),
} : { slot: primary("antigravity"), quota: signal => runtime.quota(now(), signal), usage: signal => runtime.usage(join(launch.home, ".gemini/antigravity-cli"), now(), signal) };
const sources: Source[] = [codex, {
slot: primary("openRouter"), quota: async signal => {
const key = openRouterKey(launch); if (!key) throw new Unavailable("dataNotFound", "OpenRouter API key");
Expand All @@ -106,9 +111,7 @@ export function compose(launch: Launch, prefs: Preferences, options: { command?:
if (existsSync(path)) { try { const { DatabaseSync } = await import("node:sqlite"), db = new DatabaseSync(path, { readOnly: true }); try { const rows = parseOpenCodeRows(JSON.stringify(db.prepare(openCodeQuery).all())); if (rows.length) return openCodeUsage(rows, now()); } finally { db.close(); } } catch { /* The supported CLI is the existing fallback. */ } }
return openCodeUsage(parseOpenCodeRows(await command(cliPath("opencode", launch.home, env), ["db", "--format", "json", openCodeQuery], { env, signal })), now());
},
}, {
slot: primary("antigravity"), quota: signal => runtime.quota(now(), signal), usage: signal => runtime.usage(join(launch.home, ".gemini/antigravity-cli"), now(), signal),
}, {
}, antigravity, {
slot: primary("grok"), quota: async signal => { const key = grokToken(launch); if (!key) throw new Unavailable("dataNotFound", "Grok credentials"); return parseGrok(await http(endpoints.grok, "grok", key, signal), now()); }, usage: async signal => { const configured = text(env.GROK_HOME), root = configured ? isAbsolute(configured) ? configured : join(launch.home, configured) : join(launch.home, ".grok"); return scanGrok(join(root, "sessions"), now(), signal); },
}, claude];
for (const p of ["cursor", "copilot", "gemini"] as const) {
Expand Down
Loading
Loading