@indigoai-us/hq-cli 5.345.91 → 5.345.93

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 (47) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +17 -4
  3. package/dist/command-catalog.generated.d.ts +252 -0
  4. package/dist/command-catalog.generated.js +306 -0
  5. package/dist/command-registration-plan.d.ts +6 -0
  6. package/dist/command-registration-plan.js +1 -0
  7. package/dist/commands/capacity.d.ts +71 -0
  8. package/dist/commands/capacity.js +666 -0
  9. package/dist/commands/integrations-connect.js +64 -5
  10. package/dist/commands/lanes-accounts.d.ts +1 -0
  11. package/dist/commands/lanes-accounts.js +6 -0
  12. package/dist/lib/capacity/accounts.d.ts +36 -0
  13. package/dist/lib/capacity/accounts.js +90 -0
  14. package/dist/lib/capacity/collect.d.ts +33 -0
  15. package/dist/lib/capacity/collect.js +164 -0
  16. package/dist/lib/capacity/collectors/claude-oauth.d.ts +145 -0
  17. package/dist/lib/capacity/collectors/claude-oauth.js +1211 -0
  18. package/dist/lib/capacity/collectors/claude-statusline.d.ts +72 -0
  19. package/dist/lib/capacity/collectors/claude-statusline.js +436 -0
  20. package/dist/lib/capacity/collectors/codex.d.ts +120 -0
  21. package/dist/lib/capacity/collectors/codex.js +1134 -0
  22. package/dist/lib/capacity/collectors/grok.d.ts +86 -0
  23. package/dist/lib/capacity/collectors/grok.js +588 -0
  24. package/dist/lib/capacity/lane-login.d.ts +29 -0
  25. package/dist/lib/capacity/lane-login.js +91 -0
  26. package/dist/lib/capacity/ledger.d.ts +399 -0
  27. package/dist/lib/capacity/ledger.js +1441 -0
  28. package/dist/lib/capacity/project.d.ts +12 -0
  29. package/dist/lib/capacity/project.js +264 -0
  30. package/dist/lib/capacity/read.d.ts +62 -0
  31. package/dist/lib/capacity/read.js +264 -0
  32. package/dist/lib/capacity/refusal.d.ts +30 -0
  33. package/dist/lib/capacity/refusal.js +183 -0
  34. package/dist/lib/capacity/scheduler.d.ts +76 -0
  35. package/dist/lib/capacity/scheduler.js +800 -0
  36. package/dist/lib/capacity/sign-in.d.ts +103 -0
  37. package/dist/lib/capacity/sign-in.js +517 -0
  38. package/dist/lib/capacity/widget.d.ts +22 -0
  39. package/dist/lib/capacity/widget.js +418 -0
  40. package/dist/lib/lanes/spawn.js +12 -0
  41. package/dist/lib/mesh/live/daemon/run.d.ts +5 -0
  42. package/dist/lib/mesh/live/daemon/run.js +29 -1
  43. package/dist/lib/search-index/background.d.ts +31 -8
  44. package/dist/lib/search-index/background.js +86 -7
  45. package/dist/lib/search-index/index.d.ts +24 -0
  46. package/dist/lib/search-index/index.js +25 -0
  47. package/package.json +1 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Grok billing collector (US-007).
