openlimiter 2.0.0 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/api-spend.d.ts +29 -0
  2. package/dist/api-spend.d.ts.map +1 -0
  3. package/dist/api-spend.js +113 -0
  4. package/dist/api-spend.js.map +1 -0
  5. package/dist/cli.d.ts +7 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +85 -16
  8. package/dist/cli.js.map +1 -1
  9. package/dist/codex-device-login.d.ts.map +1 -1
  10. package/dist/codex-device-login.js +4 -1
  11. package/dist/codex-device-login.js.map +1 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +2 -1
  14. package/dist/config.js.map +1 -1
  15. package/dist/hub-auth.d.ts +2 -0
  16. package/dist/hub-auth.d.ts.map +1 -1
  17. package/dist/hub-auth.js +18 -1
  18. package/dist/hub-auth.js.map +1 -1
  19. package/dist/hub-sync.d.ts +1 -1
  20. package/dist/hub-sync.js +1 -1
  21. package/dist/ingest.d.ts +24 -0
  22. package/dist/ingest.d.ts.map +1 -1
  23. package/dist/ingest.js +34 -2
  24. package/dist/ingest.js.map +1 -1
  25. package/dist/session.d.ts.map +1 -1
  26. package/dist/session.js +1 -4
  27. package/dist/session.js.map +1 -1
  28. package/dist/statusline-wrapper.d.ts.map +1 -1
  29. package/dist/statusline-wrapper.js +2 -1
  30. package/dist/statusline-wrapper.js.map +1 -1
  31. package/dist/statusline.d.ts +2 -0
  32. package/dist/statusline.d.ts.map +1 -1
  33. package/dist/statusline.js +34 -15
  34. package/dist/statusline.js.map +1 -1
  35. package/dist/terminal-backup.d.ts +85 -1
  36. package/dist/terminal-backup.d.ts.map +1 -1
  37. package/dist/terminal-backup.js +148 -22
  38. package/dist/terminal-backup.js.map +1 -1
  39. package/dist/terminal-fallback.d.ts.map +1 -1
  40. package/dist/terminal-fallback.js +13 -3
  41. package/dist/terminal-fallback.js.map +1 -1
  42. package/dist/terminal-toml.d.ts +8 -0
  43. package/dist/terminal-toml.d.ts.map +1 -1
  44. package/dist/terminal-toml.js +59 -2
  45. package/dist/terminal-toml.js.map +1 -1
  46. package/dist/terminal.d.ts.map +1 -1
  47. package/dist/terminal.js +145 -36
  48. package/dist/terminal.js.map +1 -1
  49. package/package.json +4 -4
