@v1nvn/zai 0.36.0 → 0.37.0

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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @v1nvn/zai
2
2
 
3
3
  The home of the zai plugin — and its plugin root: the `zai-usage` CLI in `src/`,
4
- the mod's hooks module at `hooks/register.ts` (`.ts`/`.tsx` the engine loads
4
+ the mod's hooks module at `hooks/register.tsx` (`.ts`/`.tsx` the engine loads
5
5
  directly, no build), engine tests at `tests/`. It reports GLM Coding Plan quota
6
6
  and usage for the current account — models, tools, limits — straight to the
7
7
  terminal; no browser, nothing written.
@@ -11,8 +11,8 @@ User-facing docs: [root README](../../README.md).
11
11
  ## Quickstart
12
12
 
13
13
  In Claude Code, the plugin is the way in — a mod serves the one surface the
14
- model never reads: `/zai-usage` execs the shipped CLI and shows its report as
15
- dim transcript rows (nothing execs until the command runs).
14
+ model never reads: `/zai-usage` execs the shipped CLI and opens its report as
15
+ a pane (nothing execs until the command runs).
16
16
 
17
17
  ```sh
18
18
  claude plugin marketplace add v1nvn/agentic
@@ -22,18 +22,18 @@ claude plugin install zai@agentic
22
22
  In a terminal, bare:
23
23
 
24
24
  ```sh
