fleetlens 1.0.7 → 1.0.8

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 (54) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/build-manifest.json +3 -3
  3. package/app/apps/web/.next/prerender-manifest.json +3 -3
  4. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  5. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  6. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/app/apps/web/.next/server/app/agent/page_client-reference-manifest.js +1 -1
  13. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  14. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  15. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  18. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  19. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  20. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  21. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  22. package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
  23. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  24. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  25. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  26. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  27. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  28. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  29. package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +1 -1
  30. package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
  31. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  32. package/app/apps/web/.next/server/chunks/[root-of-the-server]__02.51sr._.js +1 -1
  33. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0zyh554._.js +1 -1
  34. package/app/apps/web/.next/server/chunks/ssr/_0k8yioi._.js +1 -1
  35. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
  36. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
  37. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  38. package/app/apps/web/.next/server/middleware-manifest.json +5 -5
  39. package/app/apps/web/.next/server/pages/500.html +1 -1
  40. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  41. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  42. package/app/apps/web/.next/static/chunks/17s-kgtdv385l.js +1 -0
  43. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  44. package/app/apps/web/package.json +1 -1
  45. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  46. package/dist/daemon-worker.js +20 -10
  47. package/dist/index.js +2118 -1715
  48. package/menubar/Fleetlens.app/Contents/Info.plist +2 -2
  49. package/menubar/Fleetlens.app/Contents/MacOS/FleetlensMenubar +0 -0
  50. package/package.json +1 -1
  51. package/app/apps/web/.next/static/chunks/0pw75k-13ve.3.js +0 -1
  52. /package/app/apps/web/.next/static/{rSw7StGuB4J2DfY6_pElg → qeUfJEEwWX-_kBAEMzh_M}/_buildManifest.js +0 -0
  53. /package/app/apps/web/.next/static/{rSw7StGuB4J2DfY6_pElg → qeUfJEEwWX-_kBAEMzh_M}/_clientMiddlewareManifest.js +0 -0
  54. /package/app/apps/web/.next/static/{rSw7StGuB4J2DfY6_pElg → qeUfJEEwWX-_kBAEMzh_M}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -6298,9 +6298,9 @@ function onDiskVersion() {
6298
6298
  try {
6299
6299
  const here = dirname3(fileURLToPath(import.meta.url));
6300
6300
  const pkg = JSON.parse(readFileSync5(join3(here, "..", "package.json"), "utf8"));
6301
- return pkg.version ?? "1.0.7";
6301
+ return pkg.version ?? "1.0.8";
6302
6302
  } catch {
6303
- return "1.0.7";
6303
+ return "1.0.8";
6304
6304
  }
6305
6305
  }