@@ -0,0 +1,29 @@
1
+ /**
2
+ * API money for the terminal status line.
3
+ *
4
+ * The desktop app polls provider spend and balances and persists them to
5
+ * `api-spend-v1.json` in the state directory, the same folder the CLI already
6
+ * resolves for the quota cache on every OS. This module only reads that file,
7
+ * once, with no network. Anything unexpected draws nothing: a status row runs
8
+ * inside somebody else's tool and must never fail over a money cell.
9
+ */
10
+ export declare const API_SPEND_FILE_NAME = "api-spend-v1.json";
11
+ /** Money providers the desktop app saves, with the short tag the row uses. */
12
+ export declare const MONEY_TAGS: Readonly<Record<string, string>>;
13
+ export declare function readApiSpend(stateDirectory: string): Promise<unknown>;
14
+ export interface MoneyCell {
15
+ readonly plain: string;
16
+ /** Band value for the shared colour rule, or null when the cell stays uncoloured. */
17
+ readonly band: number | null;
18
+ readonly prefix: string;
19
+ readonly amount: string;
20
+ }
21
+ export interface MoneyOptions {
22
+ readonly now: string;
23
+ /** The quota cache already drew an `or` cell, so the file adds none. */
24
+ readonly hasOpenRouter: boolean;
25
+ /** Whether a provider name (`openai`, `openrouter`...) may be drawn. */
26
+ readonly allowed: (provider: string) => boolean;
27
+ }
28
+ export declare function moneyCells(document: unknown, options: MoneyOptions): readonly MoneyCell[];
29
+ //# sourceMappingURL=api-spend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-spend.d.ts","sourceRoot":"","sources":["../src/api-spend.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,eAAO,MAAM,mBAAmB,sBAAsB,CAAC;AAEvD,8EAA8E;AAC9E,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMvD,CAAC;AAIF,wBAAsB,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG3E;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACjD;AAuBD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,SAAS,SAAS,EAAE,CA2DzF"}
@@ -0,0 +1,113 @@
1
+ import path from "node:path";
2
+ import { freshnessPolicy, readJsonFileSafely } from "@openlimiter/core";
3
+ /**
4
+ * API money for the terminal status line.
5
+ *
6
+ * The desktop app polls provider spend and balances and persists them to
7
+ * `api-spend-v1.json` in the state directory, the same folder the CLI already
8
+ * resolves for the quota cache on every OS. This module only reads that file,
9
+ * once, with no network. Anything unexpected draws nothing: a status row runs
10
+ * inside somebody else's tool and must never fail over a money cell.
11
+ */
12
+ export const API_SPEND_FILE_NAME = "api-spend-v1.json";
13
+ /** Money providers the desktop app saves, with the short tag the row uses. */
14
+ export const MONEY_TAGS = {
15
+ openai: "oa",
16
+ anthropic: "an",
17
+ xai: "xa",
18
+ moonshot: "ms",
19
+ deepseek: "ds"
20
+ };
21
+ const MAX_SOURCES = 64;
22
+ export async function readApiSpend(stateDirectory) {
23
+ const result = await readJsonFileSafely(path.join(stateDirectory, API_SPEND_FILE_NAME)).catch(() => null);
24
+ return result?.ok === true ? result.value : undefined;
25
+ }
26
+ function isRecord(value) {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+ function decimal(value) {
30
+ if (typeof value !== "string" || !/^\d+(\.\d+)?$/u.test(value))
31
+ return null;
32
+ const parsed = Number(value);
33
+ return Number.isFinite(parsed) ? parsed : null;
34
+ }
35
+ const money = (value) => "$" + value.toFixed(2);
36
+ /** Lower is worse: yellow under $5, red under $1, as the `or` balance cell. */
37
+ const balanceBand = (value) => value < 1 ? 95 : value < 5 ? 65 : 0;
38
+ /** The band rule's percent of budget; an uncoloured cell has no budget to measure. */
39
+ function budgetBand(spend, budget) {
40
+ const limit = decimal(budget);
41
+ return limit !== null && limit > 0 ? spend / limit * 100 : null;
42
+ }
43
+ export function moneyCells(document, options) {
44
+ if (!isRecord(document) || !Array.isArray(document["sources"]) || !Array.isArray(document["samples"]))
45
+ return [];
46
+ const month = options.now.slice(0, 7) + "-01";
47
+ const newest = new Map();
48
+ for (const sample of document["samples"]) {
49
+ if (!isRecord(sample) || typeof sample["sourceId"] !== "string")
50
+ continue;
51
+ const observed = Date.parse(String(sample["observedAt"]));
52
+ if (!Number.isFinite(observed))
53
+ continue;
54
+ const held = newest.get(sample["sourceId"]);
55
+ const heldAt = held === undefined ? -Infinity : Date.parse(String(held["observedAt"]));
56
+ if (observed > heldAt || observed === heldAt && Number(sample["sequence"]) > Number(held?.["sequence"])) {
57
+ newest.set(sample["sourceId"], sample);
58
+ }
59
+ }
60
+ const cells = [];
61
+ let openRouter;
62
+ for (const source of document["sources"].slice(0, MAX_SOURCES)) {
63
+ if (!isRecord(source) || source["enabled"] !== true || typeof source["id"] !== "string")
64
+ continue;
65
+ const provider = source["provider"];
66
+ if (typeof provider !== "string" || !options.allowed(provider))
67
+ continue;
68
+ const sample = newest.get(source["id"]);
69
+ if (sample === undefined)
70
+ continue;
71
+ const tag = provider === "openrouter" ? "or" : MONEY_TAGS[provider];
72
+ if (tag === undefined || provider === "openrouter" && options.hasOpenRouter)
73
+ continue;
74
+ const stale = freshnessPolicy({
75
+ sourceClass: "internal_payload", observedAt: String(sample["observedAt"]), now: options.now, writer: "desktop"
76
+ }).availability === "stale";
77
+ const prefix = tag + " ";
78
+ const mark = stale ? "~" : "";
79
+ const usd = sample["currencySource"] === "provider_usd";
80
+ if (provider === "moonshot" || provider === "deepseek") {
81
+ const balance = decimal(sample["balanceUsd"]);
82
+ const tooLow = source["status"] === "too_low_for_api_calls";
83
+ if (usd && balance !== null) {
84
+ const amount = mark + money(balance);
85
+ cells.push({ plain: prefix + amount, prefix, amount, band: tooLow ? 95 : balanceBand(balance) });
86
+ }
87
+ else if (provider === "deepseek" && sample["currencySource"] === "provider_cny" &&
88
+ sample["balanceUsd"] == null && sample["spendUsd"] == null) {
89
+ // Yuan is never converted, so the cell says the currency and no amount.
90
+ const amount = mark + "CNY";
91
+ cells.push({ plain: prefix + amount, prefix, amount, band: null });
92
+ }
93
+ continue;
94
+ }
95
+ const spend = decimal(sample["spendUsd"]);
96
+ // A spend from an earlier month is not this month's spend, so it is not shown.
97
+ if (!usd || spend === null || sample["month"] !== month)
98
+ continue;
99
+ const amount = mark + money(spend);
100
+ const suffix = provider === "openrouter" ? " spent" : "";
101
+ const cell = { plain: prefix + amount + suffix, prefix, amount: amount + suffix, band: budgetBand(spend, source["budgetUsd"]) };
102
+ if (provider === "openrouter") {
103
+ // One `or` cell at most: the most recently observed source wins, the first on a tie.
104
+ const observedAt = Date.parse(String(sample["observedAt"]));
105
+ if (openRouter !== undefined && observedAt <= openRouter.observedAt)
106
+ continue;
107
+ openRouter = { cell, observedAt };
108
+ }
109
+ cells.push(cell);
110
+ }
111
+ return cells.filter(cell => cell.prefix !== "or " || cell === openRouter?.cell);
112
+ }
113
+ //# sourceMappingURL=api-spend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-spend.js","sourceRoot":"","sources":["../src/api-spend.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -142,6 +142,13 @@ export declare const EXIT_OK = 0;
142
142
  export declare const EXIT_FAILURE = 1;
143
143
  export declare const EXIT_USAGE = 2;
144
144
  export declare const EXIT_NO_DATA = 3;
145
+ export interface BrowserOpenInvocation {
146
+ readonly executable: string;
147
+ readonly arguments: readonly string[];
148
+ readonly cwd: string;
149
+ }
150
+ /** Build a direct browser opener invocation without a command shell. */
151
+ export declare function browserOpenInvocation(url: string, platform: NodeJS.Platform, environment?: Readonly<Record<string, string | undefined>>): BrowserOpenInvocation | null;
145
152
  export declare function runtimeDependencies(): Pick<CliDependencies, "acquisitionTransport" | "spawnDetached" | "windowsCredentialRunner" | "probeAntigravity" | "resolveExecutablePath" | "hubTransport" | "openBrowser" | "emit" | "windowsAclRunner" | "codexDeviceLoginRunnerFactory">;
146
153
  export interface RefreshResult {
147
154
  snapshots: Snapshot[];
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ACQUISITION_OUTCOME_SENTENCE, ACQUISITION_PROVIDERS, registerManagedCodexAccount, CREDENTIAL_FAILURE_SENTENCE, PROVIDER_CODES, acquireRefreshLock, clearRefreshSpawnFailure, mergeAcquiredSnapshots, createFetchTransport, readRefreshSpawnFailure, antigravitySpec, buildAdvice, canonicalJson, claudeSpec, codexSpec, cursorSpec, dedupeFailures, desktopHoldsCache, failureFromConnectorReason, freshness, geminiCliSpec, grokSpec, isProviderDue, kimiSpec, mergeSnapshots, normalizeMeters, normalizeMetersReport, openrouterSpec, readAcquisitionCredential, readAcquisitionSchedule, readSnapshotCache, readWindowsCredentialWith, resolveStateDirectory, runAcquisition, spawnDetachedRefresh, writeAcquisitionSchedule, probeAntigravity, resolveAgyExecutablePath } from "@openlimiter/core";
1
+ import { ACQUISITION_OUTCOME_SENTENCE, captureStatuslineAccount, pruneSnapshotCache, ACQUISITION_PROVIDERS, registerManagedCodexAccount, CREDENTIAL_FAILURE_SENTENCE, PROVIDER_CODES, acquireRefreshLock, clearRefreshSpawnFailure, mergeAcquiredSnapshots, createFetchTransport, readRefreshSpawnFailure, antigravitySpec, buildAdvice, canonicalJson, claudeSpec, codexSpec, cursorSpec, dedupeFailures, desktopHoldsCache, failureFromConnectorReason, freshness, geminiCliSpec, grokSpec, isProviderDue, kimiSpec, mergeSnapshotCache, mergeSnapshots, normalizeMeters, normalizeMetersReport, openrouterSpec, readAcquisitionCredential, readAcquisitionSchedule, readSnapshotCache, readWindowsCredentialWith, resolveStateDirectory, runAcquisition, spawnDetachedRefresh, trustedHelperWorkingDirectory, writeAcquisitionSchedule, windowsSystemTool, probeAntigravity, resolveAgyExecutablePath } from "@openlimiter/core";
2
2
  import { antigravityFixture, claudeFixture, codexFixture, grokFixture, kimiFixture, connectors, manualFixture, opencodeFixture, openrouterFixture, parseAntigravityCodeAssistPayload, parseAntigravityPayload, parseClaudePayload, parseCodexPayload, parseCursorPayload, parseGeminiCliPayload, parseGrokPayload, parseKimiPayload, parseManualPayload, parseOpencodePayload, parseOpenrouterPayload } from "@openlimiter/connectors";
3
3
  import { AGENT_COMPATIBILITY, agentContextFromCache, agentContextSpillFromCache, agentVersionCompatibility, changeAgentHook, detectAgentInstallation, readAgentHookStatus, runAgentHook, validateAgentExecutableStamp, writeAgentContextSnapshot, loadHostedContextTrust, renderClaudeStatusline } from "@openlimiter/adapters";
4
4
  import { execFile, spawn } from "node:child_process";
@@ -7,11 +7,12 @@ import packageJson from "../package.json" with { type: "json" };
7
7
  import { homedir } from "node:os";
8
8
  import path from "node:path";
9
9
  import { PROVIDER_KEYS, STATUSLINE_KEYS, defaultConfig, initialize, isProviderKey, isStatuslineKey, providerValueText, readConfig, readProvidersConfig, readStatuslineConfig, setProviderValue, setStatuslineValue, statuslineValueText, writeConfig } from "./config.js";
10
- import { ACQUISITION_PROVENANCE, ANTIGRAVITY_STATUSLINE_PROVENANCE, GROK_STATUSLINE_PROVENANCE, INGEST_PROVENANCE, MANUAL_PROVENANCE, STATUSLINE_PROVENANCE, environmentWithLocalMarkers, STDIN_BYTE_LIMIT, parseAntigravityStatuslinePayload, parseGrokStatuslinePayload, parseJsonText, persistSnapshots, readManualDocument, withProvenance } from "./ingest.js";
10
+ import { ACQUISITION_PROVENANCE, ANTIGRAVITY_STATUSLINE_PROVENANCE, GROK_STATUSLINE_PROVENANCE, INGEST_PROVENANCE, MANUAL_PROVENANCE, STATUSLINE_PROVENANCE, environmentWithLocalMarkers, STDIN_BYTE_LIMIT, parseAntigravityStatuslinePayload, parseGrokStatuslinePayload, parseJsonText, persistSnapshots, clearStatuslineFailure, readStatuslineFailure, recordStatuslineFailure, readManualDocument, withProvenance } from "./ingest.js";
11
11
  import { UnavailableCredentialStore } from "./credentials.js";
12
12
  import { failureLine, failureLines, renderTable, supportsColor } from "./render.js";
13
13
  import { isStatuslineHost, renderStatuslineLayout, statuslineColor, statuslineUnicode } from "./statusline.js";
14
14
  import { parseStatuslineSession } from "./statusline-ingest.js";
15
+ import { readApiSpend } from "./api-spend.js";
15
16
  import { TERMINAL_HOST_NAMES, installHost, terminalHide, terminalShow, terminalStatusTable, uninstallHost } from "./terminal.js";
16
17
  import { createFetchHubTransport, hubConfigured } from "./hub.js";
17
18
  import { DELIVERY_UNCONFIRMED_SENTENCE, REVOKED_SENTENCE, ensureFreshSession, isAborted, runDeviceLogin } from "./hub-auth.js";
@@ -88,7 +89,12 @@ const NEWLINE = "\n";
88
89
  const execFileRunner = async (executable, helperArguments, timeoutMilliseconds) => await new Promise((resolve) => {
89
90
  execFile(executable, [...helperArguments], (() => {
90
91
  if (!executable.toLowerCase().endsWith("powershell.exe")) {
91
- return { timeout: timeoutMilliseconds, maxBuffer: 262_144, windowsHide: true };
92
+ return {
93
+ cwd: trustedHelperWorkingDirectory(process.platform, process.env),
94
+ timeout: timeoutMilliseconds,
95
+ maxBuffer: 262_144,
96
+ windowsHide: true
97
+ };
92
98
  }
93
99
  const environment = { ...process.env };
94
100
  /* Windows environment names ignore case, but a copied process.env keeps
@@ -100,6 +106,7 @@ const execFileRunner = async (executable, helperArguments, timeoutMilliseconds)
100
106
  }
101
107
  environment["PSModulePath"] = path.win32.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "Modules");
102
108
  return {
109
+ cwd: trustedHelperWorkingDirectory(process.platform, environment),
103
110
  timeout: timeoutMilliseconds,
104
111
  maxBuffer: 262_144,
105
112
  windowsHide: true,
@@ -109,17 +116,48 @@ const execFileRunner = async (executable, helperArguments, timeoutMilliseconds)
109
116
  resolve(error === null ? { ok: true, stdout } : { ok: false });
110
117
  });
111
118
  });
119
+ /** Build a direct browser opener invocation without a command shell. */
120
+ export function browserOpenInvocation(url, platform, environment = process.env) {
121
+ if (url.length > 2_048)
122
+ return null;
123
+ try {
124
+ if (new URL(url).protocol !== "https:")
125
+ return null;
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ if (platform === "win32") {
131
+ return {
132
+ executable: windowsSystemTool("rundll32.exe", environment),
133
+ arguments: ["url.dll,FileProtocolHandler", url],
134
+ cwd: trustedHelperWorkingDirectory(platform, environment)
135
+ };
136
+ }
137
+ return platform === "darwin"
138
+ ? {
139
+ executable: "open",
140
+ arguments: [url],
141
+ cwd: trustedHelperWorkingDirectory(platform, environment)
142
+ }
143
+ : {
144
+ executable: "xdg-open",
145
+ arguments: [url],
146
+ cwd: trustedHelperWorkingDirectory(platform, environment)
147
+ };
148
+ }
112
149
  /** Open a URL in the person's browser, best effort and never awaited. */
113
150
  function openBrowserPlatform(url, platform) {
114
- if (!/^https:\/\//u.test(url) || url.length > 2_048)
151
+ const invocation = browserOpenInvocation(url, platform);
152
+ if (invocation === null)
115
153
  return;
116
- const [command, commandArguments] = platform === "win32"
117
- ? ["cmd", ["/c", "start", "", url]]
118
- : platform === "darwin"
119
- ? ["open", [url]]
120
- : ["xdg-open", [url]];
121
154
  try {
122
- const child = spawn(command, commandArguments, { stdio: "ignore", detached: true });
155
+ const child = spawn(invocation.executable, [...invocation.arguments], {
156
+ cwd: invocation.cwd,
157
+ stdio: "ignore",
158
+ detached: true,
159
+ shell: false
160
+ });
123
161
  child.once("error", () => undefined);
124
162
  child.unref();
125
163
  }
@@ -138,6 +176,7 @@ export function runtimeDependencies() {
138
176
  resolveExecutablePath: resolveAgyExecutablePath,
139
177
  spawnDetached: (executable, argumentsList, options) => {
140
178
  const child = spawn(executable, [...argumentsList], {
179
+ cwd: options.cwd,
141
180
  detached: true,
142
181
  stdio: "ignore",
143
182
  windowsHide: true
@@ -423,6 +462,12 @@ async function acquisitionStatusRow(spec, schedule, readCredential, snapshots, n
423
462
  nextAttemptAt
424
463
  };
425
464
  }
465
+ /** What each status line write failure means, in words with no dashes. */
466
+ const STATUSLINE_FAILURE_SENTENCE = {
467
+ ingest: "the last status line payload could not be read",
468
+ cache_write: "the last status line reading could not be saved to the cache",
469
+ agent_context: "the last status line reading was saved, the agent context file could not be written"
470
+ };
426
471
  /** The acquisition block doctor prints under the connector block. */
427
472
  async function acquisitionDoctorRows(dependencies, snapshots, now) {
428
473
  const providers = await readProvidersConfig(dependencies.stateDirectory);
@@ -437,6 +482,11 @@ async function acquisitionDoctorRows(dependencies, snapshots, now) {
437
482
  rows.push("REFRESH SPAWN FAILED " + failedAt +
438
483
  " the background refresh could not be started on this machine");
439
484
  }
485
+ const statusline = await readStatuslineFailure(dependencies.stateDirectory);
486
+ if (statusline !== null) {
487
+ rows.push(["STATUSLINE WRITE FAILED", statusline.at, statusline.stage, statusline.errorClass,
488
+ STATUSLINE_FAILURE_SENTENCE[statusline.stage]].join(" "));
489
+ }
440
490
  return [ACQUISITION_HEADER, ...rows].join(NEWLINE);
441
491
  }
442
492
  /**
@@ -461,7 +511,7 @@ function acquisitionLine(row, snapshots = []) {
461
511
  .filter((part) => part !== "")
462
512
  .join(", ");
463
513
  return [
464
- row.provider.toLowerCase() + (row.accountId === undefined ? "" : "/" + row.accountId),
514
+ row.provider.toLowerCase(),
465
515
  row.detected ? "yes" : "no",
466
516
  row.status,
467
517
  row.nextAttemptAt ?? "NONE",
@@ -702,6 +752,9 @@ async function ingestStandardInput(dependencies, now, host = "claude") {
702
752
  if (host === "codex" || host === "shell")
703
753
  return null;
704
754
  try {
755
+ const accountId = await captureStatuslineAccount(host === "antigravity" ? "ANTIGRAVITY" : host === "grok" ? "GROK" : "CLAUDE", {
756
+ environment: dependencies.environment, homeDirectory: dependencies.homeDirectory, platform: dependencies.platform, ...(dependencies.stateDirectory ? { stateDirectory: dependencies.stateDirectory } : {}), now
757
+ });
705
758
  const document = parseJsonText(await dependencies.readStandardInput());
706
759
  if (!document.ok)
707
760
  return null;
@@ -719,19 +772,31 @@ async function ingestStandardInput(dependencies, now, host = "claude") {
719
772
  : STATUSLINE_PROVENANCE;
720
773
  /* The host wrote this to our standard input in this session. It is a live
721
774
  reading, and it says so. */
722
- const incoming = normalizeMeters(withProvenance(meters, provenance));
775
+ const incoming = normalizeMeters(withProvenance(meters, provenance).map(meter => ({ ...meter, ...(accountId ? { accountId } : {}) })));
723
776
  if (incoming.length === 0)
724
777
  return { snapshots: null, payload: document.value };
778
+ /* The render still draws when a write fails, so each failure is kept for
779
+ doctor by its stage, and the next full success clears it. */
780
+ let merged;
725
781
  try {
726
- const { merged } = await persistSnapshots(incoming, dependencies.stateDirectory, now);
727
- return { snapshots: merged, payload: document.value };
782
+ merged = (await mergeSnapshotCache(incoming, dependencies.stateDirectory ?? resolveStateDirectory(), Date.parse(now))).merged;
728
783
  }
729
- catch {
784
+ catch (error) {
785
+ await recordStatuslineFailure(dependencies.stateDirectory, now, "cache_write", error);
730
786
  const existing = await cachedSnapshots(dependencies.stateDirectory);
731
787
  return { snapshots: mergeSnapshots(existing, incoming), payload: document.value };
732
788
  }
789
+ try {
790
+ await writeAgentContextSnapshot(merged, dependencies.stateDirectory, now);
791
+ await clearStatuslineFailure(dependencies.stateDirectory);
792
+ }
793
+ catch (error) {
794
+ await recordStatuslineFailure(dependencies.stateDirectory, now, "agent_context", error);
795
+ }
796
+ return { snapshots: merged, payload: document.value };
733
797
  }
734
- catch {
798
+ catch (error) {
799
+ await recordStatuslineFailure(dependencies.stateDirectory, now, "ingest", error);
735
800
  return null;
736
801
  }
737
802
  }
@@ -956,6 +1021,7 @@ async function statuslineCommand(dependencies, argumentsList, now) {
956
1021
  now,
957
1022
  config,
958
1023
  session: parseStatuslineSession(ingested?.payload),
1024
+ apiSpend: await readApiSpend(dependencies.stateDirectory ?? resolveStateDirectory()),
959
1025
  color: statuslineColor(config.color, dependencies.environment, dependencies.colorOutput, host),
960
1026
  unicode: statuslineUnicode(dependencies.environment),
961
1027
  host
@@ -1766,6 +1832,9 @@ export async function runCli(argumentsList, overrides = {}) {
1766
1832
  const command = argumentsList[0] ?? "setup";
1767
1833
  const now = dependencies.now();
1768
1834
  try {
1835
+ if (["snapshot", "statusline", "refresh", "status"].includes(command)) {
1836
+ await pruneSnapshotCache(dependencies.stateDirectory, Date.parse(now)).catch(() => 0);
1837
+ }
1769
1838
  if (command === "--version" || command === "-v" || command === "version") {
1770
1839
  return succeed(packageJson.version);
1771
1840
  }