openmausbot 0.1.92 → 0.1.94

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 (163) hide show
  1. package/dist/assets/{DesktopViewer-Do6lBa4C.js → DesktopViewer-bxM7NV3N.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-Cd3X7Hxc.js → abnfDiagram-O67JEVCF-BhMdJhRJ.js} +1 -1
  3. package/dist/assets/{arc-DIYc7Ymx.js → arc-B5itKDcx.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-DBmRGVyP.js → architectureDiagram-NJMV4G6O-C5d7UY6U.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CPFePSEE.js → blockDiagram-BEXU5L5S-BrZIhMmu.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-DD_DO1qy.js → c4Diagram-YGBWAQC7-7I0ArfbD.js} +1 -1
  7. package/dist/assets/channel-CdJX6i8k.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BNEeNSjX.js → chunk-2Q5K7J3B-BnEsyN36.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BbinLaqH.js → chunk-3FUC2YCW-BXATQ52s.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-BBgYrgDR.js → chunk-5VM5RSS4-B0BpTkEx.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-ezXj-kA-.js → chunk-GTNCS2PH-BtLTz6FR.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-9FxZ3diV.js → chunk-GWA4HPMP-D6gcZOYt.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-CO-40JpI.js → chunk-JWPE2WC7-DCLrosvf.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-BEhjslYE.js → chunk-NETBCI7D-B9JsknV9.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CNeq4n7_.js → chunk-WEXAMYUT-CsoTxw7z.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-OsCY7KSi.js → chunk-XXDRQBXY-0F_frB0c.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-CloRUEtk.js → classDiagram-v2-NBCMYWYE-DkrpnflX.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-D1BGZhp4.js → cose-bilkent-JH36ORCC-BNDwYBt9.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-CUf4T2CN.js → cynefin-OW5HDTMX-JUkNr1Mk.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-DPEZrFGA.js → cynefinDiagram-VND7K2PF-DKeh9N4M.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-bDe6GIv5.js → dagre-6A5THRUB-ve-6T_wX.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-CdcUQz55.js → diagram-22UHCM2B-BuJ2d8rT.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-BZpqGg7c.js → diagram-3UASUU5V-BgOs_Ni-.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-DvGM6ugp.js → diagram-ATOU4E4O-DMK6VmcP.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-DSTmMm0s.js → diagram-CDSNMT55-DksASlcY.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-15cucnMn.js → diagram-MLGK6HIB-Bgn8I5k6.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-tMbuaE6n.js → diagram-MPIPVDR6-zK0jqO8N.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BYxYzjHI.js → ebnfDiagram-ZINNZB2B-vKrbzTwr.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-v6bj852W.js → elk-276RUBZZ-Bzv2mPNf.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-5lBBlnd2.js → erDiagram-OPXOYQCR-CvOyYuxu.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-CZyl1zIj.js → flowDiagram-KWPJA3E3-CtbnDbc0.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-BiAI-ltZ.js → ganttDiagram-FUAMR5RP-c4dMRYyG.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-BAZISkj3.js → gitGraphDiagram-X574FWY7-B34uaQho.js} +1 -1
  34. package/dist/assets/index-CPYrGsv1.js +610 -0
  35. package/dist/assets/{index-DGBaEXrM.js → index-CPiqc_DN.js} +1 -1
  36. package/dist/assets/index-CzT42GR8.css +1 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-B6ooOy-Z.js → infoDiagram-VRGFBTTK-o64ma4CI.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js → ishikawaDiagram-OU5B5YK6-DV4C9BAm.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DWqlzIEe.js → journeyDiagram-ZHPQQLJL-Bf0AHk1H.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-JX6piDwL.js → kanban-definition-PNTS6WVX-B1UMSKmE.js} +1 -1
  41. package/dist/assets/{linear-CmOWzX0P.js → linear-BpKhuyLm.js} +1 -1
  42. package/dist/assets/{mermaid.core-CF1zwoQ-.js → mermaid.core-B_BKNCw5.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-CGh2K4fW.js → mindmap-definition-NLK3R4M7-DeARruT1.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-CDwYN0s7.js → pegDiagram-GJSIUBJH-BOQEk_1D.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-1rfjJocB.js → pieDiagram-5QR66LMP-EIfoiTZo.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-tKPvPiok.js → quadrantDiagram-O4NWA36T-GgZwT-fi.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BHLm5vXR.js → railroadDiagram-XR7U4H2S-D9rDQomj.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-CB45STp7.js → requirementDiagram-PLB6GJNP-DAh1MIeo.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-55QA_huC.js → sankeyDiagram-IPEJSGJF-CPr6Uz64.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-BckFmQHn.js → sequenceDiagram-PO4LG4MO-BU6VIdcm.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BFCOJF11.js → sizeCapture-INFHLROL-qF2HO41r.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-CZvmiVVN.js → stateDiagram-v2-GCMORJYK-DlJ8G4MD.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-BpDUCEKy.js → swimlanes-2SLR337P-DzYE8I54.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BpxHq6IK.js → timeline-definition-EJHVYXUP-B7wzEQ0A.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-Cj66vm02.js → usecaseDiagram-POWQR4AR-BvGgrxIP.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C9nRkWE_.js → vennDiagram-UO4OBE2U-CEECdgQg.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-D-me_nWj.js → wardleyDiagram-VNRHLVJA-XQHA0P_P.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-Dc_fL4mk.js → xychartDiagram-PMCCYNJV-B5mLcZI2.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +1 -0
  62. package/dist-server/companion/src/routes.js +45 -1
  63. package/dist-server/container-mcp.js +99 -77
  64. package/dist-server/drivers/agents-proxy.js +40 -1
  65. package/dist-server/drivers/pi-mcp-extension.ts +245 -299
  66. package/dist-server/enterprise/server/index.js +1 -0
  67. package/dist-server/index.js +18361 -13794
  68. package/dist-server/local-computer-proxy.js +5 -1
  69. package/dist-server/local-computer.js +1 -0
  70. package/dist-server/mcp-gate.js +152 -20
  71. package/dist-server/mcp-remote-proxy.js +437 -0
  72. package/dist-server/openmausbot.js +8872 -7620
  73. package/dist-server/pair-cli.js +8872 -7620
  74. package/dist-server/permission-proxy.js +65 -1
  75. package/dist-server/proxy-paths.js +1 -0
  76. package/dist-server/server/auto-approve.js +33 -11
  77. package/dist-server/server/boat.js +3 -3
  78. package/dist-server/server/browser-live.js +14 -5
  79. package/dist-server/server/browser-runtime.js +6 -0
  80. package/dist-server/server/channel-queue.js +4 -1
  81. package/dist-server/server/cli.js +32 -12
  82. package/dist-server/server/cloud-move-http.js +9 -3
  83. package/dist-server/server/cloud-move.js +18 -0
  84. package/dist-server/server/cloud-owner.js +1 -1
  85. package/dist-server/server/computer-control.js +6 -0
  86. package/dist-server/server/computer-wait.js +33 -13
  87. package/dist-server/server/config.js +57 -10
  88. package/dist-server/server/container-computer.js +43 -32
  89. package/dist-server/server/decision-log.js +9 -6
  90. package/dist-server/server/delegations.js +47 -22
  91. package/dist-server/server/digest.js +12 -2
  92. package/dist-server/server/drivers/acp/core.js +229 -20
  93. package/dist-server/server/drivers/acp/cursor.js +13 -1
  94. package/dist-server/server/drivers/acp/grok.js +208 -3
  95. package/dist-server/server/drivers/acp/quiet-status.js +257 -0
  96. package/dist-server/server/drivers/acp/qwen.js +18 -0
  97. package/dist-server/server/drivers/agents-call.js +28 -0
  98. package/dist-server/server/drivers/agents-catalog.js +10 -1
  99. package/dist-server/server/drivers/boatagent.js +2 -0
  100. package/dist-server/server/drivers/builtIn.js +2 -0
  101. package/dist-server/server/drivers/cerebras.js +108 -0
  102. package/dist-server/server/drivers/chat-mcp-tools.js +45 -15
  103. package/dist-server/server/drivers/claude.js +35 -9
  104. package/dist-server/server/drivers/codex.js +84 -10
  105. package/dist-server/server/drivers/openai-chat.js +37 -5
  106. package/dist-server/server/drivers/pi.js +48 -4
  107. package/dist-server/server/drivers/prompt-split.js +2 -0
  108. package/dist-server/server/env-path.js +6 -2
  109. package/dist-server/server/environment.js +3 -0
  110. package/dist-server/server/guarded-requests.js +2 -1
  111. package/dist-server/server/index.js +1353 -311
  112. package/dist-server/server/live-call-controller.js +868 -0
  113. package/dist-server/server/live-call.js +216 -0
  114. package/dist-server/server/local-vm-stop-reason.js +26 -0
  115. package/dist-server/server/mcp-gate-config.js +43 -12
  116. package/dist-server/server/mcp-gate.js +127 -25
  117. package/dist-server/server/mcp-http.js +46 -8
  118. package/dist-server/server/mcp-remote-proxy.js +108 -0
  119. package/dist-server/server/new-bot-defaults.js +3 -0
  120. package/dist-server/server/peer-provenance.js +4 -2
  121. package/dist-server/server/permission-proxy.js +15 -1
  122. package/dist-server/server/plan-usage.js +797 -0
  123. package/dist-server/server/provider-key-check.js +2 -1
  124. package/dist-server/server/proxy-paths.js +1 -0
  125. package/dist-server/server/redact.js +2 -0
  126. package/dist-server/server/request-auth.js +3 -2
  127. package/dist-server/server/routes/desktop-viewer.js +50 -6
  128. package/dist-server/server/routes/live.js +98 -0
  129. package/dist-server/server/routines.js +6 -3
  130. package/dist-server/server/skill-library.js +207 -0
  131. package/dist-server/server/skills-library-migration.js +235 -0
  132. package/dist-server/server/skills.js +66 -3
  133. package/dist-server/server/steer-queue.js +13 -1
  134. package/dist-server/server/store.js +86 -4
  135. package/dist-server/server/surface.js +9 -3
  136. package/dist-server/server/team-backup.js +2 -1
  137. package/dist-server/server/thread-events.js +1 -1
  138. package/dist-server/server/tunnel.js +20 -1
  139. package/dist-server/server/turn-log.js +10 -0
  140. package/dist-server/server/turn-resources.js +124 -0
  141. package/dist-server/server/vps-computer.js +1 -1
  142. package/dist-server/server/workspace-backup.js +22 -0
  143. package/dist-server/server/workspace.js +1 -1
  144. package/dist-server/shared/approval-mode.js +1 -1
  145. package/dist-server/shared/ask-question.js +13 -0
  146. package/dist-server/shared/call-consent.js +28 -0
  147. package/dist-server/shared/live-approval.js +124 -0
  148. package/dist-server/shared/live-call.js +176 -0
  149. package/dist-server/shared/redact.js +1 -0
  150. package/dist-server/shared/skill-md.js +10 -0
  151. package/dist-server/shared/team-backup.js +3 -0
  152. package/dist-server/shared/tool-name.js +22 -0
  153. package/dist-server/shared/tool-scope-support.js +25 -0
  154. package/dist-server/shared/tool-scope.js +136 -0
  155. package/dist-server/src/lib/thread-refs.js +406 -0
  156. package/dist-server/vps-container-mcp.js +99 -77
  157. package/dist-server/workspace-backup.worker.js +103 -100
  158. package/enterprise/server/index.js +1 -0
  159. package/package.json +1 -1
  160. package/dist/assets/channel-Dtk5Mtic.js +0 -1
  161. package/dist/assets/index-Cdmjrl28.css +0 -1
  162. package/dist/assets/index-DR7YPMkW.js +0 -597
  163. package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +0 -8
@@ -0,0 +1,797 @@
1
+ // Subscription allowance for the engines OpenMausBot already signs in
2
+ // (Claude, Codex, Grok). Parsers are pure. The fetcher takes fetch and a
3
+ // credential reader so tests never touch the network or a real login file.
4
+ // Access tokens stay in the request header only — never in the JSON result.
5
+ import { execFile } from "node:child_process";
6
+ import { createHash } from "node:crypto";
7
+ import { readFileSync, statSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { resolveClaudeConfigDir } from "./drivers/claude.js";
10
+ import { codexHome } from "./drivers/codex-identity.js";
11
+ import { harnessHome } from "./env-path.js";
12
+ const PROVIDER_TIMEOUT_MS = 8_000;
13
+ const PLAN_USAGE_CACHE_MS = 45_000;
14
+ const CREDENTIAL_READ_MAX_BYTES = 1_000_000;
15
+ const KEYCHAIN_TIMEOUT_MS = 5_000;
16
+ const KEYCHAIN_MAX_BUFFER = 1_000_000;
17
+ const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
18
+ const CLAUDE_MODEL_FAMILIES = ["opus", "sonnet", "haiku"];
19
+ const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
20
+ const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
21
+ const GROK_CREDITS_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
22
+ const GROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing";
23
+ const GROK_SETTINGS_URL = "https://cli-chat-proxy.grok.com/v1/settings";
24
+ const FIVE_HOUR_MIN_SECONDS = 3 * 3600;
25
+ const FIVE_HOUR_MAX_SECONDS = 8 * 3600;
26
+ const WEEK_MIN_SECONDS = 5 * 86400;
27
+ const WEEK_MAX_SECONDS = 9 * 86400;
28
+ const MONTH_MIN_SECONDS = 27 * 86400;
29
+ const MONTH_MAX_SECONDS = 32 * 86400;
30
+ const DRIVER_OF = {
31
+ claudeAgent: "claude",
32
+ codex: "codex",
33
+ grokAgent: "grok",
34
+ };
35
+ const PRODUCT_NAME = {
36
+ claude: "Claude",
37
+ codex: "Codex",
38
+ grok: "Grok",
39
+ };
40
+ export function planAccountsFromInstances(instances) {
41
+ const accounts = [];
42
+ for (const [id, entry] of Object.entries(instances)) {
43
+ const driver = DRIVER_OF[entry.driver];
44
+ if (!driver)
45
+ continue;
46
+ const config = asRecord(entry.config);
47
+ const configDir = typeof config?.configDir === "string" ? config.configDir : undefined;
48
+ accounts.push({
49
+ id,
50
+ name: entry.displayName?.trim() || PRODUCT_NAME[driver],
51
+ driver,
52
+ environment: { ...entry.environment },
53
+ ...(configDir ? { configDir } : {}),
54
+ });
55
+ }
56
+ return accounts;
57
+ }
58
+ export function parseClaudeUsage(body) {
59
+ const root = claudeRoot(body);
60
+ const extra = [];
61
+ const models = [];
62
+ if (root) {
63
+ for (const [key, value] of Object.entries(root)) {
64
+ if (key === "five_hour" || key === "seven_day")
65
+ continue;
66
+ if (!/^[a-z][a-z0-9_]{0,40}$/i.test(key))
67
+ continue;
68
+ const window = readClaudeWindow(value);
69
+ if (!window)
70
+ continue;
71
+ const model = claudeModelWindow(key);
72
+ if (model)
73
+ addModelWindow(models, model.name, extraLine(model.window, window));
74
+ else
75
+ extra.push(extraLine(claudeExtraLabel(key), window));
76
+ }
77
+ }
78
+ sortModelWindows(models);
79
+ return {
80
+ plan: planLabel(root?.subscription_type ?? root?.plan ?? root?.plan_type),
81
+ fiveHour: (root && readClaudeWindow(root.five_hour)) ?? closedWindow(),
82
+ weekly: (root && readClaudeWindow(root.seven_day)) ?? closedWindow(),
83
+ extra,
84
+ models,
85
+ };
86
+ }
87
+ export function parseCodexUsage(body) {
88
+ const root = asRecord(body);
89
+ const assigned = assignDurationWindows(durationWindows(root?.rate_limit));
90
+ const models = [];
91
+ const extra = [...assigned.extra];
92
+ if (root)
93
+ collectCodexNamedLimits(root, models, extra);
94
+ sortModelWindows(models);
95
+ return { ...assigned, extra, models, plan: planLabel(root?.plan_type ?? root?.plan) };
96
+ }
97
+ export function parseGrokUsage(credits, billing, settings) {
98
+ let fiveHour = closedWindow();
99
+ let weekly = closedWindow();
100
+ const extra = [];
101
+ const models = [];
102
+ let sawMonthly = false;
103
+ const place = (sample) => {
104
+ if (!sample)
105
+ return;
106
+ const window = openWindow(sample.used, sample.resetsAt);
107
+ if (sample.slot === "fiveHour") {
108
+ if (!fiveHour.available)
109
+ fiveHour = window;
110
+ }
111
+ else if (sample.slot === "weekly") {
112
+ if (!weekly.available)
113
+ weekly = window;
114
+ }
115
+ else if (sample.slot === "monthly") {
116
+ if (!sawMonthly) {
117
+ sawMonthly = true;
118
+ extra.push(extraLine("Monthly", window));
119
+ }
120
+ }
121
+ else {
122
+ extra.push(extraLine(sample.seconds == null ? "Credits" : durationLabel(sample.seconds), window));
123
+ }
124
+ };
125
+ const creditSample = readGrokSample(credits);
126
+ place(creditSample);
127
+ const productSample = creditSample ?? { used: 0, slot: "other", resetsAt: null, seconds: null };
128
+ for (const model of readGrokProducts(credits, productSample))
129
+ addModelWindow(models, model.name, model.window);
130
+ if (billing !== undefined)
131
+ place(readGrokSample(billing));
132
+ sortModelWindows(models);
133
+ return { plan: grokPlan(settings), fiveHour, weekly, extra, models };
134
+ }
135
+ export function fileCredentialReader(source) {
136
+ const env = source.env ?? process.env;
137
+ const now = source.now ?? Date.now;
138
+ return {
139
+ async read(account) {
140
+ const merged = { ...env, ...account.environment };
141
+ const at = now();
142
+ if (account.driver === "claude")
143
+ return readClaudeCredential(account, merged, source, at);
144
+ if (account.driver === "codex")
145
+ return readCodexCredential(merged, source.readText, at);
146
+ return readGrokCredential(merged, source.readText, at);
147
+ },
148
+ };
149
+ }
150
+ export function defaultCredentialReader(env = process.env) {
151
+ return fileCredentialReader({
152
+ env,
153
+ readText(path) {
154
+ try {
155
+ const stat = statSync(path);
156
+ if (!stat.isFile() || stat.size > CREDENTIAL_READ_MAX_BYTES)
157
+ return null;
158
+ return readFileSync(path, "utf8");
159
+ }
160
+ catch {
161
+ return null;
162
+ }
163
+ },
164
+ ...(process.platform === "darwin" ? { readClaudeKeychain: readDarwinClaudeKeychain } : {}),
165
+ });
166
+ }
167
+ export async function fetchPlanUsage(accounts, deps) {
168
+ const now = deps.now?.() ?? Date.now();
169
+ const providers = await Promise.all(accounts.map((account) => fetchProvider(account, deps)));
170
+ return { fetchedAt: new Date(now).toISOString(), providers };
171
+ }
172
+ let cachedReport = null;
173
+ export function clearPlanUsageCache() {
174
+ cachedReport = null;
175
+ }
176
+ export async function loadPlanUsage(input) {
177
+ const now = input.now ?? Date.now();
178
+ const key = planUsageCacheKey(input.accounts);
179
+ if (!input.refresh && cachedReport && cachedReport.key === key && now - cachedReport.at < PLAN_USAGE_CACHE_MS) {
180
+ return cachedReport.report;
181
+ }
182
+ const report = await fetchPlanUsage(input.accounts, {
183
+ fetch: input.fetch ?? ((url, init) => globalThis.fetch(url, init)),
184
+ credentials: input.credentials ?? defaultCredentialReader(input.env),
185
+ now: () => now,
186
+ timeoutMs: input.timeoutMs,
187
+ });
188
+ cachedReport = { at: now, key, report };
189
+ return report;
190
+ }
191
+ function closedWindow() {
192
+ return { available: false, remainingPercent: null, usedPercent: null, resetsAt: null };
193
+ }
194
+ function openWindow(usedPercent, resetsAt) {
195
+ const used = clampPercent(usedPercent);
196
+ return {
197
+ available: true,
198
+ usedPercent: used,
199
+ remainingPercent: clampPercent(100 - used),
200
+ resetsAt,
201
+ };
202
+ }
203
+ function extraLine(label, window) {
204
+ return {
205
+ label,
206
+ remainingPercent: window.remainingPercent ?? 0,
207
+ usedPercent: window.usedPercent ?? 0,
208
+ resetsAt: window.resetsAt,
209
+ };
210
+ }
211
+ function clampPercent(value) {
212
+ if (!Number.isFinite(value))
213
+ return 0;
214
+ return Math.min(100, Math.max(0, value));
215
+ }
216
+ function asRecord(value) {
217
+ if (!value || typeof value !== "object" || Array.isArray(value))
218
+ return null;
219
+ return value;
220
+ }
221
+ function finiteNumber(value) {
222
+ if (typeof value !== "number" || !Number.isFinite(value))
223
+ return null;
224
+ return value;
225
+ }
226
+ function timeMs(value) {
227
+ if (typeof value === "number" && Number.isFinite(value)) {
228
+ return Math.abs(value) >= 1e12 ? value : value * 1000;
229
+ }
230
+ if (typeof value !== "string")
231
+ return null;
232
+ const trimmed = value.trim();
233
+ if (!trimmed || trimmed.length > 80)
234
+ return null;
235
+ if (/^\d+(\.\d+)?$/.test(trimmed))
236
+ return timeMs(Number(trimmed));
237
+ const parsed = Date.parse(trimmed);
238
+ return Number.isFinite(parsed) ? parsed : null;
239
+ }
240
+ function isoOrNull(value) {
241
+ const ms = timeMs(value);
242
+ if (ms == null)
243
+ return null;
244
+ const date = new Date(ms);
245
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
246
+ }
247
+ function planLabel(value) {
248
+ if (typeof value !== "string")
249
+ return null;
250
+ const trimmed = value.trim();
251
+ if (!trimmed || trimmed.length > 80 || /[\r\n]/.test(trimmed))
252
+ return null;
253
+ if (/bearer\s|access_token|refresh_token|sk-ant-|eyJ[a-zA-Z0-9_-]{8,}/i.test(trimmed))
254
+ return null;
255
+ return trimmed;
256
+ }
257
+ function secretString(value) {
258
+ if (typeof value !== "string")
259
+ return null;
260
+ const trimmed = value.trim();
261
+ if (!trimmed || trimmed.length > 16_000 || /[\r\n]/.test(trimmed))
262
+ return null;
263
+ return trimmed;
264
+ }
265
+ function parseJson(text) {
266
+ if (!text)
267
+ return null;
268
+ try {
269
+ return JSON.parse(text);
270
+ }
271
+ catch {
272
+ return null;
273
+ }
274
+ }
275
+ function missingCredential() {
276
+ return { token: null, accountId: null, expired: false };
277
+ }
278
+ function expiredCredential() {
279
+ return { token: null, accountId: null, expired: true };
280
+ }
281
+ function signInAgain(driver) {
282
+ return `Sign in again in ${PRODUCT_NAME[driver]}`;
283
+ }
284
+ function couldNotReach(driver) {
285
+ return `Could not reach ${PRODUCT_NAME[driver]}`;
286
+ }
287
+ function unexpectedUsage(driver) {
288
+ return `${PRODUCT_NAME[driver]} returned an unexpected usage response`;
289
+ }
290
+ function claudeRoot(body) {
291
+ const root = asRecord(body);
292
+ if (!root)
293
+ return null;
294
+ if ("five_hour" in root || "seven_day" in root)
295
+ return root;
296
+ const nested = asRecord(root.usage) ?? asRecord(root.windows);
297
+ if (nested && ("five_hour" in nested || "seven_day" in nested))
298
+ return nested;
299
+ return root;
300
+ }
301
+ function readClaudeWindow(value) {
302
+ const record = asRecord(value);
303
+ if (!record)
304
+ return null;
305
+ const utilization = finiteNumber(record.utilization);
306
+ if (utilization == null)
307
+ return null;
308
+ return openWindow(utilization, isoOrNull(record.resets_at ?? record.resetsAt));
309
+ }
310
+ function claudeExtraLabel(key) {
311
+ const suffix = key.startsWith("seven_day_")
312
+ ? key.slice("seven_day_".length)
313
+ : key.replace(/^five_hour_/, "");
314
+ return titleWords(suffix);
315
+ }
316
+ function titleWords(suffix) {
317
+ const words = suffix.split("_").filter((part) => part.length > 0);
318
+ if (words.length === 0)
319
+ return "Window";
320
+ return words.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
321
+ }
322
+ /** Opus, Sonnet, and Haiku only. `seven_day_oauth_apps` and other windows stay extra lines. */
323
+ function claudeModelWindow(key) {
324
+ const weekly = key.startsWith("seven_day_");
325
+ const fiveHour = key.startsWith("five_hour_");
326
+ if (!weekly && !fiveHour)
327
+ return null;
328
+ const suffix = key.slice(weekly ? "seven_day_".length : "five_hour_".length);
329
+ const lower = suffix.toLowerCase();
330
+ const family = CLAUDE_MODEL_FAMILIES.find((name) => lower === name || lower.startsWith(`${name}_`));
331
+ if (!family)
332
+ return null;
333
+ return { name: titleWords(suffix), window: weekly ? "Weekly" : "5-hour" };
334
+ }
335
+ function addModelWindow(models, name, line) {
336
+ const found = models.find((model) => model.name.toLowerCase() === name.toLowerCase());
337
+ if (found)
338
+ found.windows.push(line);
339
+ else
340
+ models.push({ name, windows: [line] });
341
+ }
342
+ function sortModelWindows(models) {
343
+ const rank = (label) => (label === "5-hour" ? 0 : label === "Weekly" ? 1 : 2);
344
+ for (const model of models)
345
+ model.windows.sort((a, b) => rank(a.label) - rank(b.label));
346
+ }
347
+ function readDurationWindow(value) {
348
+ const record = asRecord(value);
349
+ if (!record)
350
+ return null;
351
+ const used = finiteNumber(record.used_percent);
352
+ let seconds = finiteNumber(record.limit_window_seconds);
353
+ if (seconds == null) {
354
+ const minutes = finiteNumber(record.window_minutes);
355
+ if (minutes != null)
356
+ seconds = minutes * 60;
357
+ }
358
+ if (used == null || seconds == null || seconds <= 0)
359
+ return null;
360
+ return { used, seconds, resetsAt: isoOrNull(record.reset_at ?? record.resetAt) };
361
+ }
362
+ /** Primary and secondary windows, or one flat window when those keys are absent. */
363
+ function durationWindows(node) {
364
+ const record = asRecord(node);
365
+ if (!record)
366
+ return [];
367
+ const source = asRecord(record.rate_limit) ?? record;
368
+ const windows = [];
369
+ for (const key of ["primary_window", "secondary_window"]) {
370
+ const window = readDurationWindow(source[key]);
371
+ if (window)
372
+ windows.push(window);
373
+ }
374
+ if (windows.length === 0) {
375
+ const self = readDurationWindow(source);
376
+ if (self)
377
+ windows.push(self);
378
+ }
379
+ return windows;
380
+ }
381
+ function codexLimitName(record) {
382
+ return planLabel(record.limit_name) ?? planLabel(record.metered_feature) ?? planLabel(record.name);
383
+ }
384
+ function pushAssignedLines(assigned, into) {
385
+ if (assigned.fiveHour.available)
386
+ into.push(extraLine("5-hour", assigned.fiveHour));
387
+ if (assigned.weekly.available)
388
+ into.push(extraLine("Weekly", assigned.weekly));
389
+ into.push(...assigned.extra);
390
+ }
391
+ function collectCodexNamedLimits(root, models, extra) {
392
+ const buckets = [];
393
+ if (root.code_review_rate_limit)
394
+ buckets.push({ name: "Code review", node: root.code_review_rate_limit });
395
+ const additional = root.additional_rate_limits;
396
+ if (Array.isArray(additional)) {
397
+ for (const item of additional) {
398
+ const record = asRecord(item);
399
+ buckets.push({ name: record ? codexLimitName(record) : null, node: item });
400
+ }
401
+ }
402
+ else {
403
+ const named = asRecord(additional);
404
+ if (named) {
405
+ for (const [key, value] of Object.entries(named))
406
+ buckets.push({ name: planLabel(key), node: value });
407
+ }
408
+ }
409
+ for (const bucket of buckets) {
410
+ const assigned = assignDurationWindows(durationWindows(bucket.node));
411
+ if (!bucket.name) {
412
+ pushAssignedLines(assigned, extra);
413
+ continue;
414
+ }
415
+ const lines = [];
416
+ pushAssignedLines(assigned, lines);
417
+ for (const line of lines)
418
+ addModelWindow(models, bucket.name, line);
419
+ }
420
+ }
421
+ function readGrokProducts(body, sample) {
422
+ const root = asRecord(body);
423
+ if (!root)
424
+ return [];
425
+ const config = asRecord(root.config) ?? root;
426
+ if (!Array.isArray(config.productUsage))
427
+ return [];
428
+ const label = sample.slot === "fiveHour" ? "5-hour" : sample.slot === "weekly" ? "Weekly" : sample.slot === "monthly" ? "Monthly" : "Used";
429
+ const rows = [];
430
+ for (const item of config.productUsage) {
431
+ const record = asRecord(item);
432
+ if (!record)
433
+ continue;
434
+ const raw = typeof record.product === "string" ? record.product : typeof record.name === "string" ? record.name : "";
435
+ const name = planLabel(raw.replace(/([a-z])([A-Z])/g, "$1 $2"));
436
+ const used = finiteNumber(record.usagePercent ?? record.usedPercent ?? record.utilization);
437
+ if (!name || used == null)
438
+ continue;
439
+ rows.push({ name, window: extraLine(label, openWindow(used, sample.resetsAt)) });
440
+ }
441
+ return rows;
442
+ }
443
+ function durationLabel(seconds) {
444
+ if (seconds >= 86400)
445
+ return `${Math.max(1, Math.round(seconds / 86400))}-day`;
446
+ if (seconds >= 3600)
447
+ return `${Math.max(1, Math.round(seconds / 3600))}-hour`;
448
+ return `${Math.max(1, Math.round(seconds / 60))}-minute`;
449
+ }
450
+ function assignDurationWindows(windows) {
451
+ let fiveHour = closedWindow();
452
+ let weekly = closedWindow();
453
+ const extra = [];
454
+ for (const window of windows) {
455
+ const open = openWindow(window.used, window.resetsAt);
456
+ const fiveHourSlot = window.seconds >= FIVE_HOUR_MIN_SECONDS && window.seconds <= FIVE_HOUR_MAX_SECONDS;
457
+ const weeklySlot = window.seconds >= WEEK_MIN_SECONDS && window.seconds <= WEEK_MAX_SECONDS;
458
+ if (fiveHourSlot && !fiveHour.available)
459
+ fiveHour = open;
460
+ else if (weeklySlot && !weekly.available)
461
+ weekly = open;
462
+ else
463
+ extra.push(extraLine(durationLabel(window.seconds), open));
464
+ }
465
+ return { plan: null, fiveHour, weekly, extra, models: [] };
466
+ }
467
+ function classifyGrok(typeValue, seconds) {
468
+ const type = typeof typeValue === "string" ? typeValue.toUpperCase() : "";
469
+ if (type.includes("WEEK"))
470
+ return "weekly";
471
+ if (type.includes("MONTH"))
472
+ return "monthly";
473
+ if (/(^|[^A-Z0-9])(5|FIVE)[^A-Z0-9]*HOUR/.test(type))
474
+ return "fiveHour";
475
+ if (seconds == null)
476
+ return "other";
477
+ if (seconds >= FIVE_HOUR_MIN_SECONDS && seconds <= FIVE_HOUR_MAX_SECONDS)
478
+ return "fiveHour";
479
+ if (seconds >= WEEK_MIN_SECONDS && seconds <= WEEK_MAX_SECONDS)
480
+ return "weekly";
481
+ if (seconds >= MONTH_MIN_SECONDS && seconds <= MONTH_MAX_SECONDS)
482
+ return "monthly";
483
+ return "other";
484
+ }
485
+ function readGrokSample(body) {
486
+ const root = asRecord(body);
487
+ if (!root)
488
+ return null;
489
+ const config = asRecord(root.config) ?? root;
490
+ let used = finiteNumber(config.creditUsagePercent);
491
+ if (used == null) {
492
+ const cap = finiteNumber(asRecord(config.onDemandCap)?.val ?? config.onDemandCap);
493
+ const spent = finiteNumber(asRecord(config.onDemandUsed)?.val ?? config.onDemandUsed);
494
+ if (cap != null && spent != null && cap > 0)
495
+ used = (spent / cap) * 100;
496
+ }
497
+ if (used == null)
498
+ return null;
499
+ const period = asRecord(config.currentPeriod);
500
+ const type = period?.type ?? period?.period ?? config.period ?? config.interval;
501
+ const start = period?.start ?? period?.periodStart;
502
+ const end = period?.end ?? period?.periodEnd ?? config.billingPeriodEnd ?? root.billingPeriodEnd;
503
+ const startMs = timeMs(start);
504
+ const endMs = timeMs(end);
505
+ const seconds = startMs != null && endMs != null && endMs > startMs ? (endMs - startMs) / 1000 : null;
506
+ return { used, slot: classifyGrok(type, seconds), resetsAt: isoOrNull(end), seconds };
507
+ }
508
+ function grokPlan(settings) {
509
+ const root = asRecord(settings);
510
+ if (!root)
511
+ return null;
512
+ const nested = asRecord(root.settings);
513
+ return planLabel(nested?.subscription_tier_display ?? root.subscription_tier_display);
514
+ }
515
+ function planUsageCacheKey(accounts) {
516
+ return accounts.map((account) => {
517
+ const environment = Object.entries(account.environment)
518
+ .filter((entry) => typeof entry[1] === "string")
519
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
520
+ .map(([name, value]) => `${name}=${value}`)
521
+ .join("\n");
522
+ return [account.id, account.driver, account.name, account.configDir ?? "", environment].join("\u0000");
523
+ }).join("\n");
524
+ }
525
+ function claudeCredentialFromText(text, now) {
526
+ const oauth = asRecord(asRecord(parseJson(text))?.claudeAiOauth);
527
+ const token = secretString(oauth?.accessToken);
528
+ if (!token)
529
+ return missingCredential();
530
+ const expiry = timeMs(oauth?.expiresAt);
531
+ if (expiry != null && expiry <= now)
532
+ return expiredCredential();
533
+ return { token, accountId: null, expired: false };
534
+ }
535
+ /** macOS 26 `security -w` may print the JSON secret as hex. */
536
+ function decodeKeychainSecret(raw) {
537
+ if (!raw)
538
+ return null;
539
+ const trimmed = raw.trim();
540
+ if (!trimmed)
541
+ return null;
542
+ if (!trimmed.startsWith("{") && trimmed.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(trimmed)) {
543
+ return Buffer.from(trimmed, "hex").toString("utf8");
544
+ }
545
+ return trimmed;
546
+ }
547
+ function claudeKeychainServices(account, env, resolvedDir) {
548
+ if (!account.configDir?.trim() && !env.CLAUDE_CONFIG_DIR?.trim())
549
+ return [CLAUDE_KEYCHAIN_SERVICE];
550
+ let defaultDir = null;
551
+ try {
552
+ const withoutConfig = { ...env };
553
+ delete withoutConfig.CLAUDE_CONFIG_DIR;
554
+ defaultDir = resolveClaudeConfigDir(undefined, withoutConfig);
555
+ }
556
+ catch {
557
+ defaultDir = null;
558
+ }
559
+ if (defaultDir != null && resolvedDir === defaultDir)
560
+ return [CLAUDE_KEYCHAIN_SERVICE];
561
+ const suffix = createHash("sha256").update(resolvedDir).digest("hex").slice(0, 8);
562
+ return [`${CLAUDE_KEYCHAIN_SERVICE}-${suffix}`];
563
+ }
564
+ function readDarwinClaudeKeychain(service) {
565
+ if (process.platform !== "darwin")
566
+ return Promise.resolve(null);
567
+ return new Promise((resolve) => {
568
+ try {
569
+ execFile("security", ["find-generic-password", "-s", service, "-w"], {
570
+ timeout: KEYCHAIN_TIMEOUT_MS,
571
+ maxBuffer: KEYCHAIN_MAX_BUFFER,
572
+ windowsHide: true,
573
+ shell: false,
574
+ encoding: "utf8",
575
+ }, (error, stdout) => {
576
+ resolve(error || !stdout ? null : stdout);
577
+ });
578
+ }
579
+ catch {
580
+ resolve(null);
581
+ }
582
+ });
583
+ }
584
+ async function readClaudeCredential(account, env, source, now) {
585
+ let resolvedDir;
586
+ try {
587
+ resolvedDir = resolveClaudeConfigDir(account.configDir, env);
588
+ }
589
+ catch {
590
+ return missingCredential();
591
+ }
592
+ const file = claudeCredentialFromText(source.readText(join(resolvedDir, ".credentials.json")), now);
593
+ if (file.token || !source.readClaudeKeychain)
594
+ return file;
595
+ let sawExpired = file.expired;
596
+ for (const service of claudeKeychainServices(account, env, resolvedDir)) {
597
+ let raw = null;
598
+ try {
599
+ raw = (await source.readClaudeKeychain(service)) ?? null;
600
+ }
601
+ catch {
602
+ raw = null;
603
+ }
604
+ const parsed = claudeCredentialFromText(decodeKeychainSecret(raw), now);
605
+ if (parsed.token)
606
+ return parsed;
607
+ if (parsed.expired)
608
+ sawExpired = true;
609
+ }
610
+ return sawExpired ? expiredCredential() : missingCredential();
611
+ }
612
+ function readCodexCredential(env, readText, now) {
613
+ const home = codexHome(env);
614
+ if (!home)
615
+ return missingCredential();
616
+ const root = asRecord(parseJson(readText(join(home, "auth.json"))));
617
+ if (!root)
618
+ return missingCredential();
619
+ const tokens = asRecord(root.tokens);
620
+ const token = secretString(tokens?.access_token) ?? secretString(root.access_token);
621
+ if (!token)
622
+ return missingCredential();
623
+ const expiry = timeMs(tokens?.expires_at ?? tokens?.expiresAt ?? root.expires_at ?? root.expiresAt);
624
+ if (expiry != null && expiry <= now)
625
+ return expiredCredential();
626
+ const accountId = secretString(tokens?.account_id) ?? secretString(root.account_id);
627
+ return { token, accountId, expired: false };
628
+ }
629
+ function grokCandidate(record) {
630
+ const token = secretString(record.access_token) ?? secretString(record.key);
631
+ if (!token)
632
+ return null;
633
+ return { token, expiresAt: timeMs(record.expires_at ?? record.expiresAt) };
634
+ }
635
+ function readGrokCredential(env, readText, now) {
636
+ const rootDir = env.GROK_HOME?.trim() || harnessHome("grok", env);
637
+ const root = asRecord(parseJson(readText(join(rootDir, "auth.json"))));
638
+ if (!root)
639
+ return missingCredential();
640
+ const direct = grokCandidate(root);
641
+ if (direct) {
642
+ if (direct.expiresAt != null && direct.expiresAt <= now)
643
+ return expiredCredential();
644
+ return { token: direct.token, accountId: null, expired: false };
645
+ }
646
+ let sawExpired = false;
647
+ let best = null;
648
+ for (const value of Object.values(root)) {
649
+ const record = asRecord(value);
650
+ if (!record)
651
+ continue;
652
+ const candidate = grokCandidate(record);
653
+ if (!candidate)
654
+ continue;
655
+ if (candidate.expiresAt != null && candidate.expiresAt <= now) {
656
+ sawExpired = true;
657
+ continue;
658
+ }
659
+ const bestStamp = best?.expiresAt ?? Number.NEGATIVE_INFINITY;
660
+ const stamp = candidate.expiresAt ?? Number.NEGATIVE_INFINITY;
661
+ if (!best || stamp >= bestStamp)
662
+ best = candidate;
663
+ }
664
+ if (best)
665
+ return { token: best.token, accountId: null, expired: false };
666
+ return sawExpired ? expiredCredential() : missingCredential();
667
+ }
668
+ function errorRow(account, error) {
669
+ return {
670
+ id: account.id,
671
+ name: account.name,
672
+ driver: account.driver,
673
+ plan: null,
674
+ ok: false,
675
+ error,
676
+ fiveHour: closedWindow(),
677
+ weekly: closedWindow(),
678
+ extra: [],
679
+ models: [],
680
+ };
681
+ }
682
+ function okRow(account, windows) {
683
+ return {
684
+ id: account.id,
685
+ name: account.name,
686
+ driver: account.driver,
687
+ plan: windows.plan,
688
+ ok: true,
689
+ error: null,
690
+ fiveHour: windows.fiveHour,
691
+ weekly: windows.weekly,
692
+ extra: windows.extra,
693
+ models: windows.models,
694
+ };
695
+ }
696
+ async function fetchProvider(account, deps) {
697
+ let credential;
698
+ try {
699
+ credential = await deps.credentials.read(account);
700
+ }
701
+ catch {
702
+ return errorRow(account, signInAgain(account.driver));
703
+ }
704
+ if (credential.expired || !credential.token)
705
+ return errorRow(account, signInAgain(account.driver));
706
+ try {
707
+ if (account.driver === "claude")
708
+ return await fetchClaude(account, credential.token, deps);
709
+ if (account.driver === "codex")
710
+ return await fetchCodex(account, credential.token, credential.accountId, deps);
711
+ return await fetchGrok(account, credential.token, deps);
712
+ }
713
+ catch {
714
+ return errorRow(account, couldNotReach(account.driver));
715
+ }
716
+ }
717
+ async function fetchJson(fetchImpl, url, headers, timeoutMs) {
718
+ try {
719
+ const response = await fetchImpl(url, { headers, signal: AbortSignal.timeout(timeoutMs) });
720
+ let text = "";
721
+ try {
722
+ text = await response.text();
723
+ }
724
+ catch {
725
+ text = "";
726
+ }
727
+ let body = null;
728
+ if (text.trim()) {
729
+ try {
730
+ body = JSON.parse(text);
731
+ }
732
+ catch {
733
+ body = null;
734
+ }
735
+ }
736
+ return { status: response.status, ok: response.ok, body };
737
+ }
738
+ catch {
739
+ return { status: 0, ok: false, body: null };
740
+ }
741
+ }
742
+ function authRejected(status) {
743
+ return status === 401 || status === 403;
744
+ }
745
+ async function fetchClaude(account, token, deps) {
746
+ const result = await fetchJson(deps.fetch, CLAUDE_USAGE_URL, {
747
+ Authorization: `Bearer ${token}`,
748
+ Accept: "application/json",
749
+ "anthropic-beta": "oauth-2025-04-20",
750
+ }, deps.timeoutMs ?? PROVIDER_TIMEOUT_MS);
751
+ if (authRejected(result.status))
752
+ return errorRow(account, signInAgain("claude"));
753
+ if (!result.ok || result.body === null || typeof result.body !== "object") {
754
+ return errorRow(account, result.status === 0 || result.status >= 500 ? couldNotReach("claude") : unexpectedUsage("claude"));
755
+ }
756
+ return okRow(account, parseClaudeUsage(result.body));
757
+ }
758
+ async function fetchCodex(account, token, accountId, deps) {
759
+ const headers = {
760
+ Authorization: `Bearer ${token}`,
761
+ Accept: "application/json",
762
+ };
763
+ if (accountId)
764
+ headers["ChatGPT-Account-Id"] = accountId;
765
+ const result = await fetchJson(deps.fetch, CODEX_USAGE_URL, headers, deps.timeoutMs ?? PROVIDER_TIMEOUT_MS);
766
+ if (authRejected(result.status))
767
+ return errorRow(account, signInAgain("codex"));
768
+ if (!result.ok || result.body === null || typeof result.body !== "object") {
769
+ return errorRow(account, result.status === 0 || result.status >= 500 ? couldNotReach("codex") : unexpectedUsage("codex"));
770
+ }
771
+ return okRow(account, parseCodexUsage(result.body));
772
+ }
773
+ async function fetchGrok(account, token, deps) {
774
+ const headers = {
775
+ Authorization: `Bearer ${token}`,
776
+ Accept: "application/json",
777
+ "X-XAI-Token-Auth": "xai-grok-cli",
778
+ };
779
+ const timeout = deps.timeoutMs ?? PROVIDER_TIMEOUT_MS;
780
+ const [credits, billing, settings] = await Promise.all([
781
+ fetchJson(deps.fetch, GROK_CREDITS_URL, headers, timeout),
782
+ fetchJson(deps.fetch, GROK_BILLING_URL, headers, timeout),
783
+ fetchJson(deps.fetch, GROK_SETTINGS_URL, headers, timeout),
784
+ ]);
785
+ if (authRejected(credits.status) || (authRejected(billing.status) && !credits.ok)) {
786
+ return errorRow(account, signInAgain("grok"));
787
+ }
788
+ const creditsBody = credits.ok ? credits.body : null;
789
+ const billingBody = billing.ok && !authRejected(billing.status) ? billing.body : null;
790
+ if (creditsBody === null && billingBody === null) {
791
+ return errorRow(account, credits.status === 0 || credits.status >= 500 || billing.status === 0 || billing.status >= 500
792
+ ? couldNotReach("grok")
793
+ : unexpectedUsage("grok"));
794
+ }
795
+ const windows = parseGrokUsage(creditsBody, billingBody ?? undefined, settings.ok ? settings.body : undefined);
796
+ return okRow(account, windows);
797
+ }