3
+ *
4
+ * Reads the bearer from ~/.grok/auth.json (path injectable) and GETs
5
+ * https://cli-chat-proxy.grok.com/v1/billing?format=credits. The wire body
6
+ * is camelCase under `config` (US-001). Snake_case names from the October
7
+ * notes are drift, not a second dialect. This module does not refresh the
8
+ * token, write ~/.grok, start the daemon, or call the grok CLI except
9
+ * `grok --version` when the caller does not pass cliVersion.
10
+ *
11
+ * observedAt is the response time. Readings and parser failures from one
12
+ * response are appended in one rebuild. A missing creditUsagePercent never
13
+ * becomes a zero reading.
14
+ */
15
+ import { type LedgerOptions, type NowView, type ParserFailureEvent, type ReadingInput } from "../ledger.js";
16
+ export declare const GROK_PARSER_VERSION = "grok-billing.1";
17
+ export declare const GROK_PROVIDER = "grok";
18
+ export declare const GROK_SOURCE: "grok-billing";
19
+ export declare const GROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
20
+ export declare const GROK_POLL_MS: number;
21
+ export declare const GROK_FRESH_MS: number;
22
+ export declare const GROK_POLL_FILE = "grok-last-call.json";
23
+ export declare const GROK_FETCH_TIMEOUT_MS = 20000;
24
+ export interface GrokFetchInit {
25
+ method: "GET";
26
+ headers: Record<string, string>;
27
+ redirect: "manual";
28
+ signal?: AbortSignal;
29
+ }
30
+ export type GrokFetch = (url: string, init: GrokFetchInit) => Promise<{
31
+ status: number;
32
+ text: () => Promise<string>;
33
+ }>;
34
+ export interface GrokCollectResult {
35
+ readings: ReadingInput[];
36
+ failures: ParserFailureEvent[];
37
+ view: NowView | null;
38
+ /** Set when the five-minute guard skipped the HTTP call. */
39
+ skipped?: "rate-limit" | "disabled";
40
+ accountId: string | null;
41
+ }
42
+ export interface CollectGrokOptions {
43
+ /** Defaults to ~/.grok/auth.json. Read-only. */
44
+ authPath?: string;
45
+ /** Full billing URL. Defaults to GROK_BILLING_URL. */
46
+ endpoint?: string;
47
+ /** Installed grok version. When set, grok --version is not run. */
48
+ cliVersion?: string;
49
+ /** Replaces `grok --version` output. Used when cliVersion is omitted. */
50
+ readVersion?: () => string;
51
+ now?: () => Date;
52
+ fetchImpl?: GrokFetch;
53
+ ledger?: LedgerOptions;
54
+ /** When set, read `<home>/auth.json` for each label instead of the single default file. */
55
+ accounts?: readonly {
56
+ label: string;
57
+ home: string;
58
+ }[];
59
+ /** One-shot collect reads while the resident collector stays disabled. */
60
+ allowDisabled?: boolean;
61
+ }
62
+ interface MapOptions {
63
+ observedAt: string;
64
+ cliVersion: string;
65
+ accountId: string;
66
+ accountLabel?: string;
67
+ accountLabels?: string[];
68
+ organization?: string | null;
69
+ accountHome?: string;
70
+ }
71
+ export declare function grokInstallSalt(root: string): Buffer;
72
+ export declare function hashGrokAccountId(raw: string, salt?: Buffer): string;
73
+ export declare function parseGrokCliVersion(text: string): string | null;
74
+ export declare function grokPollPath(root: string): string;
75
+ export declare function defaultGrokAuthPath(home?: string): string;
76
+ export declare function readInstalledGrokVersion(readVersion?: () => string): string;
77
+ /** Async `grok --version` for the daemon. The sync reader stays for one-shot callers. */
78
+ export declare function readInstalledGrokVersionAsync(): Promise<string>;
79
+ /** Map a billing JSON body. Does not read auth or call the network. */
80
+ export declare function mapGrokBillingBody(body: unknown, options: MapOptions): {
81
+ readings: ReadingInput[];
82
+ failures: ParserFailureEvent[];
83
+ };
84
+ export declare function collectGrok(options?: CollectGrokOptions): Promise<GrokCollectResult>;
85
+ export {};
86
+ //# sourceMappingURL=grok.d.ts.map
@@ -0,0 +1,588 @@
1
+ /**
2
+ * Grok billing collector (US-007).
3
+ *
4
+ * Reads the bearer from ~/.grok/auth.json (path injectable) and GETs
5
+ * https://cli-chat-proxy.grok.com/v1/billing?format=credits. The wire body
6
+ * is camelCase under `config` (US-001). Snake_case names from the October
7
+ * notes are drift, not a second dialect. This module does not refresh the
8
+ * token, write ~/.grok, start the daemon, or call the grok CLI except
9
+ * `grok --version` when the caller does not pass cliVersion.
10
+ *
11
+ * observedAt is the response time. Readings and parser failures from one
12
+ * response are appended in one rebuild. A missing creditUsagePercent never
13
+ * becomes a zero reading.
14
+ */
15
+ import { execFile, spawnSync } from "node:child_process";
16
+ import * as crypto from "node:crypto";
17
+ import * as fs from "node:fs";
18
+ import * as os from "node:os";
19
+ import * as path from "node:path";
20
+ import { DIR_MODE, FILE_MODE, appendReadings, capacityRoot, readCollectorMark, } from "../ledger.js";
21
+ export const GROK_PARSER_VERSION = "grok-billing.1";
22
+ export const GROK_PROVIDER = "grok";
23
+ export const GROK_SOURCE = "grok-billing";
24
+ export const GROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
25
+ export const GROK_POLL_MS = 5 * 60 * 1000;
26
+ export const GROK_FRESH_MS = GROK_POLL_MS - 15_000;
27
+ export const GROK_POLL_FILE = "grok-last-call.json";
28
+ export const GROK_FETCH_TIMEOUT_MS = 20_000;
29
+ const WEEKLY_MINUTES = 7 * 24 * 60;
30
+ export function grokInstallSalt(root) {
31
+ const dest = path.join(root, "grok-install-salt");
32
+ try {
33
+ const existing = fs.readFileSync(dest);
34
+ if (existing.length >= 16)
35
+ return existing;
36
+ }
37
+ catch {
38
+ // Created below.
39
+ }
40
+ fs.mkdirSync(root, { recursive: true, mode: DIR_MODE });
41
+ const salt = crypto.randomBytes(32);
42
+ const tmp = `${dest}.${process.pid}.tmp`;
43
+ fs.writeFileSync(tmp, salt, { mode: FILE_MODE });
44
+ fs.renameSync(tmp, dest);
45
+ fs.chmodSync(dest, FILE_MODE);
46
+ return salt;
47
+ }
48
+ export function hashGrokAccountId(raw, salt) {
49
+ const digest = salt
50
+ ? crypto.createHmac("sha256", salt).update(raw, "utf8").digest("hex")
51
+ : crypto.createHash("sha256").update(raw, "utf8").digest("hex");
52
+ return `acct_${digest.slice(0, 16)}`;
53
+ }
54
+ export function parseGrokCliVersion(text) {
55
+ const match = /\b(\d+\.\d+\.\d+)\b/.exec(text);
56
+ return match?.[1] ?? null;
57
+ }
58
+ export function grokPollPath(root) {
59
+ return path.join(root, GROK_POLL_FILE);
60
+ }
61
+ export function defaultGrokAuthPath(home = os.homedir()) {
62
+ return path.join(home, ".grok", "auth.json");
63
+ }
64
+ export function readInstalledGrokVersion(readVersion = defaultGrokVersionText) {
65
+ try {
66
+ return parseGrokCliVersion(readVersion()) ?? "unknown";
67
+ }
68
+ catch {
69
+ return "unknown";
70
+ }
71
+ }
72
+ /** Async `grok --version` for the daemon. The sync reader stays for one-shot callers. */
73
+ export function readInstalledGrokVersionAsync() {
74
+ return new Promise((resolve) => {
75
+ execFile("grok", ["--version"], { timeout: 5_000, encoding: "utf8", windowsHide: true }, (error, stdout, stderr) => {
76
+ if (error && !stdout && !stderr) {
77
+ resolve("unknown");
78
+ return;
79
+ }
80
+ resolve(parseGrokCliVersion(`${stdout ?? ""}\n${stderr ?? ""}`) ?? "unknown");
81
+ });
82
+ });
83
+ }
84
+ /** Map a billing JSON body. Does not read auth or call the network. */
85
+ export function mapGrokBillingBody(body, options) {
86
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
87
+ return { readings: [], failures: [failure(options, "grok billing response is not an object")] };
88
+ }
89
+ const root = body;
90
+ const configValue = root.config ?? root;
91
+ if (!configValue || typeof configValue !== "object" || Array.isArray(configValue)) {
92
+ return { readings: [], failures: [failure(options, "grok billing field drift: config")] };
93
+ }
94
+ const config = configValue;
95
+ if (!("creditUsagePercent" in config) || !isFiniteNumber(config.creditUsagePercent) || config.creditUsagePercent < 0) {
96
+ return { readings: [], failures: [failure(options, "grok billing field drift: creditUsagePercent")] };
97
+ }
98
+ const period = readPeriod(config.currentPeriod);
99
+ if (!period) {
100
+ return { readings: [], failures: [failure(options, "grok billing field drift: currentPeriod.end")] };
101
+ }
102
+ const planType = typeof config.subscriptionTier === "string" && config.subscriptionTier.trim()
103
+ ? config.subscriptionTier.trim()
104
+ : null;
105
+ if (options.accountLabel && options.organization === undefined) {
106
+ const tier = typeof config.tier === "string" && config.tier.trim() ? config.tier.trim() : null;
107
+ const named = typeof config.planType === "string" && config.planType.trim() ? config.planType.trim() : null;
108
+ options.organization = planType ?? named ?? tier;
109
+ }
110
+ const weeklyPercent = config.creditUsagePercent;
111
+ const readings = [
112
+ reading(options, {
113
+ limitId: "weekly",
114
+ kind: "weekly_all",
115
+ scope: null,
116
+ usedPercent: weeklyPercent,
117
+ windowMinutes: period.windowMinutes,
118
+ resetsAt: period.end,
119
+ balance: null,
120
+ planType,
121
+ }),
122
+ ];
123
+ const failures = [];
124
+ if ("prepaidBalance" in config) {
125
+ const prepaid = centVal(config.prepaidBalance);
126
+ if (prepaid === null) {
127
+ failures.push(failure(options, "grok billing field drift: prepaidBalance"));
128
+ }
129
+ else {
130
+ readings.push(reading(options, {
131
+ limitId: "prepaid",
132
+ kind: "prepaid",
133
+ scope: null,
134
+ usedPercent: null,
135
+ windowMinutes: null,
136
+ resetsAt: null,
137
+ balance: prepaid,
138
+ planType,
139
+ }));
140
+ }
141
+ }
142
+ if ("onDemandUsed" in config || "onDemandCap" in config) {
143
+ const used = centVal(config.onDemandUsed);
144
+ const cap = centVal(config.onDemandCap);
145
+ if (used === null || cap === null) {
146
+ failures.push(failure(options, "grok billing field drift: onDemandUsed"));
147
+ }
148
+ else {
149
+ readings.push(reading(options, {
150
+ limitId: "on_demand",
151
+ kind: "on_demand",
152
+ scope: null,
153
+ usedPercent: cap > 0 ? (used / cap) * 100 : null,
154
+ windowMinutes: null,
155
+ resetsAt: null,
156
+ balance: `${used}/${cap}`,
157
+ planType,
158
+ }));
159
+ }
160
+ }
161
+ if ("productUsage" in config) {
162
+ const products = config.productUsage;
163
+ if (!Array.isArray(products)) {
164
+ failures.push(failure(options, "grok billing field drift: productUsage"));
165
+ }
166
+ else {
167
+ const seen = new Set();
168
+ for (const item of products) {
169
+ const mapped = mapProduct(item);
170
+ if (!mapped) {
171
+ failures.push(failure(options, "grok billing field drift: productUsage"));
172
+ continue;
173
+ }
174
+ if (seen.has(mapped.product))
175
+ continue;
176
+ seen.add(mapped.product);
177
+ readings.push(reading(options, {
178
+ limitId: `product:${mapped.product}`,
179
+ kind: "weekly_scoped",
180
+ scope: mapped.product,
181
+ usedPercent: mapped.usagePercent,
182
+ windowMinutes: period.windowMinutes,
183
+ resetsAt: period.end,
184
+ balance: null,
185
+ planType,
186
+ }));
187
+ }
188
+ }
189
+ }
190
+ return { readings, failures };
191
+ }
192
+ export async function collectGrok(options = {}) {
193
+ const ledger = ledgerOptions(options);
194
+ const root = ledger.root ?? capacityRoot(ledger.env, ledger.home);
195
+ const scoped = { ...ledger, root };
196
+ if (readCollectorMark(scoped)?.enabled === false && !options.allowDisabled) {
197
+ return { readings: [], failures: [], view: null, skipped: "disabled", accountId: null };
198
+ }
199
+ if (options.accounts)
200
+ return collectEnumeratedGrok(options);
201
+ const clock = options.now ?? ledger.now ?? (() => new Date());
202
+ const authPath = options.authPath ?? defaultGrokAuthPath(ledger.home);
203
+ const loaded = readGrokAuth(authPath, root);
204
+ if ("error" in loaded) {
205
+ const cliVersion = options.cliVersion ?? readInstalledGrokVersion(options.readVersion);
206
+ const accountId = hashGrokAccountId(`home:${path.resolve(path.dirname(authPath))}`);
207
+ const observedAt = clock().toISOString();
208
+ const failures = [failure({ observedAt, cliVersion, accountId }, loaded.error)];
209
+ const view = appendReadings([], failures, null, scoped);
210
+ return { readings: [], failures, view, accountId };
211
+ }
212
+ const accountId = loaded.auth.accountId;
213
+ const nowMs = clock().getTime();
214
+ if (pollIsFresh(root, accountId, nowMs)) {
215
+ return { readings: [], failures: [], view: null, skipped: "rate-limit", accountId };
216
+ }
217
+ const cliVersion = options.cliVersion ?? readInstalledGrokVersion(options.readVersion);
218
+ writePoll(root, accountId, new Date(nowMs).toISOString());
219
+ const endpoint = options.endpoint ?? GROK_BILLING_URL;
220
+ const fetchImpl = options.fetchImpl ?? defaultGrokFetch;
221
+ let status;
222
+ let text;
223
+ try {
224
+ const response = await fetchImpl(endpoint, grokBillingInit(loaded.auth, cliVersion));
225
+ status = response.status;
226
+ text = status === 200 ? await response.text() : "";
227
+ }
228
+ catch {
229
+ const observedAt = clock().toISOString();
230
+ const failures = [failure({ observedAt, cliVersion, accountId }, "grok billing request failed")];
231
+ const view = appendReadings([], failures, null, scoped);
232
+ return { readings: [], failures, view, accountId };
233
+ }
234
+ const observedAt = clock().toISOString();
235
+ const mapOptions = { observedAt, cliVersion, accountId };
236
+ if (status === 401) {
237
+ const failures = [failure(mapOptions, "grok billing unavailable: http 401")];
238
+ const view = appendReadings([], failures, null, scoped);
239
+ return { readings: [], failures, view, accountId };
240
+ }
241
+ if (status !== 200) {
242
+ const failures = [failure(mapOptions, `grok billing unavailable: http ${status}`)];
243
+ const view = appendReadings([], failures, null, scoped);
244
+ return { readings: [], failures, view, accountId };
245
+ }
246
+ let parsed;
247
+ try {
248
+ parsed = JSON.parse(text);
249
+ }
250
+ catch {
251
+ const failures = [failure(mapOptions, "grok billing response is not JSON")];
252
+ const view = appendReadings([], failures, null, scoped);
253
+ return { readings: [], failures, view, accountId };
254
+ }
255
+ const mapped = mapGrokBillingBody(parsed, mapOptions);
256
+ const view = appendReadings(mapped.readings, mapped.failures, null, scoped);
257
+ return { readings: mapped.readings, failures: mapped.failures, view, accountId };
258
+ }
259
+ function ledgerOptions(options) {
260
+ return { ...(options.ledger ?? {}) };
261
+ }
262
+ function grokBillingInit(auth, cliVersion) {
263
+ const headers = {
264
+ Authorization: `Bearer ${auth.bearer}`,
265
+ "X-XAI-Token-Auth": "xai-grok-cli",
266
+ Accept: "application/json",
267
+ };
268
+ if (auth.userId)
269
+ headers["x-userid"] = auth.userId;
270
+ const version = parseGrokCliVersion(cliVersion) ?? (cliVersion !== "unknown" ? cliVersion : null);
271
+ if (version)
272
+ headers["x-grok-client-version"] = version;
273
+ return {
274
+ method: "GET",
275
+ headers,
276
+ redirect: "manual",
277
+ signal: AbortSignal.timeout(GROK_FETCH_TIMEOUT_MS),
278
+ };
279
+ }
280
+ async function defaultGrokFetch(url, init) {
281
+ const response = await fetch(url, init);
282
+ return { status: response.status, text: () => response.text() };
283
+ }
284
+ function defaultGrokVersionText() {
285
+ const result = spawnSync("grok", ["--version"], {
286
+ encoding: "utf8",
287
+ timeout: 5_000,
288
+ stdio: ["ignore", "pipe", "pipe"],
289
+ windowsHide: true,
290
+ });
291
+ if (result.error)
292
+ throw result.error;
293
+ return `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
294
+ }
295
+ function readGrokAuth(file, root) {
296
+ let text;
297
+ try {
298
+ text = fs.readFileSync(file, "utf8");
299
+ }
300
+ catch (err) {
301
+ const code = err && typeof err === "object" && "code" in err ? err.code : "";
302
+ return { error: code === "ENOENT" ? "grok auth file is missing" : "grok auth file is unreadable" };
303
+ }
304
+ let parsed;
305
+ try {
306
+ parsed = JSON.parse(text);
307
+ }
308
+ catch {
309
+ return { error: "grok auth file is not JSON" };
310
+ }
311
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
312
+ return { error: "grok auth file is not an object" };
313
+ }
314
+ for (const value of Object.values(parsed)) {
315
+ if (!value || typeof value !== "object" || Array.isArray(value))
316
+ continue;
317
+ const record = value;
318
+ if (typeof record.key !== "string" || record.key.trim() === "")
319
+ continue;
320
+ const accountRaw = firstAccountKey(record, `home:${path.resolve(path.dirname(file))}`);
321
+ const userId = typeof record.user_id === "string" && record.user_id.trim() ? record.user_id.trim() : null;
322
+ return {
323
+ auth: {
324
+ bearer: record.key,
325
+ accountId: hashGrokMaterial(accountRaw, root),
326
+ userId,
327
+ },
328
+ };
329
+ }
330
+ return { error: "grok auth file has no bearer" };
331
+ }
332
+ function firstAccountKey(record, homeFallback) {
333
+ for (const field of ["user_id", "principal_id"]) {
334
+ const value = record[field];
335
+ if (typeof value === "string" && value.trim())
336
+ return `id:${value.trim()}`;
337
+ }
338
+ const email = record.email;
339
+ if (typeof email === "string" && email.includes("@"))
340
+ return `email:${email.trim()}`;
341
+ return homeFallback;
342
+ }
343
+ function hashGrokMaterial(raw, root) {
344
+ if (raw.startsWith("email:") && root)
345
+ return hashGrokAccountId(raw.slice("email:".length), grokInstallSalt(root));
346
+ const material = raw.startsWith("id:") ? raw.slice("id:".length) : raw;
347
+ return hashGrokAccountId(material);
348
+ }
349
+ function pollIsFresh(root, accountId, nowMs) {
350
+ const accounts = readPoll(grokPollPath(root));
351
+ const last = accounts[accountId];
352
+ if (!last)
353
+ return false;
354
+ const at = Date.parse(last);
355
+ if (Number.isNaN(at))
356
+ return false;
357
+ const delta = nowMs - at;
358
+ return delta >= 0 && delta < GROK_FRESH_MS;
359
+ }
360
+ function readPoll(file) {
361
+ try {
362
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
363
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
364
+ return {};
365
+ const accounts = parsed.accounts;
366
+ if (!accounts || typeof accounts !== "object" || Array.isArray(accounts))
367
+ return {};
368
+ const out = {};
369
+ for (const [key, value] of Object.entries(accounts)) {
370
+ if (typeof value === "string")
371
+ out[key] = value;
372
+ }
373
+ return out;
374
+ }
375
+ catch {
376
+ return {};
377
+ }
378
+ }
379
+ function writePoll(root, accountId, at) {
380
+ fs.mkdirSync(root, { recursive: true, mode: DIR_MODE });
381
+ fs.chmodSync(root, DIR_MODE);
382
+ const dest = grokPollPath(root);
383
+ const accounts = readPoll(dest);
384
+ accounts[accountId] = at;
385
+ const body = `${JSON.stringify({ accounts }, null, 2)}\n`;
386
+ const tmp = path.join(root, `.${GROK_POLL_FILE}.${process.pid}.tmp`);
387
+ fs.writeFileSync(tmp, body, { mode: FILE_MODE });
388
+ fs.renameSync(tmp, dest);
389
+ fs.chmodSync(dest, FILE_MODE);
390
+ }
391
+ function readPeriod(value) {
392
+ if (!value || typeof value !== "object" || Array.isArray(value))
393
+ return null;
394
+ const record = value;
395
+ const endRaw = record.end;
396
+ if (typeof endRaw !== "string")
397
+ return null;
398
+ const endMs = Date.parse(endRaw);
399
+ if (Number.isNaN(endMs))
400
+ return null;
401
+ const end = new Date(endMs).toISOString();
402
+ let windowMinutes = WEEKLY_MINUTES;
403
+ if (typeof record.start === "string") {
404
+ const startMs = Date.parse(record.start);
405
+ if (!Number.isNaN(startMs) && endMs > startMs) {
406
+ windowMinutes = Math.round((endMs - startMs) / 60_000);
407
+ }
408
+ }
409
+ return { end, windowMinutes };
410
+ }
411
+ function mapProduct(value) {
412
+ if (!value || typeof value !== "object" || Array.isArray(value))
413
+ return null;
414
+ const record = value;
415
+ const product = typeof record.product === "string" ? record.product.trim() : "";
416
+ if (!product)
417
+ return null;
418
+ if (!isFiniteNumber(record.usagePercent) || record.usagePercent < 0)
419
+ return null;
420
+ return { product, usagePercent: record.usagePercent };
421
+ }
422
+ function centVal(value) {
423
+ if (!value || typeof value !== "object" || Array.isArray(value))
424
+ return null;
425
+ const val = value.val;
426
+ if (!isFiniteNumber(val))
427
+ return null;
428
+ return val;
429
+ }
430
+ function reading(options, limit) {
431
+ const row = {
432
+ readingId: makeReadingId([
433
+ GROK_SOURCE,
434
+ options.accountId,
435
+ limit.limitId,
436
+ options.observedAt,
437
+ String(limit.usedPercent),
438
+ String(limit.balance),
439
+ ]),
440
+ observedAt: options.observedAt,
441
+ provider: GROK_PROVIDER,
442
+ accountId: options.accountId,
443
+ source: GROK_SOURCE,
444
+ cliVersion: options.cliVersion,
445
+ confidence: "fetched",
446
+ parserVersion: GROK_PARSER_VERSION,
447
+ limit,
448
+ };
449
+ return stampAccount(row, options, limit.planType);
450
+ }
451
+ function failure(options, error) {
452
+ const event = {
453
+ event: "parser_failure",
454
+ observedAt: options.observedAt,
455
+ provider: GROK_PROVIDER,
456
+ source: GROK_SOURCE,
457
+ error,
458
+ accountId: options.accountId,
459
+ cliVersion: options.cliVersion,
460
+ parserVersion: GROK_PARSER_VERSION,
461
+ };
462
+ return stampAccount(event, options, null);
463
+ }
464
+ function stampAccount(row, options, planType) {
465
+ if (!options.accountLabel)
466
+ return row;
467
+ row.accountLabel = options.accountLabel;
468
+ row.accountLabels = options.accountLabels ? [...options.accountLabels] : [options.accountLabel];
469
+ if (options.accountHome)
470
+ row.accountHome = options.accountHome;
471
+ row.organization = options.organization !== undefined ? options.organization : planType;
472
+ return row;
473
+ }
474
+ async function collectEnumeratedGrok(options) {
475
+ const ledger = ledgerOptions(options);
476
+ const root = ledger.root ?? capacityRoot(ledger.env, ledger.home);
477
+ const scoped = { ...ledger, root };
478
+ const clock = options.now ?? ledger.now ?? (() => new Date());
479
+ const cliVersion = options.cliVersion ?? readInstalledGrokVersion(options.readVersion);
480
+ const groups = new Map();
481
+ for (const account of options.accounts ?? []) {
482
+ const key = path.resolve(account.home);
483
+ const group = groups.get(key) ?? { home: account.home, labels: [] };
484
+ if (!group.labels.includes(account.label))
485
+ group.labels.push(account.label);
486
+ groups.set(key, group);
487
+ }
488
+ const pending = [];
489
+ const failures = [];
490
+ for (const group of groups.values()) {
491
+ const loaded = readGrokAuth(path.join(group.home, "auth.json"), root);
492
+ const label = group.labels.find((item) => item !== "default") ?? group.labels[0] ?? "default";
493
+ if ("error" in loaded) {
494
+ const accountId = hashGrokAccountId(`missing:${path.resolve(group.home)}`);
495
+ const reason = loaded.error === "grok auth file is missing"
496
+ ? "signed out: credential missing"
497
+ : "signed out: credential unreadable";
498
+ failures.push(failure({
499
+ observedAt: clock().toISOString(),
500
+ cliVersion,
501
+ accountId,
502
+ accountLabel: label,
503
+ accountLabels: group.labels,
504
+ accountHome: group.home,
505
+ }, reason));
506
+ continue;
507
+ }
508
+ const existing = pending.find((row) => row.auth.accountId === loaded.auth.accountId);
509
+ if (existing) {
510
+ for (const item of group.labels) {
511
+ if (!existing.labels.includes(item))
512
+ existing.labels.push(item);
513
+ }
514
+ if (!existing.homes.includes(group.home))
515
+ existing.homes.push(group.home);
516
+ continue;
517
+ }
518
+ pending.push({ auth: loaded.auth, labels: [...group.labels], homes: [group.home] });
519
+ }
520
+ const readings = [];
521
+ const endpoint = options.endpoint ?? GROK_BILLING_URL;
522
+ const fetchImpl = options.fetchImpl ?? defaultGrokFetch;
523
+ const nowMs = clock().getTime();
524
+ let fetched = false;
525
+ for (const account of pending) {
526
+ if (pollIsFresh(root, account.auth.accountId, nowMs))
527
+ continue;
528
+ writePoll(root, account.auth.accountId, new Date(nowMs).toISOString());
529
+ fetched = true;
530
+ const label = account.labels.find((item) => item !== "default") ?? account.labels[0] ?? "default";
531
+ const observedAt = clock().toISOString();
532
+ const mapOptions = {
533
+ observedAt,
534
+ cliVersion,
535
+ accountId: account.auth.accountId,
536
+ accountLabel: label,
537
+ accountLabels: account.labels,
538
+ accountHome: account.homes[0],
539
+ };
540
+ let status;
541
+ let text;
542
+ try {
543
+ const response = await fetchImpl(endpoint, grokBillingInit(account.auth, cliVersion));
544
+ status = response.status;
545
+ text = status === 200 ? await response.text() : "";
546
+ }
547
+ catch {
548
+ failures.push(failure(mapOptions, "grok billing request failed"));
549
+ continue;
550
+ }
551
+ if (status === 401 || status === 403) {
552
+ failures.push(failure(mapOptions, "signed out: auth rejected"));
553
+ continue;
554
+ }
555
+ if (status !== 200) {
556
+ failures.push(failure(mapOptions, `grok billing unavailable: http ${status}`));
557
+ continue;
558
+ }
559
+ let parsed;
560
+ try {
561
+ parsed = JSON.parse(text);
562
+ }
563
+ catch {
564
+ failures.push(failure(mapOptions, "grok billing response is not JSON"));
565
+ continue;
566
+ }
567
+ const mapped = mapGrokBillingBody(parsed, mapOptions);
568
+ readings.push(...mapped.readings);
569
+ failures.push(...mapped.failures);
570
+ }
571
+ const accountId = pending[0]?.auth.accountId ?? null;
572
+ if (!fetched && failures.length === 0) {
573
+ return { readings: [], failures: [], view: null, skipped: "rate-limit", accountId };
574
+ }
575
+ if (readings.length === 0 && failures.length === 0) {
576
+ return { readings, failures, view: null, accountId };
577
+ }
578
+ const view = appendReadings(readings, failures, null, scoped);
579
+ return { readings, failures, view, accountId };
580
+ }
581
+ function makeReadingId(parts) {
582
+ const digest = crypto.createHash("sha256").update(parts.join("\0"), "utf8").digest("hex").slice(0, 20);
583
+ return `rdg_${digest}`;
584
+ }
585
+ function isFiniteNumber(value) {
586
+ return typeof value === "number" && Number.isFinite(value);
587
+ }
588
+ //# sourceMappingURL=grok.js.map