openlimiter 2.0.2 → 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 +77 -14
  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 +1 -0
  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 +33 -1
  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 +24 -1
  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":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAEvD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,UAAU,GAAqC;IAC1D,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,IAAI;IACf,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,IAAI;IACd,QAAQ,EAAE,IAAI;CACf,CAAC;AAEF,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,cAAsB;IACvD,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1G,OAAO,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAkBD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5E,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhE,+EAA+E;AAC/E,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnF,sFAAsF;AACtF,SAAS,UAAU,CAAC,KAAa,EAAE,MAAe;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAiB,EAAE,OAAqB;IACjE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACjH,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmC,CAAC;IAC1D,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAc,EAAE,CAAC;QACtD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,QAAQ;YAAE,SAAS;QAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,SAAS;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACvF,IAAI,QAAQ,GAAG,MAAM,IAAI,QAAQ,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YACxG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,UAA+D,CAAC;IACpE,KAAK,MAAM,MAAM,IAAK,QAAQ,CAAC,SAAS,CAAe,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,CAAC;QAC9E,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,QAAQ;YAAE,SAAS;QAClG,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QACpC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;YAAE,SAAS;QACzE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,MAAM,GAAG,GAAG,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACpE,IAAI,GAAG,KAAK,SAAS,IAAI,QAAQ,KAAK,YAAY,IAAI,OAAO,CAAC,aAAa;YAAE,SAAS;QACtF,MAAM,KAAK,GAAG,eAAe,CAAC;YAC5B,WAAW,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS;SAC/G,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC;QAC5B,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,gBAAgB,CAAC,KAAK,cAAc,CAAC;QACxD,IAAI,QAAQ,KAAK,UAAU,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;YACvD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,uBAAuB,CAAC;YAC5D,IAAI,GAAG,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;gBACrC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACnG,CAAC;iBAAM,IAAI,QAAQ,KAAK,UAAU,IAAI,MAAM,CAAC,gBAAgB,CAAC,KAAK,cAAc;gBAC7E,MAAM,CAAC,YAAY,CAAC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC/D,wEAAwE;gBACxE,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;gBAC5B,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QAC1C,+EAA+E;QAC/E,IAAI,CAAC,GAAG,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,KAAK;YAAE,SAAS;QAClE,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QAChI,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC9B,qFAAqF;YACrF,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5D,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,IAAI,UAAU,CAAC,UAAU;gBAAE,SAAS;YAC9E,UAAU,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,UAAU,EAAE,IAAI,CAAC,CAAC;AAClF,CAAC"}
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, 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, 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
  /**
@@ -725,16 +775,28 @@ async function ingestStandardInput(dependencies, now, host = "claude") {
725
775
  const incoming = normalizeMeters(withProvenance(meters, provenance).map(meter => ({ ...meter, ...(accountId ? { accountId } : {}) })));
726
776
  if (incoming.length === 0)
727
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;
728
781
  try {
729
- const { merged } = await persistSnapshots(incoming, dependencies.stateDirectory, now);
730
- return { snapshots: merged, payload: document.value };
782
+ merged = (await mergeSnapshotCache(incoming, dependencies.stateDirectory ?? resolveStateDirectory(), Date.parse(now))).merged;
731
783
  }
732
- catch {
784
+ catch (error) {
785
+ await recordStatuslineFailure(dependencies.stateDirectory, now, "cache_write", error);
733
786
  const existing = await cachedSnapshots(dependencies.stateDirectory);
734
787
  return { snapshots: mergeSnapshots(existing, incoming), payload: document.value };
735
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 };
736
797
  }
737
- catch {
798
+ catch (error) {
799
+ await recordStatuslineFailure(dependencies.stateDirectory, now, "ingest", error);
738
800
  return null;
739
801
  }
740
802
  }
@@ -959,6 +1021,7 @@ async function statuslineCommand(dependencies, argumentsList, now) {
959
1021
  now,
960
1022
  config,
961
1023
  session: parseStatuslineSession(ingested?.payload),
1024
+ apiSpend: await readApiSpend(dependencies.stateDirectory ?? resolveStateDirectory()),
962
1025
  color: statuslineColor(config.color, dependencies.environment, dependencies.colorOutput, host),
963
1026
  unicode: statuslineUnicode(dependencies.environment),
964
1027
  host