/** * `devtools`: read locally-stored state of the AI coding agents harnery * supports — Claude Code (`~/.claude`), Codex (`~/.codex`), and Cursor * (`~/.cursor`) — into one uniform status shape: login state, plan/tier, * auth expiry, session counts, and (where the tool stores them locally) * rate-limit / quota windows. * * Everything here reads files on disk — no network, no vendor API, no * credentials leave the machine (auth tokens are inspected for their * non-secret claims only; the token strings themselves are never returned). * The signals a tool keeps server-side (Cursor usage + billing, Claude's live * rate-limit windows) surface as `null` with a note rather than a guess. * * Pure toolkit tier: depends only on `node:*`, never on `src/core/`. */ export type DevtoolName = "claude-code" | "codex" | "cursor"; export interface QuotaWindow { /** Human label for the reset window (e.g. "5h", "weekly", "45m"). */ window: string; /** Percent of the window's allowance consumed, 0-100, or null if unknown. */ usedPercent: number | null; /** ISO timestamp when the window resets, or null if unknown. */ resetsAt: string | null; } export interface ApiEnrichment { /** The configured API key authenticated successfully. */ ok: boolean; /** Human name the vendor reports for the key (e.g. Cursor's apiKeyName). */ keyName: string | null; /** Cloud-agent activity (Cursor Cloud Agent API), when available. */ cloudAgents: { total: number; active: number; } | null; /** Error message when the key is configured but a call failed. */ error: string | null; } /** * Cursor billing-cycle + usage snapshot, fetched from cursor.com's own dashboard * API using the IDE's locally-stored session token — the same call Cursor's UI * makes for the Spending page. No API key to mint; it reads what's already on * disk. Percentages are 0-100; cent amounts are raw (divide by 100 for dollars). */ export interface CursorUsage { /** ISO start of the current billing cycle. */ cycleStart: string | null; /** ISO end of the current billing cycle (the "resets on …" date). */ cycleEnd: string | null; /** Percent of included total usage consumed this cycle (Cursor's "Total"). */ totalPercentUsed: number | null; /** Percent of included API usage consumed (named-model / "API"). */ apiPercentUsed: number | null; /** Percent of included first-party ("Auto") model usage consumed. */ firstPartyPercentUsed: number | null; /** Included usage allowance in cents (e.g. 7000 = $70). */ includedLimitCents: number | null; } /** * Overage / on-demand dollar spend against a cap — Cursor's on-demand usage and * Claude's extra-usage credits are the same idea. Cents are raw (÷100 for USD). */ export interface SpendStatus { /** Human label for what this spend covers (e.g. "On-demand", "Extra usage"). */ label: string; /** Amount spent this cycle in cents. */ usedCents: number | null; /** Spend cap in cents, or null when unset/unlimited. */ limitCents: number | null; } export interface ToolStatus { tool: DevtoolName; /** The tool's config directory exists on this machine. */ installed: boolean; /** A credential is present and not obviously expired. null when undeterminable. */ loggedIn: boolean | null; /** Account identifier (email where the tool exposes one), else null. */ account: string | null; /** Plan / seat tier as the tool records it locally (e.g. "team", "team_tier_1"). */ plan: string | null; /** Rate-limit tier string where the tool records one, else null. */ rateLimitTier: string | null; /** ISO expiry of the active access credential, else null. */ authExpiresAt: string | null; /** Count of local session transcripts, else null. */ sessions: number | null; /** ISO timestamp of the most recent local session activity, else null. */ lastActivity: string | null; /** Locally-known quota/rate-limit windows, or null when the tool keeps them server-side. */ quota: QuotaWindow[] | null; /** * Total tokens observed in local transcripts within the scan window * (`windowDays`). Codex always reports it — its per-session cumulative total * is one tail-read per rollout, cheap enough for every render. Claude Code's * is `--usage`-gated (a full transcript scan, potentially gigabytes) and null * otherwise. Cursor keeps token counts server-side, so it stays null. */ tokensUsed: number | null; /** * Result of the optional API enrichment (network), populated by * `enrichFromApi` when a key is configured. `null` when no enrichment ran. */ api: ApiEnrichment | null; /** * Cursor billing-cycle + usage, populated by `enrichFromApi` from the IDE's * own session token (no API key needed). `null` when no enrichment ran, the * token is stale, or the tool isn't Cursor. */ usage: CursorUsage | null; /** * Overage / on-demand dollar spend, populated by `enrichFromApi` (Cursor's * on-demand, Claude's extra-usage credits). `null` when no enrichment ran or * the plan has no overage concept. */ spend: SpendStatus | null; /** Caveats about what is and isn't derivable locally for this tool. */ notes: string[]; } export interface DevtoolsReport { generatedAt: string; windowDays: number | null; tools: ToolStatus[]; } /** One endpoint's health, from `probeEndpoints` (the `devtools doctor` check). */ export interface ProbeResult { tool: DevtoolName; endpoint: string; /** Client version we put in the request's User-Agent, else null. */ clientVersion: string | null; /** HTTP status, or null when no call was made / the request threw. */ status: number | null; outcome: "ok" | "rate_limited" | "auth_rejected" | "shape_changed" | "unreachable" | "no_credential"; detail: string; } export interface ReadDevtoolsOpts { /** Home directory to resolve tool config against. Defaults to os.homedir(). */ home?: string; /** Scan transcripts for token totals (opt-in; can be slow). Default false. */ usage?: boolean; /** When scanning usage, only include transcripts modified within N days. Default 7. */ windowDays?: number; /** Clock injection for tests. Default Date.now(). */ now?: number; /** Restrict to a subset of tools. Default all three. */ only?: readonly DevtoolName[]; } /** Path of the machine-local Cursor API key file (honors XDG_CONFIG_HOME). */ export declare function cursorApiKeyPath(): string; /** Resolve a Cursor API key: env `CURSOR_API_KEY` first, then the key file. */ export declare function resolveCursorApiKey(): string | null; /** * Enrich a report's entries over the network with live usage each tool keeps * server-side, authenticating with the credential already on disk. Best-effort * and network-guarded: every failure degrades to a note, never throws. No-op * for a tool that isn't installed / present in the report. */ export declare function enrichFromApi(report: DevtoolsReport, opts?: { cursorKey?: string | null; timeoutMs?: number; home?: string; /** Cache TTL for the usage endpoints (ms). 0 disables caching. Default 120s. */ cacheTtlMs?: number; }): Promise; /** * One live call per usage endpoint (cache-bypassing) to check the integration * still works — the occasional "did a client change its headers?" test. It * exercises the EXACT request builders production uses, so an `auth_rejected` * result means our headers/token stopped being accepted, and `shape_changed` * means the response schema drifted. Rate-limited results are reported as such, * not as failures. Makes at most one request per tool; run it by hand (it is not * scheduled) so it can't itself cause a rate limit. */ export declare function probeEndpoints(opts?: { home?: string; timeoutMs?: number; only?: readonly DevtoolName[]; }): Promise; export declare function readDevtools(opts?: ReadDevtoolsOpts): DevtoolsReport; //# sourceMappingURL=devtools.d.ts.map