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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ follows [Semantic Versioning](https://semver.org/).

### Changed

- Add opt-in Grok Bot **Connect via Cursor (Beta)** in Electron Settings.
Sign in to Cursor, check Bot allowance, then quit it before connecting.
Immutable SQLite performs no reader writes; process/filesystem guards reject
active Cursor, nonempty WAL/journal files, ambiguous evidence and observable
changes. Verify account identity before bounded allowance requests, keep
credentials only in memory, and require reopening Cursor then quit/reconnect
after expiry. Native/live support and universal shared-pool coverage remain
unverified. The published build is unchanged.
- Shorten heatmap hover/focus totals to values such as `42.4B tokens` and
`1.6K sessions` across local and account history. Keep exact grouped counts
in tooltips and accessibility labels. Summary counts compact from 1,000.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ for Codex, Claude, side notch and Settings.
The beta includes Codex browser sign-in/account history, optional Claude Desktop
allowance, existing CLI sources, compact provider tabs and hover cards, provider
reordering and readable token totals. Account coverage depends on the source.
Grok remains Beta and CLI-only; Antigravity desktop login is follow-up work.
Build 54 keeps Grok Beta CLI-only; Antigravity desktop login is follow-up work.
Current source additionally offers opt-in [Grok Bot via Cursor Beta](docs/DESKTOP-CONNECTIONS.md#grok-bot-via-cursor-beta).
Sign in to Cursor, check Bot allowance, then quit Cursor before connecting.
Native/live support and Grok Desktop, X and shared-pool coverage remain unverified.
Build 54's Settings footer incorrectly says `0.2.41`; the packaged version is
`0.3.0-beta.1`. See [known beta limits](docs/BETA.md#known-limits).

Expand Down
49 changes: 46 additions & 3 deletions docs/DESKTOP-CONNECTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ below it to apply the choice. Existing profiles keep their current source.
Alternatively, choose **Claude Code CLI** and **Use CLI** to read local Claude
Code activity. CLI allowance requires a companion usage file; this option
does not provide Claude website token history.
- Grok: automatic consumer connection is unavailable. Existing command-based
collection remains available for existing users. It does not establish
browser/mobile consumer support. The setup does not ask for manual numbers.
- Grok: choose **Grok Bot via Cursor (Beta)**. Sign in to Cursor on this Mac,
check Bot allowance, then quit Cursor before choosing **Beta Connect via
Cursor** and approving access. No CLI is required. Cursor must have closed
with its SQLite store checkpointed; an active process, nonempty WAL/journal,
ambiguous evidence or observed store changes prevent access. MeterUsage never
stops Cursor. When access expires, reopen Cursor, then quit and reconnect.
Existing **Grok CLI** collection remains an explicit alternative. Neither
method establishes universal Grok Desktop, X or shared-pool coverage.

**Use CLI** first disconnects only the MeterUsage-owned account connection and
waits for any Codex logout cleanup to succeed. A failed cleanup keeps local
Expand Down Expand Up @@ -237,3 +242,41 @@ 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).

## Grok Bot via Cursor Beta

This source-only compatibility connection uses Cursor's current desktop token
in main-process memory. It selects only `cursorAuth/accessToken` from `ItemTable`
in `Library/Application Support/Cursor/User/globalStorage/state.vscdb`. It opens
the original database with SQLite `immutable=1&mode=ro`. No credential store or
file is copied to temporary disk, no refresh occurs, and SQLite performs no
writes or sidecar bookkeeping. Filesystem access-time metadata remains subject
to the operating system's read policy.

Pre/post process and filesystem guards require closed/checkpointed evidence.
They detect observable races; they provide no mutual exclusion and do not prove
Cursor never ran between observations. Immutable SQLite guarantees this reader
does not write even if another process races it. Nonempty WAL or rollback
journal files are rejected because immutable reads ignore uncheckpointed data.
A changed database, sidecar, directory identity or ambiguous evidence fails
closed. The guards run again before Bot allowance and after the request.

Each request first verifies the JWT subject against fixed HTTPS
`cursor.com/api/auth/me`, then POSTs `{}` to
`cursor.com/api/dashboard/get-sand-usage-status` with Cursor's required Origin.
Redirects are refused, requests have a 30-second timeout and a 1 MiB response
limit, and credentials must remain valid for more than 60 seconds. Identity is
case-sensitive. Only its opaque fingerprint and the source choice persist.
The current identity and store fingerprint must match before results are used.

Included quota honors `includedLimitZero` before the legacy included-limit
flag. A trial requires a future `sandTrialExpiresAt`; trial expiry is never a
reset timestamp. Missing data stays unavailable. Reset cadence is not inferred.
Connected allowance has no local CLI activity attached. Disconnect and source
changes clear account-bound quota and reject late results; ordinary failures
use the existing coordinator backoff. There is no automatic token renewal.

Synthetic tests cover the reader, guards, account binding, consent, cancellation,
disconnect, expiry and request bounds. Native Cursor formats, Electron SQLite,
visible setup and live provider support remain **UNVERIFIED**. This change is
not in published build 54. See [ADR 0027](adr/0027-grok-cursor-closed-reader.md).
7 changes: 4 additions & 3 deletions docs/KB.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ Release documentation refresh: 2026-10-16; build 54 source review is unchanged.
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
sign-in or CLI, Claude Desktop or Claude Code CLI, and Grok CLI or opt-in
Grok Bot via Cursor Beta. 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.
Expand Down Expand Up @@ -143,7 +144,7 @@ Release documentation refresh: 2026-10-16; build 54 source review is unchanged.
profiles retain their preference. [ADR 0014](adr/0014-settings-onboarding.md)
records this placement; native acceptance is separate from source validation.

- The Electron candidate implements opt-in desktop account setup in `src/main/connections.ts`, `src/main/providers/claude-desktop.ts` and the renderer. Codex discovers an existing app helper and uses a separate generated keyring-only profile with browser login. Claude uses a fixed native Keychain helper, read-only Desktop cache/cookie selection and the OAuth allowance endpoint after consent. Grok consumer connection remains unavailable. These are source and synthetic-fixture facts, not native or live-provider proof. [Desktop connections](DESKTOP-CONNECTIONS.md) and ADR 0011 define setup, data coverage, privacy and acceptance gaps. Connected-account cards omit local CLI activity, clear old quota on account changes and do not restore quota archives across launches.
- The Electron candidate implements opt-in desktop account setup in `src/main/connections.ts`, `src/main/providers/claude-desktop.ts` and the renderer. Codex discovers an existing app helper and uses a separate generated keyring-only profile with browser login. Claude uses a fixed native Keychain helper, read-only Desktop cache/cookie selection and the OAuth allowance endpoint after consent. Grok Bot adds an opt-in closed/checkpointed Cursor Beta reader using immutable SQLite, fixed identity-before-allowance requests and opaque account binding. Nonempty WAL/journal files, ambiguous evidence and observed races fail closed. These are source and synthetic-fixture facts, not native or live-provider proof. [Desktop connections](DESKTOP-CONNECTIONS.md), ADR 0011 and [ADR 0027](adr/0027-grok-cursor-closed-reader.md) define setup, data coverage, privacy and acceptance gaps. Connected-account cards omit local CLI activity, clear old quota on account changes and do not restore quota archives across launches.

- meterusage is a macOS menu-bar app that displays AI coding-assistant quota and usage signals. `README.md` describes provider clusters, quota cards, heatmaps, sparklines, alerts, diagnostics, and a scriptable JSON CLI.
- 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.
Expand Down Expand Up @@ -250,6 +251,6 @@ Reviewed Swift base: `4b913db3a3a8eb2602296489872af2ecaa5d0530`. Source checks d

- Electron Diagnostics restores explicit **Send report** through the existing private support relay. Main generates the same allowlisted status summary used by Copy diagnostics with the running app version; renderer IPC accepts intent only. In-flight clicks are deduplicated, uncertain retries reuse the report UUID/snapshot, and a matching HTTP201 receipt supplies the support reference. No raw logs, account labels/IDs, paths, credentials or transcripts are uploaded. See [REPORTING.md](REPORTING.md). Transport fixtures do not prove live Linear delivery.

- Grok is labeled **Beta** in Electron usage cards, setup and provider settings. Official-source desktop research found X Subscription per-seat shared Grok/Cursor usage, unified local Antigravity authentication and API-key-based OpenCode Go setup. These do not establish new MeterUsage connections. See [desktop login research](DESKTOP-LOGIN-RESEARCH.md).
- Grok is labeled **Beta** in Electron usage cards, setup and provider settings. Official-source desktop research found X Subscription per-seat shared Grok/Cursor usage, unified local Antigravity authentication and API-key-based OpenCode Go setup. Those research findings alone do not establish connections. The source-only Cursor Beta implementation is separately documented in [ADR 0027](adr/0027-grok-cursor-closed-reader.md); universal shared-pool coverage remains unverified. See [desktop login research](DESKTOP-LOGIN-RESEARCH.md).

- Diagnostic report attempts have a 10-minute main-process cooldown persisted as a private native preference before HTTP. Relaunch and failed sends retain the limit. Storage failure prevents transmission; Copy diagnostics remains available. Only the timestamp persists, with no report content, account identifier or receipt. See [REPORTING.md](REPORTING.md).
41 changes: 36 additions & 5 deletions docs/PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ action. Selecting an option alone performs no account access. Applying CLI
selection disconnects only MeterUsage-owned account state and waits for its
logout cleanup before enabling local collection. Cleanup failure prevents the
switch. Ordinary Disconnect never silently falls back to local data. The
selector adds no credential store, provider endpoint or authentication flow;
CLI sources retain the input and coverage limits above.
CLI transition adds no credential store, provider endpoint or authentication flow;
CLI sources retain the input and coverage limits above. The separately consented
Cursor Beta account option is described below.

Codex browser sign-in uses the official app-server helper discovered in an
existing Codex app, with existing command installations as a compatibility path.
Expand Down Expand Up @@ -125,9 +126,39 @@ 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.
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.
Demo mode blocks live connection code before helper or key discovery.
[ADR 0011](adr/0011-desktop-account-connections.md) amends the Codex/Claude
candidate boundary; it does not change the published Swift release.

Grok Bot offers a separate opt-in **Connect via Cursor (Beta)** connection under
[ADR 0027](adr/0027-grok-cursor-closed-reader.md). Sign in to Cursor, check Bot
allowance, then quit Cursor. Pre/post process and filesystem guards reject
active Cursor, unavailable or ambiguous evidence, nonempty WAL/journal files
and observed changes. These snapshots detect races; they do not exclude other
processes or prove Cursor never ran. The original SQLite database is opened
with `immutable=1&mode=ro`, which disables reader writes, locking and sidecar
bookkeeping regardless of other processes. No credential stores are copied to
temporary files. Read access times are controlled by the filesystem.

The main process selects only `ItemTable`'s `cursorAuth/accessToken`, requires
JWT expiry beyond 60 seconds, constructs the established
`WorkosCursorSessionToken` cookie, verifies the exact subject through fixed
`https://cursor.com/api/auth/me`, then sends POST `{}` to fixed
`https://cursor.com/api/dashboard/get-sand-usage-status` with
`Origin: https://cursor.com`. Redirects are refused; each request has a
30-second timeout and 1 MiB response cap. Token strings stay in memory and
request headers. They are never rendered, logged or persisted. No refresh or
model requests occur. Only an opaque account fingerprint/source preference and
projected aggregate quota persist; store fingerprints stay in memory.

The account and store are checked before allowance and after each attempt.
Cancellation or disconnect prevents further credential reads and rejects late
results. Revoked access, missing quota and expiry clear account-bound archives;
ordinary transport failures can retain dated quota only after account/store
verification. Reopen Cursor, quit it, then reconnect after expiry. No local CLI
history is attached. Trial expiry is not a replenishment timestamp. Universal
Grok Desktop, X and shared-pool coverage is unverified, as are native/live
compatibility and packaged acceptance. See [setup](DESKTOP-CONNECTIONS.md).

## Selective parsing and display boundary

Expand Down
43 changes: 43 additions & 0 deletions docs/adr/0027-grok-cursor-closed-reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ADR 0027: Read Grok Bot allowance through closed Cursor

- Status: Implemented in source; native and live-provider acceptance pending
- Date: 2026-10-16
- Amends: ADR 0011's unavailable Grok connection; retains existing CLI choice

Offer opt-in **Connect via Cursor (Beta)** without a CLI requirement. The user
signs in to Cursor, checks Bot allowance, then quits Cursor. MeterUsage never
stops Cursor. Expiry requires reopening Cursor, then quit/reconnect. This is a
compatibility interface, not a universal Grok Desktop, X or shared-pool contract.

Open the original Cursor SQLite store with `immutable=1&mode=ro`. SQLite must
perform zero reader writes, including sidecar bookkeeping, regardless of other
processes. Do not copy credential stores or files to temporary disk. Reject
nonempty WAL/journal files rather than silently ignoring uncheckpointed data.
Use pre/post process and filesystem guards, and reject unavailable, ambiguous
or changed evidence. These observations detect races, not mutual exclusion;
they cannot prove Cursor never ran between observations. Operating-system
read access-time updates are outside SQLite's byte-write guarantee.

Select only `ItemTable` key `cursorAuth/accessToken`. Parse the JWT's final
pipe-separated subject component and require expiry more than 60 seconds away.
Keep account identity case-sensitive. Construct the established session cookie,
verify the subject through fixed `https://cursor.com/api/auth/me`, then POST
`{}` to fixed `https://cursor.com/api/dashboard/get-sand-usage-status` with
`Origin: https://cursor.com`. Refuse redirects, cap responses at 1 MiB and each
request at 30 seconds. No refresh or inference requests are permitted.

Persist only an opaque account fingerprint/source preference and projected
quota. Tokens and store fingerprints stay in main-process memory. Revalidate
account and store before allowance and after attempts. Disconnect, cancellation
and source replacement prevent stale publication and further private reads.
Reuse existing coordinator backoff and account archive clearing. Never attach
local CLI history to connected-account quota.

`includedLimitZero` overrides the legacy included-limit flag. Trials require a
future `sandTrialExpiresAt`; trial expiry is not a reset. Retain only reported
usage percent and optional reset timestamp without inferring a reset cadence.

Synthetic reader, protocol, lifecycle, IPC and UI tests are required. Native
Cursor format, packaged Electron SQLite behavior, GUI setup and live provider
support remain unverified. The published Swift release and beta build 54 are
unchanged. See [setup](../DESKTOP-CONNECTIONS.md) and [privacy](../PRIVACY.md).
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 |
| [0027](0027-grok-cursor-closed-reader.md) | Opt-in Grok Bot via closed/checkpointed Cursor with immutable SQLite and account binding. | Source implemented; native/live unverified |
6 changes: 5 additions & 1 deletion src/main/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ 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 grokIdentity = String(prefs.values.desktopGrokIdentity);
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 @@ -108,7 +109,10 @@ export function compose(launch: Launch, prefs: Preferences, options: { command?:
},
}, {
slot: primary("antigravity"), quota: signal => runtime.quota(now(), signal), usage: signal => runtime.usage(join(launch.home, ".gemini/antigravity-cli"), now(), signal),
}, {
}, grokIdentity !== "" ? {
slot: primary("grok"), accountBound: true,
quota: signal => options.connections ? options.connections.quota("grok", signal, grokIdentity) : Promise.reject(new Unavailable("notSignedIn", "Grok Bot via Cursor")),
} : {
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