25
- npx -y @v1nvn/zai@0.36.0
25
+ npx -y @v1nvn/zai@0.37.0
26
26
  ```
27
27
 
28
28
  ## Usage
29
29
 
30
30
  | Invocation | Does |
31
31
  |---|---|
32
- | `/zai-usage` | the account's usage report, as dim transcript rows |
33
- | `npx -y @v1nvn/zai@0.36.0` | the same report, printed bare |
34
- | `npx -y @v1nvn/zai@0.36.0 --auth-token TOKEN` | same, key on the command line (visible in `ps`) |
35
- | `npx -y @v1nvn/zai@0.36.0 --auth-token=TOKEN` | `=` form — zsh quoting-safe |
36
- | `npx -y @v1nvn/zai@0.36.0 --base-url URL` | another GLM endpoint (default `api.z.ai`) |
32
+ | `/zai-usage` | the account's usage report, as a pane |
33
+ | `npx -y @v1nvn/zai@0.37.0` | the same report, printed bare |
34
+ | `npx -y @v1nvn/zai@0.37.0 --auth-token TOKEN` | same, key on the command line (visible in `ps`) |
35
+ | `npx -y @v1nvn/zai@0.37.0 --auth-token=TOKEN` | `=` form — zsh quoting-safe |
36
+ | `npx -y @v1nvn/zai@0.37.0 --base-url URL` | another GLM endpoint (default `api.z.ai`) |
37
37
 
38
38
  Each setting takes the first source that provides it:
39
39
 
@@ -73,17 +73,17 @@ and re-vendor `types/claude-code.d.ts` (repo root) from the engine-laid
73
73
  | File | Role |
74
74
  |---|---|
75
75
  | `src/index.ts` | bin entry (`zai-usage`) — dispatch, exit codes |
76
- | `src/usage.ts` | the GLM API call and the report it builds |
76
+ | `src/usage.ts` | the GLM API call and the report input it assembles |
77
77
  | `src/resolve.ts` | auth-token resolution chain |
78
- | `src/format.ts` | the printed table |
79
- | `hooks/register.ts` | the mod: registers `/zai-usage`, execs the CLI, shows its report |
78
+ | `src/format.ts` | the report's lines — printed by the CLI, drawn by the pane |
79
+ | `hooks/register.tsx` | the mod: registers `/zai-usage`, execs the CLI, opens its report as a pane |
80
80
  | `bin/usage.mjs` | the standalone build the mod execs — committed, synced by `yarn build` |
81
81
 
82
82
  ## Contracts
83
83
 
84
84
  - One job: print the report, exit 0. Nothing is written anywhere.
85
- - `/zai-usage` is one `node <plugin root>/bin/usage.mjs` exec through the
86
- session's Bash tool — the same query `npx zai-usage` runs, awaited as-is. The
87
- one allow rule it needs is `Bash(node <plugin root>/bin/usage.mjs)`; the
88
- first run asks for it once, then it is remembered. Its report is dim
89
- transcript rows the model never reads.
85
+ - `/zai-usage` is one `node <plugin root>/bin/usage.mjs --json` exec through
86
+ the session's Bash tool — the same query `npx zai-usage` runs, awaited
87
+ as-is. The one allow rule it needs is
88
+ `Bash(node <plugin root>/bin/usage.mjs --json)`; the first run asks for it
89
+ once, then it is remembered. Its report is a pane the model never reads.
package/dist/index.js CHANGED
@@ -16,77 +16,32 @@ function printUsageAndExit(program) {
16
16
  console.error(program.helpInformation());
17
17
  process.exit(1);
18
18
  }
19
- Number.MAX_SAFE_INTEGER;
20
- //#endregion
21
- //#region src/resolve.ts
22
- var DEFAULT_BASE_URL = "https://api.z.ai";
23
- var GLM_HOSTS = /* @__PURE__ */ new Set([
24
- "api.z.ai",
25
- "dev.bigmodel.cn",
26
- "open.bigmodel.cn"
27
- ]);
28
- function fromEnv(value) {
29
- return value ? value : void 0;
30
- }
31
- function originOf(value) {
19
+ async function runMain(main) {
32
20
  try {
33
- const url = new URL(value);
34
- return url.protocol === "https:" || url.protocol === "http:" ? url.origin : void 0;
35
- } catch {
36
- return;
37
- }
38
- }
39
- function isGlm(origin) {
40
- return GLM_HOSTS.has(new URL(origin).hostname);
41
- }
42
- function resolveBaseUrl(flag, env) {
43
- if (flag !== void 0) {
44
- const origin = originOf(flag);
45
- if (origin === void 0) throw new Error(`invalid base URL: ${flag}`);
46
- return {
47
- glm: isGlm(origin),
48
- origin
49
- };
21
+ await main();
22
+ } catch (e) {
23
+ console.error(e.message);
24
+ process.exit(1);
50
25
  }
51
- const inherited = fromEnv(env.ANTHROPIC_BASE_URL);
52
- const origin = inherited === void 0 ? void 0 : originOf(inherited);
53
- if (origin !== void 0 && isGlm(origin)) return {
54
- glm: true,
55
- origin
56
- };
57
- return {
58
- glm: false,
59
- origin: DEFAULT_BASE_URL
60
- };
61
26
  }
62
- function buildProgram() {
63
- return new Command().name("zai-usage").addOption(new Option("--auth-token <token>", "API key").env("ZAI_AUTH_TOKEN")).addOption(new Option("--base-url <url>", "base URL").env("ZAI_BASE_URL"));
27
+ Number.MAX_SAFE_INTEGER;
28
+ //#endregion
29
+ //#region ../tokens/dist/assets/text-D_R3UWAk.js
30
+ function plain(text) {
31
+ return { text };
64
32
  }
65
- function parseArgs(args) {
66
- const program = parseQuietly(buildProgram(), args);
67
- if (program === void 0) return;
68
- const { authToken, baseUrl } = program.opts();
33
+ function dim(text) {
69
34
  return {
70
- authToken: authToken || void 0,
71
- baseUrl: baseUrl || void 0
35
+ text,
36
+ ink: "dim"
72
37
  };
73
38
  }
74
- function resolveConfig(env, parsed) {
75
- const base = resolveBaseUrl(parsed.baseUrl, env);
76
- const token = parsed.authToken ?? (base.glm ? fromEnv(env.ANTHROPIC_AUTH_TOKEN) : void 0);
77
- if (token === void 0) throw new Error(env.ANTHROPIC_AUTH_TOKEN ? "ANTHROPIC_AUTH_TOKEN is set, but the resolved base URL does not name a GLM host, so it is not a GLM Coding Plan token — set ZAI_AUTH_TOKEN (or --auth-token)" : "no API key: set ZAI_AUTH_TOKEN (or --auth-token)");
39
+ function bold(text) {
78
40
  return {
79
- token,
80
- url: base.origin
41
+ text,
42
+ ink: "bold"
81
43
  };
82
44
  }
83
- //#endregion
84
- //#region ../tokens/dist/text.js
85
- /**
86
- * Plain-text rendering primitives for the fixed-width reports. Output targets
87
- * a monospace terminal — the pane — so everything here is fixed-width:
88
- * padding, block-glyph bars, and compact number formatting.
89
- */
90
45
  var MONTHS = [
91
46
  "Jan",
92
47
  "Feb",
@@ -155,16 +110,18 @@ function meter(pct, width) {
155
110
  filled = Math.max(0, Math.min(width, filled));
156
111
  return "█".repeat(filled) + "░".repeat(width - filled);
157
112
  }
113
+ function renderLines(lines) {
114
+ return lines.map((line) => line.map((seg) => seg.text).join("")).join("\n");
115
+ }
158
116
  //#endregion
159
117
  //#region src/format.ts
160
118
  /**
161
- * Plain-text usage report renderer.
162
- *
163
- * Rendered for a monospace terminal / hook-block `reason`, so it must NOT rely on
164
- * markdown (bold, tables, fenced code). Alignment comes from fixed-width columns
165
- * and unicode block glyphs. Input is the parsed `data` of the three ZAI/ZHIPU
166
- * monitor endpoints (model-usage, tool-usage, quota/limit — the last already
167
- * passed through processQuotaLimit).
119
+ * Usage-report line model: `reportLines` lays the report out once as ink-tagged
120
+ * segments (the pane draws those), and `render` joins them into the CLI's
121
+ * monospace terminal / hook-block `reason` bytes — no markdown; alignment comes
122
+ * from fixed-width columns and unicode block glyphs. Input is the parsed `data`
123
+ * of the three ZAI/ZHIPU monitor endpoints (model-usage, tool-usage,
124
+ * quota/limit — the last already passed through processQuotaLimit).
168
125
  */
169
126
  var VBLOCKS = "▁▂▃▄▅▆▇";
170
127
  function parseSlot(s) {
@@ -337,7 +294,7 @@ function hourlyVerticalChart({ x, tok, nh, maxTok, peakIdx, slotFn }) {
337
294
  if (mark.some((c) => c !== " ")) lines.push(mark.join("").trimEnd());
338
295
  return lines;
339
296
  }
340
- function render({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetMin, now = /* @__PURE__ */ new Date() }) {
297
+ function reportLines({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetMin, now = /* @__PURE__ */ new Date() }) {
341
298
  const out = [];
342
299
  const toOffset = localOffsetMin != null ? localOffsetMin : -(/* @__PURE__ */ new Date()).getTimezoneOffset();
343
300
  function convertSlot(s) {
@@ -364,14 +321,16 @@ function render({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetM
364
321
  const firstSlot = convertSlot(x[0]);
365
322
  const lastSlot = convertSlot(x[nh - 1]);
366
323
  const win = firstSlot && lastSlot ? `${firstSlot.day} ${firstSlot.time} → ${lastSlot.day} ${lastSlot.time} · ${nh}h` : platform;
367
- out.push(rule());
368
- out.push(left + padL(win, 68 - left.length));
369
- out.push(rule());
370
- let lead = ` ${fmtTokens(total)} tokens across ${fmtNum(totalCalls)} model calls`;
371
- if (peakIdx >= 0 && peakSlot) lead += ` — ${Math.round(pctPeak)}% of it in a single hour (${peakSlot.day} ${peakSlot.time}, ${fmtTokens(peakTok)} tokens / ${fmtNum(peakCalls)} calls)`;
372
- lead += ".";
373
- out.push("");
374
- out.push(lead);
324
+ out.push([plain(rule())]);
325
+ out.push([bold(left), dim(padL(win, 68 - left.length))]);
326
+ out.push([plain(rule())]);
327
+ const head = plain(` ${fmtTokens(total)} tokens across ${fmtNum(totalCalls)} model calls`);
328
+ out.push([]);
329
+ if (peakIdx >= 0 && peakSlot) {
330
+ out.push([head, plain(" — ")]);
331
+ out.push([bold(`${Math.round(pctPeak)}%`), plain(" of it in a single hour")]);
332
+ out.push([plain(` (${peakSlot.day} ${peakSlot.time}, ${fmtTokens(peakTok)} tokens / ${fmtNum(peakCalls)} calls).`)]);
333
+ } else out.push([head, plain(".")]);
375
334
  const activeHours = tok.filter((t) => t > 0).length;
376
335
  const longest = idleRuns(tok).filter((r) => r.len >= 2).sort((a, b) => b.len - a.len).at(0);
377
336
  const tt = tool.totalUsage ?? {};
@@ -387,13 +346,17 @@ function render({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetM
387
346
  }
388
347
  const peakWinPct = total > 0 ? peakWinTokens / total * 100 : 0;
389
348
  const peakWin = localPeakWindow(apiOffsetMin, toOffset);
390
- out.push("");
391
- out.push(` Peak ${padR(peakSlot ? peakSlot.day + " " + peakSlot.time : "—", 15)}${padL(fmtTokens(peakTok), 7)} tokens · ${padL(fmtNum(peakCalls), 5)} calls`);
392
- out.push(` Active ${padR(`${activeHours} / ${nh} hours`, 15)}${longest ? idleLabel(longest, x, convertSlot) : "no idle gaps"}`);
393
- out.push(` Tools ${padR(`${toolTotal} calls`, 15)}${searchN} searches · ${readN} reads${zreadN ? ` · ${zreadN} zread` : ""}`);
394
- if (peakWin) out.push(` Peak hrs Mon–Fri ${peakWin.start}–${peakWin.end} · GLM-5.2 3× · ${peakWinActive}h active · ${fmtTokens(peakWinTokens)} (${Math.round(peakWinPct)}%)`);
395
- out.push("");
396
- out.push(" Hourly tokens · ↑ peak hour " + "─".repeat(Math.max(0, 39)));
349
+ out.push([]);
350
+ out.push([plain(` Peak ${padR(peakSlot ? peakSlot.day + " " + peakSlot.time : "—", 15)}${padL(fmtTokens(peakTok), 7)} tokens · ${padL(fmtNum(peakCalls), 5)} calls`)]);
351
+ out.push([plain(` Active ${padR(`${activeHours} / ${nh} hours`, 15)}${longest ? idleLabel(longest, x, convertSlot) : "no idle gaps"}`)]);
352
+ out.push([plain(` Tools ${padR(`${toolTotal} calls`, 15)}${searchN} searches · ${readN} reads${zreadN ? ` · ${zreadN} zread` : ""}`)]);
353
+ if (peakWin) out.push([
354
+ plain(` Peak hrs Mon–Fri ${peakWin.start}–${peakWin.end} · GLM-5.2 3× · ${peakWinActive}h active · ${fmtTokens(peakWinTokens)} (`),
355
+ bold(`${Math.round(peakWinPct)}%`),
356
+ plain(")")
357
+ ]);
358
+ out.push([]);
359
+ out.push([dim(" Hourly tokens · ↑ peak hour " + "─".repeat(Math.max(0, 39)))]);
397
360
  const chartLines = hourlyVerticalChart({
398
361
  x,
399
362
  tok,
@@ -403,48 +366,126 @@ function render({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetM
403
366
  slotFn: convertSlot
404
367
  });
405
368
  if (chartLines) {
406
- out.push(...chartLines);
407
- if (peakIdx >= 0 && peakSlot) out.push(` ◂ peak ${peakSlot.day} ${peakSlot.time} ${fmtTokens(peakTok)} tokens · ${fmtNum(peakCalls)} calls`);
408
- } else out.push(" (not enough hourly data to chart)");
409
- out.push("");
410
- out.push(" Model mix " + "─".repeat(Math.max(0, 57)));
369
+ out.push(...chartLines.map((l) => [plain(l)]));
370
+ if (peakIdx >= 0 && peakSlot) out.push([plain(` ◂ peak ${peakSlot.day} ${peakSlot.time} ${fmtTokens(peakTok)} tokens · ${fmtNum(peakCalls)} calls`)]);
371
+ } else out.push([plain(" (not enough hourly data to chart)")]);
372
+ out.push([]);
373
+ out.push([dim(" Model mix " + "─".repeat(Math.max(0, 57)))]);
411
374
  const mixSrc = model.modelSummaryList ?? model.modelDataList ?? [];
412
375
  const denom = mixSrc.reduce((a, m) => a + (m.totalTokens ?? 0), 0) || 1;
413
376
  const mixSorted = [...mixSrc].sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
414
377
  for (const m of mixSorted) {
415
378
  const pct = (m.totalTokens ?? 0) / denom * 100;
416
- out.push(` ${padR(m.modelName || "?", 11)}${padL(fmtTokens(m.totalTokens), 8)} ${padL(pct.toFixed(1) + "%", 6)} ${barField(pct, 100, 20)}`);
379
+ out.push([
380
+ plain(` ${padR(m.modelName || "?", 11)}${padL(fmtTokens(m.totalTokens), 8)} `),
381
+ bold(padL(pct.toFixed(1) + "%", 6)),
382
+ plain(` ${barField(pct, 100, 20)}`)
383
+ ]);
417
384
  }
418
- out.push("");
419
- out.push(" Limits " + "─".repeat(Math.max(0, 60)));
385
+ out.push([]);
386
+ out.push([dim(" Limits " + "─".repeat(Math.max(0, 60)))]);
420
387
  const bj = new Date(now.getTime() + apiOffsetMin * 6e4);
421
388
  const bjDay = bj.getUTCDay();
422
389
  const elapsedMin = bjDay >= 1 && bjDay <= 5 ? Math.max(0, Math.min(240, (bj.getUTCHours() - 14) * 60 + bj.getUTCMinutes())) : 0;
423
- if (peakWin) out.push(` ${padR("Peak", 16)}${padL(peakWin.start, 5)} ${meter(Math.round(elapsedMin / 240 * 100), 22)} ${peakWin.end}`);
390
+ if (peakWin) out.push([dim(` ${padR("Peak", 16)}`), plain(`${padL(peakWin.start, 5)} ${meter(Math.round(elapsedMin / 240 * 100), 22)} ${peakWin.end}`)]);
424
391
  const limits = quota.limits ?? [];
425
392
  const mcp = limits.find((l) => /mcp/i.test(l.type ?? ""));
426
393
  const tok5 = limits.find((l) => /token/i.test(l.type ?? ""));
427
394
  if (tok5) {
428
395
  const reset = new Date(tok5.nextResetTime ?? "").toLocaleTimeString("en-IN");
429
- out.push(` ${padR("Tokens · 5h", 16)}${padL(`${tok5.percentage || 0}%`, 5)} ${meter(tok5.percentage, 22)} ${reset}`);
396
+ out.push([
397
+ dim(` ${padR("Tokens · 5h", 16)}`),
398
+ bold(padL(`${tok5.percentage || 0}%`, 5)),
399
+ plain(` ${meter(tok5.percentage, 22)} ${reset}`)
400
+ ]);
430
401
  }
431
- if (tok5 && mcp) out.push("");
402
+ if (tok5 && mcp) out.push([]);
432
403
  if (mcp) {
433
404
  const reset = new Date(mcp.nextResetTime ?? "").toLocaleString("en-IN");
434
- out.push(` ${padR("MCP · this month", 16)}${padL(`${mcp.percentage || 0}%`, 5)} ${meter(mcp.percentage, 22)} ${reset}`);
405
+ out.push([
406
+ dim(` ${padR("MCP · this month", 16)}`),
407
+ bold(padL(`${mcp.percentage || 0}%`, 5)),
408
+ plain(` ${meter(mcp.percentage, 22)} ${reset}`)
409
+ ]);
435
410
  const parts = (mcp.usageDetails ?? []).map((d) => `${friendlyTool(d.modelCode)} ${fmtNum(d.usage)}`).join(" ");
436
- if (parts) out.push(` ${fmtNum(mcp.currentUsage)}M / ${fmtNum(mcp.totol)}M · ${parts}`);
411
+ if (parts) out.push([plain(` ${fmtNum(mcp.currentUsage)}M / ${fmtNum(mcp.totol)}M · ${parts}`)]);
412
+ }
413
+ out.push([]);
414
+ out.push([plain(rule())]);
415
+ return out;
416
+ }
417
+ function render(input) {
418
+ return renderLines(reportLines(input));
419
+ }
420
+ //#endregion
421
+ //#region src/resolve.ts
422
+ var DEFAULT_BASE_URL = "https://api.z.ai";
423
+ var GLM_HOSTS = /* @__PURE__ */ new Set([
424
+ "api.z.ai",
425
+ "dev.bigmodel.cn",
426
+ "open.bigmodel.cn"
427
+ ]);
428
+ function fromEnv(value) {
429
+ return value ? value : void 0;
430
+ }
431
+ function originOf(value) {
432
+ try {
433
+ const url = new URL(value);
434
+ return url.protocol === "https:" || url.protocol === "http:" ? url.origin : void 0;
435
+ } catch {
436
+ return;
437
+ }
438
+ }
439
+ function isGlm(origin) {
440
+ return GLM_HOSTS.has(new URL(origin).hostname);
441
+ }
442
+ function resolveBaseUrl(flag, env) {
443
+ if (flag !== void 0) {
444
+ const origin = originOf(flag);
445
+ if (origin === void 0) throw new Error(`invalid base URL: ${flag}`);
446
+ return {
447
+ glm: isGlm(origin),
448
+ origin
449
+ };
437
450
  }
438
- out.push("");
439
- out.push(rule());
440
- return out.join("\n");
451
+ const inherited = fromEnv(env.ANTHROPIC_BASE_URL);
452
+ const origin = inherited === void 0 ? void 0 : originOf(inherited);
453
+ if (origin !== void 0 && isGlm(origin)) return {
454
+ glm: true,
455
+ origin
456
+ };
457
+ return {
458
+ glm: false,
459
+ origin: DEFAULT_BASE_URL
460
+ };
461
+ }
462
+ function buildProgram() {
463
+ return new Command().name("zai-usage").addOption(new Option("--auth-token <token>", "API key").env("ZAI_AUTH_TOKEN")).addOption(new Option("--base-url <url>", "base URL").env("ZAI_BASE_URL")).addOption(new Option("--json", "print the report lines as JSON for the zai mod"));
464
+ }
465
+ function parseArgs(args) {
466
+ const program = parseQuietly(buildProgram(), args);
467
+ if (program === void 0) return;
468
+ const { authToken, baseUrl, json } = program.opts();
469
+ return {
470
+ authToken: authToken || void 0,
471
+ baseUrl: baseUrl || void 0,
472
+ json: json === true
473
+ };
474
+ }
475
+ function resolveConfig(env, parsed) {
476
+ const base = resolveBaseUrl(parsed.baseUrl, env);
477
+ const token = parsed.authToken ?? (base.glm ? fromEnv(env.ANTHROPIC_AUTH_TOKEN) : void 0);
478
+ if (token === void 0) throw new Error(env.ANTHROPIC_AUTH_TOKEN ? "ANTHROPIC_AUTH_TOKEN is set, but the resolved base URL does not name a GLM host, so it is not a GLM Coding Plan token — set ZAI_AUTH_TOKEN (or --auth-token)" : "no API key: set ZAI_AUTH_TOKEN (or --auth-token)");
479
+ return {
480
+ token,
481
+ url: base.origin
482
+ };
441
483
  }
442
484
  //#endregion
443
485
  //#region src/usage.ts
444
486
  /**
445
- * Usage query. Fetches model usage, tool usage, and quota limits from the GLM
446
- * Coding Plan monitor API of the resolved base URL (paths are fixed) and
447
- * renders the plain-text report.
487
+ * Usage query: the three GLM monitor endpoints of the resolved base URL,
488
+ * assembled into the report input.
448
489
  */
449
490
  function queryParams(now = /* @__PURE__ */ new Date()) {
450
491
  const startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, now.getHours(), 0, 0, 0);
@@ -500,12 +541,12 @@ async function fetchReport(config) {
500
541
  fetchJson(`${config.url}/api/monitor/usage/tool-usage`, "Tool usage", config.token, query),
501
542
  fetchJson(`${config.url}/api/monitor/usage/quota/limit`, "Quota limit", config.token)
502
543
  ]);
503
- return render({
544
+ return {
504
545
  platform,
505
546
  model,
506
547
  tool,
507
548
  quota: processQuotaLimit(quotaRaw)
508
- });
549
+ };
509
550
  } catch (e) {
510
551
  throw new Error(`Request failed: ${e.message}`, { cause: e });
511
552
  }
@@ -513,12 +554,10 @@ async function fetchReport(config) {
513
554
  //#endregion
514
555
  //#region src/index.ts
515
556
  var parsed = parseArgs(process.argv.slice(2)) ?? printUsageAndExit(buildProgram());
516
- try {
517
- console.log(await fetchReport(resolveConfig(process.env, parsed)));
518
- } catch (e) {
519
- console.error(e.message);
520
- process.exit(1);
521
- }
557
+ await runMain(async () => {
558
+ const input = await fetchReport(resolveConfig(process.env, parsed));
559
+ console.log(parsed.json ? JSON.stringify({ lines: reportLines(input) }) : render(input));
560
+ });
522
561
  //#endregion
523
562
  export {};
524
563