mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 08:02:35 +00:00
* feat(rate-limits): add Cursor usage tracking ## ELI5 If you use Cursor, Orca now shows how much of your monthly Cursor plan you have used, next to the Claude, Codex and Grok meters, and in Settings → Accounts. It reads the sign-in Cursor already saved on this computer and never changes it. ## What changed Cursor becomes a rate-limit provider like Grok: a status-bar meter (default-on, with its own toggle), a row in the usage roster, and a Settings → Accounts section naming the signed-in account. The credential is read from whichever of three stores has it, first match wins, all read-only: - the macOS login keychain item `cursor-access-token` / `cursor-user`, which is where `cursor-agent` 2026.06+ keeps the session; - `~/.cursor/auth.json` and its platform variants, used by older CLIs; - the Cursor IDE's `state.vscdb` (`cursorAuth/accessToken`), for people who never run the CLI. The keychain entry is the one current CLIs use, and reading only `auth.json` finds nothing on an up-to-date macOS install. A locked keychain cannot mask a readable `auth.json`, and a locked `state.vscdb` cannot mask either. `~/.cursor/cli-config.json` supplies the account's email and display name; it never holds a token. Usage comes from the dashboard route the Cursor web dashboard itself reads, because Cursor documents no individual-user usage API — every documented API is team- or Enterprise-scoped. Per Cursor's pricing docs an individual plan has two pools, Cursor Models and Other Models, both resetting with the billing cycle, plus optional on-demand spend; each becomes a named bucket. The headline percentage prefers `used / limit` over the sibling percentage fields, which are pre-rounded for the dashboard's own copy. Because the route is undocumented the mapping is defensive: an unrecognised payload resolves to `unavailable` and hides the bar rather than publishing a zero that reads as "no usage". Orca never runs `cursor-agent login` and never writes, refreshes or rotates a Cursor credential. An expired token short-circuits to an actionable "run cursor-agent login" instead of spending a request that can only 401 — not a rare case, since `cursor-agent status` still reports `isAuthenticated: true` against a token that expired months ago. ## Why this shape Six open PRs implement this feature and none reads the keychain, so each finds nothing for a large share of users; this takes the auth layer further and keeps what those PRs verified live. The bar is not gated on `cursor-agent` being on PATH, unlike other CLI providers, because an IDE-only session is real usage with no CLI to detect. `readKeychainPassword` moved out of the Claude keychain reader into `src/main/macos-keychain/generic-password.ts` so both providers share one `security(1)` wrapper. It is a byte-for-byte relocation, so Claude's credential path is unchanged; the two child_process allowlists move the entry with it and neither ratchet count changes. Co-authored-by: Preschian Febryantara <preschian@users.noreply.github.com> Co-authored-by: Qwesdy <qwezdi@proton.me> Co-authored-by: ivo922 <github.concur614@passmail.net> Co-authored-by: Mihail Vratchanski <mivrkiki@gmail.com> Co-authored-by: Tauri-EPO <enrico.pin@gmail.com> Co-authored-by: Raajik <44516546+Raajik@users.noreply.github.com> * test(rate-limits): name the JWT helper's segment type in the Cursor tests The anti-slop gate rejects a bare `object` parameter; the fixtures build a claims record, so say that. * fix(rate-limits): render Cursor's pools and keep its plan total visible Review of the first commit found the meter effectively blank for a healthy account, which the screenshots missed because the only Cursor session on hand had expired and never reached the success path. - The verbose status-bar segment filtered buckets through an allowlist written for Gemini's experimental models, so both Cursor pools were dropped and the fallback needed a `session` window Cursor never reports. A signed-in account rendered an icon and no number. The allowlist now admits Cursor's pools, and the fallback accepts a monthly window. - `getWindowSections` dropped `monthly` whenever buckets existed. Cursor puts the plan total there and its sub-pools in buckets, so a plan at 92% showed as 50% in the roster, the tooltip, and the tightest-usage pick. - A plan reporting `enabled: false` still published its 0% pools, painting a healthy meter for a pool the account does not own and skipping the request-quota fallback. - `redirect: 'error'` turned the dashboard's bounce to /login into a generic network failure, hiding the actionable sign-in message. - A busy `state.vscdb` (the IDE holds it open) surfaced as a provider error, which would pin an alert bar on Cursor IDE users who never set Cursor up in Orca. It falls through to "no credential" instead. - Refreshing the Accounts section read the keychain twice for one update. * fix(rate-limits): pin the platform in the Cursor keychain tests Review caught three cases that assumed macOS: the keychain source is behind an explicit `process.platform` check, so on the Linux CI runner the mocked read was never reached and the tests read the CLI file instead. They now set the platform they mean, and two new cases assert the off-macOS fall-through. Also track the credentials reference doc (docs/** is ignored by default, so a new reference needs its own allowlist entry) and give the visibility fixtures their own provider id instead of Grok's. * fix(rate-limits): prefer a live Cursor session and report a failed refresh Review round two, from CodeRabbit and Pullfrog. - Credential precedence returned the first token that parsed, so an expired keychain token in front of a fresh Cursor IDE session reported "sign-in expired" on every poll while a usable session sat one source below. A live session now wins; the expired one is returned only when nothing live exists, so the actionable message still appears in that case. - The usage schema took `.optional()` where the route sends `null` for an absent sub-object, so one null pool failed the parse for the whole body and threw away valid pools and the billing cycle with it. - Cursor usage could survive an account switch: a failed refresh for account B kept account A's figures beside B's name in Accounts. The snapshot now carries a hashed account fingerprint, and a known-and-changed identity clears the previous reading. A refresh that names no account still keeps its own. - The Accounts section rendered nothing at all when a signed-in account's fetch failed, and could repaint an older account when two status reads overlapped. It now states the failure — beside the numbers when a stale snapshot remains — and ignores superseded reads. - A web client claimed "not signed in" for a host it cannot read, contradicting the meter beside it; it now says the detail is host-only. - Signed-out copy named `cursor-agent login` as the only way in, though an IDE sign-in works just as well. - The census comment ended at 4219 after the pacer squash without naming the two modules #22616 added; recorded them, re-measured on a clean origin/main. - Narrowed the docs claim: Cursor documents all-plan APIs, but no individual usage endpoint. * fix(i18n): localize the web client's Cursor host-only notice It reaches the Accounts pane like any other string, so the coverage gate is right to want it in the catalog rather than allowlisted. * fix(rate-limits): name the Cursor account on failed refreshes, and ship the reworded copy Review round three. Both findings say an earlier fix did not actually take. - The account-switch guard reads `authProvenance` off the fresh result, but the fetcher stamped it only on success and network failures. The `stale-token`, 429, 5xx and parse results omitted it, and so did the expired-session branch — so a switch whose first refresh failed, which is precisely the case the guard exists for, still rendered the previous account's figures under the new name. Every failure holding a readable session now names its account; a missing or unreadable credential still names none. The service test also fed a result shape the fetcher never produces, so it proved nothing; it now uses the real stale-token shape, and the fetcher test asserts provenance across 401/429/5xx and expiry. - The reworded signed-out copy never rendered: a present catalog value beats the `translate()` fallback, and `sync:localization-catalog` only adds missing keys rather than updating changed defaults. Updated both strings in en.json, which also prunes them from the runtime-required catalog now that they match. * docs: keep the Cursor credentials reference out of the tree Its content lives in the PR description instead; docs/** stays ignored rather than gaining an allowlist entry for this branch. * test(mobile): drop the census note main no longer pins main removed `SESSION_ROUTE_MODULES` and re-pinned this lane on a different count, so the paragraph this branch added documents a number series that is gone. The branch touches nothing in this file now. --------- Co-authored-by: Preschian Febryantara <preschian@users.noreply.github.com> Co-authored-by: Qwesdy <qwezdi@proton.me> Co-authored-by: ivo922 <github.concur614@passmail.net> Co-authored-by: Mihail Vratchanski <mivrkiki@gmail.com> Co-authored-by: Tauri-EPO <enrico.pin@gmail.com> Co-authored-by: Raajik <44516546+Raajik@users.noreply.github.com>
164 lines
8.0 KiB
JSON
164 lines
8.0 KiB
JSON
{
|
|
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
|
|
"include": [
|
|
"../src/cli/**/*",
|
|
"../src/shared/**/*",
|
|
"../src/main/agent-state-file-reader.ts",
|
|
"../src/main/agent-hooks/grok-replay-guard.ts",
|
|
"../src/main/claude/hook-script.ts",
|
|
"../src/main/claude/claude-session-end-hook-capability.ts",
|
|
"../src/main/agent-hooks/hook-stdin-contract.ts",
|
|
"../src/main/agent-hooks/hook-post-command.ts",
|
|
"../src/main/agent-hooks/hook-config-write-path.ts",
|
|
"../src/main/agent-hooks/hooks-json-read.ts",
|
|
"../src/main/agent-hooks/installer-utils.ts",
|
|
"../src/main/agent-hooks/installer-utils-remote.ts",
|
|
"../src/main/agent-hooks/local-agent-cli-presence.ts",
|
|
"../src/main/agent-hooks/managed-toml-ownership.ts",
|
|
"../src/main/agent-hooks/managed-agent-hook-controls.ts",
|
|
"../src/main/agent-hooks/managed-agent-hook-registry.ts",
|
|
"../src/main/agent-hooks/managed-hook-script-refresh.ts",
|
|
"../src/main/agent-hooks/posix-hook-command.ts",
|
|
"../src/main/agent-hooks/runtime-home-hook-command.ts",
|
|
"../src/main/agent-hooks/windows-direct-cmd-hook-command.ts",
|
|
"../src/main/agent-hooks/windows-powershell-hook-launcher.ts",
|
|
"../src/main/amp/agent-status-plugin-source.ts",
|
|
"../src/main/amp/hook-service.ts",
|
|
"../src/main/amp/managed-plugin-install-status.ts",
|
|
"../src/main/antigravity/hook-events.ts",
|
|
"../src/main/antigravity/hook-script.ts",
|
|
"../src/main/antigravity/hook-service.ts",
|
|
"../src/main/antigravity/hooks-json-bundle.ts",
|
|
"../src/main/claude/hook-settings.ts",
|
|
"../src/main/claude/hook-service.ts",
|
|
"../src/main/claude/statusline-script.ts",
|
|
"../src/main/claude-accounts/keychain.ts",
|
|
"../src/main/macos-keychain/generic-password.ts",
|
|
"../src/main/codex/codex-app-server-capability-cache.ts",
|
|
"../src/main/codex/codex-app-server-capability-signal.ts",
|
|
"../src/main/codex/codex-app-server-client.ts",
|
|
"../src/main/codex/codex-app-server-process-tree-kill.ts",
|
|
"../src/main/codex/codex-app-server-record-reader.ts",
|
|
"../src/main/codex/codex-app-server-session.ts",
|
|
"../src/main/codex/codex-config-mirror.ts",
|
|
"../src/main/codex/codex-config-path-reference-rewrite.ts",
|
|
"../src/main/codex/codex-config-settings-preservation.ts",
|
|
"../src/main/codex/codex-config-settings-removal.ts",
|
|
"../src/main/codex/codex-config-settings-upsert.ts",
|
|
"../src/main/codex/codex-daemon-socket-path-guard.ts",
|
|
"../src/main/codex/codex-home-paths.ts",
|
|
"../src/main/codex/codex-hook-definition.ts",
|
|
"../src/main/codex/codex-managed-home-resource-copy-marker.ts",
|
|
"../src/main/codex/codex-managed-trust-grant-plan.ts",
|
|
"../src/main/codex/codex-path-observation.ts",
|
|
"../src/main/codex/codex-hook-identity.ts",
|
|
"../src/main/codex/codex-hook-legacy-cleanup.ts",
|
|
"../src/main/codex/codex-hook-local-install.ts",
|
|
"../src/main/codex/codex-hook-local-maintenance.ts",
|
|
"../src/main/codex/codex-hook-remote-install.ts",
|
|
"../src/main/codex/codex-hook-script.ts",
|
|
"../src/main/codex/codex-hook-service-implementation.ts",
|
|
"../src/main/codex/codex-hook-status.ts",
|
|
"../src/main/codex/codex-hook-system-trust.ts",
|
|
"../src/main/codex/codex-hook-trust-cleanup.ts",
|
|
"../src/main/codex/codex-hook-trust-grant.ts",
|
|
"../src/main/codex/codex-hook-trust-queue.ts",
|
|
"../src/main/codex/codex-hook-user-mirroring.ts",
|
|
"../src/main/codex/codex-hook-wsl-runtime.ts",
|
|
"../src/main/codex/codex-managed-trust-reconciliation.ts",
|
|
"../src/main/codex/codex-process-exit-deadline.ts",
|
|
"../src/main/codex/codex-state-db.ts",
|
|
"../src/main/codex/codex-trust-identity.ts",
|
|
"../src/main/codex/codex-trust-config-rollback.ts",
|
|
"../src/main/codex/codex-trust-config-mutation-queue.ts",
|
|
"../src/main/codex/codex-trust-grant-telemetry.ts",
|
|
"../src/main/codex/codex-trust-grant-host.ts",
|
|
"../src/main/codex/codex-trust-grant-ledger.ts",
|
|
"../src/main/codex/codex-user-hook-trust-rebase-client.ts",
|
|
"../src/main/codex/codex-user-hook-trust-rebase.ts",
|
|
"../src/main/codex/codex-wsl-hook-install-plan.ts",
|
|
"../src/main/codex/config-settings-baseline.ts",
|
|
"../src/main/codex/config-settings-conflict-resolution.ts",
|
|
"../src/main/codex/config-plugin-registration-promotion.ts",
|
|
"../src/main/codex/config-toml-plugin-registration-tables.ts",
|
|
"../src/main/codex/config-toml-promoted-setting-values.ts",
|
|
"../src/main/codex/config-settings-promotion.ts",
|
|
"../src/main/codex/config-settings-promotion-write-target.ts",
|
|
"../src/main/codex/config-sync-stall.ts",
|
|
"../src/main/codex/config-toml-atomic-write.ts",
|
|
"../src/main/codex/config-toml-deprecated-hook-flag.ts",
|
|
"../src/main/codex/config-toml-hook-trust-blocks.ts",
|
|
"../src/main/codex/config-toml-hook-trust-edit.ts",
|
|
"../src/main/codex/config-toml-hook-trust-read.ts",
|
|
"../src/main/codex/config-toml-key-path.ts",
|
|
"../src/main/codex/config-toml-line-scan.ts",
|
|
"../src/main/codex/config-toml-project-trust.ts",
|
|
"../src/main/codex/config-toml-runtime-owned-sections.ts",
|
|
"../src/main/codex/config-toml-syntax.ts",
|
|
"../src/main/codex/config-toml-trust.ts",
|
|
"../src/main/codex/hook-service.ts",
|
|
"../src/main/codex/hook-trust-promotion.ts",
|
|
"../src/main/codex/managed-home-shell-preflight.ts",
|
|
"../src/main/codex-accounts/fs-utils.ts",
|
|
"../src/main/codex-accounts/wsl-codex-command.ts",
|
|
"../src/main/codex-cli/command.ts",
|
|
"../src/main/command-code/command-code-managed-script.ts",
|
|
"../src/main/command-code/hook-service.ts",
|
|
"../src/main/copilot/copilot-managed-hook-definitions.ts",
|
|
"../src/main/copilot/copilot-managed-script.ts",
|
|
"../src/main/copilot/copilot-remote-hook-install.ts",
|
|
"../src/main/copilot/hook-service.ts",
|
|
"../src/main/cursor/hook-events.ts",
|
|
"../src/main/cursor/hook-script.ts",
|
|
"../src/main/cursor/hook-service.ts",
|
|
"../src/main/droid/hook-service.ts",
|
|
"../src/main/gemini/hook-service.ts",
|
|
"../src/main/grok/grok-hook-config.ts",
|
|
"../src/main/grok/grok-hook-config-cleanup.ts",
|
|
"../src/main/grok/grok-hook-config-file.ts",
|
|
"../src/main/grok/grok-hook-owners.ts",
|
|
"../src/main/grok/grok-hook-remote-install.ts",
|
|
"../src/main/grok/grok-hook-script.ts",
|
|
"../src/main/grok/grok-hook-symlink-cleanup-marker.ts",
|
|
"../src/main/grok/hook-service.ts",
|
|
"../src/main/grok/windows-grok-hook-script.ts",
|
|
"../src/main/devin/hook-settings.ts",
|
|
"../src/main/devin/hook-service.ts",
|
|
"../src/main/devin/hook-config-json.ts",
|
|
"../src/main/hermes/hermes-config-yaml.ts",
|
|
"../src/main/hermes/hermes-home-filesystem.ts",
|
|
"../src/main/hermes/hermes-managed-plugin-source.ts",
|
|
"../src/main/hermes/hook-service.ts",
|
|
"../src/main/git-bash.ts",
|
|
"../src/main/in-flight-run-dedupe.ts",
|
|
"../src/main/kimi/hook-service.ts",
|
|
"../src/main/kimi/kimi-hook-config-toml.ts",
|
|
"../src/main/muse/hook-config-json.ts",
|
|
"../src/main/muse/hook-service.ts",
|
|
"../src/main/muse/hook-settings.ts",
|
|
"../src/main/zcode/hook-config-json.ts",
|
|
"../src/main/zcode/hook-service.ts",
|
|
"../src/main/zcode/hook-settings.ts",
|
|
"../src/main/openclaude/hook-service.ts",
|
|
"../src/main/rolling-file-backup.ts",
|
|
"../src/main/startup/hydrate-shell-path.ts",
|
|
"../src/main/startup/windows-shell-path-ownership.ts",
|
|
// Why: serve-electron-flag-parity.test.ts checks the Electron-side serve argv rewrite against this
|
|
// project's serve spec; the module has no imports, so listing it pulls in nothing else.
|
|
"../src/main/startup/serve-mode-argv.ts",
|
|
// The parity test keeps this import-free list aligned with COMMAND_SPECS.
|
|
"../src/main/startup/cli-command-names.ts",
|
|
"../src/main/runtime/runtime-metadata.ts",
|
|
"../src/main/sqlite/sync-database.ts",
|
|
"../src/main/win32-utils.ts"
|
|
],
|
|
"compilerOptions": {
|
|
"composite": true,
|
|
// TypeScript 7 removed node10 resolution; Node16 preserves CommonJS emit for this package.
|
|
"module": "Node16",
|
|
"moduleResolution": "Node16",
|
|
"rootDir": "../src",
|
|
"outDir": "../out"
|
|
}
|
|
}
|