6306
6306
  function getServerStatus() {
@@ -6312,7 +6312,7 @@ function getServerStatus() {
6312
6312
  return { running: false };
6313
6313
  }
6314
6314
  function isServerStale(status2) {
6315
- return status2.running === true && status2.version !== "1.0.7";
6315
+ return status2.running === true && status2.version !== "1.0.8";
6316
6316
  }
6317
6317
  async function ensureCurrentServer(opts = {}) {
6318
6318
  const status2 = getServerStatus();
@@ -6457,10 +6457,21 @@ __export(storage_exports, {
6457
6457
  appendSnapshot: () => appendSnapshot,
6458
6458
  latestClaudeCodeSnapshot: () => latestClaudeCodeSnapshot,
6459
6459
  latestSnapshot: () => latestSnapshot,
6460
+ latestSnapshotsByAgent: () => latestSnapshotsByAgent,
6460
6461
  pruneAgent: () => pruneAgent,
6461
6462
  readSnapshots: () => readSnapshots
6462
6463
  });
6463
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "node:fs";
6464
+ import {
6465
+ appendFileSync as appendFileSync2,
6466
+ mkdirSync as mkdirSync4,
6467
+ readFileSync as readFileSync6,
6468
+ writeFileSync as writeFileSync4,
6469
+ existsSync as existsSync4,
6470
+ openSync,
6471
+ readSync,
6472
+ fstatSync,
6473
+ closeSync
6474
+ } from "node:fs";
6464
6475
  import { dirname as dirname4 } from "node:path";
6465
6476
  function appendSnapshot(filePath, snapshot) {
6466
6477
  mkdirSync4(dirname4(filePath), { recursive: true });
@@ -6504,6 +6515,50 @@ function latestClaudeCodeSnapshot(filePath) {
6504
6515
  }
6505
6516
  return null;
6506
6517
  }
6518
+ function latestSnapshotsByAgent(filePath, opts = {}) {
6519
+ if (!existsSync4(filePath)) return {};
6520
+ const tailBytes = opts.tailBytes ?? 262144;
6521
+ const raw = readTail(filePath, tailBytes);
6522
+ const byAgent = {};
6523
+ const lines = raw.split("\n");
6524
+ for (let i = lines.length - 1; i >= 0; i--) {
6525
+ const line = lines[i].trim();
6526
+ if (!line) continue;
6527
+ try {
6528
+ const s = JSON.parse(line);
6529
+ const agent = s.agent ?? "claude-code";
6530
+ if (byAgent[agent] === void 0) byAgent[agent] = s;
6531
+ } catch {
6532
+ }
6533
+ }
6534
+ return byAgent;
6535
+ }
6536
+ function readTail(filePath, maxBytes) {
6537
+ let fd;
6538
+ try {
6539
+ fd = openSync(filePath, "r");
6540
+ const size = fstatSync(fd).size;
6541
+ if (size <= 0) return "";
6542
+ const start2 = Math.max(0, size - maxBytes);
6543
+ const len = size - start2;
6544
+ const buf = Buffer.alloc(len);
6545
+ readSync(fd, buf, 0, len, start2);
6546
+ let text = buf.toString("utf8");
6547
+ if (start2 > 0) {
6548
+ const nl = text.indexOf("\n");
6549
+ if (nl !== -1) text = text.slice(nl + 1);
6550
+ }
6551
+ return text;
6552
+ } catch {
6553
+ try {
6554
+ return readFileSync6(filePath, "utf8");
6555
+ } catch {
6556
+ return "";
6557
+ }
6558
+ } finally {
6559
+ if (fd !== void 0) closeSync(fd);
6560
+ }
6561
+ }
6507
6562
  var init_storage = __esm({
6508
6563
  "src/usage/storage.ts"() {
6509
6564
  "use strict";
@@ -6773,9 +6828,9 @@ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath
6773
6828
  console.warn(
6774
6829
  ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
6775
6830
  );
6776
- } else if (verify.installedVersion !== "1.0.7") {
6831
+ } else if (verify.installedVersion !== "1.0.8") {
6777
6832
  console.log(
6778
- ` \u2192 This process is still running ${"1.0.7"}. Next invocation will use ${verify.installedVersion}.`
6833
+ ` \u2192 This process is still running ${"1.0.8"}. Next invocation will use ${verify.installedVersion}.`
6779
6834
  );
6780
6835
  }
6781
6836
  }
@@ -6810,14 +6865,14 @@ async function getUpdateAvailability() {
6810
6865
  if (isDevMode()) return null;
6811
6866
  const latest = await fetchLatestVersion();
6812
6867
  if (latest === null) return {
6813
- current: "1.0.7",
6868
+ current: "1.0.8",
6814
6869
  latest: null,
6815
6870
  updateAvailable: false
6816
6871
  };
6817
6872
  return {
6818
- current: "1.0.7",
6873
+ current: "1.0.8",
6819
6874
  latest,
6820
- updateAvailable: shouldUpdate("1.0.7", latest)
6875
+ updateAvailable: shouldUpdate("1.0.8", latest)
6821
6876
  };
6822
6877
  }
6823
6878
  async function checkForUpdate() {
@@ -6907,7 +6962,7 @@ async function restartServices(state) {
6907
6962
  }
6908
6963
  async function forceUpdate() {
6909
6964
  const latest = await fetchLatestVersion();
6910
- const current = "1.0.7";
6965
+ const current = "1.0.8";
6911
6966
  if (latest === null) {
6912
6967
  console.error("Could not reach npm registry. Check your network.");
6913
6968
  process.exit(1);
@@ -7328,7 +7383,7 @@ async function status() {
7328
7383
  }
7329
7384
  if (isServerStale(server)) {
7330
7385
  console.log(
7331
- ` \u26A0 serving stale ${ver} (CLI is ${"1.0.7"}) \u2014 run 'fleetlens start' to restart on the current version`
7386
+ ` \u26A0 serving stale ${ver} (CLI is ${"1.0.8"}) \u2014 run 'fleetlens start' to restart on the current version`
7332
7387
  );
7333
7388
  }
7334
7389
  } else {
@@ -7408,368 +7463,642 @@ var init_update = __esm({
7408
7463
  }
7409
7464
  });
7410
7465
 
7411
- // src/usage/token.ts
7412
- import { execFileSync as execFileSync2 } from "node:child_process";
7413
- import { readFileSync as readFileSync10 } from "node:fs";
7414
- import { homedir as homedir4, platform } from "node:os";
7415
- import { join as join7 } from "node:path";
7416
- function readOAuthCredentials() {
7417
- if (platform() === "darwin") {
7418
- return readFromMacKeychain() ?? readFromCredentialsFile();
7419
- }
7420
- return readFromCredentialsFile();
7421
- }
7422
- function readFromMacKeychain() {
7423
- try {
7424
- const blob = execFileSync2(
7425
- "security",
7426
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
7427
- { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }
7428
- );
7429
- return extractCredentials(blob);
7430
- } catch {
7431
- return null;
7432
- }
7433
- }
7434
- function readFromCredentialsFile() {
7435
- const candidates = [
7436
- join7(homedir4(), ".claude", ".credentials.json"),
7437
- join7(homedir4(), ".config", "claude", "credentials.json")
7438
- ];
7439
- for (const path11 of candidates) {
7440
- try {
7441
- const blob = readFileSync10(path11, "utf8");
7442
- const creds = extractCredentials(blob);
7443
- if (creds) return creds;
7444
- } catch {
7445
- }
7446
- }
7447
- return null;
7448
- }
7449
- function extractCredentials(blob) {
7450
- try {
7451
- const parsed = JSON.parse(blob);
7452
- const oauth = parsed.claudeAiOauth;
7453
- if (!oauth) return null;
7454
- const accessToken = typeof oauth.accessToken === "string" ? oauth.accessToken : null;
7455
- const expiresAt = typeof oauth.expiresAt === "number" ? oauth.expiresAt : null;
7456
- if (!accessToken || expiresAt === null) return null;
7457
- return { accessToken, expiresAt };
7458
- } catch {
7459
- return null;
7460
- }
7461
- }
7462
- var init_token = __esm({
7463
- "src/usage/token.ts"() {
7466
+ // ../parser/dist/types.js
7467
+ var init_types = __esm({
7468
+ "../parser/dist/types.js"() {
7464
7469
  "use strict";
7465
7470
  }
7466
7471
  });
7467
7472
 
7468
- // src/usage/api.ts
7469
- var api_exports = {};
7470
- __export(api_exports, {
7471
- UsageApiError: () => UsageApiError,
7472
- fetchUsage: () => fetchUsage
7473
- });
7474
- async function fetchUsage() {
7475
- const creds = readOAuthCredentials();
7476
- if (!creds) {
7477
- throw new UsageApiError(
7478
- "No Claude Code OAuth token found. Run `claude` to log in first.",
7479
- "no_token"
7480
- );
7481
- }
7482
- if (creds.expiresAt - EXPIRY_SKEW_MS <= Date.now()) {
7483
- throw new UsageApiError(
7484
- "Claude Code OAuth token expired. Open Claude Code to refresh it.",
7485
- "expired"
7486
- );
7487
- }
7488
- let res;
7489
- try {
7490
- res = await fetch(USAGE_ENDPOINT, {
7491
- headers: {
7492
- Authorization: `Bearer ${creds.accessToken}`,
7493
- "anthropic-beta": BETA_HEADER
7473
+ // ../parser/dist/presentation.js
7474
+ function rowPrimaryIndex(r) {
7475
+ if (r.kind === "tool-group")
7476
+ return r.events[0].index;
7477
+ return r.event.index;
7478
+ }
7479
+ function buildMegaRows(rows) {
7480
+ const out = [];
7481
+ let buffer = [];
7482
+ let turnAnchor = null;
7483
+ const flush = () => {
7484
+ if (buffer.length === 0)
7485
+ return;
7486
+ let agentMessages = 0;
7487
+ let toolCalls = 0;
7488
+ let errors = 0;
7489
+ let firstAgentIndex;
7490
+ const toolMap = /* @__PURE__ */ new Map();
7491
+ const seenMsgIds = /* @__PURE__ */ new Set();
7492
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
7493
+ for (let i = 0; i < buffer.length; i++) {
7494
+ const r = buffer[i];
7495
+ if (r.kind === "agent") {
7496
+ agentMessages++;
7497
+ if (firstAgentIndex === void 0)
7498
+ firstAgentIndex = i;
7499
+ const msgId = r.event.messageId;
7500
+ const usage2 = r.event.usage;
7501
+ if (msgId && !seenMsgIds.has(msgId) && usage2) {
7502
+ seenMsgIds.add(msgId);
7503
+ tokens.input += usage2.input;
7504
+ tokens.output += usage2.output;
7505
+ tokens.cacheRead += usage2.cacheRead;
7506
+ tokens.cacheWrite += usage2.cacheWrite;
7507
+ }
7508
+ } else if (r.kind === "tool-group") {
7509
+ toolCalls += r.count;
7510
+ for (const t of r.toolNames) {
7511
+ toolMap.set(t.name, (toolMap.get(t.name) ?? 0) + t.count);
7512
+ }
7513
+ } else if (r.kind === "error") {
7514
+ errors++;
7515
+ }
7516
+ }
7517
+ let finalAgentIndex;
7518
+ for (let i = buffer.length - 1; i >= 0; i--) {
7519
+ const r = buffer[i];
7520
+ if (r.kind !== "agent")
7521
+ continue;
7522
+ const prev = buffer[i - 1];
7523
+ if (prev?.kind === "task-notification")
7524
+ continue;
7525
+ finalAgentIndex = i;
7526
+ break;
7527
+ }
7528
+ if (finalAgentIndex === void 0) {
7529
+ for (let i = buffer.length - 1; i >= 0; i--) {
7530
+ if (buffer[i].kind === "agent") {
7531
+ finalAgentIndex = i;
7532
+ break;
7533
+ }
7534
+ }
7535
+ }
7536
+ const firstAgentPreview = firstAgentIndex !== void 0 && buffer[firstAgentIndex].kind === "agent" ? buffer[firstAgentIndex].event.preview : void 0;
7537
+ const finalAgentPreview = finalAgentIndex !== void 0 && buffer[finalAgentIndex].kind === "agent" ? buffer[finalAgentIndex].event.preview : void 0;
7538
+ const toolNames = Array.from(toolMap.entries()).sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
7539
+ const first = buffer[0];
7540
+ const last = buffer[buffer.length - 1];
7541
+ const anchorOffset = turnAnchor?.tOffsetMs;
7542
+ const start2 = anchorOffset ?? first.tOffsetMs;
7543
+ const end = last.tOffsetMs;
7544
+ const durationMs = start2 !== void 0 && end !== void 0 && end >= start2 ? end - start2 : void 0;
7545
+ out.push({
7546
+ kind: "turn",
7547
+ rows: buffer,
7548
+ firstPrimaryIndex: rowPrimaryIndex(first),
7549
+ tOffsetMs: start2,
7550
+ durationMs,
7551
+ summary: {
7552
+ agentMessages,
7553
+ toolCalls,
7554
+ errors,
7555
+ firstAgentPreview,
7556
+ finalAgentPreview,
7557
+ firstAgentIndex,
7558
+ finalAgentIndex,
7559
+ toolNames,
7560
+ totalTokens: tokens
7494
7561
  }
7495
7562
  });
7496
- } catch (err) {
7497
- throw new UsageApiError(
7498
- `Network error reaching Anthropic: ${err.message}`,
7499
- "network"
7500
- );
7501
- }
7502
- if (!res.ok) {
7503
- throw new UsageApiError(
7504
- `Usage endpoint returned ${res.status} ${res.statusText}`,
7505
- "http"
7506
- );
7507
- }
7508
- let body;
7509
- try {
7510
- body = await res.json();
7511
- } catch (err) {
7512
- throw new UsageApiError(`Failed to parse usage response: ${err.message}`, "parse");
7513
- }
7514
- const b = body;
7515
- return {
7516
- captured_at: (/* @__PURE__ */ new Date()).toISOString(),
7517
- agent: "claude-code",
7518
- five_hour: normalizeWindow(b.five_hour) ?? { utilization: null, resets_at: null },
7519
- seven_day: normalizeWindow(b.seven_day) ?? { utilization: null, resets_at: null },
7520
- seven_day_opus: normalizeWindow(b.seven_day_opus),
7521
- seven_day_sonnet: normalizeWindow(b.seven_day_sonnet),
7522
- seven_day_oauth_apps: normalizeWindow(b.seven_day_oauth_apps),
7523
- seven_day_cowork: normalizeWindow(b.seven_day_cowork),
7524
- extra_usage: normalizeExtra(b.extra_usage),
7525
- plan_type: null
7563
+ buffer = [];
7526
7564
  };
7565
+ for (const r of rows) {
7566
+ if (r.kind === "user" || r.kind === "interrupt") {
7567
+ flush();
7568
+ out.push(r);
7569
+ turnAnchor = r;
7570
+ } else {
7571
+ buffer.push(r);
7572
+ }
7573
+ }
7574
+ flush();
7575
+ return out;
7527
7576
  }
7528
- function normalizeWindow(raw) {
7529
- if (!raw || typeof raw !== "object") return null;
7530
- const r = raw;
7577
+ function parseTaskNotification(text) {
7578
+ if (!TASK_NOTIFICATION_RE.test(text))
7579
+ return null;
7580
+ const pick = (tag) => {
7581
+ const m = text.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
7582
+ return m ? m[1].trim() : void 0;
7583
+ };
7584
+ const statusRaw = (pick("status") ?? "").toLowerCase();
7585
+ let status2 = "unknown";
7586
+ if (statusRaw === "success" || statusRaw === "ok" || statusRaw === "done" || statusRaw === "completed")
7587
+ status2 = "success";
7588
+ else if (statusRaw === "failed" || statusRaw === "error" || statusRaw === "fail")
7589
+ status2 = "failed";
7590
+ else if (statusRaw === "running" || statusRaw === "in_progress")
7591
+ status2 = "running";
7531
7592
  return {
7532
- utilization: typeof r.utilization === "number" ? r.utilization : null,
7533
- resets_at: typeof r.resets_at === "string" ? r.resets_at : null
7593
+ status: status2,
7594
+ summary: pick("summary") ?? "Background task notification",
7595
+ taskId: pick("task-id"),
7596
+ toolUseId: pick("tool-use-id"),
7597
+ outputFile: pick("output-file")
7534
7598
  };
7535
7599
  }
7536
- function normalizeExtra(raw) {
7537
- if (!raw || typeof raw !== "object") return null;
7538
- const r = raw;
7539
- return {
7540
- is_enabled: r.is_enabled === true,
7541
- monthly_limit: typeof r.monthly_limit === "number" ? r.monthly_limit : null,
7542
- used_credits: typeof r.used_credits === "number" ? r.used_credits : null,
7543
- utilization: typeof r.utilization === "number" ? r.utilization : null
7544
- };
7600
+ function isAttachmentOrMeta(e) {
7601
+ return e.role === "system" || e.role === "meta";
7545
7602
  }
7546
- var EXPIRY_SKEW_MS, USAGE_ENDPOINT, BETA_HEADER, UsageApiError;
7547
- var init_api = __esm({
7548
- "src/usage/api.ts"() {
7549
- "use strict";
7550
- init_token();
7551
- EXPIRY_SKEW_MS = 60 * 1e3;
7552
- USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
7553
- BETA_HEADER = "oauth-2025-04-20";
7554
- UsageApiError = class extends Error {
7555
- constructor(message, code) {
7556
- super(message);
7557
- this.code = code;
7558
- }
7559
- };
7603
+ function firstTextOfBlocks(blocks) {
7604
+ for (const b of blocks) {
7605
+ if (b?.type === "text")
7606
+ return b.text;
7560
7607
  }
7561
- });
7562
-
7563
- // src/usage/grok.ts
7564
- import { existsSync as existsSync8, readFileSync as readFileSync11 } from "node:fs";
7565
- import os9 from "node:os";
7566
- import path9 from "node:path";
7567
- function grokHome() {
7568
- return process.env.GROK_HOME || path9.join(os9.homedir(), ".grok");
7608
+ return void 0;
7569
7609
  }
7570
- function authPath() {
7571
- return path9.join(grokHome(), "auth.json");
7610
+ function hasZeroUsage(e) {
7611
+ if (!e.usage)
7612
+ return false;
7613
+ const u = e.usage;
7614
+ return u.input === 0 && u.output === 0 && u.cacheRead === 0 && u.cacheWrite === 0;
7572
7615
  }
7573
- function loadAuthEntry() {
7574
- const p = authPath();
7575
- if (!existsSync8(p)) {
7576
- throw new GrokApiError("Grok not logged in. Run `grok login`.", "no_auth");
7577
- }
7578
- let raw;
7579
- try {
7580
- raw = JSON.parse(readFileSync11(p, "utf8"));
7581
- } catch {
7582
- throw new GrokApiError("Grok auth.json is unreadable. Run `grok login`.", "no_auth");
7583
- }
7584
- for (const [entryKey2, entry] of Object.entries(raw)) {
7585
- const token = typeof entry?.key === "string" ? entry.key.trim() : "";
7586
- if (token) return { token, entry, entryKey: entryKey2 };
7616
+ function buildPresentation(events) {
7617
+ const byMessageId = /* @__PURE__ */ new Map();
7618
+ for (const e of events) {
7619
+ if (!e.messageId)
7620
+ continue;
7621
+ const list = byMessageId.get(e.messageId) ?? [];
7622
+ list.push(e);
7623
+ byMessageId.set(e.messageId, list);
7587
7624
  }
7588
- throw new GrokApiError("Grok auth has no access token. Run `grok login`.", "no_auth");
7589
- }
7590
- function needsRefresh(entry) {
7591
- const exp = entry.expires_at;
7592
- if (!exp) return false;
7593
- const ms = Date.parse(exp);
7594
- if (!Number.isFinite(ms)) return false;
7595
- return Date.now() >= ms - REFRESH_BUFFER_MS;
7596
- }
7597
- async function refreshAccessToken(entry) {
7598
- const refresh = entry.refresh_token?.trim();
7599
- if (!refresh) return null;
7600
- const clientId = entry.oidc_client_id?.trim() || DEFAULT_CLIENT_ID;
7601
- const body = new URLSearchParams({
7602
- grant_type: "refresh_token",
7603
- client_id: clientId,
7604
- refresh_token: refresh
7625
+ const visible = events.filter((e) => {
7626
+ if (isAttachmentOrMeta(e))
7627
+ return false;
7628
+ if (e.role === "tool-result")
7629
+ return false;
7630
+ if (e.role === "agent-thinking")
7631
+ return false;
7632
+ if (e.role === "user") {
7633
+ const txt = firstTextOfBlocks(e.blocks) ?? "";
7634
+ if (e.blocks.length === 0 || !txt.trim())
7635
+ return false;
7636
+ if (SLASH_COMMAND_RE.test(txt))
7637
+ return true;
7638
+ if (TASK_NOTIFICATION_RE.test(txt))
7639
+ return true;
7640
+ if (isFrameworkInjectedUserInput(txt))
7641
+ return false;
7642
+ }
7643
+ return true;
7605
7644
  });
7606
- let res;
7607
- try {
7608
- res = await fetch(REFRESH_URL, {
7609
- method: "POST",
7610
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
7611
- body: body.toString()
7612
- });
7613
- } catch {
7614
- return null;
7615
- }
7616
- if (!res.ok) return null;
7617
- try {
7618
- const j = await res.json();
7619
- if (typeof j.access_token !== "string" || !j.access_token) return null;
7620
- try {
7621
- const p = authPath();
7622
- const raw = JSON.parse(readFileSync11(p, "utf8"));
7623
- for (const key of Object.keys(raw)) {
7624
- if (raw[key]?.key === entry.key || raw[key]?.refresh_token === refresh) {
7625
- raw[key] = {
7626
- ...raw[key],
7627
- key: j.access_token,
7628
- refresh_token: j.refresh_token ?? raw[key].refresh_token,
7629
- expires_at: typeof j.expires_in === "number" ? new Date(Date.now() + j.expires_in * 1e3).toISOString() : raw[key].expires_at
7630
- };
7631
- break;
7645
+ const rows = [];
7646
+ for (let i = 0; i < visible.length; i++) {
7647
+ const e = visible[i];
7648
+ if (e.role === "tool-call") {
7649
+ const start2 = i;
7650
+ while (i + 1 < visible.length && visible[i + 1].role === "tool-call") {
7651
+ i++;
7652
+ }
7653
+ const group = visible.slice(start2, i + 1);
7654
+ const order = [];
7655
+ const counts = /* @__PURE__ */ new Map();
7656
+ for (const ev of group) {
7657
+ const name = ev.toolName ?? "tool";
7658
+ if (!counts.has(name))
7659
+ order.push(name);
7660
+ counts.set(name, (counts.get(name) ?? 0) + 1);
7661
+ }
7662
+ const toolNames = order.map((name) => ({
7663
+ name,
7664
+ count: counts.get(name) ?? 1
7665
+ }));
7666
+ rows.push({
7667
+ kind: "tool-group",
7668
+ toolNames,
7669
+ count: group.length,
7670
+ events: group,
7671
+ tOffsetMs: group[0].tOffsetMs,
7672
+ gapMs: group[0].gapMs
7673
+ });
7674
+ continue;
7675
+ }
7676
+ if (e.role === "user") {
7677
+ const txt = firstTextOfBlocks(e.blocks) ?? "";
7678
+ const taskNotif = parseTaskNotification(txt);
7679
+ if (taskNotif) {
7680
+ rows.push({
7681
+ kind: "task-notification",
7682
+ event: e,
7683
+ status: taskNotif.status,
7684
+ summary: taskNotif.summary,
7685
+ taskId: taskNotif.taskId,
7686
+ toolUseId: taskNotif.toolUseId,
7687
+ outputFile: taskNotif.outputFile,
7688
+ tOffsetMs: e.tOffsetMs,
7689
+ gapMs: e.gapMs
7690
+ });
7691
+ continue;
7692
+ }
7693
+ if (INTERRUPT_RE.test(txt)) {
7694
+ rows.push({
7695
+ kind: "interrupt",
7696
+ event: e,
7697
+ tOffsetMs: e.tOffsetMs,
7698
+ gapMs: e.gapMs
7699
+ });
7700
+ continue;
7701
+ }
7702
+ const slashMatch = txt.match(SLASH_COMMAND_RE);
7703
+ if (slashMatch) {
7704
+ const cmd = slashMatch[1];
7705
+ const args2 = (slashMatch[2] ?? "").trim();
7706
+ const pretty = args2 ? `${cmd} ${args2}` : cmd;
7707
+ rows.push({
7708
+ kind: "user",
7709
+ event: e,
7710
+ displayPreview: pretty,
7711
+ displayBlocks: [{ type: "text", text: pretty }],
7712
+ tOffsetMs: e.tOffsetMs,
7713
+ gapMs: e.gapMs
7714
+ });
7715
+ continue;
7716
+ }
7717
+ rows.push({
7718
+ kind: "user",
7719
+ event: e,
7720
+ tOffsetMs: e.tOffsetMs,
7721
+ gapMs: e.gapMs
7722
+ });
7723
+ continue;
7724
+ }
7725
+ if (e.role === "agent") {
7726
+ const txt = firstTextOfBlocks(e.blocks) ?? "";
7727
+ if (hasZeroUsage(e)) {
7728
+ rows.push({
7729
+ kind: "model",
7730
+ event: e,
7731
+ tOffsetMs: e.tOffsetMs,
7732
+ gapMs: e.gapMs
7733
+ });
7734
+ if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
7735
+ rows.push({
7736
+ kind: "error",
7737
+ event: e,
7738
+ message: txt,
7739
+ tOffsetMs: e.tOffsetMs,
7740
+ gapMs: 0
7741
+ });
7632
7742
  }
7743
+ continue;
7633
7744
  }
7634
- const { writeFileSync: writeFileSync18 } = await import("node:fs");
7635
- writeFileSync18(p, JSON.stringify(raw, null, 2), "utf8");
7636
- } catch {
7745
+ if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
7746
+ rows.push({
7747
+ kind: "error",
7748
+ event: e,
7749
+ message: txt,
7750
+ tOffsetMs: e.tOffsetMs,
7751
+ gapMs: e.gapMs
7752
+ });
7753
+ continue;
7754
+ }
7755
+ const grouped = e.messageId ? byMessageId.get(e.messageId) ?? [e] : [e];
7756
+ rows.push({
7757
+ kind: "agent",
7758
+ event: e,
7759
+ groupedEvents: grouped,
7760
+ tOffsetMs: e.tOffsetMs,
7761
+ gapMs: e.gapMs
7762
+ });
7763
+ continue;
7637
7764
  }
7638
- return j.access_token;
7639
- } catch {
7640
- return null;
7641
7765
  }
7766
+ return rows;
7642
7767
  }
7643
- function authHeaders(token) {
7644
- return {
7645
- Authorization: `Bearer ${token.trim()}`,
7646
- "X-XAI-Token-Auth": TOKEN_AUTH,
7647
- Accept: "application/json",
7648
- "User-Agent": "fleetlens"
7649
- };
7768
+ var INTERRUPT_RE, RATE_LIMIT_RE, SLASH_COMMAND_RE, TASK_NOTIFICATION_RE;
7769
+ var init_presentation = __esm({
7770
+ "../parser/dist/presentation.js"() {
7771
+ "use strict";
7772
+ init_user_input();
7773
+ INTERRUPT_RE = /\[request interrupted|interrupted by user/i;
7774
+ RATE_LIMIT_RE = /rate.?limit|overloaded_error|api error|Error sending message/i;
7775
+ SLASH_COMMAND_RE = /<command-name>(\/[^<\s]+)<\/command-name>(?:[\s\S]*?<command-args>([\s\S]*?)<\/command-args>)?/;
7776
+ TASK_NOTIFICATION_RE = /<task-notification>/;
7777
+ }
7778
+ });
7779
+
7780
+ // ../parser/dist/aggregate.js
7781
+ function isoDay(d) {
7782
+ return toLocalDay(d.getTime());
7650
7783
  }
7651
- function parseGrokCreditsConfig(body) {
7652
- if (!body || typeof body !== "object") {
7653
- throw new GrokApiError("Grok billing response missing body", "parse");
7784
+ function rangeDays(start2, end) {
7785
+ const out = [];
7786
+ const cur = new Date(start2.getFullYear(), start2.getMonth(), start2.getDate());
7787
+ const stop2 = new Date(end.getFullYear(), end.getMonth(), end.getDate());
7788
+ while (cur.getTime() <= stop2.getTime()) {
7789
+ out.push(new Date(cur));
7790
+ cur.setDate(cur.getDate() + 1);
7654
7791
  }
7655
- const config = body.config;
7656
- if (!config || typeof config !== "object") {
7657
- throw new GrokApiError("Grok billing response missing config", "parse");
7792
+ return out;
7793
+ }
7794
+ function calendarWeek(ref = /* @__PURE__ */ new Date()) {
7795
+ const d = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate());
7796
+ const dow = d.getDay();
7797
+ const daysSinceMon = (dow + 6) % 7;
7798
+ const monday = new Date(d);
7799
+ monday.setDate(d.getDate() - daysSinceMon);
7800
+ const sunday = new Date(monday);
7801
+ sunday.setDate(monday.getDate() + 6);
7802
+ return { start: monday, end: sunday };
7803
+ }
7804
+ function priorCalendarWeek(ref = /* @__PURE__ */ new Date()) {
7805
+ const cur = calendarWeek(ref);
7806
+ const prevEnd = new Date(cur.start);
7807
+ prevEnd.setDate(cur.start.getDate() - 1);
7808
+ const prevStart = new Date(prevEnd);
7809
+ prevStart.setDate(prevEnd.getDate() - 6);
7810
+ return { start: prevStart, end: prevEnd };
7811
+ }
7812
+ function last4CompletedWeeks(ref = /* @__PURE__ */ new Date()) {
7813
+ const { end } = priorCalendarWeek(ref);
7814
+ const start2 = new Date(end);
7815
+ start2.setDate(end.getDate() - 27);
7816
+ return { start: start2, end };
7817
+ }
7818
+ function calendarMonth(ref = /* @__PURE__ */ new Date()) {
7819
+ const start2 = new Date(ref.getFullYear(), ref.getMonth(), 1);
7820
+ const end = new Date(ref.getFullYear(), ref.getMonth() + 1, 0);
7821
+ return { start: start2, end };
7822
+ }
7823
+ function priorCalendarMonth(ref = /* @__PURE__ */ new Date()) {
7824
+ const start2 = new Date(ref.getFullYear(), ref.getMonth() - 1, 1);
7825
+ const end = new Date(ref.getFullYear(), ref.getMonth(), 0);
7826
+ return { start: start2, end };
7827
+ }
7828
+ function aggregateConcurrency(bursts, period) {
7829
+ const days = rangeDays(period.start, period.end);
7830
+ const byDayKey = /* @__PURE__ */ new Map();
7831
+ for (const d of days)
7832
+ byDayKey.set(isoDay(d), { peak: 0, has_cross_project: false });
7833
+ for (const b of bursts) {
7834
+ const dayKey = isoDay(new Date(b.startMs));
7835
+ const cur = byDayKey.get(dayKey);
7836
+ if (!cur)
7837
+ continue;
7838
+ if (b.peak > cur.peak)
7839
+ cur.peak = b.peak;
7840
+ if (b.crossProject)
7841
+ cur.has_cross_project = true;
7658
7842
  }
7659
- const period = config.currentPeriod;
7660
- if (!period || typeof period !== "object") {
7661
- throw new GrokApiError("Grok billing response missing currentPeriod", "parse");
7843
+ const by_day = days.map((d) => {
7844
+ const k = isoDay(d);
7845
+ const v = byDayKey.get(k);
7846
+ return { date: k, peak: v.peak, has_cross_project: v.has_cross_project };
7847
+ });
7848
+ let peak = 0;
7849
+ let peak_day;
7850
+ let multi_agent_days = 0;
7851
+ let cross_project_days = 0;
7852
+ for (const d of by_day) {
7853
+ if (d.peak > peak) {
7854
+ peak = d.peak;
7855
+ peak_day = d.date;
7856
+ }
7857
+ if (d.peak >= 3)
7858
+ multi_agent_days++;
7859
+ if (d.has_cross_project)
7860
+ cross_project_days++;
7662
7861
  }
7663
- const periodType = typeof period.type === "string" ? period.type.trim() : "";
7664
- if (!periodType) {
7665
- throw new GrokApiError("Grok billing period type missing", "parse");
7862
+ return { by_day, peak, peak_day, multi_agent_days, cross_project_days };
7863
+ }
7864
+ var init_aggregate = __esm({
7865
+ "../parser/dist/aggregate.js"() {
7866
+ "use strict";
7867
+ init_analytics();
7666
7868
  }
7667
- let utilization = 0;
7668
- if (config.creditUsagePercent !== void 0 && config.creditUsagePercent !== null) {
7669
- const n = Number(config.creditUsagePercent);
7670
- if (!Number.isFinite(n)) {
7671
- throw new GrokApiError("Grok creditUsagePercent is not a number", "parse");
7672
- }
7673
- utilization = Math.min(100, Math.max(0, n));
7869
+ });
7870
+
7871
+ // ../parser/dist/index.js
7872
+ var dist_exports = {};
7873
+ __export(dist_exports, {
7874
+ ANTIGRAVITY_METADATA: () => ANTIGRAVITY_METADATA,
7875
+ CLAUDE_CODE_METADATA: () => CLAUDE_CODE_METADATA,
7876
+ CODEX_METADATA: () => CODEX_METADATA,
7877
+ COPILOT_METADATA: () => COPILOT_METADATA,
7878
+ COWORK_METADATA: () => COWORK_METADATA,
7879
+ GEMINI_METADATA: () => GEMINI_METADATA,
7880
+ GROK_METADATA: () => GROK_METADATA,
7881
+ PRICES: () => PRICES,
7882
+ RATE_PER_PCT: () => RATE_PER_PCT,
7883
+ RATE_PER_PCT_5H: () => RATE_PER_PCT_5H,
7884
+ RATE_PER_PCT_7D: () => RATE_PER_PCT_7D,
7885
+ ZAI_METADATA: () => ZAI_METADATA,
7886
+ agentMetadata: () => agentMetadata,
7887
+ aggregateConcurrency: () => aggregateConcurrency,
7888
+ buildGanttDay: () => buildGanttDay,
7889
+ buildMegaRows: () => buildMegaRows,
7890
+ buildPresentation: () => buildPresentation,
7891
+ buildProjectKeyResolver: () => buildProjectKeyResolver,
7892
+ buildSpendIndex: () => buildSpendIndex,
7893
+ calendarMonth: () => calendarMonth,
7894
+ calendarWeek: () => calendarWeek,
7895
+ canonicalProjectName: () => canonicalProjectName,
7896
+ collectSnapPairRates: () => collectSnapPairRates,
7897
+ computeActiveSegments: () => computeActiveSegments,
7898
+ computeBurstsFromSessions: () => computeBurstsFromSessions,
7899
+ computeParallelism: () => computeParallelism,
7900
+ computeParallelismBursts: () => computeParallelismBursts,
7901
+ dailyActivity: () => dailyActivity,
7902
+ daysBetween: () => daysBetween,
7903
+ detectParallelRuns: () => detectParallelRuns,
7904
+ detectPrMarkers: () => detectPrMarkers,
7905
+ dollarsInRange: () => dollarsInRange,
7906
+ eventDollars: () => eventDollars,
7907
+ getAgentMetadata: () => getAgentMetadata,
7908
+ groupByProject: () => groupByProject,
7909
+ groupByTeam: () => groupByTeam,
7910
+ groupSnapsByCycle: () => groupSnapsByCycle,
7911
+ highLevelMetrics: () => highLevelMetrics,
7912
+ isAgentKind: () => isAgentKind,
7913
+ isFrameworkInjectedUserInput: () => isFrameworkInjectedUserInput,
7914
+ last4CompletedWeeks: () => last4CompletedWeeks,
7915
+ modelFamily: () => modelFamily,
7916
+ parseTranscript: () => parseTranscript,
7917
+ predictAnchored: () => predictAnchored,
7918
+ priorCalendarMonth: () => priorCalendarMonth,
7919
+ priorCalendarWeek: () => priorCalendarWeek,
7920
+ projectKey: () => projectKey,
7921
+ projectRepoName: () => projectRepoName,
7922
+ rowPrimaryIndex: () => rowPrimaryIndex,
7923
+ sessionAirTimeMs: () => sessionAirTimeMs,
7924
+ sessionDay: () => sessionDay,
7925
+ sessionProjectKey: () => sessionProjectKey,
7926
+ stripFrameworkBoilerplate: () => stripFrameworkBoilerplate,
7927
+ summarizeBursts: () => summarizeBursts,
7928
+ toLocalDay: () => toLocalDay,
7929
+ userSoloRate: () => userSoloRate,
7930
+ worktreeName: () => worktreeName
7931
+ });
7932
+ var init_dist = __esm({
7933
+ "../parser/dist/index.js"() {
7934
+ "use strict";
7935
+ init_types();
7936
+ init_parser();
7937
+ init_user_input();
7938
+ init_presentation();
7939
+ init_analytics();
7940
+ init_aggregate();
7941
+ init_calibration();
7942
+ init_agent_metadata();
7943
+ init_team();
7674
7944
  }
7675
- const end = typeof period.end === "string" && period.end ? new Date(period.end).toISOString() : null;
7676
- let onDemandCap = 0;
7677
- const cap = config.onDemandCap;
7678
- if (cap && typeof cap === "object" && "val" in cap) {
7679
- const v = Number(cap.val);
7680
- if (Number.isFinite(v)) onDemandCap = v;
7945
+ });
7946
+
7947
+ // src/args.ts
7948
+ function flag(args2, name) {
7949
+ const idx = args2.indexOf(name);
7950
+ return idx === -1 ? void 0 : args2[idx + 1];
7951
+ }
7952
+ var init_args = __esm({
7953
+ "src/args.ts"() {
7954
+ "use strict";
7681
7955
  }
7682
- return {
7683
- utilization,
7684
- resets_at: end && !Number.isNaN(Date.parse(end)) ? end : null,
7685
- period_type: periodType,
7686
- on_demand_cap: onDemandCap
7687
- };
7956
+ });
7957
+
7958
+ // src/usage/token.ts
7959
+ import { execFileSync as execFileSync2 } from "node:child_process";
7960
+ import { readFileSync as readFileSync10 } from "node:fs";
7961
+ import { homedir as homedir4, platform } from "node:os";
7962
+ import { join as join7 } from "node:path";
7963
+ function readOAuthCredentials() {
7964
+ if (platform() === "darwin") {
7965
+ return readFromMacKeychain() ?? readFromCredentialsFile();
7966
+ }
7967
+ return readFromCredentialsFile();
7688
7968
  }
7689
- async function fetchJson(url, token) {
7690
- let res;
7969
+ function readFromMacKeychain() {
7691
7970
  try {
7692
- res = await fetch(url, { headers: authHeaders(token) });
7693
- } catch (err) {
7694
- throw new GrokApiError(
7695
- `Network error reaching Grok billing: ${err.message}`,
7696
- "network"
7971
+ const blob = execFileSync2(
7972
+ "security",
7973
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
7974
+ { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }
7697
7975
  );
7698
- }
7699
- let body = null;
7700
- try {
7701
- body = await res.json();
7976
+ return extractCredentials(blob);
7702
7977
  } catch {
7978
+ return null;
7703
7979
  }
7704
- return { status: res.status, body };
7705
7980
  }
7706
- async function fetchGrokUsage() {
7707
- const loaded = loadAuthEntry();
7708
- const entry = loaded.entry;
7709
- let token = loaded.token;
7710
- if (needsRefresh(entry)) {
7711
- const refreshed = await refreshAccessToken(entry);
7712
- if (refreshed) token = refreshed;
7713
- }
7714
- let { status: status2, body } = await fetchJson(CREDITS_URL, token);
7715
- if (status2 === 401 || status2 === 403) {
7716
- const refreshed = await refreshAccessToken(entry);
7717
- if (refreshed) {
7718
- token = refreshed;
7719
- ({ status: status2, body } = await fetchJson(CREDITS_URL, token));
7981
+ function readFromCredentialsFile() {
7982
+ const candidates = [
7983
+ join7(homedir4(), ".claude", ".credentials.json"),
7984
+ join7(homedir4(), ".config", "claude", "credentials.json")
7985
+ ];
7986
+ for (const path11 of candidates) {
7987
+ try {
7988
+ const blob = readFileSync10(path11, "utf8");
7989
+ const creds = extractCredentials(blob);
7990
+ if (creds) return creds;
7991
+ } catch {
7720
7992
  }
7721
7993
  }
7722
- if (status2 < 200 || status2 >= 300) {
7723
- throw new GrokApiError(
7724
- `Grok billing returned HTTP ${status2}`,
7725
- status2 === 401 || status2 === 403 ? "no_auth" : "http"
7994
+ return null;
7995
+ }
7996
+ function extractCredentials(blob) {
7997
+ try {
7998
+ const parsed = JSON.parse(blob);
7999
+ const oauth = parsed.claudeAiOauth;
8000
+ if (!oauth) return null;
8001
+ const accessToken = typeof oauth.accessToken === "string" ? oauth.accessToken : null;
8002
+ const expiresAt = typeof oauth.expiresAt === "number" ? oauth.expiresAt : null;
8003
+ if (!accessToken || expiresAt === null) return null;
8004
+ return { accessToken, expiresAt };
8005
+ } catch {
8006
+ return null;
8007
+ }
8008
+ }
8009
+ var init_token = __esm({
8010
+ "src/usage/token.ts"() {
8011
+ "use strict";
8012
+ }
8013
+ });
8014
+
8015
+ // src/usage/api.ts
8016
+ var api_exports = {};
8017
+ __export(api_exports, {
8018
+ UsageApiError: () => UsageApiError,
8019
+ fetchUsage: () => fetchUsage
8020
+ });
8021
+ async function fetchUsage() {
8022
+ const creds = readOAuthCredentials();
8023
+ if (!creds) {
8024
+ throw new UsageApiError(
8025
+ "No Claude Code OAuth token found. Run `claude` to log in first.",
8026
+ "no_token"
7726
8027
  );
7727
8028
  }
7728
- const credits = parseGrokCreditsConfig(body);
7729
- if (credits.period_type !== WEEKLY_PERIOD) {
7730
- throw new GrokApiError(
7731
- `Grok period is ${credits.period_type}, not weekly`,
7732
- "not_weekly"
8029
+ if (creds.expiresAt - EXPIRY_SKEW_MS <= Date.now()) {
8030
+ throw new UsageApiError(
8031
+ "Claude Code OAuth token expired. Open Claude Code to refresh it.",
8032
+ "expired"
7733
8033
  );
7734
8034
  }
7735
- let planType = null;
8035
+ let res;
7736
8036
  try {
7737
- const settings = await fetchJson(SETTINGS_URL, token);
7738
- if (settings.status >= 200 && settings.status < 300 && settings.body && typeof settings.body === "object") {
7739
- const tier = settings.body.subscription_tier_display;
7740
- if (typeof tier === "string" && tier.trim()) planType = tier.trim();
7741
- }
7742
- } catch {
8037
+ res = await fetch(USAGE_ENDPOINT, {
8038
+ headers: {
8039
+ Authorization: `Bearer ${creds.accessToken}`,
8040
+ "anthropic-beta": BETA_HEADER
8041
+ }
8042
+ });
8043
+ } catch (err) {
8044
+ throw new UsageApiError(
8045
+ `Network error reaching Anthropic: ${err.message}`,
8046
+ "network"
8047
+ );
8048
+ }
8049
+ if (!res.ok) {
8050
+ throw new UsageApiError(
8051
+ `Usage endpoint returned ${res.status} ${res.statusText}`,
8052
+ "http"
8053
+ );
8054
+ }
8055
+ let body;
8056
+ try {
8057
+ body = await res.json();
8058
+ } catch (err) {
8059
+ throw new UsageApiError(`Failed to parse usage response: ${err.message}`, "parse");
7743
8060
  }
8061
+ const b = body;
7744
8062
  return {
7745
8063
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
7746
- agent: "grok",
7747
- // No 5-hour window — menubar / strip show "—" on the top line.
7748
- five_hour: { utilization: null, resets_at: null },
7749
- seven_day: {
7750
- utilization: credits.utilization,
7751
- resets_at: credits.resets_at
7752
- },
7753
- seven_day_opus: null,
7754
- seven_day_sonnet: null,
7755
- seven_day_oauth_apps: null,
7756
- seven_day_cowork: null,
7757
- extra_usage: null,
7758
- plan_type: planType
8064
+ agent: "claude-code",
8065
+ five_hour: normalizeWindow(b.five_hour) ?? { utilization: null, resets_at: null },
8066
+ seven_day: normalizeWindow(b.seven_day) ?? { utilization: null, resets_at: null },
8067
+ seven_day_opus: normalizeWindow(b.seven_day_opus),
8068
+ seven_day_sonnet: normalizeWindow(b.seven_day_sonnet),
8069
+ seven_day_oauth_apps: normalizeWindow(b.seven_day_oauth_apps),
8070
+ seven_day_cowork: normalizeWindow(b.seven_day_cowork),
8071
+ extra_usage: normalizeExtra(b.extra_usage),
8072
+ plan_type: null
7759
8073
  };
7760
8074
  }
7761
- var CREDITS_URL, SETTINGS_URL, REFRESH_URL, TOKEN_AUTH, DEFAULT_CLIENT_ID, WEEKLY_PERIOD, REFRESH_BUFFER_MS, GrokApiError;
7762
- var init_grok2 = __esm({
7763
- "src/usage/grok.ts"() {
8075
+ function normalizeWindow(raw) {
8076
+ if (!raw || typeof raw !== "object") return null;
8077
+ const r = raw;
8078
+ return {
8079
+ utilization: typeof r.utilization === "number" ? r.utilization : null,
8080
+ resets_at: typeof r.resets_at === "string" ? r.resets_at : null
8081
+ };
8082
+ }
8083
+ function normalizeExtra(raw) {
8084
+ if (!raw || typeof raw !== "object") return null;
8085
+ const r = raw;
8086
+ return {
8087
+ is_enabled: r.is_enabled === true,
8088
+ monthly_limit: typeof r.monthly_limit === "number" ? r.monthly_limit : null,
8089
+ used_credits: typeof r.used_credits === "number" ? r.used_credits : null,
8090
+ utilization: typeof r.utilization === "number" ? r.utilization : null
8091
+ };
8092
+ }
8093
+ var EXPIRY_SKEW_MS, USAGE_ENDPOINT, BETA_HEADER, UsageApiError;
8094
+ var init_api = __esm({
8095
+ "src/usage/api.ts"() {
7764
8096
  "use strict";
7765
- CREDITS_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
7766
- SETTINGS_URL = "https://cli-chat-proxy.grok.com/v1/settings";
7767
- REFRESH_URL = "https://auth.x.ai/oauth2/token";
7768
- TOKEN_AUTH = "xai-grok-cli";
7769
- DEFAULT_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
7770
- WEEKLY_PERIOD = "USAGE_PERIOD_TYPE_WEEKLY";
7771
- REFRESH_BUFFER_MS = 5 * 60 * 1e3;
7772
- GrokApiError = class extends Error {
8097
+ init_token();
8098
+ EXPIRY_SKEW_MS = 60 * 1e3;
8099
+ USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
8100
+ BETA_HEADER = "oauth-2025-04-20";
8101
+ UsageApiError = class extends Error {
7773
8102
  constructor(message, code) {
7774
8103
  super(message);
7775
8104
  this.code = code;
@@ -7778,90 +8107,52 @@ var init_grok2 = __esm({
7778
8107
  }
7779
8108
  });
7780
8109
 
7781
- // src/usage/codex.ts
7782
- import {
7783
- existsSync as existsSync9,
7784
- readFileSync as readFileSync12,
7785
- writeFileSync as writeFileSync6
7786
- } from "node:fs";
7787
- import os10 from "node:os";
7788
- import path10 from "node:path";
7789
- function codexHome() {
7790
- const env = process.env.CODEX_HOME?.trim();
7791
- if (env) return env;
7792
- return path10.join(os10.homedir(), ".codex");
8110
+ // src/usage/grok.ts
8111
+ import { existsSync as existsSync8, readFileSync as readFileSync11 } from "node:fs";
8112
+ import os9 from "node:os";
8113
+ import path9 from "node:path";
8114
+ function grokHome() {
8115
+ return process.env.GROK_HOME || path9.join(os9.homedir(), ".grok");
7793
8116
  }
7794
- function authPath2() {
7795
- return path10.join(codexHome(), "auth.json");
8117
+ function authPath() {
8118
+ return path9.join(grokHome(), "auth.json");
7796
8119
  }
7797
- function loadAuth() {
7798
- const p = authPath2();
7799
- if (!existsSync9(p)) {
7800
- throw new CodexApiError(
7801
- "Codex not logged in. Run `codex` to authenticate.",
7802
- "no_auth"
7803
- );
8120
+ function loadAuthEntry() {
8121
+ const p = authPath();
8122
+ if (!existsSync8(p)) {
8123
+ throw new GrokApiError("Grok not logged in. Run `grok login`.", "no_auth");
7804
8124
  }
7805
- let auth;
8125
+ let raw;
7806
8126
  try {
7807
- auth = JSON.parse(readFileSync12(p, "utf8"));
8127
+ raw = JSON.parse(readFileSync11(p, "utf8"));
7808
8128
  } catch {
7809
- throw new CodexApiError(
7810
- "Codex auth.json is unreadable. Run `codex` to log in again.",
7811
- "no_auth"
7812
- );
7813
- }
7814
- const access = auth.tokens?.access_token?.trim();
7815
- if (!access) {
7816
- if (auth.OPENAI_API_KEY?.trim()) {
7817
- throw new CodexApiError(
7818
- "Codex usage is not available for API-key-only auth. Sign in with a ChatGPT account via `codex`.",
7819
- "api_key_only"
7820
- );
7821
- }
7822
- throw new CodexApiError(
7823
- "Codex auth has no access token. Run `codex` to log in.",
7824
- "no_auth"
7825
- );
8129
+ throw new GrokApiError("Grok auth.json is unreadable. Run `grok login`.", "no_auth");
7826
8130
  }
7827
- return { auth, path: p };
7828
- }
7829
- function accessTokenExpiresAtMs(token) {
7830
- const parts = token.split(".");
7831
- if (parts.length < 2) return null;
7832
- try {
7833
- const pad2 = "=".repeat((4 - parts[1].length % 4) % 4);
7834
- const json = Buffer.from(parts[1] + pad2, "base64url").toString("utf8");
7835
- const payload = JSON.parse(json);
7836
- if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
7837
- return null;
7838
- }
7839
- return payload.exp * 1e3;
7840
- } catch {
7841
- return null;
8131
+ for (const [entryKey2, entry] of Object.entries(raw)) {
8132
+ const token = typeof entry?.key === "string" ? entry.key.trim() : "";
8133
+ if (token) return { token, entry, entryKey: entryKey2 };
7842
8134
  }
8135
+ throw new GrokApiError("Grok auth has no access token. Run `grok login`.", "no_auth");
7843
8136
  }
7844
- function needsRefresh2(accessToken, lastRefresh, nowMs = Date.now()) {
7845
- const expMs = accessTokenExpiresAtMs(accessToken);
7846
- if (expMs !== null) {
7847
- return nowMs >= expMs - REFRESH_BUFFER_MS2;
7848
- }
7849
- if (!lastRefresh) return false;
7850
- const ms = Date.parse(lastRefresh);
8137
+ function needsRefresh(entry) {
8138
+ const exp = entry.expires_at;
8139
+ if (!exp) return false;
8140
+ const ms = Date.parse(exp);
7851
8141
  if (!Number.isFinite(ms)) return false;
7852
- return nowMs - ms > 8 * 24 * 60 * 60 * 1e3;
8142
+ return Date.now() >= ms - REFRESH_BUFFER_MS;
7853
8143
  }
7854
- async function refreshAccessToken2(auth, filePath) {
7855
- const refresh = auth.tokens?.refresh_token?.trim();
8144
+ async function refreshAccessToken(entry) {
8145
+ const refresh = entry.refresh_token?.trim();
7856
8146
  if (!refresh) return null;
8147
+ const clientId = entry.oidc_client_id?.trim() || DEFAULT_CLIENT_ID;
7857
8148
  const body = new URLSearchParams({
7858
8149
  grant_type: "refresh_token",
7859
- client_id: CLIENT_ID,
8150
+ client_id: clientId,
7860
8151
  refresh_token: refresh
7861
8152
  });
7862
8153
  let res;
7863
8154
  try {
7864
- res = await fetch(REFRESH_URL2, {
8155
+ res = await fetch(REFRESH_URL, {
7865
8156
  method: "POST",
7866
8157
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
7867
8158
  body: body.toString()
@@ -7873,189 +8164,159 @@ async function refreshAccessToken2(auth, filePath) {
7873
8164
  try {
7874
8165
  const j = await res.json();
7875
8166
  if (typeof j.access_token !== "string" || !j.access_token) return null;
7876
- const next = {
7877
- ...auth,
7878
- tokens: {
7879
- ...auth.tokens,
7880
- access_token: j.access_token,
7881
- refresh_token: j.refresh_token ?? auth.tokens?.refresh_token,
7882
- id_token: j.id_token ?? auth.tokens?.id_token
7883
- },
7884
- last_refresh: (/* @__PURE__ */ new Date()).toISOString()
7885
- };
7886
8167
  try {
7887
- writeFileSync6(filePath, `${JSON.stringify(next, null, 2)}
7888
- `, {
7889
- encoding: "utf8",
7890
- mode: 384
7891
- });
8168
+ const p = authPath();
8169
+ const raw = JSON.parse(readFileSync11(p, "utf8"));
8170
+ for (const key of Object.keys(raw)) {
8171
+ if (raw[key]?.key === entry.key || raw[key]?.refresh_token === refresh) {
8172
+ raw[key] = {
8173
+ ...raw[key],
8174
+ key: j.access_token,
8175
+ refresh_token: j.refresh_token ?? raw[key].refresh_token,
8176
+ expires_at: typeof j.expires_in === "number" ? new Date(Date.now() + j.expires_in * 1e3).toISOString() : raw[key].expires_at
8177
+ };
8178
+ break;
8179
+ }
8180
+ }
8181
+ const { writeFileSync: writeFileSync18 } = await import("node:fs");
8182
+ writeFileSync18(p, JSON.stringify(raw, null, 2), "utf8");
7892
8183
  } catch {
7893
8184
  }
7894
- return { accessToken: j.access_token, auth: next };
8185
+ return j.access_token;
7895
8186
  } catch {
7896
8187
  return null;
7897
8188
  }
7898
8189
  }
7899
- function authHeaders2(accessToken, accountId) {
7900
- const headers = {
7901
- Authorization: `Bearer ${accessToken}`,
8190
+ function authHeaders(token) {
8191
+ return {
8192
+ Authorization: `Bearer ${token.trim()}`,
8193
+ "X-XAI-Token-Auth": TOKEN_AUTH,
7902
8194
  Accept: "application/json",
7903
8195
  "User-Agent": "fleetlens"
7904
8196
  };
7905
- if (accountId?.trim()) {
7906
- headers["ChatGPT-Account-Id"] = accountId.trim();
7907
- }
7908
- return headers;
7909
8197
  }
7910
- function numberOf3(v) {
7911
- if (typeof v === "number" && Number.isFinite(v)) return v;
7912
- if (typeof v === "string" && v.trim()) {
7913
- const n = Number(v);
7914
- if (Number.isFinite(n)) return n;
8198
+ function parseGrokCreditsConfig(body) {
8199
+ if (!body || typeof body !== "object") {
8200
+ throw new GrokApiError("Grok billing response missing body", "parse");
7915
8201
  }
7916
- return void 0;
7917
- }
7918
- function windowSeconds(raw) {
7919
- if (!raw) return void 0;
7920
- const sec = numberOf3(raw.limit_window_seconds);
7921
- if (sec !== void 0) return sec;
7922
- const minutes = numberOf3(raw.window_minutes);
7923
- if (minutes !== void 0) return minutes * 60;
7924
- return void 0;
7925
- }
7926
- function normalizeWindow2(raw, headerPercent, nowMs) {
7927
- const used = numberOf3(raw?.used_percent) ?? headerPercent;
7928
- if (used === void 0) return null;
7929
- let resetsAt = null;
7930
- const resetUnix = numberOf3(raw?.reset_at) ?? numberOf3(raw?.resets_at);
7931
- if (resetUnix !== void 0) {
7932
- resetsAt = new Date(resetUnix * 1e3).toISOString();
7933
- } else {
7934
- const after = numberOf3(raw?.reset_after_seconds);
7935
- if (after !== void 0) {
7936
- resetsAt = new Date(nowMs + after * 1e3).toISOString();
7937
- }
8202
+ const config = body.config;
8203
+ if (!config || typeof config !== "object") {
8204
+ throw new GrokApiError("Grok billing response missing config", "parse");
7938
8205
  }
7939
- return {
7940
- utilization: Math.min(100, Math.max(0, used)),
7941
- resets_at: resetsAt
7942
- };
7943
- }
7944
- function parseCodexWhamUsage(body, headers = {}, nowMs = Date.now()) {
7945
- if (!body || typeof body !== "object") {
7946
- throw new CodexApiError("Codex usage response missing body", "parse");
8206
+ const period = config.currentPeriod;
8207
+ if (!period || typeof period !== "object") {
8208
+ throw new GrokApiError("Grok billing response missing currentPeriod", "parse");
7947
8209
  }
7948
- const b = body;
7949
- const rateLimit = b.rate_limit && typeof b.rate_limit === "object" ? b.rate_limit : {};
7950
- const headerPrimary = numberOf3(headers["x-codex-primary-used-percent"]);
7951
- const headerSecondary = numberOf3(headers["x-codex-secondary-used-percent"]);
7952
- const primary = rateLimit.primary_window && typeof rateLimit.primary_window === "object" ? rateLimit.primary_window : null;
7953
- const secondary = rateLimit.secondary_window && typeof rateLimit.secondary_window === "object" ? rateLimit.secondary_window : null;
7954
- const candidates = [
7955
- { raw: primary, headerPercent: headerPrimary, fallback: "five_hour" },
7956
- { raw: secondary, headerPercent: headerSecondary, fallback: "seven_day" }
7957
- ];
7958
- let fiveHour = null;
7959
- let sevenDay = null;
7960
- for (const c of candidates) {
7961
- const secs = windowSeconds(c.raw);
7962
- const window = normalizeWindow2(c.raw, c.headerPercent, nowMs);
7963
- if (!window) continue;
7964
- if (secs === FIVE_HOUR_SECONDS) fiveHour = window;
7965
- else if (secs === SEVEN_DAY_SECONDS) sevenDay = window;
8210
+ const periodType = typeof period.type === "string" ? period.type.trim() : "";
8211
+ if (!periodType) {
8212
+ throw new GrokApiError("Grok billing period type missing", "parse");
7966
8213
  }
7967
- if (!fiveHour || !sevenDay) {
7968
- for (const c of candidates) {
7969
- const secs = windowSeconds(c.raw);
7970
- if (secs === FIVE_HOUR_SECONDS || secs === SEVEN_DAY_SECONDS) continue;
7971
- const window = normalizeWindow2(c.raw, c.headerPercent, nowMs);
7972
- if (!window) continue;
7973
- if (c.fallback === "five_hour" && !fiveHour) fiveHour = window;
7974
- else if (c.fallback === "seven_day" && !sevenDay) sevenDay = window;
8214
+ let utilization = 0;
8215
+ if (config.creditUsagePercent !== void 0 && config.creditUsagePercent !== null) {
8216
+ const n = Number(config.creditUsagePercent);
8217
+ if (!Number.isFinite(n)) {
8218
+ throw new GrokApiError("Grok creditUsagePercent is not a number", "parse");
7975
8219
  }
8220
+ utilization = Math.min(100, Math.max(0, n));
8221
+ }
8222
+ const end = typeof period.end === "string" && period.end ? new Date(period.end).toISOString() : null;
8223
+ let onDemandCap = 0;
8224
+ const cap = config.onDemandCap;
8225
+ if (cap && typeof cap === "object" && "val" in cap) {
8226
+ const v = Number(cap.val);
8227
+ if (Number.isFinite(v)) onDemandCap = v;
7976
8228
  }
7977
- const planRaw = b.plan_type;
7978
- const planType = typeof planRaw === "string" && planRaw.trim() ? planRaw.trim() : null;
7979
8229
  return {
7980
- five_hour: fiveHour ?? { utilization: null, resets_at: null },
7981
- seven_day: sevenDay ?? { utilization: null, resets_at: null },
7982
- plan_type: planType
8230
+ utilization,
8231
+ resets_at: end && !Number.isNaN(Date.parse(end)) ? end : null,
8232
+ period_type: periodType,
8233
+ on_demand_cap: onDemandCap
7983
8234
  };
7984
8235
  }
7985
- async function fetchUsageResponse(accessToken, accountId) {
8236
+ async function fetchJson(url, token) {
7986
8237
  let res;
7987
8238
  try {
7988
- res = await fetch(USAGE_URL, {
7989
- headers: authHeaders2(accessToken, accountId)
7990
- });
8239
+ res = await fetch(url, { headers: authHeaders(token) });
7991
8240
  } catch (err) {
7992
- throw new CodexApiError(
7993
- `Network error reaching Codex usage: ${err.message}`,
8241
+ throw new GrokApiError(
8242
+ `Network error reaching Grok billing: ${err.message}`,
7994
8243
  "network"
7995
8244
  );
7996
8245
  }
7997
- const headers = {};
7998
- res.headers.forEach((value, key) => {
7999
- headers[key.toLowerCase()] = value;
8000
- });
8001
8246
  let body = null;
8002
8247
  try {
8003
8248
  body = await res.json();
8004
8249
  } catch {
8005
8250
  }
8006
- return { status: res.status, body, headers };
8251
+ return { status: res.status, body };
8007
8252
  }
8008
- async function fetchCodexUsage() {
8009
- const loaded = loadAuth();
8010
- let auth = loaded.auth;
8011
- let access = auth.tokens.access_token.trim();
8012
- const accountId = auth.tokens?.account_id;
8013
- if (needsRefresh2(access, auth.last_refresh)) {
8014
- const refreshed = await refreshAccessToken2(auth, loaded.path);
8015
- if (refreshed) {
8016
- access = refreshed.accessToken;
8017
- auth = refreshed.auth;
8018
- }
8253
+ async function fetchGrokUsage() {
8254
+ const loaded = loadAuthEntry();
8255
+ const entry = loaded.entry;
8256
+ let token = loaded.token;
8257
+ if (needsRefresh(entry)) {
8258
+ const refreshed = await refreshAccessToken(entry);
8259
+ if (refreshed) token = refreshed;
8019
8260
  }
8020
- let { status: status2, body, headers } = await fetchUsageResponse(access, accountId);
8261
+ let { status: status2, body } = await fetchJson(CREDITS_URL, token);
8021
8262
  if (status2 === 401 || status2 === 403) {
8022
- const refreshed = await refreshAccessToken2(auth, loaded.path);
8263
+ const refreshed = await refreshAccessToken(entry);
8023
8264
  if (refreshed) {
8024
- access = refreshed.accessToken;
8025
- ({ status: status2, body, headers } = await fetchUsageResponse(access, accountId));
8265
+ token = refreshed;
8266
+ ({ status: status2, body } = await fetchJson(CREDITS_URL, token));
8026
8267
  }
8027
8268
  }
8028
8269
  if (status2 < 200 || status2 >= 300) {
8029
- throw new CodexApiError(
8030
- `Codex usage returned HTTP ${status2}`,
8270
+ throw new GrokApiError(
8271
+ `Grok billing returned HTTP ${status2}`,
8031
8272
  status2 === 401 || status2 === 403 ? "no_auth" : "http"
8032
8273
  );
8033
8274
  }
8034
- const windows = parseCodexWhamUsage(body, headers);
8275
+ const credits = parseGrokCreditsConfig(body);
8276
+ if (credits.period_type !== WEEKLY_PERIOD) {
8277
+ throw new GrokApiError(
8278
+ `Grok period is ${credits.period_type}, not weekly`,
8279
+ "not_weekly"
8280
+ );
8281
+ }
8282
+ let planType = null;
8283
+ try {
8284
+ const settings = await fetchJson(SETTINGS_URL, token);
8285
+ if (settings.status >= 200 && settings.status < 300 && settings.body && typeof settings.body === "object") {
8286
+ const tier = settings.body.subscription_tier_display;
8287
+ if (typeof tier === "string" && tier.trim()) planType = tier.trim();
8288
+ }
8289
+ } catch {
8290
+ }
8035
8291
  return {
8036
8292
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
8037
- agent: "codex",
8038
- five_hour: windows.five_hour,
8039
- seven_day: windows.seven_day,
8293
+ agent: "grok",
8294
+ // No 5-hour window — menubar / strip show "—" on the top line.
8295
+ five_hour: { utilization: null, resets_at: null },
8296
+ seven_day: {
8297
+ utilization: credits.utilization,
8298
+ resets_at: credits.resets_at
8299
+ },
8040
8300
  seven_day_opus: null,
8041
8301
  seven_day_sonnet: null,
8042
8302
  seven_day_oauth_apps: null,
8043
8303
  seven_day_cowork: null,
8044
8304
  extra_usage: null,
8045
- plan_type: windows.plan_type
8305
+ plan_type: planType
8046
8306
  };
8047
8307
  }
8048
- var USAGE_URL, REFRESH_URL2, CLIENT_ID, FIVE_HOUR_SECONDS, SEVEN_DAY_SECONDS, REFRESH_BUFFER_MS2, CodexApiError;
8049
- var init_codex2 = __esm({
8050
- "src/usage/codex.ts"() {
8308
+ var CREDITS_URL, SETTINGS_URL, REFRESH_URL, TOKEN_AUTH, DEFAULT_CLIENT_ID, WEEKLY_PERIOD, REFRESH_BUFFER_MS, GrokApiError;
8309
+ var init_grok2 = __esm({
8310
+ "src/usage/grok.ts"() {
8051
8311
  "use strict";
8052
- USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
8053
- REFRESH_URL2 = "https://auth.openai.com/oauth/token";
8054
- CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
8055
- FIVE_HOUR_SECONDS = 5 * 60 * 60;
8056
- SEVEN_DAY_SECONDS = 7 * 24 * 60 * 60;
8057
- REFRESH_BUFFER_MS2 = 5 * 60 * 1e3;
8058
- CodexApiError = class extends Error {
8312
+ CREDITS_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
8313
+ SETTINGS_URL = "https://cli-chat-proxy.grok.com/v1/settings";
8314
+ REFRESH_URL = "https://auth.x.ai/oauth2/token";
8315
+ TOKEN_AUTH = "xai-grok-cli";
8316
+ DEFAULT_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
8317
+ WEEKLY_PERIOD = "USAGE_PERIOD_TYPE_WEEKLY";
8318
+ REFRESH_BUFFER_MS = 5 * 60 * 1e3;
8319
+ GrokApiError = class extends Error {
8059
8320
  constructor(message, code) {
8060
8321
  super(message);
8061
8322
  this.code = code;
@@ -8064,1205 +8325,1358 @@ var init_codex2 = __esm({
8064
8325
  }
8065
8326
  });
8066
8327
 
8067
- // src/usage/copilot.ts
8068
- import { spawn as spawn3 } from "node:child_process";
8069
- import { existsSync as existsSync10 } from "node:fs";
8070
- import { dirname as dirname8, join as join8 } from "node:path";
8071
- function recordOf2(value) {
8072
- return typeof value === "object" && value !== null ? value : void 0;
8073
- }
8074
- function numberOf4(value) {
8075
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
8076
- }
8077
- function clampPercent(value) {
8078
- return Math.min(100, Math.max(0, value));
8328
+ // src/usage/codex.ts
8329
+ import {
8330
+ existsSync as existsSync9,
8331
+ readFileSync as readFileSync12,
8332
+ writeFileSync as writeFileSync6
8333
+ } from "node:fs";
8334
+ import os10 from "node:os";
8335
+ import path10 from "node:path";
8336
+ function codexHome() {
8337
+ const env = process.env.CODEX_HOME?.trim();
8338
+ if (env) return env;
8339
+ return path10.join(os10.homedir(), ".codex");
8079
8340
  }
8080
- function nextCopilotMonthlyReset(nowMs = Date.now()) {
8081
- const now = new Date(nowMs);
8082
- return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1)).toISOString();
8341
+ function authPath2() {
8342
+ return path10.join(codexHome(), "auth.json");
8083
8343
  }
8084
- function quotaResetDate(value, nowMs) {
8085
- const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
8086
- return Number.isFinite(parsed) && parsed > nowMs + 6e4 ? new Date(parsed).toISOString() : nextCopilotMonthlyReset(nowMs);
8087
- }
8088
- function parseCopilotQuota(body, nowMs = Date.now()) {
8089
- const root = recordOf2(body);
8090
- const snapshots = recordOf2(root?.quotaSnapshots);
8091
- if (!snapshots) {
8092
- throw new CopilotApiError("Copilot quota response missing quotaSnapshots", "parse");
8344
+ function loadAuth() {
8345
+ const p = authPath2();
8346
+ if (!existsSync9(p)) {
8347
+ throw new CodexApiError(
8348
+ "Codex not logged in. Run `codex` to authenticate.",
8349
+ "no_auth"
8350
+ );
8093
8351
  }
8094
- const chat = recordOf2(snapshots.chat);
8095
- const legacy = recordOf2(snapshots.premium_interactions);
8096
- const chatLimit = numberOf4(chat?.entitlementRequests);
8097
- const quota = chat && (chatLimit !== void 0 && chatLimit > 0 || chat.isUnlimitedEntitlement === true) ? chat : legacy;
8098
- if (!quota) {
8099
- throw new CopilotApiError("Copilot quota response has no chat allowance", "parse");
8352
+ let auth;
8353
+ try {
8354
+ auth = JSON.parse(readFileSync12(p, "utf8"));
8355
+ } catch {
8356
+ throw new CodexApiError(
8357
+ "Codex auth.json is unreadable. Run `codex` to log in again.",
8358
+ "no_auth"
8359
+ );
8100
8360
  }
8101
- const unlimited = quota.isUnlimitedEntitlement === true;
8102
- const remainingPercent = numberOf4(quota.remainingPercentage);
8103
- const utilization = unlimited ? null : remainingPercent === void 0 ? null : clampPercent(100 - remainingPercent);
8104
- const limit = numberOf4(quota.entitlementRequests) ?? null;
8105
- const used = numberOf4(quota.usedRequests) ?? null;
8106
- const unit = quota.tokenBasedBilling === true ? "ai-credits" : "premium-requests";
8107
- return {
8108
- captured_at: new Date(nowMs).toISOString(),
8109
- agent: "copilot",
8110
- five_hour: { utilization: null, resets_at: null },
8111
- seven_day: { utilization: null, resets_at: null },
8112
- monthly: {
8113
- utilization,
8114
- resets_at: quotaResetDate(quota.resetDate, nowMs)
8115
- },
8116
- monthly_quota: {
8117
- used,
8118
- limit,
8119
- remaining: used !== null && limit !== null ? Math.max(0, limit - used) : null,
8120
- unit,
8121
- unlimited
8122
- },
8123
- seven_day_opus: null,
8124
- seven_day_sonnet: null,
8125
- seven_day_oauth_apps: null,
8126
- seven_day_cowork: null,
8127
- extra_usage: null,
8128
- plan_type: unit === "ai-credits" ? "AI credits" : "premium requests"
8129
- };
8361
+ const access = auth.tokens?.access_token?.trim();
8362
+ if (!access) {
8363
+ if (auth.OPENAI_API_KEY?.trim()) {
8364
+ throw new CodexApiError(
8365
+ "Codex usage is not available for API-key-only auth. Sign in with a ChatGPT account via `codex`.",
8366
+ "api_key_only"
8367
+ );
8368
+ }
8369
+ throw new CodexApiError(
8370
+ "Codex auth has no access token. Run `codex` to log in.",
8371
+ "no_auth"
8372
+ );
8373
+ }
8374
+ return { auth, path: p };
8130
8375
  }
8131
- function rpcFrame(message) {
8132
- const body = Buffer.from(JSON.stringify(message), "utf8");
8133
- return Buffer.concat([
8134
- Buffer.from(`Content-Length: ${body.length}\r
8135
- \r
8136
- `, "ascii"),
8137
- body
8138
- ]);
8376
+ function accessTokenExpiresAtMs(token) {
8377
+ const parts = token.split(".");
8378
+ if (parts.length < 2) return null;
8379
+ try {
8380
+ const pad2 = "=".repeat((4 - parts[1].length % 4) % 4);
8381
+ const json = Buffer.from(parts[1] + pad2, "base64url").toString("utf8");
8382
+ const payload = JSON.parse(json);
8383
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
8384
+ return null;
8385
+ }
8386
+ return payload.exp * 1e3;
8387
+ } catch {
8388
+ return null;
8389
+ }
8139
8390
  }
8140
- function resolveCopilotCliPath() {
8141
- const configured = process.env.COPILOT_CLI_PATH?.trim();
8142
- if (configured) return configured;
8143
- const sibling = join8(dirname8(process.execPath), process.platform === "win32" ? "copilot.cmd" : "copilot");
8144
- return existsSync10(sibling) ? sibling : process.platform === "win32" ? "copilot.cmd" : "copilot";
8391
+ function needsRefresh2(accessToken, lastRefresh, nowMs = Date.now()) {
8392
+ const expMs = accessTokenExpiresAtMs(accessToken);
8393
+ if (expMs !== null) {
8394
+ return nowMs >= expMs - REFRESH_BUFFER_MS2;
8395
+ }
8396
+ if (!lastRefresh) return false;
8397
+ const ms = Date.parse(lastRefresh);
8398
+ if (!Number.isFinite(ms)) return false;
8399
+ return nowMs - ms > 8 * 24 * 60 * 60 * 1e3;
8145
8400
  }
8146
- async function requestQuota(child) {
8147
- const decoder = new ContentLengthDecoder();
8148
- const pending = /* @__PURE__ */ new Map();
8149
- let nextId = 1;
8150
- const rejectAll = (error) => {
8151
- for (const waiter of pending.values()) waiter.reject(error);
8152
- pending.clear();
8153
- };
8154
- child.stdout.on("data", (chunk2) => {
8155
- try {
8156
- for (const value of decoder.push(chunk2)) {
8157
- const message = recordOf2(value);
8158
- const id = numberOf4(message?.id);
8159
- if (id === void 0) continue;
8160
- const waiter = pending.get(id);
8161
- if (!waiter) continue;
8162
- pending.delete(id);
8163
- const rpcError = recordOf2(message?.error);
8164
- if (rpcError) {
8165
- waiter.reject(new CopilotApiError(
8166
- typeof rpcError.message === "string" ? rpcError.message : "Copilot RPC request failed",
8167
- "protocol"
8168
- ));
8169
- } else {
8170
- waiter.resolve(message?.result);
8171
- }
8172
- }
8173
- } catch (error) {
8174
- rejectAll(error);
8175
- }
8176
- });
8177
- const exited = new Promise((_, reject) => {
8178
- child.once("error", (error) => {
8179
- reject(new CopilotApiError(
8180
- error.message,
8181
- error.code === "ENOENT" ? "no_cli" : "protocol"
8182
- ));
8183
- });
8184
- child.once("exit", (code) => {
8185
- reject(new CopilotApiError(`Copilot RPC exited before responding (code ${code ?? "unknown"})`, "protocol"));
8186
- });
8401
+ async function refreshAccessToken2(auth, filePath) {
8402
+ const refresh = auth.tokens?.refresh_token?.trim();
8403
+ if (!refresh) return null;
8404
+ const body = new URLSearchParams({
8405
+ grant_type: "refresh_token",
8406
+ client_id: CLIENT_ID,
8407
+ refresh_token: refresh
8187
8408
  });
8188
- const request = (method, params = {}) => {
8189
- const id = nextId++;
8190
- const response = new Promise((resolve4, reject) => {
8191
- pending.set(id, { resolve: resolve4, reject });
8192
- child.stdin.write(rpcFrame({ jsonrpc: "2.0", id, method, params }));
8409
+ let res;
8410
+ try {
8411
+ res = await fetch(REFRESH_URL2, {
8412
+ method: "POST",
8413
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
8414
+ body: body.toString()
8193
8415
  });
8194
- return Promise.race([response, exited]);
8195
- };
8196
- await request("connect", {});
8197
- return request("account.getQuota", {});
8198
- }
8199
- async function fetchCopilotUsage() {
8200
- const cliPath = resolveCopilotCliPath();
8201
- const child = spawn3(cliPath, ["--headless", "--no-auto-update", "--stdio", "--log-level", "none"], {
8202
- cwd: process.cwd(),
8203
- env: process.env,
8204
- stdio: ["pipe", "pipe", "pipe"],
8205
- windowsHide: true,
8206
- shell: process.platform === "win32"
8207
- });
8208
- let stderr = "";
8209
- child.stderr.on("data", (chunk2) => {
8210
- stderr = (stderr + chunk2.toString("utf8")).slice(-2e3);
8211
- });
8212
- const timeout = new Promise((_, reject) => {
8213
- const handle = setTimeout(() => {
8214
- reject(new CopilotApiError("Timed out reading Copilot quota", "timeout"));
8215
- }, RPC_TIMEOUT_MS);
8216
- handle.unref();
8217
- });
8416
+ } catch {
8417
+ return null;
8418
+ }
8419
+ if (!res.ok) return null;
8218
8420
  try {
8219
- const body = await Promise.race([requestQuota(child), timeout]);
8220
- return parseCopilotQuota(body);
8221
- } catch (error) {
8222
- if (error instanceof CopilotApiError) {
8223
- const message = `${error.message} ${stderr}`.toLowerCase();
8224
- if (message.includes("not logged") || message.includes("authentication")) {
8225
- throw new CopilotApiError("Copilot not logged in. Run `copilot login`.", "no_auth");
8226
- }
8227
- throw error;
8421
+ const j = await res.json();
8422
+ if (typeof j.access_token !== "string" || !j.access_token) return null;
8423
+ const next = {
8424
+ ...auth,
8425
+ tokens: {
8426
+ ...auth.tokens,
8427
+ access_token: j.access_token,
8428
+ refresh_token: j.refresh_token ?? auth.tokens?.refresh_token,
8429
+ id_token: j.id_token ?? auth.tokens?.id_token
8430
+ },
8431
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString()
8432
+ };
8433
+ try {
8434
+ writeFileSync6(filePath, `${JSON.stringify(next, null, 2)}
8435
+ `, {
8436
+ encoding: "utf8",
8437
+ mode: 384
8438
+ });
8439
+ } catch {
8228
8440
  }
8229
- throw new CopilotApiError(error.message, "protocol");
8230
- } finally {
8231
- child.kill();
8441
+ return { accessToken: j.access_token, auth: next };
8442
+ } catch {
8443
+ return null;
8232
8444
  }
8233
8445
  }
8234
- var RPC_TIMEOUT_MS, CopilotApiError, ContentLengthDecoder;
8235
- var init_copilot2 = __esm({
8236
- "src/usage/copilot.ts"() {
8237
- "use strict";
8238
- RPC_TIMEOUT_MS = 1e4;
8239
- CopilotApiError = class extends Error {
8240
- constructor(message, code) {
8241
- super(message);
8242
- this.code = code;
8243
- }
8244
- };
8245
- ContentLengthDecoder = class {
8246
- buffer = Buffer.alloc(0);
8247
- push(chunk2) {
8248
- this.buffer = Buffer.concat([this.buffer, chunk2]);
8249
- const messages = [];
8250
- while (true) {
8251
- const headerEnd = this.buffer.indexOf("\r\n\r\n");
8252
- if (headerEnd === -1) break;
8253
- const header = this.buffer.subarray(0, headerEnd).toString("ascii");
8254
- const match = header.match(/(?:^|\r\n)Content-Length:\s*(\d+)/i);
8255
- if (!match) {
8256
- throw new CopilotApiError("Copilot RPC response missing Content-Length", "protocol");
8257
- }
8258
- const length = Number(match[1]);
8259
- const bodyStart = headerEnd + 4;
8260
- if (this.buffer.length < bodyStart + length) break;
8261
- const body = this.buffer.subarray(bodyStart, bodyStart + length).toString("utf8");
8262
- this.buffer = this.buffer.subarray(bodyStart + length);
8263
- try {
8264
- messages.push(JSON.parse(body));
8265
- } catch {
8266
- throw new CopilotApiError("Copilot RPC returned invalid JSON", "protocol");
8267
- }
8268
- }
8269
- return messages;
8270
- }
8271
- };
8272
- }
8273
- });
8274
-
8275
- // src/usage/format.ts
8276
- function formatUsage(snapshot) {
8277
- const rows = [
8278
- ["5 hour", snapshot.five_hour],
8279
- ["7 day", snapshot.seven_day],
8280
- ["7 day (Opus)", snapshot.seven_day_opus],
8281
- ["7 day (Sonnet)", snapshot.seven_day_sonnet],
8282
- ["7 day (OAuth apps)", snapshot.seven_day_oauth_apps],
8283
- ["7 day (Cowork)", snapshot.seven_day_cowork]
8284
- ];
8285
- const lines = [];
8286
- lines.push("");
8287
- lines.push(` ${BOLD}Claude Code Usage${RESET}`);
8288
- lines.push("");
8289
- for (const [label, window] of rows) {
8290
- if (!window || window.utilization === null) continue;
8291
- const pct = window.utilization;
8292
- const bar = renderBar(pct);
8293
- const pctStr = `${pct.toFixed(1)}%`.padStart(6);
8294
- const labelStr = label.padEnd(16);
8295
- lines.push(` ${labelStr}${bar} ${BOLD}${pctStr}${RESET}`);
8296
- if (window.resets_at) {
8297
- lines.push(` ${" ".repeat(16)}${DIM}resets ${formatRelative(window.resets_at)}${RESET}`);
8298
- }
8299
- lines.push("");
8446
+ function authHeaders2(accessToken, accountId) {
8447
+ const headers = {
8448
+ Authorization: `Bearer ${accessToken}`,
8449
+ Accept: "application/json",
8450
+ "User-Agent": "fleetlens"
8451
+ };
8452
+ if (accountId?.trim()) {
8453
+ headers["ChatGPT-Account-Id"] = accountId.trim();
8300
8454
  }
8301
- if (snapshot.extra_usage?.is_enabled) {
8302
- const extra = snapshot.extra_usage;
8303
- lines.push(` ${BOLD}Extra usage${RESET}`);
8304
- if (extra.utilization !== null) {
8305
- lines.push(` ${" ".repeat(16)}${extra.utilization.toFixed(1)}%`);
8306
- }
8307
- if (extra.used_credits !== null && extra.monthly_limit !== null) {
8308
- lines.push(` ${" ".repeat(16)}${extra.used_credits} / ${extra.monthly_limit} credits`);
8309
- }
8310
- lines.push("");
8455
+ return headers;
8456
+ }
8457
+ function numberOf3(v) {
8458
+ if (typeof v === "number" && Number.isFinite(v)) return v;
8459
+ if (typeof v === "string" && v.trim()) {
8460
+ const n = Number(v);
8461
+ if (Number.isFinite(n)) return n;
8311
8462
  }
8312
- lines.push(` ${DIM}captured ${formatRelative(snapshot.captured_at)}${RESET}`);
8313
- lines.push("");
8314
- return lines.join("\n");
8463
+ return void 0;
8315
8464
  }
8316
- function renderBar(utilization) {
8317
- const clamped = Math.max(0, Math.min(100, utilization));
8318
- const color = clamped >= 90 ? RED : clamped >= 70 ? YELLOW : GREEN;
8319
- const totalEighths = Math.round(clamped / 100 * BAR_WIDTH * 8);
8320
- const fullBlocks = Math.floor(totalEighths / 8);
8321
- const remainder = totalEighths % 8;
8322
- const partial = EIGHTHS[remainder];
8323
- const filledCells = fullBlocks + (partial ? 1 : 0);
8324
- const emptyCells = BAR_WIDTH - filledCells;
8325
- const filled = "\u2588".repeat(fullBlocks) + partial;
8326
- const empty = "\xB7".repeat(emptyCells);
8327
- return `${color}${filled}${RESET}${DIM}${empty}${RESET}`;
8465
+ function windowSeconds(raw) {
8466
+ if (!raw) return void 0;
8467
+ const sec = numberOf3(raw.limit_window_seconds);
8468
+ if (sec !== void 0) return sec;
8469
+ const minutes = numberOf3(raw.window_minutes);
8470
+ if (minutes !== void 0) return minutes * 60;
8471
+ return void 0;
8328
8472
  }
8329
- function formatRelative(iso) {
8330
- const then = new Date(iso).getTime();
8331
- const now = Date.now();
8332
- const diffSec = Math.round((then - now) / 1e3);
8333
- const abs = Math.abs(diffSec);
8334
- const past = diffSec < 0;
8335
- let value;
8336
- if (abs < 60) {
8337
- value = `${abs}s`;
8338
- } else if (abs < 3600) {
8339
- value = `${Math.floor(abs / 60)}m`;
8340
- } else if (abs < 86400) {
8341
- const h = Math.floor(abs / 3600);
8342
- const m = Math.floor(abs % 3600 / 60);
8343
- value = m > 0 ? `${h}h${m}m` : `${h}h`;
8473
+ function normalizeWindow2(raw, headerPercent, nowMs) {
8474
+ const used = numberOf3(raw?.used_percent) ?? headerPercent;
8475
+ if (used === void 0) return null;
8476
+ let resetsAt = null;
8477
+ const resetUnix = numberOf3(raw?.reset_at) ?? numberOf3(raw?.resets_at);
8478
+ if (resetUnix !== void 0) {
8479
+ resetsAt = new Date(resetUnix * 1e3).toISOString();
8344
8480
  } else {
8345
- const d = Math.floor(abs / 86400);
8346
- const h = Math.floor(abs % 86400 / 3600);
8347
- value = h > 0 ? `${d}d${h}h` : `${d}d`;
8481
+ const after = numberOf3(raw?.reset_after_seconds);
8482
+ if (after !== void 0) {
8483
+ resetsAt = new Date(nowMs + after * 1e3).toISOString();
8484
+ }
8348
8485
  }
8349
- return past ? `${value} ago` : `in ${value}`;
8486
+ return {
8487
+ utilization: Math.min(100, Math.max(0, used)),
8488
+ resets_at: resetsAt
8489
+ };
8350
8490
  }
8351
- var DIM, BOLD, RESET, GREEN, YELLOW, RED, BAR_WIDTH, EIGHTHS;
8352
- var init_format = __esm({
8353
- "src/usage/format.ts"() {
8354
- "use strict";
8355
- DIM = "\x1B[2m";
8356
- BOLD = "\x1B[1m";
8357
- RESET = "\x1B[0m";
8358
- GREEN = "\x1B[32m";
8359
- YELLOW = "\x1B[33m";
8360
- RED = "\x1B[31m";
8361
- BAR_WIDTH = 40;
8362
- EIGHTHS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
8491
+ function parseCodexWhamUsage(body, headers = {}, nowMs = Date.now()) {
8492
+ if (!body || typeof body !== "object") {
8493
+ throw new CodexApiError("Codex usage response missing body", "parse");
8363
8494
  }
8364
- });
8365
-
8366
- // src/pricing.ts
8367
- function findPricing(model) {
8368
- for (const [prefix, pricing] of PRICING) {
8369
- if (model.startsWith(prefix)) return pricing;
8495
+ const b = body;
8496
+ const rateLimit = b.rate_limit && typeof b.rate_limit === "object" ? b.rate_limit : {};
8497
+ const headerPrimary = numberOf3(headers["x-codex-primary-used-percent"]);
8498
+ const headerSecondary = numberOf3(headers["x-codex-secondary-used-percent"]);
8499
+ const primary = rateLimit.primary_window && typeof rateLimit.primary_window === "object" ? rateLimit.primary_window : null;
8500
+ const secondary = rateLimit.secondary_window && typeof rateLimit.secondary_window === "object" ? rateLimit.secondary_window : null;
8501
+ const candidates = [
8502
+ { raw: primary, headerPercent: headerPrimary, fallback: "five_hour" },
8503
+ { raw: secondary, headerPercent: headerSecondary, fallback: "seven_day" }
8504
+ ];
8505
+ let fiveHour = null;
8506
+ let sevenDay = null;
8507
+ for (const c of candidates) {
8508
+ const secs = windowSeconds(c.raw);
8509
+ const window = normalizeWindow2(c.raw, c.headerPercent, nowMs);
8510
+ if (!window) continue;
8511
+ if (secs === FIVE_HOUR_SECONDS) fiveHour = window;
8512
+ else if (secs === SEVEN_DAY_SECONDS) sevenDay = window;
8370
8513
  }
8371
- return null;
8514
+ if (!fiveHour || !sevenDay) {
8515
+ for (const c of candidates) {
8516
+ const secs = windowSeconds(c.raw);
8517
+ if (secs === FIVE_HOUR_SECONDS || secs === SEVEN_DAY_SECONDS) continue;
8518
+ const window = normalizeWindow2(c.raw, c.headerPercent, nowMs);
8519
+ if (!window) continue;
8520
+ if (c.fallback === "five_hour" && !fiveHour) fiveHour = window;
8521
+ else if (c.fallback === "seven_day" && !sevenDay) sevenDay = window;
8522
+ }
8523
+ }
8524
+ const planRaw = b.plan_type;
8525
+ const planType = typeof planRaw === "string" && planRaw.trim() ? planRaw.trim() : null;
8526
+ return {
8527
+ five_hour: fiveHour ?? { utilization: null, resets_at: null },
8528
+ seven_day: sevenDay ?? { utilization: null, resets_at: null },
8529
+ plan_type: planType
8530
+ };
8372
8531
  }
8373
- function estimateCost(model, usage2) {
8374
- const p = findPricing(model);
8375
- if (!p) return null;
8376
- return (usage2.input * p.input + usage2.output * p.output + usage2.cacheRead * p.cacheRead + usage2.cacheWrite * p.cacheWrite) / 1e6;
8532
+ async function fetchUsageResponse(accessToken, accountId) {
8533
+ let res;
8534
+ try {
8535
+ res = await fetch(USAGE_URL, {
8536
+ headers: authHeaders2(accessToken, accountId)
8537
+ });
8538
+ } catch (err) {
8539
+ throw new CodexApiError(
8540
+ `Network error reaching Codex usage: ${err.message}`,
8541
+ "network"
8542
+ );
8543
+ }
8544
+ const headers = {};
8545
+ res.headers.forEach((value, key) => {
8546
+ headers[key.toLowerCase()] = value;
8547
+ });
8548
+ let body = null;
8549
+ try {
8550
+ body = await res.json();
8551
+ } catch {
8552
+ }
8553
+ return { status: res.status, body, headers };
8377
8554
  }
8378
- var PRICING;
8379
- var init_pricing = __esm({
8380
- "src/pricing.ts"() {
8555
+ async function fetchCodexUsage() {
8556
+ const loaded = loadAuth();
8557
+ let auth = loaded.auth;
8558
+ let access = auth.tokens.access_token.trim();
8559
+ const accountId = auth.tokens?.account_id;
8560
+ if (needsRefresh2(access, auth.last_refresh)) {
8561
+ const refreshed = await refreshAccessToken2(auth, loaded.path);
8562
+ if (refreshed) {
8563
+ access = refreshed.accessToken;
8564
+ auth = refreshed.auth;
8565
+ }
8566
+ }
8567
+ let { status: status2, body, headers } = await fetchUsageResponse(access, accountId);
8568
+ if (status2 === 401 || status2 === 403) {
8569
+ const refreshed = await refreshAccessToken2(auth, loaded.path);
8570
+ if (refreshed) {
8571
+ access = refreshed.accessToken;
8572
+ ({ status: status2, body, headers } = await fetchUsageResponse(access, accountId));
8573
+ }
8574
+ }
8575
+ if (status2 < 200 || status2 >= 300) {
8576
+ throw new CodexApiError(
8577
+ `Codex usage returned HTTP ${status2}`,
8578
+ status2 === 401 || status2 === 403 ? "no_auth" : "http"
8579
+ );
8580
+ }
8581
+ const windows = parseCodexWhamUsage(body, headers);
8582
+ return {
8583
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
8584
+ agent: "codex",
8585
+ five_hour: windows.five_hour,
8586
+ seven_day: windows.seven_day,
8587
+ seven_day_opus: null,
8588
+ seven_day_sonnet: null,
8589
+ seven_day_oauth_apps: null,
8590
+ seven_day_cowork: null,
8591
+ extra_usage: null,
8592
+ plan_type: windows.plan_type
8593
+ };
8594
+ }
8595
+ var USAGE_URL, REFRESH_URL2, CLIENT_ID, FIVE_HOUR_SECONDS, SEVEN_DAY_SECONDS, REFRESH_BUFFER_MS2, CodexApiError;
8596
+ var init_codex2 = __esm({
8597
+ "src/usage/codex.ts"() {
8381
8598
  "use strict";
8382
- PRICING = [
8383
- ["claude-opus-4", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }],
8384
- ["claude-sonnet-4", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
8385
- ["claude-haiku-4", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
8386
- ["claude-3-5-sonnet", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
8387
- ["claude-3-5-haiku", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
8388
- ["claude-3-opus", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }]
8389
- ];
8599
+ USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
8600
+ REFRESH_URL2 = "https://auth.openai.com/oauth/token";
8601
+ CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
8602
+ FIVE_HOUR_SECONDS = 5 * 60 * 60;
8603
+ SEVEN_DAY_SECONDS = 7 * 24 * 60 * 60;
8604
+ REFRESH_BUFFER_MS2 = 5 * 60 * 1e3;
8605
+ CodexApiError = class extends Error {
8606
+ constructor(message, code) {
8607
+ super(message);
8608
+ this.code = code;
8609
+ }
8610
+ };
8390
8611
  }
8391
8612
  });
8392
8613
 
8393
- // src/table.ts
8394
- function fmtNum(n) {
8395
- return n.toLocaleString("en-US");
8614
+ // src/usage/copilot.ts
8615
+ import { spawn as spawn3 } from "node:child_process";
8616
+ import { existsSync as existsSync10 } from "node:fs";
8617
+ import { dirname as dirname8, join as join8 } from "node:path";
8618
+ function recordOf2(value) {
8619
+ return typeof value === "object" && value !== null ? value : void 0;
8396
8620
  }
8397
- function fmtCost(cost, partial = false) {
8398
- if (cost === null) return "\u2014";
8399
- return `${partial ? "\u2265" : ""}$${cost.toFixed(2)}`;
8621
+ function numberOf4(value) {
8622
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
8400
8623
  }
8401
- function pad(s, width, align = "right") {
8402
- if (align === "left") return s.padEnd(width);
8403
- return s.padStart(width);
8624
+ function clampPercent(value) {
8625
+ return Math.min(100, Math.max(0, value));
8404
8626
  }
8405
- function renderTable(rows, title) {
8406
- const lines = [];
8407
- lines.push("");
8408
- lines.push(`${BOLD2}${title}${RESET2}`);
8409
- lines.push("");
8410
- const header = [
8411
- pad("Date", 12, "left"),
8412
- pad("Models", 20, "left"),
8413
- pad("Input", 12),
8414
- pad("Output", 12),
8415
- pad("Cache Create", 14),
8416
- pad("Cache Read", 14),
8417
- pad("Total", 14),
8418
- pad("Cost (USD)", 12)
8419
- ].join(" ");
8420
- lines.push(`${DIM2}${header}${RESET2}`);
8421
- lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
8422
- let costIncomplete = false;
8423
- for (const r of rows) {
8424
- if (r.cost === null || r.costPartial) costIncomplete = true;
8425
- lines.push([
8426
- pad(r.date, 12, "left"),
8427
- pad(r.models, 20, "left"),
8428
- pad(fmtNum(r.input), 12),
8429
- pad(fmtNum(r.output), 12),
8430
- pad(fmtNum(r.cacheCreate), 14),
8431
- pad(fmtNum(r.cacheRead), 14),
8432
- pad(fmtNum(r.totalTokens), 14),
8433
- pad(fmtCost(r.cost, r.costPartial), 12)
8434
- ].join(" "));
8435
- }
8436
- const totals = rows.reduce(
8437
- (acc, r) => ({
8438
- input: acc.input + r.input,
8439
- output: acc.output + r.output,
8440
- cacheCreate: acc.cacheCreate + r.cacheCreate,
8441
- cacheRead: acc.cacheRead + r.cacheRead,
8442
- totalTokens: acc.totalTokens + r.totalTokens,
8443
- cost: acc.cost + (r.cost ?? 0),
8444
- anyCost: acc.anyCost || r.cost !== null
8445
- }),
8446
- { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, totalTokens: 0, cost: 0, anyCost: false }
8447
- );
8448
- lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
8449
- lines.push(
8450
- `${BOLD2}${[
8451
- pad("Total", 12, "left"),
8452
- pad("", 20, "left"),
8453
- pad(fmtNum(totals.input), 12),
8454
- pad(fmtNum(totals.output), 12),
8455
- pad(fmtNum(totals.cacheCreate), 14),
8456
- pad(fmtNum(totals.cacheRead), 14),
8457
- pad(fmtNum(totals.totalTokens), 14),
8458
- pad(totals.anyCost ? fmtCost(totals.cost, costIncomplete) : fmtCost(null), 12)
8459
- ].join(" ")}${RESET2}`
8460
- );
8461
- if (costIncomplete) {
8462
- lines.push("");
8463
- lines.push(
8464
- `${DIM2} Cost covers priced usage only \u2014 "\u2014" = no priced sessions that day, "\u2265" = lower bound (some sessions had unknown pricing).${RESET2}`
8465
- );
8466
- }
8467
- lines.push("");
8468
- return lines.join("\n");
8627
+ function nextCopilotMonthlyReset(nowMs = Date.now()) {
8628
+ const now = new Date(nowMs);
8629
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1)).toISOString();
8469
8630
  }
8470
- var DIM2, BOLD2, RESET2;
8471
- var init_table = __esm({
8472
- "src/table.ts"() {
8473
- "use strict";
8474
- DIM2 = "\x1B[2m";
8475
- BOLD2 = "\x1B[1m";
8476
- RESET2 = "\x1B[0m";
8477
- }
8478
- });
8479
-
8480
- // src/commands/usage-history.ts
8481
- var usage_history_exports = {};
8482
- __export(usage_history_exports, {
8483
- history: () => history
8484
- });
8485
- async function history(args2) {
8486
- const live = args2.includes("--live");
8487
- const sinceIdx = args2.indexOf("-s");
8488
- const daysIdx = args2.indexOf("--days");
8489
- let sinceDate = null;
8490
- if (sinceIdx !== -1 && args2[sinceIdx + 1]) {
8491
- const raw = args2[sinceIdx + 1];
8492
- sinceDate = /* @__PURE__ */ new Date(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`);
8493
- } else if (daysIdx !== -1 && args2[daysIdx + 1]) {
8494
- const days = parseInt(args2[daysIdx + 1], 10);
8495
- sinceDate = /* @__PURE__ */ new Date();
8496
- sinceDate.setDate(sinceDate.getDate() - days);
8497
- sinceDate.setHours(0, 0, 0, 0);
8631
+ function quotaResetDate(value, nowMs) {
8632
+ const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
8633
+ return Number.isFinite(parsed) && parsed > nowMs + 6e4 ? new Date(parsed).toISOString() : nextCopilotMonthlyReset(nowMs);
8634
+ }
8635
+ function parseCopilotQuota(body, nowMs = Date.now()) {
8636
+ const root = recordOf2(body);
8637
+ const snapshots = recordOf2(root?.quotaSnapshots);
8638
+ if (!snapshots) {
8639
+ throw new CopilotApiError("Copilot quota response missing quotaSnapshots", "parse");
8498
8640
  }
8499
- if (live) {
8500
- await liveStats(sinceDate);
8501
- } else {
8502
- await printStats(sinceDate);
8641
+ const chat = recordOf2(snapshots.chat);
8642
+ const legacy = recordOf2(snapshots.premium_interactions);
8643
+ const chatLimit = numberOf4(chat?.entitlementRequests);
8644
+ const quota = chat && (chatLimit !== void 0 && chatLimit > 0 || chat.isUnlimitedEntitlement === true) ? chat : legacy;
8645
+ if (!quota) {
8646
+ throw new CopilotApiError("Copilot quota response has no chat allowance", "parse");
8503
8647
  }
8648
+ const unlimited = quota.isUnlimitedEntitlement === true;
8649
+ const remainingPercent = numberOf4(quota.remainingPercentage);
8650
+ const utilization = unlimited ? null : remainingPercent === void 0 ? null : clampPercent(100 - remainingPercent);
8651
+ const limit = numberOf4(quota.entitlementRequests) ?? null;
8652
+ const used = numberOf4(quota.usedRequests) ?? null;
8653
+ const unit = quota.tokenBasedBilling === true ? "ai-credits" : "premium-requests";
8654
+ return {
8655
+ captured_at: new Date(nowMs).toISOString(),
8656
+ agent: "copilot",
8657
+ five_hour: { utilization: null, resets_at: null },
8658
+ seven_day: { utilization: null, resets_at: null },
8659
+ monthly: {
8660
+ utilization,
8661
+ resets_at: quotaResetDate(quota.resetDate, nowMs)
8662
+ },
8663
+ monthly_quota: {
8664
+ used,
8665
+ limit,
8666
+ remaining: used !== null && limit !== null ? Math.max(0, limit - used) : null,
8667
+ unit,
8668
+ unlimited
8669
+ },
8670
+ seven_day_opus: null,
8671
+ seven_day_sonnet: null,
8672
+ seven_day_oauth_apps: null,
8673
+ seven_day_cowork: null,
8674
+ extra_usage: null,
8675
+ plan_type: unit === "ai-credits" ? "AI credits" : "premium requests"
8676
+ };
8504
8677
  }
8505
- async function printStats(sinceDate) {
8506
- const sessions = await listSessions({ limit: 1e4 });
8507
- const filtered = sinceDate ? sessions.filter((s) => s.firstTimestamp && new Date(s.firstTimestamp) >= sinceDate) : sessions;
8508
- const rows = aggregateByDay(filtered);
8509
- const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Daily");
8510
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8511
- const todaySessions = filtered.filter((s) => s.firstTimestamp?.startsWith(today)).length;
8512
- const status2 = getServerStatus();
8513
- const serverLine = status2.running ? `Server running on http://localhost:${status2.port}` : "Server not running";
8514
- console.log(output);
8515
- console.log(` ${todaySessions} sessions today \xB7 ${serverLine}`);
8516
- console.log("");
8678
+ function rpcFrame(message) {
8679
+ const body = Buffer.from(JSON.stringify(message), "utf8");
8680
+ return Buffer.concat([
8681
+ Buffer.from(`Content-Length: ${body.length}\r
8682
+ \r
8683
+ `, "ascii"),
8684
+ body
8685
+ ]);
8517
8686
  }
8518
- function displayModel(raw) {
8519
- return raw.replace(/-\d{8}$/, "").replace(/^claude-/, "");
8687
+ function resolveCopilotCliPath() {
8688
+ const configured = process.env.COPILOT_CLI_PATH?.trim();
8689
+ if (configured) return configured;
8690
+ const sibling = join8(dirname8(process.execPath), process.platform === "win32" ? "copilot.cmd" : "copilot");
8691
+ return existsSync10(sibling) ? sibling : process.platform === "win32" ? "copilot.cmd" : "copilot";
8520
8692
  }
8521
- function aggregateByDay(sessions) {
8522
- const byDay = /* @__PURE__ */ new Map();
8523
- for (const s of sessions) {
8524
- const day2 = s.firstTimestamp?.slice(0, 10) ?? "unknown";
8525
- if (!byDay.has(day2)) byDay.set(day2, { sessions: [], models: /* @__PURE__ */ new Set() });
8526
- const bucket = byDay.get(day2);
8527
- bucket.sessions.push(s);
8528
- if (s.model) bucket.models.add(displayModel(s.model));
8529
- }
8530
- const sorted = [...byDay.entries()].sort((a, b) => b[0].localeCompare(a[0]));
8531
- return sorted.map(([date, { sessions: sessions2, models }]) => {
8532
- const usage2 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
8533
- let cost = 0;
8534
- let anyPriced = false;
8535
- let anyUnpriced = false;
8536
- for (const s of sessions2) {
8537
- usage2.input += s.totalUsage.input;
8538
- usage2.output += s.totalUsage.output;
8539
- usage2.cacheRead += s.totalUsage.cacheRead;
8540
- usage2.cacheWrite += s.totalUsage.cacheWrite;
8541
- if (s.model) {
8542
- const c = estimateCost(s.model, s.totalUsage);
8543
- if (c !== null) {
8544
- cost += c;
8545
- anyPriced = true;
8693
+ async function requestQuota(child) {
8694
+ const decoder = new ContentLengthDecoder();
8695
+ const pending = /* @__PURE__ */ new Map();
8696
+ let nextId = 1;
8697
+ const rejectAll = (error) => {
8698
+ for (const waiter of pending.values()) waiter.reject(error);
8699
+ pending.clear();
8700
+ };
8701
+ child.stdout.on("data", (chunk2) => {
8702
+ try {
8703
+ for (const value of decoder.push(chunk2)) {
8704
+ const message = recordOf2(value);
8705
+ const id = numberOf4(message?.id);
8706
+ if (id === void 0) continue;
8707
+ const waiter = pending.get(id);
8708
+ if (!waiter) continue;
8709
+ pending.delete(id);
8710
+ const rpcError = recordOf2(message?.error);
8711
+ if (rpcError) {
8712
+ waiter.reject(new CopilotApiError(
8713
+ typeof rpcError.message === "string" ? rpcError.message : "Copilot RPC request failed",
8714
+ "protocol"
8715
+ ));
8546
8716
  } else {
8547
- anyUnpriced = true;
8717
+ waiter.resolve(message?.result);
8548
8718
  }
8549
8719
  }
8720
+ } catch (error) {
8721
+ rejectAll(error);
8550
8722
  }
8551
- const modelNames = [...models].join(", ");
8552
- return {
8553
- date,
8554
- models: modelNames || "\u2014",
8555
- input: usage2.input,
8556
- output: usage2.output,
8557
- cacheCreate: usage2.cacheWrite,
8558
- cacheRead: usage2.cacheRead,
8559
- totalTokens: usage2.input + usage2.output + usage2.cacheRead + usage2.cacheWrite,
8560
- cost: anyPriced ? cost : null,
8561
- costPartial: anyPriced && anyUnpriced
8562
- };
8563
8723
  });
8564
- }
8565
- async function liveStats(sinceDate) {
8566
- if (process.stdin.isTTY) {
8567
- process.stdin.setRawMode(true);
8568
- process.stdin.resume();
8569
- process.stdin.on("data", (data) => {
8570
- if (data.toString() === "q" || data.toString() === "") {
8571
- process.stdout.write("\x1B[?25h");
8572
- process.exit(0);
8573
- }
8724
+ const exited = new Promise((_, reject) => {
8725
+ child.once("error", (error) => {
8726
+ reject(new CopilotApiError(
8727
+ error.message,
8728
+ error.code === "ENOENT" ? "no_cli" : "protocol"
8729
+ ));
8574
8730
  });
8575
- }
8576
- const refresh = async () => {
8577
- const sessions = await listSessions({ limit: 1e4 });
8578
- const filtered = sinceDate ? sessions.filter((s) => s.firstTimestamp && new Date(s.firstTimestamp) >= sinceDate) : sessions;
8579
- const rows = aggregateByDay(filtered);
8580
- const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Live (q to quit)");
8581
- process.stdout.write("\x1B[2J\x1B[H");
8582
- process.stdout.write("\x1B[?25l");
8583
- process.stdout.write(output);
8584
- const running = filtered.filter((s) => s.status === "running");
8585
- if (running.length > 0) {
8586
- process.stdout.write(" Active Sessions\n");
8587
- for (const s of running.slice(0, 5)) {
8588
- const project = s.projectName.split("/").slice(-2).join("/");
8589
- const tokens = s.totalUsage.input + s.totalUsage.output;
8590
- process.stdout.write(` ${project.padEnd(30)} ${tokens.toLocaleString()} tokens
8591
- `);
8731
+ child.once("exit", (code) => {
8732
+ reject(new CopilotApiError(`Copilot RPC exited before responding (code ${code ?? "unknown"})`, "protocol"));
8733
+ });
8734
+ });
8735
+ const request = (method, params = {}) => {
8736
+ const id = nextId++;
8737
+ const response = new Promise((resolve4, reject) => {
8738
+ pending.set(id, { resolve: resolve4, reject });
8739
+ child.stdin.write(rpcFrame({ jsonrpc: "2.0", id, method, params }));
8740
+ });
8741
+ return Promise.race([response, exited]);
8742
+ };
8743
+ await request("connect", {});
8744
+ return request("account.getQuota", {});
8745
+ }
8746
+ async function fetchCopilotUsage() {
8747
+ const cliPath = resolveCopilotCliPath();
8748
+ const child = spawn3(cliPath, ["--headless", "--no-auto-update", "--stdio", "--log-level", "none"], {
8749
+ cwd: process.cwd(),
8750
+ env: process.env,
8751
+ stdio: ["pipe", "pipe", "pipe"],
8752
+ windowsHide: true,
8753
+ shell: process.platform === "win32"
8754
+ });
8755
+ let stderr = "";
8756
+ child.stderr.on("data", (chunk2) => {
8757
+ stderr = (stderr + chunk2.toString("utf8")).slice(-2e3);
8758
+ });
8759
+ const timeout = new Promise((_, reject) => {
8760
+ const handle = setTimeout(() => {
8761
+ reject(new CopilotApiError("Timed out reading Copilot quota", "timeout"));
8762
+ }, RPC_TIMEOUT_MS);
8763
+ handle.unref();
8764
+ });
8765
+ try {
8766
+ const body = await Promise.race([requestQuota(child), timeout]);
8767
+ return parseCopilotQuota(body);
8768
+ } catch (error) {
8769
+ if (error instanceof CopilotApiError) {
8770
+ const message = `${error.message} ${stderr}`.toLowerCase();
8771
+ if (message.includes("not logged") || message.includes("authentication")) {
8772
+ throw new CopilotApiError("Copilot not logged in. Run `copilot login`.", "no_auth");
8592
8773
  }
8593
- process.stdout.write("\n");
8774
+ throw error;
8594
8775
  }
8595
- };
8596
- await refresh();
8597
- setInterval(refresh, 2e3);
8776
+ throw new CopilotApiError(error.message, "protocol");
8777
+ } finally {
8778
+ child.kill();
8779
+ }
8598
8780
  }
8599
- var init_usage_history = __esm({
8600
- "src/commands/usage-history.ts"() {
8781
+ var RPC_TIMEOUT_MS, CopilotApiError, ContentLengthDecoder;
8782
+ var init_copilot2 = __esm({
8783
+ "src/usage/copilot.ts"() {
8601
8784
  "use strict";
8602
- init_fs();
8603
- init_pricing();
8604
- init_table();
8605
- init_server();
8785
+ RPC_TIMEOUT_MS = 1e4;
8786
+ CopilotApiError = class extends Error {
8787
+ constructor(message, code) {
8788
+ super(message);
8789
+ this.code = code;
8790
+ }
8791
+ };
8792
+ ContentLengthDecoder = class {
8793
+ buffer = Buffer.alloc(0);
8794
+ push(chunk2) {
8795
+ this.buffer = Buffer.concat([this.buffer, chunk2]);
8796
+ const messages = [];
8797
+ while (true) {
8798
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
8799
+ if (headerEnd === -1) break;
8800
+ const header = this.buffer.subarray(0, headerEnd).toString("ascii");
8801
+ const match = header.match(/(?:^|\r\n)Content-Length:\s*(\d+)/i);
8802
+ if (!match) {
8803
+ throw new CopilotApiError("Copilot RPC response missing Content-Length", "protocol");
8804
+ }
8805
+ const length = Number(match[1]);
8806
+ const bodyStart = headerEnd + 4;
8807
+ if (this.buffer.length < bodyStart + length) break;
8808
+ const body = this.buffer.subarray(bodyStart, bodyStart + length).toString("utf8");
8809
+ this.buffer = this.buffer.subarray(bodyStart + length);
8810
+ try {
8811
+ messages.push(JSON.parse(body));
8812
+ } catch {
8813
+ throw new CopilotApiError("Copilot RPC returned invalid JSON", "protocol");
8814
+ }
8815
+ }
8816
+ return messages;
8817
+ }
8818
+ };
8606
8819
  }
8607
8820
  });
8608
8821
 
8609
- // src/commands/usage.ts
8610
- var usage_exports = {};
8611
- __export(usage_exports, {
8612
- usage: () => usage
8613
- });
8614
- async function saveAuxiliarySnapshots() {
8615
- let saved = 0;
8616
- for (const source of agentSources) {
8617
- if (source.kind === "claude-code" || !source.usagePoller) continue;
8618
- try {
8619
- const partial = await source.usagePoller();
8620
- if (!partial) continue;
8621
- appendSnapshot(USAGE_LOG2, {
8622
- ...partial,
8623
- seven_day_opus: null,
8624
- seven_day_sonnet: null,
8625
- seven_day_oauth_apps: null,
8626
- seven_day_cowork: null,
8627
- extra_usage: null
8628
- });
8629
- saved += 1;
8630
- } catch {
8631
- }
8632
- }
8822
+ // src/usage/zai.ts
8823
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
8824
+ function resolveApiKey() {
8633
8825
  try {
8634
- appendSnapshot(USAGE_LOG2, await fetchCodexUsage());
8635
- saved += 1;
8636
- } catch (err) {
8637
- if (!(err instanceof CodexApiError) || err.code !== "no_auth" && err.code !== "api_key_only") {
8638
- console.warn(`codex usage: ${err.message}`);
8826
+ const p = cclensPath("credentials.json");
8827
+ if (existsSync11(p)) {
8828
+ const store = JSON.parse(readFileSync13(p, "utf8"));
8829
+ return store.zai?.apiKey ?? null;
8639
8830
  }
8831
+ } catch {
8640
8832
  }
8641
- try {
8642
- appendSnapshot(USAGE_LOG2, await fetchCopilotUsage());
8643
- saved += 1;
8644
- } catch (err) {
8645
- if (!(err instanceof CopilotApiError) || err.code !== "no_cli" && err.code !== "no_auth") {
8646
- console.warn(`copilot usage: ${err.message}`);
8647
- }
8833
+ return null;
8834
+ }
8835
+ function epochMsToIso(ms) {
8836
+ if (typeof ms !== "number" || Number.isNaN(ms)) return null;
8837
+ return new Date(ms).toISOString();
8838
+ }
8839
+ function windowFromLimit(limit) {
8840
+ return {
8841
+ utilization: typeof limit.percentage === "number" ? limit.percentage : null,
8842
+ resets_at: epochMsToIso(limit.nextResetTime)
8843
+ };
8844
+ }
8845
+ function zaiHeaders(key) {
8846
+ return { Authorization: key, "Accept-Language": "en-US,en" };
8847
+ }
8848
+ async function fetchZaiUsage() {
8849
+ const key = resolveApiKey();
8850
+ if (!key) {
8851
+ throw new ZaiApiError(
8852
+ "No Z.ai API key found. Set ZAI_API_KEY (or GLM_API_KEY), or write ~/.config/zai/key.json.",
8853
+ "no_key"
8854
+ );
8648
8855
  }
8856
+ let res;
8649
8857
  try {
8650
- appendSnapshot(USAGE_LOG2, await fetchGrokUsage());
8651
- saved += 1;
8858
+ res = await fetch(ZAI_QUOTA_ENDPOINT, { headers: zaiHeaders(key) });
8652
8859
  } catch (err) {
8653
- if (!(err instanceof GrokApiError) || err.code !== "no_auth" && err.code !== "not_weekly") {
8654
- console.warn(`grok usage: ${err.message}`);
8655
- }
8656
- }
8657
- return saved;
8658
- }
8659
- async function usage(args2) {
8660
- if (args2.includes("--help") || args2.includes("-h")) {
8661
- console.log(`fleetlens usage \u2014 plan utilization snapshot
8662
-
8663
- Usage:
8664
- fleetlens usage [--save] Current 5h/7d plan utilization
8665
- fleetlens usage --history [-s YYYYMMDD | --days N]
8666
- Daily token / cost history table`);
8667
- return;
8860
+ throw new ZaiApiError(`Network error reaching Z.ai: ${err.message}`, "network");
8668
8861
  }
8669
- if (args2.includes("--history")) {
8670
- const { history: history2 } = await Promise.resolve().then(() => (init_usage_history(), usage_history_exports));
8671
- await history2(args2.filter((a) => a !== "--history"));
8672
- return;
8862
+ if (!res.ok) {
8863
+ throw new ZaiApiError(`Z.ai quota endpoint returned ${res.status} ${res.statusText}`, "http");
8673
8864
  }
8674
- const save = args2.includes("--save");
8675
- let snapshot;
8865
+ let body;
8676
8866
  try {
8677
- snapshot = await fetchUsage();
8678
- if (save) appendSnapshot(USAGE_LOG2, snapshot);
8679
- process.stdout.write(formatUsage(snapshot));
8867
+ body = await res.json();
8680
8868
  } catch (err) {
8681
- if (!save || !(err instanceof UsageApiError)) {
8682
- if (!(err instanceof UsageApiError)) throw err;
8683
- console.error(`Error: ${err.message}`);
8684
- process.exitCode = 1;
8685
- return;
8869
+ throw new ZaiApiError(`Failed to parse Z.ai quota response: ${err.message}`, "parse");
8870
+ }
8871
+ const b = body;
8872
+ const limits = b.data?.limits ?? [];
8873
+ let fiveHour = null;
8874
+ let sevenDay = null;
8875
+ let webSearch = null;
8876
+ for (const limit of limits) {
8877
+ if (limit.type === "TIME_LIMIT") {
8878
+ const percentage = typeof limit.percentage === "number" ? limit.percentage : null;
8879
+ webSearch = {
8880
+ used: percentage,
8881
+ limit: percentage !== null ? 100 : null
8882
+ };
8883
+ continue;
8686
8884
  }
8687
- const saved = await saveAuxiliarySnapshots();
8688
- if (saved === 0) {
8689
- console.error(`Error: ${err.message}`);
8690
- process.exitCode = 1;
8885
+ const window = windowFromLimit(limit);
8886
+ if (limit.unit === 6) {
8887
+ sevenDay = sevenDay ?? window;
8888
+ } else {
8889
+ fiveHour = fiveHour ?? window;
8691
8890
  }
8692
- return;
8693
8891
  }
8694
- if (save) await saveAuxiliarySnapshots();
8695
- process.stdout.write("\n (tip: run with --history for the daily token/cost table)\n");
8892
+ let planType = null;
8893
+ try {
8894
+ const subRes = await fetch(ZAI_SUBSCRIPTION_ENDPOINT, { headers: zaiHeaders(key) });
8895
+ if (subRes.ok) {
8896
+ const sub = await subRes.json();
8897
+ const entry = sub.data?.[0];
8898
+ planType = entry?.productName || entry?.planName || entry?.name || null;
8899
+ }
8900
+ } catch {
8901
+ }
8902
+ return {
8903
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
8904
+ agent: "zai",
8905
+ five_hour: fiveHour ?? { utilization: null, resets_at: null },
8906
+ seven_day: sevenDay ?? { utilization: null, resets_at: null },
8907
+ seven_day_opus: null,
8908
+ seven_day_sonnet: null,
8909
+ seven_day_oauth_apps: null,
8910
+ seven_day_cowork: null,
8911
+ extra_usage: null,
8912
+ plan_type: planType,
8913
+ web_search_quota: webSearch ?? null
8914
+ };
8696
8915
  }
8697
- var USAGE_LOG2;
8698
- var init_usage = __esm({
8699
- "src/commands/usage.ts"() {
8916
+ var ZAI_QUOTA_ENDPOINT, ZAI_SUBSCRIPTION_ENDPOINT, ZaiApiError;
8917
+ var init_zai = __esm({
8918
+ "src/usage/zai.ts"() {
8700
8919
  "use strict";
8701
- init_api();
8702
- init_grok2();
8703
- init_codex2();
8704
- init_copilot2();
8705
- init_format();
8706
- init_storage();
8707
8920
  init_fs();
8708
- USAGE_LOG2 = cclensPath("usage.jsonl");
8921
+ ZAI_QUOTA_ENDPOINT = "https://api.z.ai/api/monitor/usage/quota/limit";
8922
+ ZAI_SUBSCRIPTION_ENDPOINT = "https://api.z.ai/api/biz/subscription/list";
8923
+ ZaiApiError = class extends Error {
8924
+ constructor(message, code) {
8925
+ super(message);
8926
+ this.code = code;
8927
+ }
8928
+ };
8709
8929
  }
8710
8930
  });
8711
8931
 
8712
- // ../entries/dist/types.js
8713
- function pendingEnrichment() {
8714
- return {
8715
- status: "pending",
8716
- generated_at: null,
8717
- model: null,
8718
- cost_usd: null,
8719
- error: null,
8720
- brief_summary: null,
8721
- underlying_goal: null,
8722
- friction_detail: null,
8723
- user_instructions: [],
8724
- outcome: null,
8725
- claude_helpfulness: null,
8726
- goal_categories: {},
8727
- retry_count: 0
8728
- };
8729
- }
8730
- function skippedTrivialEnrichment(generatedAt) {
8731
- return {
8732
- status: "skipped_trivial",
8733
- generated_at: generatedAt,
8734
- model: null,
8735
- cost_usd: null,
8736
- error: null,
8737
- brief_summary: null,
8738
- underlying_goal: null,
8739
- friction_detail: null,
8740
- user_instructions: [],
8741
- outcome: "trivial",
8742
- claude_helpfulness: null,
8743
- goal_categories: {},
8744
- retry_count: 0
8745
- };
8746
- }
8747
- function entryKey(sessionId, localDay2) {
8748
- return `${sessionId}__${localDay2}`;
8749
- }
8750
- function parseEntryKey(key) {
8751
- const m = /^([^_]+(?:_[^_]+)*)__(\d{4}-\d{2}-\d{2})$/.exec(key);
8752
- if (!m)
8753
- return null;
8754
- return { session_id: m[1], local_day: m[2] };
8755
- }
8756
- var CURRENT_ENTRY_SCHEMA_VERSION, GOAL_CATEGORIES, CURRENT_DAY_DIGEST_SCHEMA_VERSION, CURRENT_WEEK_DIGEST_SCHEMA_VERSION, CURRENT_MONTH_DIGEST_SCHEMA_VERSION, PROMPT_FRAME_ORIGIN;
8757
- var init_types = __esm({
8758
- "../entries/dist/types.js"() {
8759
- "use strict";
8760
- CURRENT_ENTRY_SCHEMA_VERSION = 2;
8761
- GOAL_CATEGORIES = [
8762
- "build",
8763
- "plan",
8764
- "debug",
8765
- "review",
8766
- "steer",
8767
- "meta",
8768
- "research",
8769
- "refactor",
8770
- "test",
8771
- "release",
8772
- "warmup_minimal"
8773
- ];
8774
- CURRENT_DAY_DIGEST_SCHEMA_VERSION = 2;
8775
- CURRENT_WEEK_DIGEST_SCHEMA_VERSION = 2;
8776
- CURRENT_MONTH_DIGEST_SCHEMA_VERSION = 2;
8777
- PROMPT_FRAME_ORIGIN = {
8778
- teammate: "claude-feature",
8779
- "task-notification": "claude-feature",
8780
- "local-command-caveat": "claude-feature",
8781
- "image-attached": "claude-feature",
8782
- "slash-command": "claude-feature",
8783
- "handoff-prose": "personal-habit"
8784
- };
8932
+ // src/usage/format.ts
8933
+ function formatMultiAgentUsage(byAgent, opts = {}) {
8934
+ const kinds = Object.keys(byAgent).sort(compareAgents);
8935
+ if (kinds.length === 0) return "";
8936
+ const lines = [];
8937
+ lines.push("");
8938
+ if (opts.watch) {
8939
+ const sec = opts.intervalSec ?? 2;
8940
+ lines.push(
8941
+ ` ${BOLD}Fleetlens Usage${RESET} ${DIM}live \xB7 every ${sec}s \xB7 Ctrl+C to quit${RESET}`
8942
+ );
8943
+ } else {
8944
+ lines.push(` ${BOLD}Fleetlens Usage${RESET}`);
8785
8945
  }
8786
- });
8787
-
8788
- // ../parser/dist/types.js
8789
- var init_types2 = __esm({
8790
- "../parser/dist/types.js"() {
8791
- "use strict";
8946
+ lines.push("");
8947
+ for (const kind of kinds) {
8948
+ const snap = byAgent[kind];
8949
+ lines.push(...formatAgentBlock(kind, snap));
8792
8950
  }
8793
- });
8794
-
8795
- // ../parser/dist/presentation.js
8796
- function rowPrimaryIndex(r) {
8797
- if (r.kind === "tool-group")
8798
- return r.events[0].index;
8799
- return r.event.index;
8951
+ lines.push("");
8952
+ return lines.join("\n");
8800
8953
  }
8801
- function buildMegaRows(rows) {
8802
- const out = [];
8803
- let buffer = [];
8804
- let turnAnchor = null;
8805
- const flush = () => {
8806
- if (buffer.length === 0)
8807
- return;
8808
- let agentMessages = 0;
8809
- let toolCalls = 0;
8810
- let errors = 0;
8811
- let firstAgentIndex;
8812
- const toolMap = /* @__PURE__ */ new Map();
8813
- const seenMsgIds = /* @__PURE__ */ new Set();
8814
- const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
8815
- for (let i = 0; i < buffer.length; i++) {
8816
- const r = buffer[i];
8817
- if (r.kind === "agent") {
8818
- agentMessages++;
8819
- if (firstAgentIndex === void 0)
8820
- firstAgentIndex = i;
8821
- const msgId = r.event.messageId;
8822
- const usage2 = r.event.usage;
8823
- if (msgId && !seenMsgIds.has(msgId) && usage2) {
8824
- seenMsgIds.add(msgId);
8825
- tokens.input += usage2.input;
8826
- tokens.output += usage2.output;
8827
- tokens.cacheRead += usage2.cacheRead;
8828
- tokens.cacheWrite += usage2.cacheWrite;
8829
- }
8830
- } else if (r.kind === "tool-group") {
8831
- toolCalls += r.count;
8832
- for (const t of r.toolNames) {
8833
- toolMap.set(t.name, (toolMap.get(t.name) ?? 0) + t.count);
8834
- }
8835
- } else if (r.kind === "error") {
8836
- errors++;
8837
- }
8954
+ function formatAgentBlock(kind, snapshot) {
8955
+ const lines = [];
8956
+ const title = agentTitle(kind);
8957
+ const plan = snapshot.plan_type ? ` ${DIM}${snapshot.plan_type}${RESET}` : "";
8958
+ lines.push(` ${BOLD}${title}${RESET}${plan}`);
8959
+ let renderedMeter = false;
8960
+ for (const [label, window] of agentWindows(kind, snapshot)) {
8961
+ if (!window || window.utilization === null) continue;
8962
+ renderedMeter = true;
8963
+ const pct = window.utilization;
8964
+ const bar = renderBar(pct);
8965
+ const pctStr = `${pct.toFixed(1)}%`.padStart(6);
8966
+ const labelStr = label.padEnd(16);
8967
+ lines.push(` ${labelStr}${bar} ${BOLD}${pctStr}${RESET}`);
8968
+ if (window.resets_at) {
8969
+ lines.push(` ${" ".repeat(16)}${DIM}resets ${formatRelative(window.resets_at)}${RESET}`);
8838
8970
  }
8839
- let finalAgentIndex;
8840
- for (let i = buffer.length - 1; i >= 0; i--) {
8841
- const r = buffer[i];
8842
- if (r.kind !== "agent")
8843
- continue;
8844
- const prev = buffer[i - 1];
8845
- if (prev?.kind === "task-notification")
8846
- continue;
8847
- finalAgentIndex = i;
8848
- break;
8971
+ }
8972
+ if ((kind === "claude-code" || !kind) && snapshot.extra_usage?.is_enabled) {
8973
+ renderedMeter = true;
8974
+ const extra = snapshot.extra_usage;
8975
+ if (extra.utilization !== null) {
8976
+ lines.push(
8977
+ ` ${"Extra usage".padEnd(16)}${renderBar(extra.utilization)} ${BOLD}${extra.utilization.toFixed(1)}%${RESET}`
8978
+ );
8849
8979
  }
8850
- if (finalAgentIndex === void 0) {
8851
- for (let i = buffer.length - 1; i >= 0; i--) {
8852
- if (buffer[i].kind === "agent") {
8853
- finalAgentIndex = i;
8854
- break;
8855
- }
8856
- }
8980
+ if (extra.used_credits !== null && extra.monthly_limit !== null) {
8981
+ lines.push(
8982
+ ` ${" ".repeat(16)}${DIM}${extra.used_credits} / ${extra.monthly_limit} credits${RESET}`
8983
+ );
8857
8984
  }
8858
- const firstAgentPreview = firstAgentIndex !== void 0 && buffer[firstAgentIndex].kind === "agent" ? buffer[firstAgentIndex].event.preview : void 0;
8859
- const finalAgentPreview = finalAgentIndex !== void 0 && buffer[finalAgentIndex].kind === "agent" ? buffer[finalAgentIndex].event.preview : void 0;
8860
- const toolNames = Array.from(toolMap.entries()).sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
8861
- const first = buffer[0];
8862
- const last = buffer[buffer.length - 1];
8863
- const anchorOffset = turnAnchor?.tOffsetMs;
8864
- const start2 = anchorOffset ?? first.tOffsetMs;
8865
- const end = last.tOffsetMs;
8866
- const durationMs = start2 !== void 0 && end !== void 0 && end >= start2 ? end - start2 : void 0;
8867
- out.push({
8868
- kind: "turn",
8869
- rows: buffer,
8870
- firstPrimaryIndex: rowPrimaryIndex(first),
8871
- tOffsetMs: start2,
8872
- durationMs,
8873
- summary: {
8874
- agentMessages,
8875
- toolCalls,
8876
- errors,
8877
- firstAgentPreview,
8878
- finalAgentPreview,
8879
- firstAgentIndex,
8880
- finalAgentIndex,
8881
- toolNames,
8882
- totalTokens: tokens
8985
+ }
8986
+ if (kind === "zai" && snapshot.web_search_quota) {
8987
+ const used = snapshot.web_search_quota.used;
8988
+ if (used !== null && used !== void 0) {
8989
+ renderedMeter = true;
8990
+ lines.push(
8991
+ ` ${"Web search".padEnd(16)}${renderBar(used)} ${BOLD}${used.toFixed(0)}%${RESET}`
8992
+ );
8993
+ }
8994
+ }
8995
+ if (kind === "copilot" && snapshot.monthly_quota) {
8996
+ const q = snapshot.monthly_quota;
8997
+ const unit = q.unit === "premium-requests" ? "premium requests" : "AI credits";
8998
+ if (q.unlimited) {
8999
+ renderedMeter = true;
9000
+ lines.push(` ${"Monthly".padEnd(16)}${BOLD}Limit not reported${RESET}`);
9001
+ if (q.used !== null) {
9002
+ lines.push(
9003
+ ` ${" ".repeat(16)}${DIM}${q.used.toLocaleString("en-US")} ${unit} reported by Copilot${RESET}`
9004
+ );
8883
9005
  }
8884
- });
8885
- buffer = [];
8886
- };
8887
- for (const r of rows) {
8888
- if (r.kind === "user" || r.kind === "interrupt") {
8889
- flush();
8890
- out.push(r);
8891
- turnAnchor = r;
8892
- } else {
8893
- buffer.push(r);
9006
+ } else if (q.used !== null && q.limit !== null) {
9007
+ lines.push(
9008
+ ` ${" ".repeat(16)}${DIM}${q.used} / ${q.limit} ${unit}${RESET}`
9009
+ );
8894
9010
  }
8895
9011
  }
8896
- flush();
8897
- return out;
9012
+ if (!renderedMeter) {
9013
+ lines.push(` ${DIM}(no utilization windows)${RESET}`);
9014
+ }
9015
+ lines.push(
9016
+ ` ${DIM}sampled ${formatRelative(snapshot.captured_at)}${RESET}`
9017
+ );
9018
+ lines.push("");
9019
+ return lines;
8898
9020
  }
8899
- function parseTaskNotification(text) {
8900
- if (!TASK_NOTIFICATION_RE.test(text))
8901
- return null;
8902
- const pick = (tag) => {
8903
- const m = text.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
8904
- return m ? m[1].trim() : void 0;
8905
- };
8906
- const statusRaw = (pick("status") ?? "").toLowerCase();
8907
- let status2 = "unknown";
8908
- if (statusRaw === "success" || statusRaw === "ok" || statusRaw === "done" || statusRaw === "completed")
8909
- status2 = "success";
8910
- else if (statusRaw === "failed" || statusRaw === "error" || statusRaw === "fail")
8911
- status2 = "failed";
8912
- else if (statusRaw === "running" || statusRaw === "in_progress")
8913
- status2 = "running";
8914
- return {
8915
- status: status2,
8916
- summary: pick("summary") ?? "Background task notification",
8917
- taskId: pick("task-id"),
8918
- toolUseId: pick("tool-use-id"),
8919
- outputFile: pick("output-file")
8920
- };
9021
+ function agentWindows(kind, snapshot) {
9022
+ if (kind === "copilot") {
9023
+ return [["Monthly", snapshot.monthly]];
9024
+ }
9025
+ if (kind === "grok") {
9026
+ return [["7 day", snapshot.seven_day]];
9027
+ }
9028
+ if (kind === "codex" && snapshot.five_hour?.utilization == null) {
9029
+ return [["7 day", snapshot.seven_day]];
9030
+ }
9031
+ const rows = [
9032
+ ["5 hour", snapshot.five_hour],
9033
+ ["7 day", snapshot.seven_day]
9034
+ ];
9035
+ if (kind === "claude-code" || !snapshot.agent) {
9036
+ if (snapshot.seven_day_opus?.utilization != null) {
9037
+ rows.push(["7 day (Opus)", snapshot.seven_day_opus]);
9038
+ }
9039
+ if (snapshot.seven_day_sonnet?.utilization != null) {
9040
+ rows.push(["7 day (Sonnet)", snapshot.seven_day_sonnet]);
9041
+ }
9042
+ if (snapshot.seven_day_oauth_apps?.utilization != null) {
9043
+ rows.push(["7 day (OAuth apps)", snapshot.seven_day_oauth_apps]);
9044
+ }
9045
+ if (snapshot.seven_day_cowork?.utilization != null) {
9046
+ rows.push(["7 day (Cowork)", snapshot.seven_day_cowork]);
9047
+ }
9048
+ }
9049
+ return rows;
8921
9050
  }
8922
- function isAttachmentOrMeta(e) {
8923
- return e.role === "system" || e.role === "meta";
9051
+ function agentTitle(kind) {
9052
+ if (isAgentKind(kind)) {
9053
+ return getAgentMetadata(kind)?.displayName ?? kind;
9054
+ }
9055
+ return kind;
8924
9056
  }
8925
- function firstTextOfBlocks(blocks) {
8926
- for (const b of blocks) {
8927
- if (b?.type === "text")
8928
- return b.text;
9057
+ function compareAgents(a, b) {
9058
+ const ia = AGENT_PRIORITY.indexOf(a);
9059
+ const ib = AGENT_PRIORITY.indexOf(b);
9060
+ if (ia !== -1 || ib !== -1) {
9061
+ if (ia === -1) return 1;
9062
+ if (ib === -1) return -1;
9063
+ return ia - ib;
8929
9064
  }
8930
- return void 0;
9065
+ return a.localeCompare(b);
8931
9066
  }
8932
- function hasZeroUsage(e) {
8933
- if (!e.usage)
8934
- return false;
8935
- const u = e.usage;
8936
- return u.input === 0 && u.output === 0 && u.cacheRead === 0 && u.cacheWrite === 0;
9067
+ function renderBar(utilization) {
9068
+ const clamped = Math.max(0, Math.min(100, utilization));
9069
+ const color = clamped >= 90 ? RED : clamped >= 70 ? YELLOW : GREEN;
9070
+ const totalEighths = Math.round(clamped / 100 * BAR_WIDTH * 8);
9071
+ const fullBlocks = Math.floor(totalEighths / 8);
9072
+ const remainder = totalEighths % 8;
9073
+ const partial = EIGHTHS[remainder];
9074
+ const filledCells = fullBlocks + (partial ? 1 : 0);
9075
+ const emptyCells = BAR_WIDTH - filledCells;
9076
+ const filled = "\u2588".repeat(fullBlocks) + partial;
9077
+ const empty = "\xB7".repeat(emptyCells);
9078
+ return `${color}${filled}${RESET}${DIM}${empty}${RESET}`;
8937
9079
  }
8938
- function buildPresentation(events) {
8939
- const byMessageId = /* @__PURE__ */ new Map();
8940
- for (const e of events) {
8941
- if (!e.messageId)
8942
- continue;
8943
- const list = byMessageId.get(e.messageId) ?? [];
8944
- list.push(e);
8945
- byMessageId.set(e.messageId, list);
9080
+ function formatRelative(iso) {
9081
+ const then = new Date(iso).getTime();
9082
+ const now = Date.now();
9083
+ const diffSec = Math.round((then - now) / 1e3);
9084
+ const abs = Math.abs(diffSec);
9085
+ const past = diffSec < 0;
9086
+ let value;
9087
+ if (abs < 60) {
9088
+ value = `${abs}s`;
9089
+ } else if (abs < 3600) {
9090
+ value = `${Math.floor(abs / 60)}m`;
9091
+ } else if (abs < 86400) {
9092
+ const h = Math.floor(abs / 3600);
9093
+ const m = Math.floor(abs % 3600 / 60);
9094
+ value = m > 0 ? `${h}h${m}m` : `${h}h`;
9095
+ } else {
9096
+ const d = Math.floor(abs / 86400);
9097
+ const h = Math.floor(abs % 86400 / 3600);
9098
+ value = h > 0 ? `${d}d${h}h` : `${d}d`;
8946
9099
  }
8947
- const visible = events.filter((e) => {
8948
- if (isAttachmentOrMeta(e))
8949
- return false;
8950
- if (e.role === "tool-result")
8951
- return false;
8952
- if (e.role === "agent-thinking")
8953
- return false;
8954
- if (e.role === "user") {
8955
- const txt = firstTextOfBlocks(e.blocks) ?? "";
8956
- if (e.blocks.length === 0 || !txt.trim())
8957
- return false;
8958
- if (SLASH_COMMAND_RE.test(txt))
8959
- return true;
8960
- if (TASK_NOTIFICATION_RE.test(txt))
8961
- return true;
8962
- if (isFrameworkInjectedUserInput(txt))
8963
- return false;
8964
- }
8965
- return true;
8966
- });
8967
- const rows = [];
8968
- for (let i = 0; i < visible.length; i++) {
8969
- const e = visible[i];
8970
- if (e.role === "tool-call") {
8971
- const start2 = i;
8972
- while (i + 1 < visible.length && visible[i + 1].role === "tool-call") {
8973
- i++;
8974
- }
8975
- const group = visible.slice(start2, i + 1);
8976
- const order = [];
8977
- const counts = /* @__PURE__ */ new Map();
8978
- for (const ev of group) {
8979
- const name = ev.toolName ?? "tool";
8980
- if (!counts.has(name))
8981
- order.push(name);
8982
- counts.set(name, (counts.get(name) ?? 0) + 1);
8983
- }
8984
- const toolNames = order.map((name) => ({
8985
- name,
8986
- count: counts.get(name) ?? 1
8987
- }));
8988
- rows.push({
8989
- kind: "tool-group",
8990
- toolNames,
8991
- count: group.length,
8992
- events: group,
8993
- tOffsetMs: group[0].tOffsetMs,
8994
- gapMs: group[0].gapMs
8995
- });
8996
- continue;
8997
- }
8998
- if (e.role === "user") {
8999
- const txt = firstTextOfBlocks(e.blocks) ?? "";
9000
- const taskNotif = parseTaskNotification(txt);
9001
- if (taskNotif) {
9002
- rows.push({
9003
- kind: "task-notification",
9004
- event: e,
9005
- status: taskNotif.status,
9006
- summary: taskNotif.summary,
9007
- taskId: taskNotif.taskId,
9008
- toolUseId: taskNotif.toolUseId,
9009
- outputFile: taskNotif.outputFile,
9010
- tOffsetMs: e.tOffsetMs,
9011
- gapMs: e.gapMs
9012
- });
9013
- continue;
9014
- }
9015
- if (INTERRUPT_RE.test(txt)) {
9016
- rows.push({
9017
- kind: "interrupt",
9018
- event: e,
9019
- tOffsetMs: e.tOffsetMs,
9020
- gapMs: e.gapMs
9021
- });
9022
- continue;
9023
- }
9024
- const slashMatch = txt.match(SLASH_COMMAND_RE);
9025
- if (slashMatch) {
9026
- const cmd = slashMatch[1];
9027
- const args2 = (slashMatch[2] ?? "").trim();
9028
- const pretty = args2 ? `${cmd} ${args2}` : cmd;
9029
- rows.push({
9030
- kind: "user",
9031
- event: e,
9032
- displayPreview: pretty,
9033
- displayBlocks: [{ type: "text", text: pretty }],
9034
- tOffsetMs: e.tOffsetMs,
9035
- gapMs: e.gapMs
9036
- });
9037
- continue;
9100
+ return past ? `${value} ago` : `in ${value}`;
9101
+ }
9102
+ var DIM, BOLD, RESET, GREEN, YELLOW, RED, BAR_WIDTH, EIGHTHS, AGENT_PRIORITY;
9103
+ var init_format = __esm({
9104
+ "src/usage/format.ts"() {
9105
+ "use strict";
9106
+ init_dist();
9107
+ DIM = "\x1B[2m";
9108
+ BOLD = "\x1B[1m";
9109
+ RESET = "\x1B[0m";
9110
+ GREEN = "\x1B[32m";
9111
+ YELLOW = "\x1B[33m";
9112
+ RED = "\x1B[31m";
9113
+ BAR_WIDTH = 40;
9114
+ EIGHTHS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
9115
+ AGENT_PRIORITY = ["claude-code", "codex", "copilot", "zai", "grok"];
9116
+ }
9117
+ });
9118
+
9119
+ // src/pricing.ts
9120
+ function findPricing(model) {
9121
+ for (const [prefix, pricing] of PRICING) {
9122
+ if (model.startsWith(prefix)) return pricing;
9123
+ }
9124
+ return null;
9125
+ }
9126
+ function estimateCost(model, usage2) {
9127
+ const p = findPricing(model);
9128
+ if (!p) return null;
9129
+ return (usage2.input * p.input + usage2.output * p.output + usage2.cacheRead * p.cacheRead + usage2.cacheWrite * p.cacheWrite) / 1e6;
9130
+ }
9131
+ var PRICING;
9132
+ var init_pricing = __esm({
9133
+ "src/pricing.ts"() {
9134
+ "use strict";
9135
+ PRICING = [
9136
+ ["claude-opus-4", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }],
9137
+ ["claude-sonnet-4", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
9138
+ ["claude-haiku-4", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
9139
+ ["claude-3-5-sonnet", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
9140
+ ["claude-3-5-haiku", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
9141
+ ["claude-3-opus", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }]
9142
+ ];
9143
+ }
9144
+ });
9145
+
9146
+ // src/table.ts
9147
+ function fmtNum(n) {
9148
+ return n.toLocaleString("en-US");
9149
+ }
9150
+ function fmtCost(cost, partial = false) {
9151
+ if (cost === null) return "\u2014";
9152
+ return `${partial ? "\u2265" : ""}$${cost.toFixed(2)}`;
9153
+ }
9154
+ function pad(s, width, align = "right") {
9155
+ if (align === "left") return s.padEnd(width);
9156
+ return s.padStart(width);
9157
+ }
9158
+ function renderTable(rows, title) {
9159
+ const lines = [];
9160
+ lines.push("");
9161
+ lines.push(`${BOLD2}${title}${RESET2}`);
9162
+ lines.push("");
9163
+ const header = [
9164
+ pad("Date", 12, "left"),
9165
+ pad("Models", 20, "left"),
9166
+ pad("Input", 12),
9167
+ pad("Output", 12),
9168
+ pad("Cache Create", 14),
9169
+ pad("Cache Read", 14),
9170
+ pad("Total", 14),
9171
+ pad("Cost (USD)", 12)
9172
+ ].join(" ");
9173
+ lines.push(`${DIM2}${header}${RESET2}`);
9174
+ lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
9175
+ let costIncomplete = false;
9176
+ for (const r of rows) {
9177
+ if (r.cost === null || r.costPartial) costIncomplete = true;
9178
+ lines.push([
9179
+ pad(r.date, 12, "left"),
9180
+ pad(r.models, 20, "left"),
9181
+ pad(fmtNum(r.input), 12),
9182
+ pad(fmtNum(r.output), 12),
9183
+ pad(fmtNum(r.cacheCreate), 14),
9184
+ pad(fmtNum(r.cacheRead), 14),
9185
+ pad(fmtNum(r.totalTokens), 14),
9186
+ pad(fmtCost(r.cost, r.costPartial), 12)
9187
+ ].join(" "));
9188
+ }
9189
+ const totals = rows.reduce(
9190
+ (acc, r) => ({
9191
+ input: acc.input + r.input,
9192
+ output: acc.output + r.output,
9193
+ cacheCreate: acc.cacheCreate + r.cacheCreate,
9194
+ cacheRead: acc.cacheRead + r.cacheRead,
9195
+ totalTokens: acc.totalTokens + r.totalTokens,
9196
+ cost: acc.cost + (r.cost ?? 0),
9197
+ anyCost: acc.anyCost || r.cost !== null
9198
+ }),
9199
+ { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, totalTokens: 0, cost: 0, anyCost: false }
9200
+ );
9201
+ lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
9202
+ lines.push(
9203
+ `${BOLD2}${[
9204
+ pad("Total", 12, "left"),
9205
+ pad("", 20, "left"),
9206
+ pad(fmtNum(totals.input), 12),
9207
+ pad(fmtNum(totals.output), 12),
9208
+ pad(fmtNum(totals.cacheCreate), 14),
9209
+ pad(fmtNum(totals.cacheRead), 14),
9210
+ pad(fmtNum(totals.totalTokens), 14),
9211
+ pad(totals.anyCost ? fmtCost(totals.cost, costIncomplete) : fmtCost(null), 12)
9212
+ ].join(" ")}${RESET2}`
9213
+ );
9214
+ if (costIncomplete) {
9215
+ lines.push("");
9216
+ lines.push(
9217
+ `${DIM2} Cost covers priced usage only \u2014 "\u2014" = no priced sessions that day, "\u2265" = lower bound (some sessions had unknown pricing).${RESET2}`
9218
+ );
9219
+ }
9220
+ lines.push("");
9221
+ return lines.join("\n");
9222
+ }
9223
+ var DIM2, BOLD2, RESET2;
9224
+ var init_table = __esm({
9225
+ "src/table.ts"() {
9226
+ "use strict";
9227
+ DIM2 = "\x1B[2m";
9228
+ BOLD2 = "\x1B[1m";
9229
+ RESET2 = "\x1B[0m";
9230
+ }
9231
+ });
9232
+
9233
+ // src/commands/usage-history.ts
9234
+ var usage_history_exports = {};
9235
+ __export(usage_history_exports, {
9236
+ history: () => history
9237
+ });
9238
+ async function history(args2) {
9239
+ const live = args2.includes("--live");
9240
+ const sinceIdx = args2.indexOf("-s");
9241
+ const daysIdx = args2.indexOf("--days");
9242
+ let sinceDate = null;
9243
+ if (sinceIdx !== -1 && args2[sinceIdx + 1]) {
9244
+ const raw = args2[sinceIdx + 1];
9245
+ sinceDate = /* @__PURE__ */ new Date(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`);
9246
+ } else if (daysIdx !== -1 && args2[daysIdx + 1]) {
9247
+ const days = parseInt(args2[daysIdx + 1], 10);
9248
+ sinceDate = /* @__PURE__ */ new Date();
9249
+ sinceDate.setDate(sinceDate.getDate() - days);
9250
+ sinceDate.setHours(0, 0, 0, 0);
9251
+ }
9252
+ if (live) {
9253
+ await liveStats(sinceDate);
9254
+ } else {
9255
+ await printStats(sinceDate);
9256
+ }
9257
+ }
9258
+ async function printStats(sinceDate) {
9259
+ const sessions = await listSessions({ limit: 1e4 });
9260
+ const filtered = sinceDate ? sessions.filter((s) => s.firstTimestamp && new Date(s.firstTimestamp) >= sinceDate) : sessions;
9261
+ const rows = aggregateByDay(filtered);
9262
+ const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Daily");
9263
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9264
+ const todaySessions = filtered.filter((s) => s.firstTimestamp?.startsWith(today)).length;
9265
+ const status2 = getServerStatus();
9266
+ const serverLine = status2.running ? `Server running on http://localhost:${status2.port}` : "Server not running";
9267
+ console.log(output);
9268
+ console.log(` ${todaySessions} sessions today \xB7 ${serverLine}`);
9269
+ console.log("");
9270
+ }
9271
+ function displayModel(raw) {
9272
+ return raw.replace(/-\d{8}$/, "").replace(/^claude-/, "");
9273
+ }
9274
+ function aggregateByDay(sessions) {
9275
+ const byDay = /* @__PURE__ */ new Map();
9276
+ for (const s of sessions) {
9277
+ const day2 = s.firstTimestamp?.slice(0, 10) ?? "unknown";
9278
+ if (!byDay.has(day2)) byDay.set(day2, { sessions: [], models: /* @__PURE__ */ new Set() });
9279
+ const bucket = byDay.get(day2);
9280
+ bucket.sessions.push(s);
9281
+ if (s.model) bucket.models.add(displayModel(s.model));
9282
+ }
9283
+ const sorted = [...byDay.entries()].sort((a, b) => b[0].localeCompare(a[0]));
9284
+ return sorted.map(([date, { sessions: sessions2, models }]) => {
9285
+ const usage2 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
9286
+ let cost = 0;
9287
+ let anyPriced = false;
9288
+ let anyUnpriced = false;
9289
+ for (const s of sessions2) {
9290
+ usage2.input += s.totalUsage.input;
9291
+ usage2.output += s.totalUsage.output;
9292
+ usage2.cacheRead += s.totalUsage.cacheRead;
9293
+ usage2.cacheWrite += s.totalUsage.cacheWrite;
9294
+ if (s.model) {
9295
+ const c = estimateCost(s.model, s.totalUsage);
9296
+ if (c !== null) {
9297
+ cost += c;
9298
+ anyPriced = true;
9299
+ } else {
9300
+ anyUnpriced = true;
9301
+ }
9038
9302
  }
9039
- rows.push({
9040
- kind: "user",
9041
- event: e,
9042
- tOffsetMs: e.tOffsetMs,
9043
- gapMs: e.gapMs
9044
- });
9045
- continue;
9046
9303
  }
9047
- if (e.role === "agent") {
9048
- const txt = firstTextOfBlocks(e.blocks) ?? "";
9049
- if (hasZeroUsage(e)) {
9050
- rows.push({
9051
- kind: "model",
9052
- event: e,
9053
- tOffsetMs: e.tOffsetMs,
9054
- gapMs: e.gapMs
9055
- });
9056
- if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
9057
- rows.push({
9058
- kind: "error",
9059
- event: e,
9060
- message: txt,
9061
- tOffsetMs: e.tOffsetMs,
9062
- gapMs: 0
9063
- });
9064
- }
9065
- continue;
9304
+ const modelNames = [...models].join(", ");
9305
+ return {
9306
+ date,
9307
+ models: modelNames || "\u2014",
9308
+ input: usage2.input,
9309
+ output: usage2.output,
9310
+ cacheCreate: usage2.cacheWrite,
9311
+ cacheRead: usage2.cacheRead,
9312
+ totalTokens: usage2.input + usage2.output + usage2.cacheRead + usage2.cacheWrite,
9313
+ cost: anyPriced ? cost : null,
9314
+ costPartial: anyPriced && anyUnpriced
9315
+ };
9316
+ });
9317
+ }
9318
+ async function liveStats(sinceDate) {
9319
+ if (process.stdin.isTTY) {
9320
+ process.stdin.setRawMode(true);
9321
+ process.stdin.resume();
9322
+ process.stdin.on("data", (data) => {
9323
+ if (data.toString() === "q" || data.toString() === "") {
9324
+ process.stdout.write("\x1B[?25h");
9325
+ process.exit(0);
9066
9326
  }
9067
- if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
9068
- rows.push({
9069
- kind: "error",
9070
- event: e,
9071
- message: txt,
9072
- tOffsetMs: e.tOffsetMs,
9073
- gapMs: e.gapMs
9074
- });
9075
- continue;
9327
+ });
9328
+ }
9329
+ const refresh = async () => {
9330
+ const sessions = await listSessions({ limit: 1e4 });
9331
+ const filtered = sinceDate ? sessions.filter((s) => s.firstTimestamp && new Date(s.firstTimestamp) >= sinceDate) : sessions;
9332
+ const rows = aggregateByDay(filtered);
9333
+ const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Live (q to quit)");
9334
+ process.stdout.write("\x1B[2J\x1B[H");
9335
+ process.stdout.write("\x1B[?25l");
9336
+ process.stdout.write(output);
9337
+ const running = filtered.filter((s) => s.status === "running");
9338
+ if (running.length > 0) {
9339
+ process.stdout.write(" Active Sessions\n");
9340
+ for (const s of running.slice(0, 5)) {
9341
+ const project = s.projectName.split("/").slice(-2).join("/");
9342
+ const tokens = s.totalUsage.input + s.totalUsage.output;
9343
+ process.stdout.write(` ${project.padEnd(30)} ${tokens.toLocaleString()} tokens
9344
+ `);
9076
9345
  }
9077
- const grouped = e.messageId ? byMessageId.get(e.messageId) ?? [e] : [e];
9078
- rows.push({
9079
- kind: "agent",
9080
- event: e,
9081
- groupedEvents: grouped,
9082
- tOffsetMs: e.tOffsetMs,
9083
- gapMs: e.gapMs
9084
- });
9085
- continue;
9346
+ process.stdout.write("\n");
9086
9347
  }
9087
- }
9088
- return rows;
9348
+ };
9349
+ await refresh();
9350
+ setInterval(refresh, 2e3);
9089
9351
  }
9090
- var INTERRUPT_RE, RATE_LIMIT_RE, SLASH_COMMAND_RE, TASK_NOTIFICATION_RE;
9091
- var init_presentation = __esm({
9092
- "../parser/dist/presentation.js"() {
9352
+ var init_usage_history = __esm({
9353
+ "src/commands/usage-history.ts"() {
9093
9354
  "use strict";
9094
- init_user_input();
9095
- INTERRUPT_RE = /\[request interrupted|interrupted by user/i;
9096
- RATE_LIMIT_RE = /rate.?limit|overloaded_error|api error|Error sending message/i;
9097
- SLASH_COMMAND_RE = /<command-name>(\/[^<\s]+)<\/command-name>(?:[\s\S]*?<command-args>([\s\S]*?)<\/command-args>)?/;
9098
- TASK_NOTIFICATION_RE = /<task-notification>/;
9355
+ init_fs();
9356
+ init_pricing();
9357
+ init_table();
9358
+ init_server();
9099
9359
  }
9100
9360
  });
9101
9361
 
9102
- // ../parser/dist/aggregate.js
9103
- function isoDay(d) {
9104
- return toLocalDay(d.getTime());
9105
- }
9106
- function rangeDays(start2, end) {
9107
- const out = [];
9108
- const cur = new Date(start2.getFullYear(), start2.getMonth(), start2.getDate());
9109
- const stop2 = new Date(end.getFullYear(), end.getMonth(), end.getDate());
9110
- while (cur.getTime() <= stop2.getTime()) {
9111
- out.push(new Date(cur));
9112
- cur.setDate(cur.getDate() + 1);
9362
+ // src/commands/usage.ts
9363
+ var usage_exports = {};
9364
+ __export(usage_exports, {
9365
+ usage: () => usage
9366
+ });
9367
+ async function saveAuxiliarySnapshots() {
9368
+ let saved = 0;
9369
+ for (const source of agentSources) {
9370
+ if (source.kind === "claude-code" || !source.usagePoller) continue;
9371
+ try {
9372
+ const partial = await source.usagePoller();
9373
+ if (!partial) continue;
9374
+ appendSnapshot(USAGE_LOG2, {
9375
+ ...partial,
9376
+ seven_day_opus: null,
9377
+ seven_day_sonnet: null,
9378
+ seven_day_oauth_apps: null,
9379
+ seven_day_cowork: null,
9380
+ extra_usage: null
9381
+ });
9382
+ saved += 1;
9383
+ } catch {
9384
+ }
9113
9385
  }
9114
- return out;
9115
- }
9116
- function calendarWeek(ref = /* @__PURE__ */ new Date()) {
9117
- const d = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate());
9118
- const dow = d.getDay();
9119
- const daysSinceMon = (dow + 6) % 7;
9120
- const monday = new Date(d);
9121
- monday.setDate(d.getDate() - daysSinceMon);
9122
- const sunday = new Date(monday);
9123
- sunday.setDate(monday.getDate() + 6);
9124
- return { start: monday, end: sunday };
9125
- }
9126
- function priorCalendarWeek(ref = /* @__PURE__ */ new Date()) {
9127
- const cur = calendarWeek(ref);
9128
- const prevEnd = new Date(cur.start);
9129
- prevEnd.setDate(cur.start.getDate() - 1);
9130
- const prevStart = new Date(prevEnd);
9131
- prevStart.setDate(prevEnd.getDate() - 6);
9132
- return { start: prevStart, end: prevEnd };
9386
+ try {
9387
+ appendSnapshot(USAGE_LOG2, await fetchCodexUsage());
9388
+ saved += 1;
9389
+ } catch (err) {
9390
+ if (!(err instanceof CodexApiError) || err.code !== "no_auth" && err.code !== "api_key_only") {
9391
+ console.warn(`codex usage: ${err.message}`);
9392
+ }
9393
+ }
9394
+ try {
9395
+ appendSnapshot(USAGE_LOG2, await fetchCopilotUsage());
9396
+ saved += 1;
9397
+ } catch (err) {
9398
+ if (!(err instanceof CopilotApiError) || err.code !== "no_cli" && err.code !== "no_auth") {
9399
+ console.warn(`copilot usage: ${err.message}`);
9400
+ }
9401
+ }
9402
+ try {
9403
+ appendSnapshot(USAGE_LOG2, await fetchGrokUsage());
9404
+ saved += 1;
9405
+ } catch (err) {
9406
+ if (!(err instanceof GrokApiError) || err.code !== "no_auth" && err.code !== "not_weekly") {
9407
+ console.warn(`grok usage: ${err.message}`);
9408
+ }
9409
+ }
9410
+ try {
9411
+ appendSnapshot(USAGE_LOG2, await fetchZaiUsage());
9412
+ saved += 1;
9413
+ } catch (err) {
9414
+ if (!(err instanceof ZaiApiError) || err.code !== "no_key") {
9415
+ console.warn(`zai usage: ${err.message}`);
9416
+ }
9417
+ }
9418
+ return saved;
9133
9419
  }
9134
- function last4CompletedWeeks(ref = /* @__PURE__ */ new Date()) {
9135
- const { end } = priorCalendarWeek(ref);
9136
- const start2 = new Date(end);
9137
- start2.setDate(end.getDate() - 27);
9138
- return { start: start2, end };
9420
+ function parseAgentFilter(args2) {
9421
+ const raw = flag(args2, "--agent") ?? flag(args2, "-a");
9422
+ if (!raw) return void 0;
9423
+ const kind = raw.toLowerCase();
9424
+ const aliases = {
9425
+ claude: "claude-code",
9426
+ "claude-code": "claude-code",
9427
+ codex: "codex",
9428
+ copilot: "copilot",
9429
+ zai: "zai",
9430
+ "z.ai": "zai",
9431
+ grok: "grok"
9432
+ };
9433
+ return aliases[kind] ?? kind;
9139
9434
  }
9140
- function calendarMonth(ref = /* @__PURE__ */ new Date()) {
9141
- const start2 = new Date(ref.getFullYear(), ref.getMonth(), 1);
9142
- const end = new Date(ref.getFullYear(), ref.getMonth() + 1, 0);
9143
- return { start: start2, end };
9435
+ function filterAgents(byAgent, agentFilter) {
9436
+ if (!agentFilter) return byAgent;
9437
+ const hit = byAgent[agentFilter];
9438
+ return hit ? { [agentFilter]: hit } : {};
9144
9439
  }
9145
- function priorCalendarMonth(ref = /* @__PURE__ */ new Date()) {
9146
- const start2 = new Date(ref.getFullYear(), ref.getMonth() - 1, 1);
9147
- const end = new Date(ref.getFullYear(), ref.getMonth(), 0);
9148
- return { start: start2, end };
9440
+ async function collectSnapshots(opts) {
9441
+ let saved = 0;
9442
+ if (opts.save) {
9443
+ try {
9444
+ const snapshot = await fetchUsage();
9445
+ appendSnapshot(USAGE_LOG2, snapshot);
9446
+ saved += 1;
9447
+ } catch (err) {
9448
+ if (!(err instanceof UsageApiError)) throw err;
9449
+ }
9450
+ saved += await saveAuxiliarySnapshots();
9451
+ }
9452
+ const byAgent = latestSnapshotsByAgent(USAGE_LOG2);
9453
+ if (opts.preferLiveClaude && !opts.save) {
9454
+ try {
9455
+ const live = await fetchUsage();
9456
+ byAgent["claude-code"] = live;
9457
+ } catch {
9458
+ }
9459
+ }
9460
+ return { byAgent, saved };
9149
9461
  }
9150
- function aggregateConcurrency(bursts, period) {
9151
- const days = rangeDays(period.start, period.end);
9152
- const byDayKey = /* @__PURE__ */ new Map();
9153
- for (const d of days)
9154
- byDayKey.set(isoDay(d), { peak: 0, has_cross_project: false });
9155
- for (const b of bursts) {
9156
- const dayKey = isoDay(new Date(b.startMs));
9157
- const cur = byDayKey.get(dayKey);
9158
- if (!cur)
9159
- continue;
9160
- if (b.peak > cur.peak)
9161
- cur.peak = b.peak;
9162
- if (b.crossProject)
9163
- cur.has_cross_project = true;
9462
+ async function usage(args2) {
9463
+ if (args2.includes("--help") || args2.includes("-h")) {
9464
+ console.log(`fleetlens usage \u2014 plan utilization (all agents)
9465
+
9466
+ Usage:
9467
+ fleetlens usage [--agent KIND] Multi-agent snapshot (from daemon log + live Claude)
9468
+ fleetlens usage --watch [-a KIND] Live top-style view until Ctrl+C
9469
+ fleetlens usage --save Poll every provider and append to the usage log
9470
+ fleetlens usage --history [-s D] [--days N]
9471
+ Daily token / cost history table
9472
+
9473
+ Agents: claude | codex | copilot | zai | grok (or full ids like claude-code)
9474
+ Watch options:
9475
+ --interval N Redraw every N seconds (default 2; reads ~/.cclens/usage.jsonl)
9476
+
9477
+ Tip: leave the usage daemon running (\`fleetlens daemon start\`) so --watch
9478
+ stays fresh without hammering provider APIs.`);
9479
+ return;
9480
+ }
9481
+ if (args2.includes("--history")) {
9482
+ const { history: history2 } = await Promise.resolve().then(() => (init_usage_history(), usage_history_exports));
9483
+ await history2(args2.filter((a) => a !== "--history"));
9484
+ return;
9485
+ }
9486
+ const save = args2.includes("--save");
9487
+ const watch = args2.includes("--watch") || args2.includes("-w");
9488
+ const agentFilter = parseAgentFilter(args2);
9489
+ if (agentFilter && !isAgentKind(agentFilter)) {
9490
+ console.error(
9491
+ `Error: unknown agent "${agentFilter}". Use claude, codex, copilot, zai, or grok.`
9492
+ );
9493
+ process.exitCode = 1;
9494
+ return;
9495
+ }
9496
+ const intervalRaw = flag(args2, "--interval");
9497
+ const intervalSec = Math.max(1, parseInt(intervalRaw ?? "2", 10) || 2);
9498
+ if (watch) {
9499
+ await runWatch({ agentFilter, intervalSec, saveOnce: save });
9500
+ return;
9164
9501
  }
9165
- const by_day = days.map((d) => {
9166
- const k = isoDay(d);
9167
- const v = byDayKey.get(k);
9168
- return { date: k, peak: v.peak, has_cross_project: v.has_cross_project };
9169
- });
9170
- let peak = 0;
9171
- let peak_day;
9172
- let multi_agent_days = 0;
9173
- let cross_project_days = 0;
9174
- for (const d of by_day) {
9175
- if (d.peak > peak) {
9176
- peak = d.peak;
9177
- peak_day = d.date;
9502
+ const collected = await collectSnapshots({ save, preferLiveClaude: true });
9503
+ const byAgent = filterAgents(collected.byAgent, agentFilter);
9504
+ if (save && collected.saved === 0 && Object.keys(byAgent).length === 0) {
9505
+ console.error(
9506
+ "Error: could not poll any provider and no samples are on disk."
9507
+ );
9508
+ process.exitCode = 1;
9509
+ return;
9510
+ }
9511
+ if (save && collected.saved === 0) {
9512
+ console.error(
9513
+ "Error: every usage poll failed (showing last samples from disk only)."
9514
+ );
9515
+ process.exitCode = 1;
9516
+ }
9517
+ if (Object.keys(byAgent).length === 0) {
9518
+ if (agentFilter) {
9519
+ console.error(
9520
+ `Error: no usage samples for ${agentFilter}. Run \`fleetlens usage --save\` or start the daemon.`
9521
+ );
9522
+ } else {
9523
+ console.error(
9524
+ "Error: no usage samples yet. Run `fleetlens daemon start` or `fleetlens usage --save`."
9525
+ );
9178
9526
  }
9179
- if (d.peak >= 3)
9180
- multi_agent_days++;
9181
- if (d.has_cross_project)
9182
- cross_project_days++;
9527
+ process.exitCode = 1;
9528
+ return;
9529
+ }
9530
+ process.stdout.write(formatMultiAgentUsage(byAgent));
9531
+ if (!save) {
9532
+ process.stdout.write(
9533
+ " (tip: --watch for a live view \xB7 --history for the token table \xB7 --save to poll APIs)\n"
9534
+ );
9183
9535
  }
9184
- return { by_day, peak, peak_day, multi_agent_days, cross_project_days };
9185
9536
  }
9186
- var init_aggregate = __esm({
9187
- "../parser/dist/aggregate.js"() {
9537
+ async function runWatch(opts) {
9538
+ if (opts.saveOnce) {
9539
+ const { saved } = await collectSnapshots({ save: true, preferLiveClaude: false });
9540
+ if (saved === 0 && Object.keys(latestSnapshotsByAgent(USAGE_LOG2)).length === 0) {
9541
+ console.error(
9542
+ "Error: could not poll any provider and no samples are on disk."
9543
+ );
9544
+ process.exitCode = 1;
9545
+ return;
9546
+ }
9547
+ }
9548
+ let stopping = false;
9549
+ const stop2 = () => {
9550
+ if (stopping) return;
9551
+ stopping = true;
9552
+ process.stdout.write("\x1B[?25h");
9553
+ process.stdout.write("\n");
9554
+ process.exit(0);
9555
+ };
9556
+ process.on("SIGINT", stop2);
9557
+ process.on("SIGTERM", stop2);
9558
+ if (process.stdout.isTTY) process.stdout.write("\x1B[?25l");
9559
+ const draw = () => {
9560
+ if (stopping) return;
9561
+ const byAgent = filterAgents(latestSnapshotsByAgent(USAGE_LOG2), opts.agentFilter);
9562
+ if (process.stdout.isTTY) {
9563
+ process.stdout.write("\x1B[2J\x1B[H");
9564
+ }
9565
+ if (Object.keys(byAgent).length === 0) {
9566
+ process.stdout.write(
9567
+ `
9568
+ ${opts.agentFilter ? `No samples for ${opts.agentFilter}` : "Waiting for usage samples\u2026"}
9569
+ ${" ".repeat(0)}Start the daemon: fleetlens daemon start
9570
+
9571
+ live \xB7 every ${opts.intervalSec}s \xB7 Ctrl+C to quit
9572
+ `
9573
+ );
9574
+ return;
9575
+ }
9576
+ process.stdout.write(
9577
+ formatMultiAgentUsage(byAgent, {
9578
+ watch: true,
9579
+ intervalSec: opts.intervalSec
9580
+ })
9581
+ );
9582
+ };
9583
+ draw();
9584
+ const timer = setInterval(draw, opts.intervalSec * 1e3);
9585
+ await new Promise(() => {
9586
+ void timer;
9587
+ });
9588
+ }
9589
+ var USAGE_LOG2;
9590
+ var init_usage = __esm({
9591
+ "src/commands/usage.ts"() {
9188
9592
  "use strict";
9189
- init_analytics();
9593
+ init_dist();
9594
+ init_fs();
9595
+ init_args();
9596
+ init_api();
9597
+ init_grok2();
9598
+ init_codex2();
9599
+ init_copilot2();
9600
+ init_zai();
9601
+ init_format();
9602
+ init_storage();
9603
+ USAGE_LOG2 = cclensPath("usage.jsonl");
9190
9604
  }
9191
9605
  });
9192
9606
 
9193
- // ../parser/dist/index.js
9194
- var dist_exports = {};
9195
- __export(dist_exports, {
9196
- ANTIGRAVITY_METADATA: () => ANTIGRAVITY_METADATA,
9197
- CLAUDE_CODE_METADATA: () => CLAUDE_CODE_METADATA,
9198
- CODEX_METADATA: () => CODEX_METADATA,
9199
- COPILOT_METADATA: () => COPILOT_METADATA,
9200
- COWORK_METADATA: () => COWORK_METADATA,
9201
- GEMINI_METADATA: () => GEMINI_METADATA,
9202
- GROK_METADATA: () => GROK_METADATA,
9203
- PRICES: () => PRICES,
9204
- RATE_PER_PCT: () => RATE_PER_PCT,
9205
- RATE_PER_PCT_5H: () => RATE_PER_PCT_5H,
9206
- RATE_PER_PCT_7D: () => RATE_PER_PCT_7D,
9207
- ZAI_METADATA: () => ZAI_METADATA,
9208
- agentMetadata: () => agentMetadata,
9209
- aggregateConcurrency: () => aggregateConcurrency,
9210
- buildGanttDay: () => buildGanttDay,
9211
- buildMegaRows: () => buildMegaRows,
9212
- buildPresentation: () => buildPresentation,
9213
- buildProjectKeyResolver: () => buildProjectKeyResolver,
9214
- buildSpendIndex: () => buildSpendIndex,
9215
- calendarMonth: () => calendarMonth,
9216
- calendarWeek: () => calendarWeek,
9217
- canonicalProjectName: () => canonicalProjectName,
9218
- collectSnapPairRates: () => collectSnapPairRates,
9219
- computeActiveSegments: () => computeActiveSegments,
9220
- computeBurstsFromSessions: () => computeBurstsFromSessions,
9221
- computeParallelism: () => computeParallelism,
9222
- computeParallelismBursts: () => computeParallelismBursts,
9223
- dailyActivity: () => dailyActivity,
9224
- daysBetween: () => daysBetween,
9225
- detectParallelRuns: () => detectParallelRuns,
9226
- detectPrMarkers: () => detectPrMarkers,
9227
- dollarsInRange: () => dollarsInRange,
9228
- eventDollars: () => eventDollars,
9229
- getAgentMetadata: () => getAgentMetadata,
9230
- groupByProject: () => groupByProject,
9231
- groupByTeam: () => groupByTeam,
9232
- groupSnapsByCycle: () => groupSnapsByCycle,
9233
- highLevelMetrics: () => highLevelMetrics,
9234
- isAgentKind: () => isAgentKind,
9235
- isFrameworkInjectedUserInput: () => isFrameworkInjectedUserInput,
9236
- last4CompletedWeeks: () => last4CompletedWeeks,
9237
- modelFamily: () => modelFamily,
9238
- parseTranscript: () => parseTranscript,
9239
- predictAnchored: () => predictAnchored,
9240
- priorCalendarMonth: () => priorCalendarMonth,
9241
- priorCalendarWeek: () => priorCalendarWeek,
9242
- projectKey: () => projectKey,
9243
- projectRepoName: () => projectRepoName,
9244
- rowPrimaryIndex: () => rowPrimaryIndex,
9245
- sessionAirTimeMs: () => sessionAirTimeMs,
9246
- sessionDay: () => sessionDay,
9247
- sessionProjectKey: () => sessionProjectKey,
9248
- stripFrameworkBoilerplate: () => stripFrameworkBoilerplate,
9249
- summarizeBursts: () => summarizeBursts,
9250
- toLocalDay: () => toLocalDay,
9251
- userSoloRate: () => userSoloRate,
9252
- worktreeName: () => worktreeName
9253
- });
9254
- var init_dist = __esm({
9255
- "../parser/dist/index.js"() {
9607
+ // ../entries/dist/types.js
9608
+ function pendingEnrichment() {
9609
+ return {
9610
+ status: "pending",
9611
+ generated_at: null,
9612
+ model: null,
9613
+ cost_usd: null,
9614
+ error: null,
9615
+ brief_summary: null,
9616
+ underlying_goal: null,
9617
+ friction_detail: null,
9618
+ user_instructions: [],
9619
+ outcome: null,
9620
+ claude_helpfulness: null,
9621
+ goal_categories: {},
9622
+ retry_count: 0
9623
+ };
9624
+ }
9625
+ function skippedTrivialEnrichment(generatedAt) {
9626
+ return {
9627
+ status: "skipped_trivial",
9628
+ generated_at: generatedAt,
9629
+ model: null,
9630
+ cost_usd: null,
9631
+ error: null,
9632
+ brief_summary: null,
9633
+ underlying_goal: null,
9634
+ friction_detail: null,
9635
+ user_instructions: [],
9636
+ outcome: "trivial",
9637
+ claude_helpfulness: null,
9638
+ goal_categories: {},
9639
+ retry_count: 0
9640
+ };
9641
+ }
9642
+ function entryKey(sessionId, localDay2) {
9643
+ return `${sessionId}__${localDay2}`;
9644
+ }
9645
+ function parseEntryKey(key) {
9646
+ const m = /^([^_]+(?:_[^_]+)*)__(\d{4}-\d{2}-\d{2})$/.exec(key);
9647
+ if (!m)
9648
+ return null;
9649
+ return { session_id: m[1], local_day: m[2] };
9650
+ }
9651
+ var CURRENT_ENTRY_SCHEMA_VERSION, GOAL_CATEGORIES, CURRENT_DAY_DIGEST_SCHEMA_VERSION, CURRENT_WEEK_DIGEST_SCHEMA_VERSION, CURRENT_MONTH_DIGEST_SCHEMA_VERSION, PROMPT_FRAME_ORIGIN;
9652
+ var init_types2 = __esm({
9653
+ "../entries/dist/types.js"() {
9256
9654
  "use strict";
9257
- init_types2();
9258
- init_parser();
9259
- init_user_input();
9260
- init_presentation();
9261
- init_analytics();
9262
- init_aggregate();
9263
- init_calibration();
9264
- init_agent_metadata();
9265
- init_team();
9655
+ CURRENT_ENTRY_SCHEMA_VERSION = 2;
9656
+ GOAL_CATEGORIES = [
9657
+ "build",
9658
+ "plan",
9659
+ "debug",
9660
+ "review",
9661
+ "steer",
9662
+ "meta",
9663
+ "research",
9664
+ "refactor",
9665
+ "test",
9666
+ "release",
9667
+ "warmup_minimal"
9668
+ ];
9669
+ CURRENT_DAY_DIGEST_SCHEMA_VERSION = 2;
9670
+ CURRENT_WEEK_DIGEST_SCHEMA_VERSION = 2;
9671
+ CURRENT_MONTH_DIGEST_SCHEMA_VERSION = 2;
9672
+ PROMPT_FRAME_ORIGIN = {
9673
+ teammate: "claude-feature",
9674
+ "task-notification": "claude-feature",
9675
+ "local-command-caveat": "claude-feature",
9676
+ "image-attached": "claude-feature",
9677
+ "slash-command": "claude-feature",
9678
+ "handoff-prose": "personal-habit"
9679
+ };
9266
9680
  }
9267
9681
  });
9268
9682
 
@@ -10004,7 +10418,7 @@ var init_build = __esm({
10004
10418
  "../entries/dist/build.js"() {
10005
10419
  "use strict";
10006
10420
  init_analytics();
10007
- init_types();
10421
+ init_types2();
10008
10422
  init_signals();
10009
10423
  init_trivial();
10010
10424
  IDLE_GAP_MS6 = 3 * 60 * 1e3;
@@ -10027,7 +10441,7 @@ var init_flag_glossary = __esm({
10027
10441
  var init_dist2 = __esm({
10028
10442
  "../entries/dist/index.js"() {
10029
10443
  "use strict";
10030
- init_types();
10444
+ init_types2();
10031
10445
  init_signals();
10032
10446
  init_trivial();
10033
10447
  init_build();
@@ -10036,11 +10450,11 @@ var init_dist2 = __esm({
10036
10450
  });
10037
10451
 
10038
10452
  // ../entries/dist/digest-fs.js
10039
- import { readFileSync as readFileSync13, readdirSync, writeFileSync as writeFileSync7, renameSync as renameSync2, chmodSync, mkdirSync as mkdirSync6, existsSync as existsSync11 } from "node:fs";
10453
+ import { readFileSync as readFileSync14, readdirSync, writeFileSync as writeFileSync7, renameSync as renameSync2, chmodSync, mkdirSync as mkdirSync6, existsSync as existsSync12 } from "node:fs";
10040
10454
  import { dirname as dirname9, join as join9 } from "node:path";
10041
10455
  function parseDigestFile(path11, expectedVersion) {
10042
10456
  try {
10043
- const parsed = JSON.parse(readFileSync13(path11, "utf8"));
10457
+ const parsed = JSON.parse(readFileSync14(path11, "utf8"));
10044
10458
  if (!parsed || typeof parsed !== "object")
10045
10459
  return null;
10046
10460
  if (parsed.version !== expectedVersion)
@@ -10089,7 +10503,7 @@ function writeDayDigest(digest2) {
10089
10503
  }
10090
10504
  function readDayDigest(date) {
10091
10505
  const p = dayDigestPath(date);
10092
- if (!existsSync11(p))
10506
+ if (!existsSync12(p))
10093
10507
  return null;
10094
10508
  return parseDigestFile(p, CURRENT_DAY_DIGEST_SCHEMA_VERSION);
10095
10509
  }
@@ -10125,13 +10539,13 @@ function writeWeekDigest(digest2) {
10125
10539
  }
10126
10540
  function readWeekDigest(monday) {
10127
10541
  const p = weekDigestPath(monday);
10128
- if (!existsSync11(p))
10542
+ if (!existsSync12(p))
10129
10543
  return null;
10130
10544
  return parseDigestFile(p, CURRENT_WEEK_DIGEST_SCHEMA_VERSION);
10131
10545
  }
10132
10546
  function listWeekDigestKeys() {
10133
10547
  const dir = join9(digestsDir(), "week");
10134
- if (!existsSync11(dir))
10548
+ if (!existsSync12(dir))
10135
10549
  return [];
10136
10550
  return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length)).filter((k) => /^\d{4}-\d{2}-\d{2}$/.test(k));
10137
10551
  }
@@ -10162,13 +10576,13 @@ function writeMonthDigest(digest2) {
10162
10576
  }
10163
10577
  function readMonthDigest(yearMonth) {
10164
10578
  const p = monthDigestPath(yearMonth);
10165
- if (!existsSync11(p))
10579
+ if (!existsSync12(p))
10166
10580
  return null;
10167
10581
  return parseDigestFile(p, CURRENT_MONTH_DIGEST_SCHEMA_VERSION);
10168
10582
  }
10169
10583
  function listMonthDigestKeys() {
10170
10584
  const dir = join9(digestsDir(), "month");
10171
- if (!existsSync11(dir))
10585
+ if (!existsSync12(dir))
10172
10586
  return [];
10173
10587
  return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length)).filter((k) => /^\d{4}-\d{2}$/.test(k));
10174
10588
  }
@@ -10189,7 +10603,7 @@ var init_digest_fs = __esm({
10189
10603
  "../entries/dist/digest-fs.js"() {
10190
10604
  "use strict";
10191
10605
  init_fs();
10192
- init_types();
10606
+ init_types2();
10193
10607
  digestsDirCached = null;
10194
10608
  TODAY_TTL_MS = 10 * 60 * 1e3;
10195
10609
  todayCache = null;
@@ -10230,7 +10644,7 @@ __export(fs_exports2, {
10230
10644
  writeMonthDigest: () => writeMonthDigest,
10231
10645
  writeWeekDigest: () => writeWeekDigest
10232
10646
  });
10233
- import { readFileSync as readFileSync14, readdirSync as readdirSync2, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, renameSync as renameSync3, existsSync as existsSync12 } from "node:fs";
10647
+ import { readFileSync as readFileSync15, readdirSync as readdirSync2, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, renameSync as renameSync3, existsSync as existsSync13 } from "node:fs";
10234
10648
  import { randomBytes } from "node:crypto";
10235
10649
  import { join as join10 } from "node:path";
10236
10650
  function entriesDir() {
@@ -10289,17 +10703,17 @@ function writeEntryPreservingEnrichment(fresh) {
10289
10703
  }
10290
10704
  function readEntry(sessionId, localDay2) {
10291
10705
  const p = pathFor(sessionId, localDay2);
10292
- if (!existsSync12(p))
10706
+ if (!existsSync13(p))
10293
10707
  return null;
10294
- const raw = readFileSync14(p, "utf8");
10708
+ const raw = readFileSync15(p, "utf8");
10295
10709
  return JSON.parse(raw);
10296
10710
  }
10297
10711
  function entryExists(sessionId, localDay2) {
10298
- return existsSync12(pathFor(sessionId, localDay2));
10712
+ return existsSync13(pathFor(sessionId, localDay2));
10299
10713
  }
10300
10714
  function listEntryKeys() {
10301
10715
  const dir = entriesDir();
10302
- if (!existsSync12(dir))
10716
+ if (!existsSync13(dir))
10303
10717
  return [];
10304
10718
  return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length));
10305
10719
  }
@@ -10348,7 +10762,7 @@ var init_fs2 = __esm({
10348
10762
  "../entries/dist/fs.js"() {
10349
10763
  "use strict";
10350
10764
  init_fs();
10351
- init_types();
10765
+ init_types2();
10352
10766
  init_digest_fs();
10353
10767
  entriesDirCached = null;
10354
10768
  PRESERVABLE_STATUSES = /* @__PURE__ */ new Set([
@@ -10358,17 +10772,6 @@ var init_fs2 = __esm({
10358
10772
  }
10359
10773
  });
10360
10774
 
10361
- // src/args.ts
10362
- function flag(args2, name) {
10363
- const idx = args2.indexOf(name);
10364
- return idx === -1 ? void 0 : args2[idx + 1];
10365
- }
10366
- var init_args = __esm({
10367
- "src/args.ts"() {
10368
- "use strict";
10369
- }
10370
- });
10371
-
10372
10775
  // ../../node_modules/zod/v3/helpers/util.js
10373
10776
  var util, objectUtil, ZodParsedType, getParsedType;
10374
10777
  var init_util = __esm({
@@ -14514,7 +14917,7 @@ var init_enrich = __esm({
14514
14917
  "../entries/dist/prompts/enrich.js"() {
14515
14918
  "use strict";
14516
14919
  init_zod();
14517
- init_types();
14920
+ init_types2();
14518
14921
  GoalCategoryEnum = external_exports.enum(GOAL_CATEGORIES);
14519
14922
  OutcomeEnum = external_exports.enum(["shipped", "partial", "exploratory", "blocked", "trivial"]);
14520
14923
  HelpfulnessEnum = external_exports.enum(["essential", "helpful", "neutral", "unhelpful"]);
@@ -14578,14 +14981,14 @@ RESPOND WITH ONLY VALID JSON (no prose, no code fence):
14578
14981
  });
14579
14982
 
14580
14983
  // ../entries/dist/claude-bin.js
14581
- import { existsSync as existsSync13 } from "node:fs";
14984
+ import { existsSync as existsSync14 } from "node:fs";
14582
14985
  import { join as join11, dirname as dirname10, delimiter } from "node:path";
14583
14986
  import { homedir as homedir5 } from "node:os";
14584
14987
  function resolveClaudeBin(opts = {}) {
14585
14988
  const env = opts.env ?? process.env;
14586
14989
  const home = opts.home ?? homedir5();
14587
14990
  const execPath = opts.execPath ?? process.execPath;
14588
- const exists = opts.exists ?? existsSync13;
14991
+ const exists = opts.exists ?? existsSync14;
14589
14992
  for (const dir of (env.PATH ?? "").split(delimiter)) {
14590
14993
  if (!dir)
14591
14994
  continue;
@@ -14626,7 +15029,7 @@ var init_claude_bin = __esm({
14626
15029
  });
14627
15030
 
14628
15031
  // ../entries/dist/tmux-runner.js
14629
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9, existsSync as existsSync14, readdirSync as readdirSync3, realpathSync as realpathSync5, readFileSync as readFileSync15, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
15032
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9, existsSync as existsSync15, readdirSync as readdirSync3, realpathSync as realpathSync5, readFileSync as readFileSync16, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
14630
15033
  import { join as join12 } from "node:path";
14631
15034
  import { homedir as homedir6 } from "node:os";
14632
15035
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -14645,7 +15048,7 @@ function tmuxRunnerAvailable() {
14645
15048
  }
14646
15049
  function runtimeCwd() {
14647
15050
  const dir = join12(homedir6(), ".cclens", "runtime");
14648
- if (!existsSync14(dir))
15051
+ if (!existsSync15(dir))
14649
15052
  mkdirSync8(dir, { recursive: true });
14650
15053
  return realpathSync5(dir);
14651
15054
  }
@@ -14653,7 +15056,7 @@ function ensureRuntimeCwdTrusted(cwd) {
14653
15056
  const cfgPath = join12(homedir6(), ".claude.json");
14654
15057
  let cfg;
14655
15058
  try {
14656
- cfg = JSON.parse(readFileSync15(cfgPath, "utf8"));
15059
+ cfg = JSON.parse(readFileSync16(cfgPath, "utf8"));
14657
15060
  } catch {
14658
15061
  return;
14659
15062
  }
@@ -14803,7 +15206,7 @@ function snapshotAllTranscripts() {
14803
15206
  function readJsonlCwd(path11, maxLines = 8) {
14804
15207
  let raw;
14805
15208
  try {
14806
- raw = readFileSync15(path11, "utf8");
15209
+ raw = readFileSync16(path11, "utf8");
14807
15210
  } catch {
14808
15211
  return void 0;
14809
15212
  }
@@ -14860,7 +15263,7 @@ async function discoverTranscript(targetCwd, existingBefore, startMs, timeoutMs,
14860
15263
  function parseTranscript2(path11, defaultModel) {
14861
15264
  let raw;
14862
15265
  try {
14863
- raw = readFileSync15(path11, "utf8");
15266
+ raw = readFileSync16(path11, "utf8");
14864
15267
  } catch {
14865
15268
  return { assembled: "", inputTokens: 0, outputTokens: 0, modelUsed: defaultModel, done: false };
14866
15269
  }
@@ -14955,11 +15358,11 @@ var init_tmux_runner = __esm({
14955
15358
 
14956
15359
  // ../entries/dist/llm-runner.js
14957
15360
  import { spawn as spawn4 } from "node:child_process";
14958
- import { mkdirSync as mkdirSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync10, existsSync as existsSync15 } from "node:fs";
15361
+ import { mkdirSync as mkdirSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync10, existsSync as existsSync16 } from "node:fs";
14959
15362
  import { join as join13 } from "node:path";
14960
15363
  import { randomUUID } from "node:crypto";
14961
15364
  function ensureRunsDir() {
14962
- if (!existsSync15(RUNS_DIR)) {
15365
+ if (!existsSync16(RUNS_DIR)) {
14963
15366
  try {
14964
15367
  mkdirSync9(RUNS_DIR, { recursive: true });
14965
15368
  } catch {
@@ -15412,7 +15815,7 @@ var init_enrich2 = __esm({
15412
15815
  });
15413
15816
 
15414
15817
  // ../entries/dist/budget.js
15415
- import { appendFileSync as appendFileSync4, readFileSync as readFileSync16, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "node:fs";
15818
+ import { appendFileSync as appendFileSync4, readFileSync as readFileSync17, existsSync as existsSync17, mkdirSync as mkdirSync10 } from "node:fs";
15416
15819
  import { dirname as dirname11 } from "node:path";
15417
15820
  function spendPath() {
15418
15821
  if (spendPathCached)
@@ -15430,12 +15833,12 @@ function appendSpend(record) {
15430
15833
  }
15431
15834
  function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
15432
15835
  const p = spendPath();
15433
- if (!existsSync16(p))
15836
+ if (!existsSync17(p))
15434
15837
  return 0;
15435
15838
  const year = now.getUTCFullYear();
15436
15839
  const month2 = now.getUTCMonth();
15437
15840
  let total = 0;
15438
- const raw = readFileSync16(p, "utf8");
15841
+ const raw = readFileSync17(p, "utf8");
15439
15842
  for (const line of raw.split("\n")) {
15440
15843
  if (!line)
15441
15844
  continue;
@@ -15464,7 +15867,7 @@ var init_budget = __esm({
15464
15867
  });
15465
15868
 
15466
15869
  // ../entries/dist/pipeline-lock.js
15467
- import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, existsSync as existsSync17, readFileSync as readFileSync17, statSync as statSync3 } from "node:fs";
15870
+ import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, existsSync as existsSync18, readFileSync as readFileSync18, statSync as statSync3 } from "node:fs";
15468
15871
  function lockPath() {
15469
15872
  if (lockPathCached)
15470
15873
  return lockPathCached;
@@ -15509,13 +15912,13 @@ function pidAlive(pid) {
15509
15912
  }
15510
15913
  function interactiveLockFresh(nowMs) {
15511
15914
  const p = lockPath();
15512
- if (!existsSync17(p))
15915
+ if (!existsSync18(p))
15513
15916
  return false;
15514
15917
  try {
15515
15918
  const mtime = statSync3(p).mtimeMs;
15516
15919
  if (nowMs - mtime > STALE_MS)
15517
15920
  return false;
15518
- const pid = Number(readFileSync17(p, "utf8").trim());
15921
+ const pid = Number(readFileSync18(p, "utf8").trim());
15519
15922
  if (!Number.isFinite(pid) || pid <= 0)
15520
15923
  return false;
15521
15924
  if (!pidAlive(pid))
@@ -15621,7 +16024,7 @@ var init_queue = __esm({
15621
16024
  });
15622
16025
 
15623
16026
  // ../entries/dist/settings.js
15624
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync11, existsSync as existsSync18 } from "node:fs";
16027
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync12, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync11, existsSync as existsSync19 } from "node:fs";
15625
16028
  import { dirname as dirname12 } from "node:path";
15626
16029
  function settingsPath() {
15627
16030
  if (settingsPathCached)
@@ -15657,10 +16060,10 @@ function fromDisk(d) {
15657
16060
  }
15658
16061
  function readSettings() {
15659
16062
  const p = settingsPath();
15660
- if (!existsSync18(p))
16063
+ if (!existsSync19(p))
15661
16064
  return DEFAULT_SETTINGS;
15662
16065
  try {
15663
- const raw = readFileSync18(p, "utf8");
16066
+ const raw = readFileSync19(p, "utf8");
15664
16067
  return fromDisk(JSON.parse(raw));
15665
16068
  } catch {
15666
16069
  return DEFAULT_SETTINGS;
@@ -16145,7 +16548,7 @@ var DEFAULT_MODEL2, defaultCallLLMDigest, validateDay;
16145
16548
  var init_digest_day2 = __esm({
16146
16549
  "../entries/dist/digest-day.js"() {
16147
16550
  "use strict";
16148
- init_types();
16551
+ init_types2();
16149
16552
  init_signals();
16150
16553
  init_digest_day();
16151
16554
  init_enrich2();
@@ -17364,7 +17767,7 @@ var SHAPE_ORDER, DEFAULT_MODEL3, defaultCallLLMWeek, validateWeek;
17364
17767
  var init_digest_week2 = __esm({
17365
17768
  "../entries/dist/digest-week.js"() {
17366
17769
  "use strict";
17367
- init_types();
17770
+ init_types2();
17368
17771
  init_signals();
17369
17772
  init_digest_day2();
17370
17773
  init_digest_week();
@@ -17698,7 +18101,7 @@ var DEFAULT_MODEL4, defaultCallLLMMonth, validateMonth;
17698
18101
  var init_digest_month2 = __esm({
17699
18102
  "../entries/dist/digest-month.js"() {
17700
18103
  "use strict";
17701
- init_types();
18104
+ init_types2();
17702
18105
  init_digest_month();
17703
18106
  init_enrich2();
17704
18107
  init_llm_runner();
@@ -18850,13 +19253,13 @@ var init_digest_month_pipeline = __esm({
18850
19253
  });
18851
19254
 
18852
19255
  // ../entries/dist/credentials.js
18853
- import { writeFileSync as writeFileSync13, readFileSync as readFileSync19, renameSync as renameSync6, chmodSync as chmodSync3, mkdirSync as mkdirSync12, existsSync as existsSync19, unlinkSync as unlinkSync5 } from "node:fs";
19256
+ import { writeFileSync as writeFileSync13, readFileSync as readFileSync20, renameSync as renameSync6, chmodSync as chmodSync3, mkdirSync as mkdirSync12, existsSync as existsSync20, unlinkSync as unlinkSync5 } from "node:fs";
18854
19257
  import { dirname as dirname13 } from "node:path";
18855
- async function fetchZaiUsage(candidate) {
19258
+ async function fetchZaiUsage2(candidate) {
18856
19259
  const key = candidate.trim();
18857
19260
  if (!key)
18858
19261
  throw new Error("empty key");
18859
- const res = await fetch(ZAI_QUOTA_ENDPOINT, {
19262
+ const res = await fetch(ZAI_QUOTA_ENDPOINT2, {
18860
19263
  headers: { Authorization: key, "Accept-Language": "en-US,en" }
18861
19264
  });
18862
19265
  if (!res.ok) {
@@ -18908,7 +19311,7 @@ async function fetchZaiUsage(candidate) {
18908
19311
  };
18909
19312
  }
18910
19313
  async function validateAndSnapshotZaiKey(key) {
18911
- const snap = await fetchZaiUsage(key);
19314
+ const snap = await fetchZaiUsage2(key);
18912
19315
  appendUsageSnapshot(snap);
18913
19316
  return snap;
18914
19317
  }
@@ -18921,10 +19324,10 @@ function credentialsPath() {
18921
19324
  }
18922
19325
  function readCredentials() {
18923
19326
  const p = credentialsPath();
18924
- if (!existsSync19(p))
19327
+ if (!existsSync20(p))
18925
19328
  return {};
18926
19329
  try {
18927
- const raw = readFileSync19(p, "utf8");
19330
+ const raw = readFileSync20(p, "utf8");
18928
19331
  return JSON.parse(raw);
18929
19332
  } catch {
18930
19333
  return {};
@@ -18976,12 +19379,12 @@ function deleteZaiKey() {
18976
19379
  function hasZaiKey() {
18977
19380
  return !!readCredentials().zai?.apiKey;
18978
19381
  }
18979
- var ZAI_QUOTA_ENDPOINT;
19382
+ var ZAI_QUOTA_ENDPOINT2;
18980
19383
  var init_credentials = __esm({
18981
19384
  "../entries/dist/credentials.js"() {
18982
19385
  "use strict";
18983
19386
  init_fs();
18984
- ZAI_QUOTA_ENDPOINT = "https://api.z.ai/api/monitor/usage/quota/limit";
19387
+ ZAI_QUOTA_ENDPOINT2 = "https://api.z.ai/api/monitor/usage/quota/limit";
18985
19388
  }
18986
19389
  });
18987
19390
 
@@ -19020,7 +19423,7 @@ __export(node_exports, {
19020
19423
  deleteZaiKey: () => deleteZaiKey,
19021
19424
  deleteZaiKeyAndPrune: () => deleteZaiKeyAndPrune,
19022
19425
  enrichEntry: () => enrichEntry,
19023
- fetchZaiUsage: () => fetchZaiUsage,
19426
+ fetchZaiUsage: () => fetchZaiUsage2,
19024
19427
  generateDayDigest: () => generateDayDigest,
19025
19428
  generateMonthDigest: () => generateMonthDigest,
19026
19429
  generateWeekDigest: () => generateWeekDigest,
@@ -19558,7 +19961,7 @@ var menubar_exports = {};
19558
19961
  __export(menubar_exports, {
19559
19962
  menubar: () => menubar
19560
19963
  });
19561
- import { existsSync as existsSync20, mkdirSync as mkdirSync13, cpSync, rmSync as rmSync2, readdirSync as readdirSync4, writeFileSync as writeFileSync14, realpathSync as realpathSync6 } from "node:fs";
19964
+ import { existsSync as existsSync21, mkdirSync as mkdirSync13, cpSync, rmSync as rmSync2, readdirSync as readdirSync4, writeFileSync as writeFileSync14, realpathSync as realpathSync6 } from "node:fs";
19562
19965
  import { homedir as homedir7 } from "node:os";
19563
19966
  import { join as join14, dirname as dirname14 } from "node:path";
19564
19967
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -19632,7 +20035,7 @@ async function installMenubar(opts) {
19632
20035
  return;
19633
20036
  }
19634
20037
  const src = bundledAppPath();
19635
- if (!existsSync20(src)) {
20038
+ if (!existsSync21(src)) {
19636
20039
  console.error(
19637
20040
  `Bundled widget not found at ${src}.
19638
20041
  This install was probably built without the menubar artifact. Run \`bash apps/menubar/build-app.sh && cp -R apps/menubar/dist/Fleetlens.app packages/cli/menubar/\` and rebuild.`
@@ -19678,7 +20081,7 @@ async function uninstallMenubar() {
19678
20081
  await new Promise((r) => setTimeout(r, 300));
19679
20082
  }
19680
20083
  const dest = installedAppPath();
19681
- if (existsSync20(dest)) {
20084
+ if (existsSync21(dest)) {
19682
20085
  rmSync2(dest, { recursive: true, force: true });
19683
20086
  console.log(`Removed ${dest}`);
19684
20087
  } else {
@@ -19693,7 +20096,7 @@ async function openMenubar() {
19693
20096
  return;
19694
20097
  }
19695
20098
  const dest = installedAppPath();
19696
- if (!existsSync20(dest)) {
20099
+ if (!existsSync21(dest)) {
19697
20100
  console.log("Widget not installed yet. Run `fleetlens menubar install` first.");
19698
20101
  return;
19699
20102
  }
@@ -19710,9 +20113,9 @@ async function statusMenubar() {
19710
20113
  return;
19711
20114
  }
19712
20115
  const dest = installedAppPath();
19713
- const installed = existsSync20(dest);
20116
+ const installed = existsSync21(dest);
19714
20117
  const running = await isRunning();
19715
- const bundled = existsSync20(bundledAppPath());
20118
+ const bundled = existsSync21(bundledAppPath());
19716
20119
  console.log(`Menu bar widget: ${installed ? "installed" : "not installed"}${installed ? ` (${dest})` : ""}`);
19717
20120
  console.log(`Running: ${running ? "yes" : "no"}`);
19718
20121
  console.log(`Bundled in CLI: ${bundled ? "yes" : "no \u2014 this build ships without the widget"}`);
@@ -19803,7 +20206,7 @@ var init_config = __esm({
19803
20206
  });
19804
20207
 
19805
20208
  // src/perception/build-entries.ts
19806
- import { statSync as statSync4, readFileSync as readFileSync20 } from "node:fs";
20209
+ import { statSync as statSync4, readFileSync as readFileSync21 } from "node:fs";
19807
20210
  import { basename as basename2, dirname as dirname15 } from "node:path";
19808
20211
  function decodeProjectDirName(projectDir) {
19809
20212
  if (projectDir.startsWith("-")) {
@@ -19813,7 +20216,7 @@ function decodeProjectDirName(projectDir) {
19813
20216
  }
19814
20217
  function buildEntriesForFile(filePath) {
19815
20218
  const stat = statSync4(filePath);
19816
- const raw = readFileSync20(filePath, "utf8");
20219
+ const raw = readFileSync21(filePath, "utf8");
19817
20220
  const rawLines = raw.split("\n").filter(Boolean).map((l) => {
19818
20221
  try {
19819
20222
  return JSON.parse(l);
@@ -20082,7 +20485,7 @@ function buildIngestPayload(inputs) {
20082
20485
  return {
20083
20486
  ingestId: randomUUID2(),
20084
20487
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
20085
- cliVersion: "1.0.7",
20488
+ cliVersion: "1.0.8",
20086
20489
  ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
20087
20490
  ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
20088
20491
  ...richRollup ? { richRollup } : {},
@@ -20124,7 +20527,7 @@ var init_push = __esm({
20124
20527
  });
20125
20528
 
20126
20529
  // src/team/git-remote.ts
20127
- import { existsSync as existsSync21, readFileSync as readFileSync21, statSync as statSync5 } from "node:fs";
20530
+ import { existsSync as existsSync22, readFileSync as readFileSync22, statSync as statSync5 } from "node:fs";
20128
20531
  import { dirname as dirname16, isAbsolute as isAbsolute2, join as join15, resolve as resolve2 } from "node:path";
20129
20532
  function parseGithubRemote(url) {
20130
20533
  const m = url.trim().match(GITHUB_URL_RE);
@@ -20149,12 +20552,12 @@ function gitConfigPathFor(dir) {
20149
20552
  const st = statSync5(dotGit, { throwIfNoEntry: false });
20150
20553
  if (!st) return null;
20151
20554
  if (st.isDirectory()) return join15(dotGit, "config");
20152
- const m = readFileSync21(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
20555
+ const m = readFileSync22(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
20153
20556
  if (!m) return null;
20154
20557
  const gd = isAbsolute2(m[1]) ? m[1] : resolve2(dir, m[1]);
20155
20558
  const commondirFile = join15(gd, "commondir");
20156
- if (existsSync21(commondirFile)) {
20157
- const common = readFileSync21(commondirFile, "utf8").trim();
20559
+ if (existsSync22(commondirFile)) {
20560
+ const common = readFileSync22(commondirFile, "utf8").trim();
20158
20561
  return join15(isAbsolute2(common) ? common : resolve2(gd, common), "config");
20159
20562
  }
20160
20563
  return join15(gd, "config");
@@ -20169,8 +20572,8 @@ function createRepoResolver() {
20169
20572
  try {
20170
20573
  for (let d = dir; ; d = dirname16(d)) {
20171
20574
  const config = gitConfigPathFor(d);
20172
- if (config && existsSync21(config)) {
20173
- const url = originUrl2(readFileSync21(config, "utf8"));
20575
+ if (config && existsSync22(config)) {
20576
+ const url = originUrl2(readFileSync22(config, "utf8"));
20174
20577
  repo = url ? parseGithubRemote(url) : null;
20175
20578
  break;
20176
20579
  }
@@ -20192,7 +20595,7 @@ var init_git_remote = __esm({
20192
20595
  });
20193
20596
 
20194
20597
  // src/team/queue.ts
20195
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync15, appendFileSync as appendFileSync5, statSync as statSync6, mkdirSync as mkdirSync14 } from "node:fs";
20598
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync15, appendFileSync as appendFileSync5, statSync as statSync6, mkdirSync as mkdirSync14 } from "node:fs";
20196
20599
  import { dirname as dirname17 } from "node:path";
20197
20600
  function enqueuePayload(payload, queuePath = QUEUE_PATH) {
20198
20601
  mkdirSync14(dirname17(queuePath), { recursive: true });
@@ -20209,7 +20612,7 @@ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
20209
20612
  function dequeuePayloads(queuePath = QUEUE_PATH) {
20210
20613
  let lines;
20211
20614
  try {
20212
- lines = readFileSync22(queuePath, "utf8").trim().split("\n").filter(Boolean);
20615
+ lines = readFileSync23(queuePath, "utf8").trim().split("\n").filter(Boolean);
20213
20616
  } catch {
20214
20617
  return [];
20215
20618
  }
@@ -20230,7 +20633,7 @@ function dequeuePayloads(queuePath = QUEUE_PATH) {
20230
20633
  function pruneOldest(queuePath) {
20231
20634
  let lines;
20232
20635
  try {
20233
- lines = readFileSync22(queuePath, "utf8").trim().split("\n").filter(Boolean);
20636
+ lines = readFileSync23(queuePath, "utf8").trim().split("\n").filter(Boolean);
20234
20637
  } catch {
20235
20638
  return;
20236
20639
  }
@@ -20249,7 +20652,7 @@ var init_queue2 = __esm({
20249
20652
  });
20250
20653
 
20251
20654
  // src/usage/profile.ts
20252
- import { existsSync as existsSync22, mkdirSync as mkdirSync15, readFileSync as readFileSync23, writeFileSync as writeFileSync16 } from "node:fs";
20655
+ import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
20253
20656
  import { dirname as dirname18 } from "node:path";
20254
20657
  function mapRateLimitTier(rateLimitTier) {
20255
20658
  switch (rateLimitTier) {
@@ -20290,9 +20693,9 @@ async function fetchProfile(nowMs = Date.now()) {
20290
20693
  };
20291
20694
  }
20292
20695
  function readCachedProfile(cachePath) {
20293
- if (!existsSync22(cachePath)) return null;
20696
+ if (!existsSync23(cachePath)) return null;
20294
20697
  try {
20295
- const raw = JSON.parse(readFileSync23(cachePath, "utf8"));
20698
+ const raw = JSON.parse(readFileSync24(cachePath, "utf8"));
20296
20699
  if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
20297
20700
  return raw;
20298
20701
  } catch {
@@ -20481,11 +20884,11 @@ var init_backfill = __esm({
20481
20884
  });
20482
20885
 
20483
20886
  // src/team/sync-log.ts
20484
- import { readFileSync as readFileSync24 } from "node:fs";
20887
+ import { readFileSync as readFileSync25 } from "node:fs";
20485
20888
  function readPendingSyncLog(watermark) {
20486
20889
  let raw;
20487
20890
  try {
20488
- raw = readFileSync24(cclensPath("daemon.log"), "utf8");
20891
+ raw = readFileSync25(cclensPath("daemon.log"), "utf8");
20489
20892
  } catch {
20490
20893
  return { lines: [] };
20491
20894
  }
@@ -20549,7 +20952,7 @@ __export(file_probe_exports, {
20549
20952
  probeArtifactSignals: () => probeArtifactSignals
20550
20953
  });
20551
20954
  import { createHash } from "node:crypto";
20552
- import { existsSync as existsSync23, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
20955
+ import { existsSync as existsSync24, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
20553
20956
  import { homedir as homedir8 } from "node:os";
20554
20957
  import { join as join17, relative, resolve as resolve3, sep } from "node:path";
20555
20958
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -20559,15 +20962,15 @@ function pathHash(relPath) {
20559
20962
  function defaultScanRoots(extraRoots = []) {
20560
20963
  const roots = [];
20561
20964
  const home = join17(homedir8(), ".claude");
20562
- if (existsSync23(home)) roots.push({ root: home, label: "user" });
20965
+ if (existsSync24(home)) roots.push({ root: home, label: "user" });
20563
20966
  for (const e of extraRoots) {
20564
20967
  const r = resolve3(e, ".claude");
20565
- if (existsSync23(r) && r !== home) roots.push({ root: r, label: "project" });
20968
+ if (existsSync24(r) && r !== home) roots.push({ root: r, label: "project" });
20566
20969
  }
20567
20970
  return roots;
20568
20971
  }
20569
20972
  function listMarkdownFiles(dir) {
20570
- if (!existsSync23(dir)) return [];
20973
+ if (!existsSync24(dir)) return [];
20571
20974
  const out = [];
20572
20975
  let entries2;
20573
20976
  try {
@@ -20637,7 +21040,7 @@ function scanAuthored(roots, subdir, day2) {
20637
21040
  const out = [];
20638
21041
  for (const r of roots) {
20639
21042
  const dir = join17(r.root, subdir);
20640
- if (!existsSync23(dir)) continue;
21043
+ if (!existsSync24(dir)) continue;
20641
21044
  for (const f of listMarkdownFiles(dir)) {
20642
21045
  const firstSeen = firstSeenDateOf(f);
20643
21046
  if (firstSeen !== day2) continue;
@@ -20651,7 +21054,7 @@ function scanEdited(roots, day2) {
20651
21054
  const out = [];
20652
21055
  for (const r of roots) {
20653
21056
  const dir = join17(r.root, "skills");
20654
- if (!existsSync23(dir)) continue;
21057
+ if (!existsSync24(dir)) continue;
20655
21058
  for (const f of listMarkdownFiles(dir)) {
20656
21059
  const firstSeen = firstSeenDateOf(f);
20657
21060
  if (firstSeen === day2) continue;
@@ -20674,10 +21077,10 @@ function claudemdLineDelta(extraRoots, authorEmail, day2) {
20674
21077
  const candidates = [];
20675
21078
  for (const e of extraRoots) {
20676
21079
  const f = resolve3(e, "CLAUDE.md");
20677
- if (existsSync23(f)) candidates.push(f);
21080
+ if (existsSync24(f)) candidates.push(f);
20678
21081
  }
20679
21082
  const homeClaudeMd = join17(homedir8(), "CLAUDE.md");
20680
- if (existsSync23(homeClaudeMd)) candidates.push(homeClaudeMd);
21083
+ if (existsSync24(homeClaudeMd)) candidates.push(homeClaudeMd);
20681
21084
  for (const file of candidates) {
20682
21085
  try {
20683
21086
  const out = execFileSync3(
@@ -21567,11 +21970,11 @@ var logs_exports = {};
21567
21970
  __export(logs_exports, {
21568
21971
  teamLogs: () => teamLogs
21569
21972
  });
21570
- import { readFileSync as readFileSync25 } from "node:fs";
21973
+ import { readFileSync as readFileSync26 } from "node:fs";
21571
21974
  async function teamLogs() {
21572
21975
  const logPath = cclensPath("daemon.log");
21573
21976
  try {
21574
- const lines = readFileSync25(logPath, "utf8").trim().split("\n");
21977
+ const lines = readFileSync26(logPath, "utf8").trim().split("\n");
21575
21978
  const teamLines = lines.filter((l) => l.includes("[sync] ") || l.includes("team "));
21576
21979
  if (teamLines.length === 0) {
21577
21980
  console.log("No team-related log entries found.");
@@ -21699,7 +22102,7 @@ __export(runs_exports, {
21699
22102
  runs: () => runs
21700
22103
  });
21701
22104
  import { execSync as execSync2 } from "node:child_process";
21702
- import { existsSync as existsSync24, readFileSync as readFileSync26, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
22105
+ import { existsSync as existsSync25, readFileSync as readFileSync27, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
21703
22106
  import { join as join18 } from "node:path";
21704
22107
  async function runs(args2) {
21705
22108
  const json = args2.includes("--json");
@@ -21784,8 +22187,8 @@ function listActiveRuns() {
21784
22187
  return runs2.sort((a, b) => a.elapsed_s - b.elapsed_s);
21785
22188
  }
21786
22189
  function readSpend(sinceMs) {
21787
- if (!existsSync24(SPEND_PATH)) return [];
21788
- const lines = readFileSync26(SPEND_PATH, "utf-8").split("\n").filter((l) => l.trim());
22190
+ if (!existsSync25(SPEND_PATH)) return [];
22191
+ const lines = readFileSync27(SPEND_PATH, "utf-8").split("\n").filter((l) => l.trim());
21789
22192
  const out = [];
21790
22193
  for (const line of lines) {
21791
22194
  try {
@@ -21906,7 +22309,7 @@ function ageLabel(ms) {
21906
22309
  return `${Math.floor(ms / 864e5)}d`;
21907
22310
  }
21908
22311
  function listTraceFiles() {
21909
- if (!existsSync24(RUNS_DIR2)) return [];
22312
+ if (!existsSync25(RUNS_DIR2)) return [];
21910
22313
  const entries2 = readdirSync6(RUNS_DIR2).filter((f) => f.endsWith(".jsonl"));
21911
22314
  return entries2.map((f) => {
21912
22315
  const path11 = join18(RUNS_DIR2, f);
@@ -21915,14 +22318,14 @@ function listTraceFiles() {
21915
22318
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
21916
22319
  }
21917
22320
  function resolveRunId(arg) {
21918
- if (!existsSync24(RUNS_DIR2)) return null;
22321
+ if (!existsSync25(RUNS_DIR2)) return null;
21919
22322
  if (arg === "latest") {
21920
22323
  const all2 = listTraceFiles();
21921
22324
  if (all2.length === 0) return null;
21922
22325
  return { runId: all2[0].runId, path: all2[0].path };
21923
22326
  }
21924
22327
  const exact = join18(RUNS_DIR2, `${arg}.jsonl`);
21925
- if (existsSync24(exact)) return { runId: arg, path: exact };
22328
+ if (existsSync25(exact)) return { runId: arg, path: exact };
21926
22329
  const all = listTraceFiles();
21927
22330
  const matches = all.filter((r) => r.runId.includes(arg));
21928
22331
  if (matches.length === 1) return { runId: matches[0].runId, path: matches[0].path };
@@ -21934,7 +22337,7 @@ function resolveRunId(arg) {
21934
22337
  return null;
21935
22338
  }
21936
22339
  function readTrace(path11) {
21937
- return readFileSync26(path11, "utf-8").split("\n").filter((l) => l.trim()).map((l) => {
22340
+ return readFileSync27(path11, "utf-8").split("\n").filter((l) => l.trim()).map((l) => {
21938
22341
  try {
21939
22342
  return JSON.parse(l);
21940
22343
  } catch {
@@ -21946,7 +22349,7 @@ async function inspect(arg, follow, json) {
21946
22349
  const r = resolveRunId(arg);
21947
22350
  if (!r) {
21948
22351
  console.error(`run not found: ${arg}`);
21949
- if (existsSync24(RUNS_DIR2)) {
22352
+ if (existsSync25(RUNS_DIR2)) {
21950
22353
  const all = listTraceFiles().slice(0, 5);
21951
22354
  if (all.length > 0) {
21952
22355
  console.error(`
@@ -22126,7 +22529,7 @@ async function main() {
22126
22529
  case "version":
22127
22530
  case "--version":
22128
22531
  case "-v":
22129
- console.log(`fleetlens ${"1.0.7"}`);
22532
+ console.log(`fleetlens ${"1.0.8"}`);
22130
22533
  break;
22131
22534
  case "help":
22132
22535
  case "--help":
@@ -22140,7 +22543,7 @@ Common:
22140
22543
  update Update to the latest version
22141
22544
 
22142
22545
  Terminal:
22143
- usage [--save] Plan utilization snapshot (5h/7d)
22546
+ usage [--agent KIND] [--watch] [--save] Plan utilization (all agents; --watch is live)
22144
22547
  usage --history [-s D] [--days N] Daily token/cost table
22145
22548
  entries [--day D|--session ID|--all] [--json] Perception-layer entries
22146
22549
  digest day [--date D|--yesterday|--today] [--json] Day digest