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

## [Unreleased]

### Added

- Show Grok Bot Desktop quota burn in percentage points per hour from recent
same-account readings. Collect for at least a minute, discard old/reset
observations and show an estimated run-out only when measured.
- Add separate OpenAI API and Anthropic API billing cards backed by projected
owner-only snapshots. A bundled one-run helper uses existing environment-only
organization Admin keys. Keep API billing distinct from subscription quota
and estimates; show report periods, capture age and stale state. Live billing
acceptance remains pending.
- 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

- Move API setup to its own Settings sidebar page. OpenAI and Anthropic API
setup now accepts a one-time Admin key and fetches billing directly, with no
Terminal steps. Existing legacy Admin keys can be reused through a read-only
Keychain action; newly pasted keys are not saved. Account connections and
onboarding stay separate.

- Connect new Grok Bot accounts through **Grok Bot Desktop (Beta)** after
explicit consent. Read only the active account-bound encrypted access token and team
through a fixed Keychain item; use a bounded read-only allowance request with
MeterUsage's own headers. Reject identity/team/store changes and cancelled
results. Keep saved Cursor connections compatible and CLI selection explicit.
No token refresh, app writes or separate xAI sign-in is added. Native store,
Keychain and endpoint compatibility 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.

### Fixed

- Preload the tray usage window hidden after completed onboarding, before
connection recovery or provider collection. The first click reuses the loaded
renderer instead of starting its initial load.
- Read Grok Bot's account-bound encrypted legacy v10 access values and correct
its fixed Keychain account to Grok Bot Key. Preserve scope, expiry, identity,
cancellation and store checks; reject plaintext and unsupported encryption.
- Give 30-day provider activity the same 26-column, seven-row heatmap layout.
Keep older unavailable dates distinct from measured zero-token days.
- Add **Use local OpenCode** setup for key-free Go session history and restore
executable Bun-install discovery for the read-only SQL fallback.
- Name saved legacy Grok connections as Cursor sources while new connections
use Grok Bot Desktop.

- Group Antigravity Desktop limits under compact model headings and shorten
remaining-allowance labels to match the CLI presentation. Preserve every
reported bucket, its used percentage and reset. Keep account allowance
separate from CLI activity, and describe unavailable saved connections
without claiming a live connection.
- Exclude Apple's system `CursorUIViewService` from the Cursor editor process
guard. Continue rejecting active Cursor processes and unsafe database state.
- Show the running app bundle's version in Settings instead of the legacy
hardcoded version. Source previews omit the version when none is supplied.

Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ 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 Desktop Beta](docs/DESKTOP-CONNECTIONS.md#grok-bot-desktop-beta).
Sign in through Grok Bot Desktop first, then approve read-only access.
Native/live allowance, X and shared-pool coverage remain unverified.
Current source also adds separate [OpenAI API and Anthropic API billing snapshots](docs/API_USAGE_BETA.md)
through Settings → API connections with a saved legacy Admin key, a one-time
Admin key, or the bundled environment-only helper. Existing Keychain entries
are preserved; pasted keys are not saved. OpenRouter keeps its existing
account balance and aggregate activity integration.
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 Expand Up @@ -72,8 +80,9 @@ CLI/local collection. The [Electron source choices](docs/electron/README.md#sour
also describe optional Codex browser sign-in and Claude Desktop allowance.

Local activity covers this Mac. Provider-reported account data has its own
coverage limits. Missing tokens remain unknown; costs are estimates and provider
dashboards remain the billing source of record. Read the [privacy contract](docs/PRIVACY.md)
coverage limits. Missing tokens remain unknown. Local token costs are estimates; optional API
billing cards label provider-reported amounts separately. Provider dashboards
remain the billing source of record. Read the [privacy contract](docs/PRIVACY.md)
before changing a data source or sharing screenshots.

## Can both editions coexist?
Expand Down
18 changes: 13 additions & 5 deletions Scripts/electron/desktop-keychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ import Foundation
import LocalAuthentication
import Security

// The helper has one fixed read. Background polling cannot open an auth dialog.
// Only fixed provider items may be read. Background polling cannot open an auth dialog.
let modes = CommandLine.arguments.dropFirst().filter { $0 != "--allow-ui" }
guard modes.count <= 1, modes.allSatisfy({ ["--grok-bot", "--api-openai", "--api-anthropic"].contains($0) }),
CommandLine.arguments.dropFirst().filter({ $0 == "--allow-ui" }).count <= 1 else { exit(1) }
let context = LAContext()
context.interactionNotAllowed = !CommandLine.arguments.contains("--allow-ui")
SecKeychainSetUserInteractionAllowed(!context.interactionNotAllowed)
let query: [String: Any] = [
let grok = CommandLine.arguments.contains("--grok-bot")
let api = modes.first?.hasPrefix("--api-") == true
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "Claude Safe Storage",
kSecAttrAccount as String: "Claude Key",
kSecAttrService as String: api ? "com.meterusage.api-keys" : grok ? "Grok Bot Safe Storage" : "Claude Safe Storage",
kSecAttrAccount as String: api ? modes.first == "--api-openai" ? "openAI" : "anthropic" : grok ? "Grok Bot Key" : "Claude Key",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationContext as String: context
]
if api { query[kSecAttrSynchronizable as String] = false }
var result: CFTypeRef?
guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
let status = SecItemCopyMatching(query as CFDictionary, &result)
if api && status == errSecItemNotFound { exit(0) }
guard status == errSecSuccess,
let password = result as? Data, !password.isEmpty else { exit(1) }
// Only the owning main process receives stdout. Never run this helper for QA.
FileHandle.standardOutput.write(password)
Loading
Loading