fleetlens 1.0.7 → 1.0.9

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/16ng0.1m2m026.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 +2203 -1710
  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 → vYFdh4rRszFyu8bKNXN4N}/_buildManifest.js +0 -0
  53. /package/app/apps/web/.next/static/{rSw7StGuB4J2DfY6_pElg → vYFdh4rRszFyu8bKNXN4N}/_clientMiddlewareManifest.js +0 -0
  54. /package/app/apps/web/.next/static/{rSw7StGuB4J2DfY6_pElg → vYFdh4rRszFyu8bKNXN4N}/_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.9";
6302
6302
  } catch {
6303
- return "1.0.7";
6303
+ return "1.0.9";
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.9";
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.9") {
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.9"}. 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.9",
6814
6869
  latest: null,
6815
6870
  updateAvailable: false
6816
6871
  };
6817
6872
  return {
6818
- current: "1.0.7",
6873
+ current: "1.0.9",
6819
6874
  latest,
6820
- updateAvailable: shouldUpdate("1.0.7", latest)
6875
+ updateAvailable: shouldUpdate("1.0.9", 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.9";
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.9"}) \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,1448 @@ 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() {
8932
+ // src/usage/format.ts
8933
+ function barWidthFor(columns, labelWidth) {
8934
+ return Math.max(4, columns - labelWidth - 12);
8935
+ }
8936
+ function layoutForColumns(columns) {
8937
+ const cols = Number.isFinite(columns) && columns > 0 ? Math.floor(columns) : 80;
8938
+ if (cols < 36) {
8939
+ return {
8940
+ mode: "narrow",
8941
+ barWidth: 0,
8942
+ labelWidth: 4,
8943
+ shortLabels: true,
8944
+ columns: cols
8945
+ };
8946
+ }
8947
+ if (cols < 72) {
8948
+ const labelWidth2 = 4;
8949
+ return {
8950
+ mode: "medium",
8951
+ barWidth: barWidthFor(cols, labelWidth2),
8952
+ labelWidth: labelWidth2,
8953
+ shortLabels: true,
8954
+ columns: cols
8955
+ };
8956
+ }
8957
+ const labelWidth = 16;
8714
8958
  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
8959
+ mode: "wide",
8960
+ barWidth: barWidthFor(cols, labelWidth),
8961
+ labelWidth,
8962
+ shortLabels: false,
8963
+ columns: cols
8728
8964
  };
8729
8965
  }
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] };
8966
+ function resolveColumns(explicit) {
8967
+ if (explicit !== void 0 && Number.isFinite(explicit) && explicit > 0) {
8968
+ return Math.floor(explicit);
8969
+ }
8970
+ const c = process.stdout.columns;
8971
+ if (typeof c === "number" && c > 0) return c;
8972
+ const env = parseInt(process.env.COLUMNS ?? "", 10);
8973
+ if (Number.isFinite(env) && env > 0) return env;
8974
+ return 80;
8755
8975
  }
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
- };
8976
+ function formatMultiAgentUsage(byAgent, opts = {}) {
8977
+ const kinds = Object.keys(byAgent).sort(compareAgents);
8978
+ if (kinds.length === 0) return "";
8979
+ const layout = layoutForColumns(resolveColumns(opts.columns));
8980
+ const lines = [];
8981
+ lines.push("");
8982
+ if (opts.watch) {
8983
+ const sec = opts.intervalSec ?? 2;
8984
+ lines.push(
8985
+ layout.mode === "narrow" ? ` ${BOLD}Usage${RESET} ${DIM}live ${sec}s \xB7 ^C quit${RESET}` : ` ${BOLD}Fleetlens Usage${RESET} ${DIM}live \xB7 every ${sec}s \xB7 Ctrl+C to quit${RESET}`
8986
+ );
8987
+ } else {
8988
+ lines.push(
8989
+ layout.mode === "narrow" ? ` ${BOLD}Usage${RESET}` : ` ${BOLD}Fleetlens Usage${RESET}`
8990
+ );
8785
8991
  }
8786
- });
8787
-
8788
- // ../parser/dist/types.js
8789
- var init_types2 = __esm({
8790
- "../parser/dist/types.js"() {
8791
- "use strict";
8992
+ lines.push("");
8993
+ for (const kind of kinds) {
8994
+ const snap = byAgent[kind];
8995
+ lines.push(...formatAgentBlock(kind, snap, layout));
8792
8996
  }
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;
8997
+ lines.push("");
8998
+ return lines.join("\n");
8800
8999
  }
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
- }
9000
+ function formatAgentBlock(kind, snapshot, layout) {
9001
+ const lines = [];
9002
+ const title = layout.shortLabels && isAgentKind(kind) ? getAgentMetadata(kind)?.shortLabel ?? agentTitle(kind) : agentTitle(kind);
9003
+ const plan = snapshot.plan_type && layout.mode !== "narrow" ? ` ${DIM}${snapshot.plan_type}${RESET}` : "";
9004
+ lines.push(` ${BOLD}${title}${RESET}${plan}`);
9005
+ let renderedMeter = false;
9006
+ for (const [label, window] of agentWindows(kind, snapshot, layout.shortLabels)) {
9007
+ if (!window || window.utilization === null) continue;
9008
+ renderedMeter = true;
9009
+ lines.push(...formatMeterRow(label, window.utilization, window.resets_at, layout));
9010
+ }
9011
+ if ((kind === "claude-code" || !kind) && snapshot.extra_usage?.is_enabled) {
9012
+ renderedMeter = true;
9013
+ const extra = snapshot.extra_usage;
9014
+ if (extra.utilization !== null) {
9015
+ const label = layout.shortLabels ? "xtra" : "Extra usage";
9016
+ lines.push(...formatMeterRow(label, extra.utilization, null, layout));
8838
9017
  }
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;
9018
+ if (extra.used_credits !== null && extra.monthly_limit !== null) {
9019
+ const pad2 = layout.mode === "narrow" ? 2 : layout.labelWidth + 2;
9020
+ lines.push(
9021
+ ` ${" ".repeat(pad2)}${DIM}${extra.used_credits} / ${extra.monthly_limit} credits${RESET}`
9022
+ );
8849
9023
  }
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
- }
9024
+ }
9025
+ if (kind === "zai" && snapshot.web_search_quota) {
9026
+ const used = snapshot.web_search_quota.used;
9027
+ if (used !== null && used !== void 0) {
9028
+ renderedMeter = true;
9029
+ const label = layout.shortLabels ? "web" : "Web search";
9030
+ lines.push(...formatMeterRow(label, used, null, layout, { wholePct: true }));
9031
+ }
9032
+ }
9033
+ if (kind === "copilot" && snapshot.monthly_quota) {
9034
+ const q = snapshot.monthly_quota;
9035
+ const unit = q.unit === "premium-requests" ? "premium requests" : "AI credits";
9036
+ const pad2 = layout.mode === "narrow" ? 2 : layout.labelWidth + 2;
9037
+ if (q.unlimited) {
9038
+ renderedMeter = true;
9039
+ const lab = layout.shortLabels ? "mo" : "Monthly";
9040
+ lines.push(` ${lab.padEnd(layout.labelWidth)}${BOLD}Limit not reported${RESET}`);
9041
+ if (q.used !== null) {
9042
+ lines.push(
9043
+ ` ${" ".repeat(pad2)}${DIM}${q.used.toLocaleString("en-US")} ${unit}${RESET}`
9044
+ );
8856
9045
  }
9046
+ } else if (q.used !== null && q.limit !== null) {
9047
+ lines.push(
9048
+ ` ${" ".repeat(pad2)}${DIM}${q.used} / ${q.limit} ${unit}${RESET}`
9049
+ );
8857
9050
  }
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
8883
- }
8884
- });
8885
- buffer = [];
9051
+ }
9052
+ if (!renderedMeter) {
9053
+ lines.push(` ${DIM}(no utilization windows)${RESET}`);
9054
+ }
9055
+ lines.push(
9056
+ ` ${DIM}sampled ${formatRelative(snapshot.captured_at)}${RESET}`
9057
+ );
9058
+ lines.push("");
9059
+ return lines;
9060
+ }
9061
+ function formatMeterRow(label, utilization, resetsAt, layout, opts = {}) {
9062
+ const pctStr = opts.wholePct ? `${utilization.toFixed(0)}%`.padStart(5) : `${utilization.toFixed(1)}%`.padStart(6);
9063
+ const labelStr = label.padEnd(layout.labelWidth);
9064
+ if (layout.mode === "narrow" && layout.barWidth <= 0) {
9065
+ const reset = resetsAt ? ` ${DIM}r${formatRelativeShort(resetsAt)}${RESET}` : "";
9066
+ return [` ${labelStr}${BOLD}${pctStr}${RESET}${reset}`];
9067
+ }
9068
+ const bar = renderBar(utilization, layout.barWidth);
9069
+ const lines = [` ${labelStr}${bar} ${BOLD}${pctStr}${RESET} `];
9070
+ if (resetsAt) {
9071
+ lines.push(
9072
+ ` ${" ".repeat(layout.labelWidth)}${DIM}resets ${formatRelative(resetsAt)}${RESET}`
9073
+ );
9074
+ }
9075
+ return lines;
9076
+ }
9077
+ function agentWindows(kind, snapshot, shortLabels) {
9078
+ const L = shortLabels ? { five: "5h", seven: "7d", monthly: "mo", opus: "opus", sonnet: "son", oauth: "oath", cowork: "cowk" } : {
9079
+ five: "5 hour",
9080
+ seven: "7 day",
9081
+ monthly: "Monthly",
9082
+ opus: "7 day (Opus)",
9083
+ sonnet: "7 day (Sonnet)",
9084
+ oauth: "7 day (OAuth apps)",
9085
+ cowork: "7 day (Cowork)"
8886
9086
  };
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);
9087
+ if (kind === "copilot") {
9088
+ return [[L.monthly, snapshot.monthly]];
9089
+ }
9090
+ if (kind === "grok") {
9091
+ return [[L.seven, snapshot.seven_day]];
9092
+ }
9093
+ if (kind === "codex" && snapshot.five_hour?.utilization == null) {
9094
+ return [[L.seven, snapshot.seven_day]];
9095
+ }
9096
+ const rows = [
9097
+ [L.five, snapshot.five_hour],
9098
+ [L.seven, snapshot.seven_day]
9099
+ ];
9100
+ if (kind === "claude-code" || !snapshot.agent) {
9101
+ if (snapshot.seven_day_opus?.utilization != null) {
9102
+ rows.push([L.opus, snapshot.seven_day_opus]);
9103
+ }
9104
+ if (snapshot.seven_day_sonnet?.utilization != null) {
9105
+ rows.push([L.sonnet, snapshot.seven_day_sonnet]);
9106
+ }
9107
+ if (snapshot.seven_day_oauth_apps?.utilization != null) {
9108
+ rows.push([L.oauth, snapshot.seven_day_oauth_apps]);
9109
+ }
9110
+ if (snapshot.seven_day_cowork?.utilization != null) {
9111
+ rows.push([L.cowork, snapshot.seven_day_cowork]);
8894
9112
  }
8895
9113
  }
8896
- flush();
8897
- return out;
9114
+ return rows;
8898
9115
  }
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
- };
9116
+ function agentTitle(kind) {
9117
+ if (isAgentKind(kind)) {
9118
+ return getAgentMetadata(kind)?.displayName ?? kind;
9119
+ }
9120
+ return kind;
8921
9121
  }
8922
- function isAttachmentOrMeta(e) {
8923
- return e.role === "system" || e.role === "meta";
9122
+ function compareAgents(a, b) {
9123
+ const ia = AGENT_PRIORITY.indexOf(a);
9124
+ const ib = AGENT_PRIORITY.indexOf(b);
9125
+ if (ia !== -1 || ib !== -1) {
9126
+ if (ia === -1) return 1;
9127
+ if (ib === -1) return -1;
9128
+ return ia - ib;
9129
+ }
9130
+ return a.localeCompare(b);
8924
9131
  }
8925
- function firstTextOfBlocks(blocks) {
8926
- for (const b of blocks) {
8927
- if (b?.type === "text")
8928
- return b.text;
9132
+ function renderBar(utilization, barWidth) {
9133
+ if (barWidth <= 0) return "";
9134
+ const clamped = Math.max(0, Math.min(100, utilization));
9135
+ const color = clamped >= 90 ? RED : clamped >= 70 ? YELLOW : GREEN;
9136
+ const totalEighths = Math.round(clamped / 100 * barWidth * 8);
9137
+ const fullBlocks = Math.floor(totalEighths / 8);
9138
+ const remainder = totalEighths % 8;
9139
+ const partial = EIGHTHS[remainder];
9140
+ const filledCells = fullBlocks + (partial ? 1 : 0);
9141
+ const emptyCells = Math.max(0, barWidth - filledCells);
9142
+ const filled = "\u2588".repeat(fullBlocks) + partial;
9143
+ const empty = "\xB7".repeat(emptyCells);
9144
+ return `${color}${filled}${RESET}${DIM}${empty}${RESET}`;
9145
+ }
9146
+ function formatRelative(iso) {
9147
+ const then = new Date(iso).getTime();
9148
+ const now = Date.now();
9149
+ const diffSec = Math.round((then - now) / 1e3);
9150
+ const abs = Math.abs(diffSec);
9151
+ const past = diffSec < 0;
9152
+ let value;
9153
+ if (abs < 60) {
9154
+ value = `${abs}s`;
9155
+ } else if (abs < 3600) {
9156
+ value = `${Math.floor(abs / 60)}m`;
9157
+ } else if (abs < 86400) {
9158
+ const h = Math.floor(abs / 3600);
9159
+ const m = Math.floor(abs % 3600 / 60);
9160
+ value = m > 0 ? `${h}h${m}m` : `${h}h`;
9161
+ } else {
9162
+ const d = Math.floor(abs / 86400);
9163
+ const h = Math.floor(abs % 86400 / 3600);
9164
+ value = h > 0 ? `${d}d${h}h` : `${d}d`;
8929
9165
  }
8930
- return void 0;
9166
+ return past ? `${value} ago` : `in ${value}`;
8931
9167
  }
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;
9168
+ function formatRelativeShort(iso) {
9169
+ const then = new Date(iso).getTime();
9170
+ const now = Date.now();
9171
+ const abs = Math.abs(Math.round((then - now) / 1e3));
9172
+ if (abs < 60) return `${abs}s`;
9173
+ if (abs < 3600) return `${Math.floor(abs / 60)}m`;
9174
+ if (abs < 86400) return `${Math.floor(abs / 3600)}h`;
9175
+ return `${Math.floor(abs / 86400)}d`;
8937
9176
  }
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);
9177
+ var DIM, BOLD, RESET, GREEN, YELLOW, RED, EIGHTHS, AGENT_PRIORITY;
9178
+ var init_format = __esm({
9179
+ "src/usage/format.ts"() {
9180
+ "use strict";
9181
+ init_dist();
9182
+ DIM = "\x1B[2m";
9183
+ BOLD = "\x1B[1m";
9184
+ RESET = "\x1B[0m";
9185
+ GREEN = "\x1B[32m";
9186
+ YELLOW = "\x1B[33m";
9187
+ RED = "\x1B[31m";
9188
+ EIGHTHS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
9189
+ AGENT_PRIORITY = ["claude-code", "codex", "copilot", "zai", "grok"];
8946
9190
  }
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;
9191
+ });
9192
+
9193
+ // src/pricing.ts
9194
+ function findPricing(model) {
9195
+ for (const [prefix, pricing] of PRICING) {
9196
+ if (model.startsWith(prefix)) return pricing;
9197
+ }
9198
+ return null;
9199
+ }
9200
+ function estimateCost(model, usage2) {
9201
+ const p = findPricing(model);
9202
+ if (!p) return null;
9203
+ return (usage2.input * p.input + usage2.output * p.output + usage2.cacheRead * p.cacheRead + usage2.cacheWrite * p.cacheWrite) / 1e6;
9204
+ }
9205
+ var PRICING;
9206
+ var init_pricing = __esm({
9207
+ "src/pricing.ts"() {
9208
+ "use strict";
9209
+ PRICING = [
9210
+ ["claude-opus-4", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }],
9211
+ ["claude-sonnet-4", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
9212
+ ["claude-haiku-4", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
9213
+ ["claude-3-5-sonnet", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
9214
+ ["claude-3-5-haiku", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
9215
+ ["claude-3-opus", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }]
9216
+ ];
9217
+ }
9218
+ });
9219
+
9220
+ // src/table.ts
9221
+ function fmtNum(n) {
9222
+ return n.toLocaleString("en-US");
9223
+ }
9224
+ function fmtCost(cost, partial = false) {
9225
+ if (cost === null) return "\u2014";
9226
+ return `${partial ? "\u2265" : ""}$${cost.toFixed(2)}`;
9227
+ }
9228
+ function pad(s, width, align = "right") {
9229
+ if (align === "left") return s.padEnd(width);
9230
+ return s.padStart(width);
9231
+ }
9232
+ function renderTable(rows, title) {
9233
+ const lines = [];
9234
+ lines.push("");
9235
+ lines.push(`${BOLD2}${title}${RESET2}`);
9236
+ lines.push("");
9237
+ const header = [
9238
+ pad("Date", 12, "left"),
9239
+ pad("Models", 20, "left"),
9240
+ pad("Input", 12),
9241
+ pad("Output", 12),
9242
+ pad("Cache Create", 14),
9243
+ pad("Cache Read", 14),
9244
+ pad("Total", 14),
9245
+ pad("Cost (USD)", 12)
9246
+ ].join(" ");
9247
+ lines.push(`${DIM2}${header}${RESET2}`);
9248
+ lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
9249
+ let costIncomplete = false;
9250
+ for (const r of rows) {
9251
+ if (r.cost === null || r.costPartial) costIncomplete = true;
9252
+ lines.push([
9253
+ pad(r.date, 12, "left"),
9254
+ pad(r.models, 20, "left"),
9255
+ pad(fmtNum(r.input), 12),
9256
+ pad(fmtNum(r.output), 12),
9257
+ pad(fmtNum(r.cacheCreate), 14),
9258
+ pad(fmtNum(r.cacheRead), 14),
9259
+ pad(fmtNum(r.totalTokens), 14),
9260
+ pad(fmtCost(r.cost, r.costPartial), 12)
9261
+ ].join(" "));
9262
+ }
9263
+ const totals = rows.reduce(
9264
+ (acc, r) => ({
9265
+ input: acc.input + r.input,
9266
+ output: acc.output + r.output,
9267
+ cacheCreate: acc.cacheCreate + r.cacheCreate,
9268
+ cacheRead: acc.cacheRead + r.cacheRead,
9269
+ totalTokens: acc.totalTokens + r.totalTokens,
9270
+ cost: acc.cost + (r.cost ?? 0),
9271
+ anyCost: acc.anyCost || r.cost !== null
9272
+ }),
9273
+ { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, totalTokens: 0, cost: 0, anyCost: false }
9274
+ );
9275
+ lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
9276
+ lines.push(
9277
+ `${BOLD2}${[
9278
+ pad("Total", 12, "left"),
9279
+ pad("", 20, "left"),
9280
+ pad(fmtNum(totals.input), 12),
9281
+ pad(fmtNum(totals.output), 12),
9282
+ pad(fmtNum(totals.cacheCreate), 14),
9283
+ pad(fmtNum(totals.cacheRead), 14),
9284
+ pad(fmtNum(totals.totalTokens), 14),
9285
+ pad(totals.anyCost ? fmtCost(totals.cost, costIncomplete) : fmtCost(null), 12)
9286
+ ].join(" ")}${RESET2}`
9287
+ );
9288
+ if (costIncomplete) {
9289
+ lines.push("");
9290
+ lines.push(
9291
+ `${DIM2} Cost covers priced usage only \u2014 "\u2014" = no priced sessions that day, "\u2265" = lower bound (some sessions had unknown pricing).${RESET2}`
9292
+ );
9293
+ }
9294
+ lines.push("");
9295
+ return lines.join("\n");
9296
+ }
9297
+ var DIM2, BOLD2, RESET2;
9298
+ var init_table = __esm({
9299
+ "src/table.ts"() {
9300
+ "use strict";
9301
+ DIM2 = "\x1B[2m";
9302
+ BOLD2 = "\x1B[1m";
9303
+ RESET2 = "\x1B[0m";
9304
+ }
9305
+ });
9306
+
9307
+ // src/commands/usage-history.ts
9308
+ var usage_history_exports = {};
9309
+ __export(usage_history_exports, {
9310
+ history: () => history
9311
+ });
9312
+ async function history(args2) {
9313
+ const live = args2.includes("--live");
9314
+ const sinceIdx = args2.indexOf("-s");
9315
+ const daysIdx = args2.indexOf("--days");
9316
+ let sinceDate = null;
9317
+ if (sinceIdx !== -1 && args2[sinceIdx + 1]) {
9318
+ const raw = args2[sinceIdx + 1];
9319
+ sinceDate = /* @__PURE__ */ new Date(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`);
9320
+ } else if (daysIdx !== -1 && args2[daysIdx + 1]) {
9321
+ const days = parseInt(args2[daysIdx + 1], 10);
9322
+ sinceDate = /* @__PURE__ */ new Date();
9323
+ sinceDate.setDate(sinceDate.getDate() - days);
9324
+ sinceDate.setHours(0, 0, 0, 0);
9325
+ }
9326
+ if (live) {
9327
+ await liveStats(sinceDate);
9328
+ } else {
9329
+ await printStats(sinceDate);
9330
+ }
9331
+ }
9332
+ async function printStats(sinceDate) {
9333
+ const sessions = await listSessions({ limit: 1e4 });
9334
+ const filtered = sinceDate ? sessions.filter((s) => s.firstTimestamp && new Date(s.firstTimestamp) >= sinceDate) : sessions;
9335
+ const rows = aggregateByDay(filtered);
9336
+ const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Daily");
9337
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9338
+ const todaySessions = filtered.filter((s) => s.firstTimestamp?.startsWith(today)).length;
9339
+ const status2 = getServerStatus();
9340
+ const serverLine = status2.running ? `Server running on http://localhost:${status2.port}` : "Server not running";
9341
+ console.log(output);
9342
+ console.log(` ${todaySessions} sessions today \xB7 ${serverLine}`);
9343
+ console.log("");
9344
+ }
9345
+ function displayModel(raw) {
9346
+ return raw.replace(/-\d{8}$/, "").replace(/^claude-/, "");
9347
+ }
9348
+ function aggregateByDay(sessions) {
9349
+ const byDay = /* @__PURE__ */ new Map();
9350
+ for (const s of sessions) {
9351
+ const day2 = s.firstTimestamp?.slice(0, 10) ?? "unknown";
9352
+ if (!byDay.has(day2)) byDay.set(day2, { sessions: [], models: /* @__PURE__ */ new Set() });
9353
+ const bucket = byDay.get(day2);
9354
+ bucket.sessions.push(s);
9355
+ if (s.model) bucket.models.add(displayModel(s.model));
9356
+ }
9357
+ const sorted = [...byDay.entries()].sort((a, b) => b[0].localeCompare(a[0]));
9358
+ return sorted.map(([date, { sessions: sessions2, models }]) => {
9359
+ const usage2 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
9360
+ let cost = 0;
9361
+ let anyPriced = false;
9362
+ let anyUnpriced = false;
9363
+ for (const s of sessions2) {
9364
+ usage2.input += s.totalUsage.input;
9365
+ usage2.output += s.totalUsage.output;
9366
+ usage2.cacheRead += s.totalUsage.cacheRead;
9367
+ usage2.cacheWrite += s.totalUsage.cacheWrite;
9368
+ if (s.model) {
9369
+ const c = estimateCost(s.model, s.totalUsage);
9370
+ if (c !== null) {
9371
+ cost += c;
9372
+ anyPriced = true;
9373
+ } else {
9374
+ anyUnpriced = true;
9375
+ }
9038
9376
  }
9039
- rows.push({
9040
- kind: "user",
9041
- event: e,
9042
- tOffsetMs: e.tOffsetMs,
9043
- gapMs: e.gapMs
9044
- });
9045
- continue;
9046
9377
  }
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;
9378
+ const modelNames = [...models].join(", ");
9379
+ return {
9380
+ date,
9381
+ models: modelNames || "\u2014",
9382
+ input: usage2.input,
9383
+ output: usage2.output,
9384
+ cacheCreate: usage2.cacheWrite,
9385
+ cacheRead: usage2.cacheRead,
9386
+ totalTokens: usage2.input + usage2.output + usage2.cacheRead + usage2.cacheWrite,
9387
+ cost: anyPriced ? cost : null,
9388
+ costPartial: anyPriced && anyUnpriced
9389
+ };
9390
+ });
9391
+ }
9392
+ async function liveStats(sinceDate) {
9393
+ if (process.stdin.isTTY) {
9394
+ process.stdin.setRawMode(true);
9395
+ process.stdin.resume();
9396
+ process.stdin.on("data", (data) => {
9397
+ if (data.toString() === "q" || data.toString() === "") {
9398
+ process.stdout.write("\x1B[?25h");
9399
+ process.exit(0);
9066
9400
  }
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;
9401
+ });
9402
+ }
9403
+ const refresh = async () => {
9404
+ const sessions = await listSessions({ limit: 1e4 });
9405
+ const filtered = sinceDate ? sessions.filter((s) => s.firstTimestamp && new Date(s.firstTimestamp) >= sinceDate) : sessions;
9406
+ const rows = aggregateByDay(filtered);
9407
+ const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Live (q to quit)");
9408
+ process.stdout.write("\x1B[2J\x1B[H");
9409
+ process.stdout.write("\x1B[?25l");
9410
+ process.stdout.write(output);
9411
+ const running = filtered.filter((s) => s.status === "running");
9412
+ if (running.length > 0) {
9413
+ process.stdout.write(" Active Sessions\n");
9414
+ for (const s of running.slice(0, 5)) {
9415
+ const project = s.projectName.split("/").slice(-2).join("/");
9416
+ const tokens = s.totalUsage.input + s.totalUsage.output;
9417
+ process.stdout.write(` ${project.padEnd(30)} ${tokens.toLocaleString()} tokens
9418
+ `);
9076
9419
  }
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;
9420
+ process.stdout.write("\n");
9086
9421
  }
9087
- }
9088
- return rows;
9422
+ };
9423
+ await refresh();
9424
+ setInterval(refresh, 2e3);
9089
9425
  }
9090
- var INTERRUPT_RE, RATE_LIMIT_RE, SLASH_COMMAND_RE, TASK_NOTIFICATION_RE;
9091
- var init_presentation = __esm({
9092
- "../parser/dist/presentation.js"() {
9426
+ var init_usage_history = __esm({
9427
+ "src/commands/usage-history.ts"() {
9093
9428
  "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>/;
9429
+ init_fs();
9430
+ init_pricing();
9431
+ init_table();
9432
+ init_server();
9099
9433
  }
9100
9434
  });
9101
9435
 
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);
9436
+ // src/commands/usage.ts
9437
+ var usage_exports = {};
9438
+ __export(usage_exports, {
9439
+ usage: () => usage
9440
+ });
9441
+ async function saveAuxiliarySnapshots() {
9442
+ let saved = 0;
9443
+ for (const source of agentSources) {
9444
+ if (source.kind === "claude-code" || !source.usagePoller) continue;
9445
+ try {
9446
+ const partial = await source.usagePoller();
9447
+ if (!partial) continue;
9448
+ appendSnapshot(USAGE_LOG2, {
9449
+ ...partial,
9450
+ seven_day_opus: null,
9451
+ seven_day_sonnet: null,
9452
+ seven_day_oauth_apps: null,
9453
+ seven_day_cowork: null,
9454
+ extra_usage: null
9455
+ });
9456
+ saved += 1;
9457
+ } catch {
9458
+ }
9113
9459
  }
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 };
9460
+ try {
9461
+ appendSnapshot(USAGE_LOG2, await fetchCodexUsage());
9462
+ saved += 1;
9463
+ } catch (err) {
9464
+ if (!(err instanceof CodexApiError) || err.code !== "no_auth" && err.code !== "api_key_only") {
9465
+ console.warn(`codex usage: ${err.message}`);
9466
+ }
9467
+ }
9468
+ try {
9469
+ appendSnapshot(USAGE_LOG2, await fetchCopilotUsage());
9470
+ saved += 1;
9471
+ } catch (err) {
9472
+ if (!(err instanceof CopilotApiError) || err.code !== "no_cli" && err.code !== "no_auth") {
9473
+ console.warn(`copilot usage: ${err.message}`);
9474
+ }
9475
+ }
9476
+ try {
9477
+ appendSnapshot(USAGE_LOG2, await fetchGrokUsage());
9478
+ saved += 1;
9479
+ } catch (err) {
9480
+ if (!(err instanceof GrokApiError) || err.code !== "no_auth" && err.code !== "not_weekly") {
9481
+ console.warn(`grok usage: ${err.message}`);
9482
+ }
9483
+ }
9484
+ try {
9485
+ appendSnapshot(USAGE_LOG2, await fetchZaiUsage());
9486
+ saved += 1;
9487
+ } catch (err) {
9488
+ if (!(err instanceof ZaiApiError) || err.code !== "no_key") {
9489
+ console.warn(`zai usage: ${err.message}`);
9490
+ }
9491
+ }
9492
+ return saved;
9133
9493
  }
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 };
9494
+ function parseAgentFilter(args2) {
9495
+ const raw = flag(args2, "--agent") ?? flag(args2, "-a");
9496
+ if (!raw) return void 0;
9497
+ const kind = raw.toLowerCase();
9498
+ const aliases = {
9499
+ claude: "claude-code",
9500
+ "claude-code": "claude-code",
9501
+ codex: "codex",
9502
+ copilot: "copilot",
9503
+ zai: "zai",
9504
+ "z.ai": "zai",
9505
+ grok: "grok"
9506
+ };
9507
+ return aliases[kind] ?? kind;
9139
9508
  }
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 };
9509
+ function filterAgents(byAgent, agentFilter) {
9510
+ if (!agentFilter) return byAgent;
9511
+ const hit = byAgent[agentFilter];
9512
+ return hit ? { [agentFilter]: hit } : {};
9144
9513
  }
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 };
9514
+ async function collectSnapshots(opts) {
9515
+ let saved = 0;
9516
+ if (opts.save) {
9517
+ try {
9518
+ const snapshot = await fetchUsage();
9519
+ appendSnapshot(USAGE_LOG2, snapshot);
9520
+ saved += 1;
9521
+ } catch (err) {
9522
+ if (!(err instanceof UsageApiError)) throw err;
9523
+ }
9524
+ saved += await saveAuxiliarySnapshots();
9525
+ }
9526
+ const byAgent = latestSnapshotsByAgent(USAGE_LOG2);
9527
+ if (opts.preferLiveClaude && !opts.save) {
9528
+ try {
9529
+ const live = await fetchUsage();
9530
+ byAgent["claude-code"] = live;
9531
+ } catch {
9532
+ }
9533
+ }
9534
+ return { byAgent, saved };
9149
9535
  }
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;
9536
+ async function usage(args2) {
9537
+ if (args2.includes("--help") || args2.includes("-h")) {
9538
+ console.log(`fleetlens usage \u2014 plan utilization (all agents)
9539
+
9540
+ Usage:
9541
+ fleetlens usage [--agent KIND] Multi-agent snapshot (from daemon log + live Claude)
9542
+ fleetlens usage --watch [-a KIND] Live top-style view until Ctrl+C
9543
+ fleetlens usage --save Poll every provider and append to the usage log
9544
+ fleetlens usage --history [-s D] [--days N]
9545
+ Daily token / cost history table
9546
+
9547
+ Agents: claude | codex | copilot | zai | grok (or full ids like claude-code)
9548
+ Watch options:
9549
+ --interval N Redraw every N seconds (default 2; reads ~/.cclens/usage.jsonl)
9550
+
9551
+ Tip: leave the usage daemon running (\`fleetlens daemon start\`) so --watch
9552
+ stays fresh without hammering provider APIs.`);
9553
+ return;
9554
+ }
9555
+ if (args2.includes("--history")) {
9556
+ const { history: history2 } = await Promise.resolve().then(() => (init_usage_history(), usage_history_exports));
9557
+ await history2(args2.filter((a) => a !== "--history"));
9558
+ return;
9559
+ }
9560
+ const save = args2.includes("--save");
9561
+ const watch = args2.includes("--watch") || args2.includes("-w");
9562
+ const agentFilter = parseAgentFilter(args2);
9563
+ if (agentFilter && !isAgentKind(agentFilter)) {
9564
+ console.error(
9565
+ `Error: unknown agent "${agentFilter}". Use claude, codex, copilot, zai, or grok.`
9566
+ );
9567
+ process.exitCode = 1;
9568
+ return;
9569
+ }
9570
+ const intervalRaw = flag(args2, "--interval");
9571
+ const intervalSec = Math.max(1, parseInt(intervalRaw ?? "2", 10) || 2);
9572
+ if (watch) {
9573
+ await runWatch({ agentFilter, intervalSec, saveOnce: save });
9574
+ return;
9575
+ }
9576
+ const collected = await collectSnapshots({ save, preferLiveClaude: true });
9577
+ const byAgent = filterAgents(collected.byAgent, agentFilter);
9578
+ if (save && collected.saved === 0 && Object.keys(byAgent).length === 0) {
9579
+ console.error(
9580
+ "Error: could not poll any provider and no samples are on disk."
9581
+ );
9582
+ process.exitCode = 1;
9583
+ return;
9164
9584
  }
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;
9585
+ if (save && collected.saved === 0) {
9586
+ console.error(
9587
+ "Error: every usage poll failed (showing last samples from disk only)."
9588
+ );
9589
+ process.exitCode = 1;
9590
+ }
9591
+ if (Object.keys(byAgent).length === 0) {
9592
+ if (agentFilter) {
9593
+ console.error(
9594
+ `Error: no usage samples for ${agentFilter}. Run \`fleetlens usage --save\` or start the daemon.`
9595
+ );
9596
+ } else {
9597
+ console.error(
9598
+ "Error: no usage samples yet. Run `fleetlens daemon start` or `fleetlens usage --save`."
9599
+ );
9178
9600
  }
9179
- if (d.peak >= 3)
9180
- multi_agent_days++;
9181
- if (d.has_cross_project)
9182
- cross_project_days++;
9601
+ process.exitCode = 1;
9602
+ return;
9603
+ }
9604
+ const columns = typeof process.stdout.columns === "number" && process.stdout.columns > 0 ? process.stdout.columns : void 0;
9605
+ process.stdout.write(formatMultiAgentUsage(byAgent, { columns }));
9606
+ if (!save) {
9607
+ const narrow = columns !== void 0 && columns < 48 || columns === void 0 && parseInt(process.env.COLUMNS ?? "", 10) < 48;
9608
+ process.stdout.write(
9609
+ narrow ? " (tip: --watch \xB7 --history \xB7 --save)\n" : " (tip: --watch for a live view \xB7 --history for the token table \xB7 --save to poll APIs)\n"
9610
+ );
9183
9611
  }
9184
- return { by_day, peak, peak_day, multi_agent_days, cross_project_days };
9185
9612
  }
9186
- var init_aggregate = __esm({
9187
- "../parser/dist/aggregate.js"() {
9613
+ async function runWatch(opts) {
9614
+ if (opts.saveOnce) {
9615
+ const { saved } = await collectSnapshots({ save: true, preferLiveClaude: false });
9616
+ if (saved === 0 && Object.keys(latestSnapshotsByAgent(USAGE_LOG2)).length === 0) {
9617
+ console.error(
9618
+ "Error: could not poll any provider and no samples are on disk."
9619
+ );
9620
+ process.exitCode = 1;
9621
+ return;
9622
+ }
9623
+ }
9624
+ const tty = Boolean(process.stdout.isTTY);
9625
+ let stopping = false;
9626
+ const leaveScreen = () => {
9627
+ if (!tty) return;
9628
+ process.stdout.write("\x1B[?25h\x1B[?1049l");
9629
+ };
9630
+ if (tty) {
9631
+ process.stdout.write("\x1B[?1049h\x1B[?25l");
9632
+ }
9633
+ const draw = () => {
9634
+ if (stopping) return;
9635
+ const byAgent = filterAgents(latestSnapshotsByAgent(USAGE_LOG2), opts.agentFilter);
9636
+ const columns = typeof process.stdout.columns === "number" && process.stdout.columns > 0 ? process.stdout.columns : void 0;
9637
+ if (tty) {
9638
+ process.stdout.write("\x1B[H\x1B[2J");
9639
+ } else {
9640
+ process.stdout.write("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
9641
+ }
9642
+ if (Object.keys(byAgent).length === 0) {
9643
+ process.stdout.write(
9644
+ `
9645
+ ${opts.agentFilter ? `No samples for ${opts.agentFilter}` : "Waiting for usage samples\u2026"}
9646
+ Start the daemon: fleetlens daemon start
9647
+
9648
+ live \xB7 every ${opts.intervalSec}s \xB7 Ctrl+C to quit
9649
+ `
9650
+ );
9651
+ return;
9652
+ }
9653
+ process.stdout.write(
9654
+ formatMultiAgentUsage(byAgent, {
9655
+ watch: true,
9656
+ intervalSec: opts.intervalSec,
9657
+ columns
9658
+ })
9659
+ );
9660
+ };
9661
+ if (tty) {
9662
+ process.stdout.on("resize", draw);
9663
+ }
9664
+ draw();
9665
+ const timer = setInterval(draw, opts.intervalSec * 1e3);
9666
+ const stop2 = () => {
9667
+ if (stopping) return;
9668
+ stopping = true;
9669
+ clearInterval(timer);
9670
+ leaveScreen();
9671
+ process.stdout.write("\n");
9672
+ process.exit(0);
9673
+ };
9674
+ process.on("SIGINT", stop2);
9675
+ process.on("SIGTERM", stop2);
9676
+ await new Promise(() => {
9677
+ });
9678
+ }
9679
+ var USAGE_LOG2;
9680
+ var init_usage = __esm({
9681
+ "src/commands/usage.ts"() {
9188
9682
  "use strict";
9189
- init_analytics();
9683
+ init_dist();
9684
+ init_fs();
9685
+ init_args();
9686
+ init_api();
9687
+ init_grok2();
9688
+ init_codex2();
9689
+ init_copilot2();
9690
+ init_zai();
9691
+ init_format();
9692
+ init_storage();
9693
+ USAGE_LOG2 = cclensPath("usage.jsonl");
9190
9694
  }
9191
9695
  });
9192
9696
 
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"() {
9697
+ // ../entries/dist/types.js
9698
+ function pendingEnrichment() {
9699
+ return {
9700
+ status: "pending",
9701
+ generated_at: null,
9702
+ model: null,
9703
+ cost_usd: null,
9704
+ error: null,
9705
+ brief_summary: null,
9706
+ underlying_goal: null,
9707
+ friction_detail: null,
9708
+ user_instructions: [],
9709
+ outcome: null,
9710
+ claude_helpfulness: null,
9711
+ goal_categories: {},
9712
+ retry_count: 0
9713
+ };
9714
+ }
9715
+ function skippedTrivialEnrichment(generatedAt) {
9716
+ return {
9717
+ status: "skipped_trivial",
9718
+ generated_at: generatedAt,
9719
+ model: null,
9720
+ cost_usd: null,
9721
+ error: null,
9722
+ brief_summary: null,
9723
+ underlying_goal: null,
9724
+ friction_detail: null,
9725
+ user_instructions: [],
9726
+ outcome: "trivial",
9727
+ claude_helpfulness: null,
9728
+ goal_categories: {},
9729
+ retry_count: 0
9730
+ };
9731
+ }
9732
+ function entryKey(sessionId, localDay2) {
9733
+ return `${sessionId}__${localDay2}`;
9734
+ }
9735
+ function parseEntryKey(key) {
9736
+ const m = /^([^_]+(?:_[^_]+)*)__(\d{4}-\d{2}-\d{2})$/.exec(key);
9737
+ if (!m)
9738
+ return null;
9739
+ return { session_id: m[1], local_day: m[2] };
9740
+ }
9741
+ 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;
9742
+ var init_types2 = __esm({
9743
+ "../entries/dist/types.js"() {
9256
9744
  "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();
9745
+ CURRENT_ENTRY_SCHEMA_VERSION = 2;
9746
+ GOAL_CATEGORIES = [
9747
+ "build",
9748
+ "plan",
9749
+ "debug",
9750
+ "review",
9751
+ "steer",
9752
+ "meta",
9753
+ "research",
9754
+ "refactor",
9755
+ "test",
9756
+ "release",
9757
+ "warmup_minimal"
9758
+ ];
9759
+ CURRENT_DAY_DIGEST_SCHEMA_VERSION = 2;
9760
+ CURRENT_WEEK_DIGEST_SCHEMA_VERSION = 2;
9761
+ CURRENT_MONTH_DIGEST_SCHEMA_VERSION = 2;
9762
+ PROMPT_FRAME_ORIGIN = {
9763
+ teammate: "claude-feature",
9764
+ "task-notification": "claude-feature",
9765
+ "local-command-caveat": "claude-feature",
9766
+ "image-attached": "claude-feature",
9767
+ "slash-command": "claude-feature",
9768
+ "handoff-prose": "personal-habit"
9769
+ };
9266
9770
  }
9267
9771
  });
9268
9772
 
@@ -10004,7 +10508,7 @@ var init_build = __esm({
10004
10508
  "../entries/dist/build.js"() {
10005
10509
  "use strict";
10006
10510
  init_analytics();
10007
- init_types();
10511
+ init_types2();
10008
10512
  init_signals();
10009
10513
  init_trivial();
10010
10514
  IDLE_GAP_MS6 = 3 * 60 * 1e3;
@@ -10027,7 +10531,7 @@ var init_flag_glossary = __esm({
10027
10531
  var init_dist2 = __esm({
10028
10532
  "../entries/dist/index.js"() {
10029
10533
  "use strict";
10030
- init_types();
10534
+ init_types2();
10031
10535
  init_signals();
10032
10536
  init_trivial();
10033
10537
  init_build();
@@ -10036,11 +10540,11 @@ var init_dist2 = __esm({
10036
10540
  });
10037
10541
 
10038
10542
  // ../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";
10543
+ import { readFileSync as readFileSync14, readdirSync, writeFileSync as writeFileSync7, renameSync as renameSync2, chmodSync, mkdirSync as mkdirSync6, existsSync as existsSync12 } from "node:fs";
10040
10544
  import { dirname as dirname9, join as join9 } from "node:path";
10041
10545
  function parseDigestFile(path11, expectedVersion) {
10042
10546
  try {
10043
- const parsed = JSON.parse(readFileSync13(path11, "utf8"));
10547
+ const parsed = JSON.parse(readFileSync14(path11, "utf8"));
10044
10548
  if (!parsed || typeof parsed !== "object")
10045
10549
  return null;
10046
10550
  if (parsed.version !== expectedVersion)
@@ -10089,7 +10593,7 @@ function writeDayDigest(digest2) {
10089
10593
  }
10090
10594
  function readDayDigest(date) {
10091
10595
  const p = dayDigestPath(date);
10092
- if (!existsSync11(p))
10596
+ if (!existsSync12(p))
10093
10597
  return null;
10094
10598
  return parseDigestFile(p, CURRENT_DAY_DIGEST_SCHEMA_VERSION);
10095
10599
  }
@@ -10125,13 +10629,13 @@ function writeWeekDigest(digest2) {
10125
10629
  }
10126
10630
  function readWeekDigest(monday) {
10127
10631
  const p = weekDigestPath(monday);
10128
- if (!existsSync11(p))
10632
+ if (!existsSync12(p))
10129
10633
  return null;
10130
10634
  return parseDigestFile(p, CURRENT_WEEK_DIGEST_SCHEMA_VERSION);
10131
10635
  }
10132
10636
  function listWeekDigestKeys() {
10133
10637
  const dir = join9(digestsDir(), "week");
10134
- if (!existsSync11(dir))
10638
+ if (!existsSync12(dir))
10135
10639
  return [];
10136
10640
  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
10641
  }
@@ -10162,13 +10666,13 @@ function writeMonthDigest(digest2) {
10162
10666
  }
10163
10667
  function readMonthDigest(yearMonth) {
10164
10668
  const p = monthDigestPath(yearMonth);
10165
- if (!existsSync11(p))
10669
+ if (!existsSync12(p))
10166
10670
  return null;
10167
10671
  return parseDigestFile(p, CURRENT_MONTH_DIGEST_SCHEMA_VERSION);
10168
10672
  }
10169
10673
  function listMonthDigestKeys() {
10170
10674
  const dir = join9(digestsDir(), "month");
10171
- if (!existsSync11(dir))
10675
+ if (!existsSync12(dir))
10172
10676
  return [];
10173
10677
  return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length)).filter((k) => /^\d{4}-\d{2}$/.test(k));
10174
10678
  }
@@ -10189,7 +10693,7 @@ var init_digest_fs = __esm({
10189
10693
  "../entries/dist/digest-fs.js"() {
10190
10694
  "use strict";
10191
10695
  init_fs();
10192
- init_types();
10696
+ init_types2();
10193
10697
  digestsDirCached = null;
10194
10698
  TODAY_TTL_MS = 10 * 60 * 1e3;
10195
10699
  todayCache = null;
@@ -10230,7 +10734,7 @@ __export(fs_exports2, {
10230
10734
  writeMonthDigest: () => writeMonthDigest,
10231
10735
  writeWeekDigest: () => writeWeekDigest
10232
10736
  });
10233
- import { readFileSync as readFileSync14, readdirSync as readdirSync2, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, renameSync as renameSync3, existsSync as existsSync12 } from "node:fs";
10737
+ import { readFileSync as readFileSync15, readdirSync as readdirSync2, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, renameSync as renameSync3, existsSync as existsSync13 } from "node:fs";
10234
10738
  import { randomBytes } from "node:crypto";
10235
10739
  import { join as join10 } from "node:path";
10236
10740
  function entriesDir() {
@@ -10289,17 +10793,17 @@ function writeEntryPreservingEnrichment(fresh) {
10289
10793
  }
10290
10794
  function readEntry(sessionId, localDay2) {
10291
10795
  const p = pathFor(sessionId, localDay2);
10292
- if (!existsSync12(p))
10796
+ if (!existsSync13(p))
10293
10797
  return null;
10294
- const raw = readFileSync14(p, "utf8");
10798
+ const raw = readFileSync15(p, "utf8");
10295
10799
  return JSON.parse(raw);
10296
10800
  }
10297
10801
  function entryExists(sessionId, localDay2) {
10298
- return existsSync12(pathFor(sessionId, localDay2));
10802
+ return existsSync13(pathFor(sessionId, localDay2));
10299
10803
  }
10300
10804
  function listEntryKeys() {
10301
10805
  const dir = entriesDir();
10302
- if (!existsSync12(dir))
10806
+ if (!existsSync13(dir))
10303
10807
  return [];
10304
10808
  return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length));
10305
10809
  }
@@ -10348,7 +10852,7 @@ var init_fs2 = __esm({
10348
10852
  "../entries/dist/fs.js"() {
10349
10853
  "use strict";
10350
10854
  init_fs();
10351
- init_types();
10855
+ init_types2();
10352
10856
  init_digest_fs();
10353
10857
  entriesDirCached = null;
10354
10858
  PRESERVABLE_STATUSES = /* @__PURE__ */ new Set([
@@ -10358,17 +10862,6 @@ var init_fs2 = __esm({
10358
10862
  }
10359
10863
  });
10360
10864
 
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
10865
  // ../../node_modules/zod/v3/helpers/util.js
10373
10866
  var util, objectUtil, ZodParsedType, getParsedType;
10374
10867
  var init_util = __esm({
@@ -14514,7 +15007,7 @@ var init_enrich = __esm({
14514
15007
  "../entries/dist/prompts/enrich.js"() {
14515
15008
  "use strict";
14516
15009
  init_zod();
14517
- init_types();
15010
+ init_types2();
14518
15011
  GoalCategoryEnum = external_exports.enum(GOAL_CATEGORIES);
14519
15012
  OutcomeEnum = external_exports.enum(["shipped", "partial", "exploratory", "blocked", "trivial"]);
14520
15013
  HelpfulnessEnum = external_exports.enum(["essential", "helpful", "neutral", "unhelpful"]);
@@ -14578,14 +15071,14 @@ RESPOND WITH ONLY VALID JSON (no prose, no code fence):
14578
15071
  });
14579
15072
 
14580
15073
  // ../entries/dist/claude-bin.js
14581
- import { existsSync as existsSync13 } from "node:fs";
15074
+ import { existsSync as existsSync14 } from "node:fs";
14582
15075
  import { join as join11, dirname as dirname10, delimiter } from "node:path";
14583
15076
  import { homedir as homedir5 } from "node:os";
14584
15077
  function resolveClaudeBin(opts = {}) {
14585
15078
  const env = opts.env ?? process.env;
14586
15079
  const home = opts.home ?? homedir5();
14587
15080
  const execPath = opts.execPath ?? process.execPath;
14588
- const exists = opts.exists ?? existsSync13;
15081
+ const exists = opts.exists ?? existsSync14;
14589
15082
  for (const dir of (env.PATH ?? "").split(delimiter)) {
14590
15083
  if (!dir)
14591
15084
  continue;
@@ -14626,7 +15119,7 @@ var init_claude_bin = __esm({
14626
15119
  });
14627
15120
 
14628
15121
  // ../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";
15122
+ 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
15123
  import { join as join12 } from "node:path";
14631
15124
  import { homedir as homedir6 } from "node:os";
14632
15125
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -14645,7 +15138,7 @@ function tmuxRunnerAvailable() {
14645
15138
  }
14646
15139
  function runtimeCwd() {
14647
15140
  const dir = join12(homedir6(), ".cclens", "runtime");
14648
- if (!existsSync14(dir))
15141
+ if (!existsSync15(dir))
14649
15142
  mkdirSync8(dir, { recursive: true });
14650
15143
  return realpathSync5(dir);
14651
15144
  }
@@ -14653,7 +15146,7 @@ function ensureRuntimeCwdTrusted(cwd) {
14653
15146
  const cfgPath = join12(homedir6(), ".claude.json");
14654
15147
  let cfg;
14655
15148
  try {
14656
- cfg = JSON.parse(readFileSync15(cfgPath, "utf8"));
15149
+ cfg = JSON.parse(readFileSync16(cfgPath, "utf8"));
14657
15150
  } catch {
14658
15151
  return;
14659
15152
  }
@@ -14803,7 +15296,7 @@ function snapshotAllTranscripts() {
14803
15296
  function readJsonlCwd(path11, maxLines = 8) {
14804
15297
  let raw;
14805
15298
  try {
14806
- raw = readFileSync15(path11, "utf8");
15299
+ raw = readFileSync16(path11, "utf8");
14807
15300
  } catch {
14808
15301
  return void 0;
14809
15302
  }
@@ -14860,7 +15353,7 @@ async function discoverTranscript(targetCwd, existingBefore, startMs, timeoutMs,
14860
15353
  function parseTranscript2(path11, defaultModel) {
14861
15354
  let raw;
14862
15355
  try {
14863
- raw = readFileSync15(path11, "utf8");
15356
+ raw = readFileSync16(path11, "utf8");
14864
15357
  } catch {
14865
15358
  return { assembled: "", inputTokens: 0, outputTokens: 0, modelUsed: defaultModel, done: false };
14866
15359
  }
@@ -14955,11 +15448,11 @@ var init_tmux_runner = __esm({
14955
15448
 
14956
15449
  // ../entries/dist/llm-runner.js
14957
15450
  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";
15451
+ import { mkdirSync as mkdirSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync10, existsSync as existsSync16 } from "node:fs";
14959
15452
  import { join as join13 } from "node:path";
14960
15453
  import { randomUUID } from "node:crypto";
14961
15454
  function ensureRunsDir() {
14962
- if (!existsSync15(RUNS_DIR)) {
15455
+ if (!existsSync16(RUNS_DIR)) {
14963
15456
  try {
14964
15457
  mkdirSync9(RUNS_DIR, { recursive: true });
14965
15458
  } catch {
@@ -15412,7 +15905,7 @@ var init_enrich2 = __esm({
15412
15905
  });
15413
15906
 
15414
15907
  // ../entries/dist/budget.js
15415
- import { appendFileSync as appendFileSync4, readFileSync as readFileSync16, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "node:fs";
15908
+ import { appendFileSync as appendFileSync4, readFileSync as readFileSync17, existsSync as existsSync17, mkdirSync as mkdirSync10 } from "node:fs";
15416
15909
  import { dirname as dirname11 } from "node:path";
15417
15910
  function spendPath() {
15418
15911
  if (spendPathCached)
@@ -15430,12 +15923,12 @@ function appendSpend(record) {
15430
15923
  }
15431
15924
  function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
15432
15925
  const p = spendPath();
15433
- if (!existsSync16(p))
15926
+ if (!existsSync17(p))
15434
15927
  return 0;
15435
15928
  const year = now.getUTCFullYear();
15436
15929
  const month2 = now.getUTCMonth();
15437
15930
  let total = 0;
15438
- const raw = readFileSync16(p, "utf8");
15931
+ const raw = readFileSync17(p, "utf8");
15439
15932
  for (const line of raw.split("\n")) {
15440
15933
  if (!line)
15441
15934
  continue;
@@ -15464,7 +15957,7 @@ var init_budget = __esm({
15464
15957
  });
15465
15958
 
15466
15959
  // ../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";
15960
+ import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, existsSync as existsSync18, readFileSync as readFileSync18, statSync as statSync3 } from "node:fs";
15468
15961
  function lockPath() {
15469
15962
  if (lockPathCached)
15470
15963
  return lockPathCached;
@@ -15509,13 +16002,13 @@ function pidAlive(pid) {
15509
16002
  }
15510
16003
  function interactiveLockFresh(nowMs) {
15511
16004
  const p = lockPath();
15512
- if (!existsSync17(p))
16005
+ if (!existsSync18(p))
15513
16006
  return false;
15514
16007
  try {
15515
16008
  const mtime = statSync3(p).mtimeMs;
15516
16009
  if (nowMs - mtime > STALE_MS)
15517
16010
  return false;
15518
- const pid = Number(readFileSync17(p, "utf8").trim());
16011
+ const pid = Number(readFileSync18(p, "utf8").trim());
15519
16012
  if (!Number.isFinite(pid) || pid <= 0)
15520
16013
  return false;
15521
16014
  if (!pidAlive(pid))
@@ -15621,7 +16114,7 @@ var init_queue = __esm({
15621
16114
  });
15622
16115
 
15623
16116
  // ../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";
16117
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync12, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync11, existsSync as existsSync19 } from "node:fs";
15625
16118
  import { dirname as dirname12 } from "node:path";
15626
16119
  function settingsPath() {
15627
16120
  if (settingsPathCached)
@@ -15657,10 +16150,10 @@ function fromDisk(d) {
15657
16150
  }
15658
16151
  function readSettings() {
15659
16152
  const p = settingsPath();
15660
- if (!existsSync18(p))
16153
+ if (!existsSync19(p))
15661
16154
  return DEFAULT_SETTINGS;
15662
16155
  try {
15663
- const raw = readFileSync18(p, "utf8");
16156
+ const raw = readFileSync19(p, "utf8");
15664
16157
  return fromDisk(JSON.parse(raw));
15665
16158
  } catch {
15666
16159
  return DEFAULT_SETTINGS;
@@ -16145,7 +16638,7 @@ var DEFAULT_MODEL2, defaultCallLLMDigest, validateDay;
16145
16638
  var init_digest_day2 = __esm({
16146
16639
  "../entries/dist/digest-day.js"() {
16147
16640
  "use strict";
16148
- init_types();
16641
+ init_types2();
16149
16642
  init_signals();
16150
16643
  init_digest_day();
16151
16644
  init_enrich2();
@@ -17364,7 +17857,7 @@ var SHAPE_ORDER, DEFAULT_MODEL3, defaultCallLLMWeek, validateWeek;
17364
17857
  var init_digest_week2 = __esm({
17365
17858
  "../entries/dist/digest-week.js"() {
17366
17859
  "use strict";
17367
- init_types();
17860
+ init_types2();
17368
17861
  init_signals();
17369
17862
  init_digest_day2();
17370
17863
  init_digest_week();
@@ -17698,7 +18191,7 @@ var DEFAULT_MODEL4, defaultCallLLMMonth, validateMonth;
17698
18191
  var init_digest_month2 = __esm({
17699
18192
  "../entries/dist/digest-month.js"() {
17700
18193
  "use strict";
17701
- init_types();
18194
+ init_types2();
17702
18195
  init_digest_month();
17703
18196
  init_enrich2();
17704
18197
  init_llm_runner();
@@ -18850,13 +19343,13 @@ var init_digest_month_pipeline = __esm({
18850
19343
  });
18851
19344
 
18852
19345
  // ../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";
19346
+ 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
19347
  import { dirname as dirname13 } from "node:path";
18855
- async function fetchZaiUsage(candidate) {
19348
+ async function fetchZaiUsage2(candidate) {
18856
19349
  const key = candidate.trim();
18857
19350
  if (!key)
18858
19351
  throw new Error("empty key");
18859
- const res = await fetch(ZAI_QUOTA_ENDPOINT, {
19352
+ const res = await fetch(ZAI_QUOTA_ENDPOINT2, {
18860
19353
  headers: { Authorization: key, "Accept-Language": "en-US,en" }
18861
19354
  });
18862
19355
  if (!res.ok) {
@@ -18908,7 +19401,7 @@ async function fetchZaiUsage(candidate) {
18908
19401
  };
18909
19402
  }
18910
19403
  async function validateAndSnapshotZaiKey(key) {
18911
- const snap = await fetchZaiUsage(key);
19404
+ const snap = await fetchZaiUsage2(key);
18912
19405
  appendUsageSnapshot(snap);
18913
19406
  return snap;
18914
19407
  }
@@ -18921,10 +19414,10 @@ function credentialsPath() {
18921
19414
  }
18922
19415
  function readCredentials() {
18923
19416
  const p = credentialsPath();
18924
- if (!existsSync19(p))
19417
+ if (!existsSync20(p))
18925
19418
  return {};
18926
19419
  try {
18927
- const raw = readFileSync19(p, "utf8");
19420
+ const raw = readFileSync20(p, "utf8");
18928
19421
  return JSON.parse(raw);
18929
19422
  } catch {
18930
19423
  return {};
@@ -18976,12 +19469,12 @@ function deleteZaiKey() {
18976
19469
  function hasZaiKey() {
18977
19470
  return !!readCredentials().zai?.apiKey;
18978
19471
  }
18979
- var ZAI_QUOTA_ENDPOINT;
19472
+ var ZAI_QUOTA_ENDPOINT2;
18980
19473
  var init_credentials = __esm({
18981
19474
  "../entries/dist/credentials.js"() {
18982
19475
  "use strict";
18983
19476
  init_fs();
18984
- ZAI_QUOTA_ENDPOINT = "https://api.z.ai/api/monitor/usage/quota/limit";
19477
+ ZAI_QUOTA_ENDPOINT2 = "https://api.z.ai/api/monitor/usage/quota/limit";
18985
19478
  }
18986
19479
  });
18987
19480
 
@@ -19020,7 +19513,7 @@ __export(node_exports, {
19020
19513
  deleteZaiKey: () => deleteZaiKey,
19021
19514
  deleteZaiKeyAndPrune: () => deleteZaiKeyAndPrune,
19022
19515
  enrichEntry: () => enrichEntry,
19023
- fetchZaiUsage: () => fetchZaiUsage,
19516
+ fetchZaiUsage: () => fetchZaiUsage2,
19024
19517
  generateDayDigest: () => generateDayDigest,
19025
19518
  generateMonthDigest: () => generateMonthDigest,
19026
19519
  generateWeekDigest: () => generateWeekDigest,
@@ -19558,7 +20051,7 @@ var menubar_exports = {};
19558
20051
  __export(menubar_exports, {
19559
20052
  menubar: () => menubar
19560
20053
  });
19561
- import { existsSync as existsSync20, mkdirSync as mkdirSync13, cpSync, rmSync as rmSync2, readdirSync as readdirSync4, writeFileSync as writeFileSync14, realpathSync as realpathSync6 } from "node:fs";
20054
+ import { existsSync as existsSync21, mkdirSync as mkdirSync13, cpSync, rmSync as rmSync2, readdirSync as readdirSync4, writeFileSync as writeFileSync14, realpathSync as realpathSync6 } from "node:fs";
19562
20055
  import { homedir as homedir7 } from "node:os";
19563
20056
  import { join as join14, dirname as dirname14 } from "node:path";
19564
20057
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -19632,7 +20125,7 @@ async function installMenubar(opts) {
19632
20125
  return;
19633
20126
  }
19634
20127
  const src = bundledAppPath();
19635
- if (!existsSync20(src)) {
20128
+ if (!existsSync21(src)) {
19636
20129
  console.error(
19637
20130
  `Bundled widget not found at ${src}.
19638
20131
  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 +20171,7 @@ async function uninstallMenubar() {
19678
20171
  await new Promise((r) => setTimeout(r, 300));
19679
20172
  }
19680
20173
  const dest = installedAppPath();
19681
- if (existsSync20(dest)) {
20174
+ if (existsSync21(dest)) {
19682
20175
  rmSync2(dest, { recursive: true, force: true });
19683
20176
  console.log(`Removed ${dest}`);
19684
20177
  } else {
@@ -19693,7 +20186,7 @@ async function openMenubar() {
19693
20186
  return;
19694
20187
  }
19695
20188
  const dest = installedAppPath();
19696
- if (!existsSync20(dest)) {
20189
+ if (!existsSync21(dest)) {
19697
20190
  console.log("Widget not installed yet. Run `fleetlens menubar install` first.");
19698
20191
  return;
19699
20192
  }
@@ -19710,9 +20203,9 @@ async function statusMenubar() {
19710
20203
  return;
19711
20204
  }
19712
20205
  const dest = installedAppPath();
19713
- const installed = existsSync20(dest);
20206
+ const installed = existsSync21(dest);
19714
20207
  const running = await isRunning();
19715
- const bundled = existsSync20(bundledAppPath());
20208
+ const bundled = existsSync21(bundledAppPath());
19716
20209
  console.log(`Menu bar widget: ${installed ? "installed" : "not installed"}${installed ? ` (${dest})` : ""}`);
19717
20210
  console.log(`Running: ${running ? "yes" : "no"}`);
19718
20211
  console.log(`Bundled in CLI: ${bundled ? "yes" : "no \u2014 this build ships without the widget"}`);
@@ -19803,7 +20296,7 @@ var init_config = __esm({
19803
20296
  });
19804
20297
 
19805
20298
  // src/perception/build-entries.ts
19806
- import { statSync as statSync4, readFileSync as readFileSync20 } from "node:fs";
20299
+ import { statSync as statSync4, readFileSync as readFileSync21 } from "node:fs";
19807
20300
  import { basename as basename2, dirname as dirname15 } from "node:path";
19808
20301
  function decodeProjectDirName(projectDir) {
19809
20302
  if (projectDir.startsWith("-")) {
@@ -19813,7 +20306,7 @@ function decodeProjectDirName(projectDir) {
19813
20306
  }
19814
20307
  function buildEntriesForFile(filePath) {
19815
20308
  const stat = statSync4(filePath);
19816
- const raw = readFileSync20(filePath, "utf8");
20309
+ const raw = readFileSync21(filePath, "utf8");
19817
20310
  const rawLines = raw.split("\n").filter(Boolean).map((l) => {
19818
20311
  try {
19819
20312
  return JSON.parse(l);
@@ -20082,7 +20575,7 @@ function buildIngestPayload(inputs) {
20082
20575
  return {
20083
20576
  ingestId: randomUUID2(),
20084
20577
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
20085
- cliVersion: "1.0.7",
20578
+ cliVersion: "1.0.9",
20086
20579
  ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
20087
20580
  ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
20088
20581
  ...richRollup ? { richRollup } : {},
@@ -20124,7 +20617,7 @@ var init_push = __esm({
20124
20617
  });
20125
20618
 
20126
20619
  // src/team/git-remote.ts
20127
- import { existsSync as existsSync21, readFileSync as readFileSync21, statSync as statSync5 } from "node:fs";
20620
+ import { existsSync as existsSync22, readFileSync as readFileSync22, statSync as statSync5 } from "node:fs";
20128
20621
  import { dirname as dirname16, isAbsolute as isAbsolute2, join as join15, resolve as resolve2 } from "node:path";
20129
20622
  function parseGithubRemote(url) {
20130
20623
  const m = url.trim().match(GITHUB_URL_RE);
@@ -20149,12 +20642,12 @@ function gitConfigPathFor(dir) {
20149
20642
  const st = statSync5(dotGit, { throwIfNoEntry: false });
20150
20643
  if (!st) return null;
20151
20644
  if (st.isDirectory()) return join15(dotGit, "config");
20152
- const m = readFileSync21(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
20645
+ const m = readFileSync22(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
20153
20646
  if (!m) return null;
20154
20647
  const gd = isAbsolute2(m[1]) ? m[1] : resolve2(dir, m[1]);
20155
20648
  const commondirFile = join15(gd, "commondir");
20156
- if (existsSync21(commondirFile)) {
20157
- const common = readFileSync21(commondirFile, "utf8").trim();
20649
+ if (existsSync22(commondirFile)) {
20650
+ const common = readFileSync22(commondirFile, "utf8").trim();
20158
20651
  return join15(isAbsolute2(common) ? common : resolve2(gd, common), "config");
20159
20652
  }
20160
20653
  return join15(gd, "config");
@@ -20169,8 +20662,8 @@ function createRepoResolver() {
20169
20662
  try {
20170
20663
  for (let d = dir; ; d = dirname16(d)) {
20171
20664
  const config = gitConfigPathFor(d);
20172
- if (config && existsSync21(config)) {
20173
- const url = originUrl2(readFileSync21(config, "utf8"));
20665
+ if (config && existsSync22(config)) {
20666
+ const url = originUrl2(readFileSync22(config, "utf8"));
20174
20667
  repo = url ? parseGithubRemote(url) : null;
20175
20668
  break;
20176
20669
  }
@@ -20192,7 +20685,7 @@ var init_git_remote = __esm({
20192
20685
  });
20193
20686
 
20194
20687
  // src/team/queue.ts
20195
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync15, appendFileSync as appendFileSync5, statSync as statSync6, mkdirSync as mkdirSync14 } from "node:fs";
20688
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync15, appendFileSync as appendFileSync5, statSync as statSync6, mkdirSync as mkdirSync14 } from "node:fs";
20196
20689
  import { dirname as dirname17 } from "node:path";
20197
20690
  function enqueuePayload(payload, queuePath = QUEUE_PATH) {
20198
20691
  mkdirSync14(dirname17(queuePath), { recursive: true });
@@ -20209,7 +20702,7 @@ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
20209
20702
  function dequeuePayloads(queuePath = QUEUE_PATH) {
20210
20703
  let lines;
20211
20704
  try {
20212
- lines = readFileSync22(queuePath, "utf8").trim().split("\n").filter(Boolean);
20705
+ lines = readFileSync23(queuePath, "utf8").trim().split("\n").filter(Boolean);
20213
20706
  } catch {
20214
20707
  return [];
20215
20708
  }
@@ -20230,7 +20723,7 @@ function dequeuePayloads(queuePath = QUEUE_PATH) {
20230
20723
  function pruneOldest(queuePath) {
20231
20724
  let lines;
20232
20725
  try {
20233
- lines = readFileSync22(queuePath, "utf8").trim().split("\n").filter(Boolean);
20726
+ lines = readFileSync23(queuePath, "utf8").trim().split("\n").filter(Boolean);
20234
20727
  } catch {
20235
20728
  return;
20236
20729
  }
@@ -20249,7 +20742,7 @@ var init_queue2 = __esm({
20249
20742
  });
20250
20743
 
20251
20744
  // src/usage/profile.ts
20252
- import { existsSync as existsSync22, mkdirSync as mkdirSync15, readFileSync as readFileSync23, writeFileSync as writeFileSync16 } from "node:fs";
20745
+ import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
20253
20746
  import { dirname as dirname18 } from "node:path";
20254
20747
  function mapRateLimitTier(rateLimitTier) {
20255
20748
  switch (rateLimitTier) {
@@ -20290,9 +20783,9 @@ async function fetchProfile(nowMs = Date.now()) {
20290
20783
  };
20291
20784
  }
20292
20785
  function readCachedProfile(cachePath) {
20293
- if (!existsSync22(cachePath)) return null;
20786
+ if (!existsSync23(cachePath)) return null;
20294
20787
  try {
20295
- const raw = JSON.parse(readFileSync23(cachePath, "utf8"));
20788
+ const raw = JSON.parse(readFileSync24(cachePath, "utf8"));
20296
20789
  if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
20297
20790
  return raw;
20298
20791
  } catch {
@@ -20481,11 +20974,11 @@ var init_backfill = __esm({
20481
20974
  });
20482
20975
 
20483
20976
  // src/team/sync-log.ts
20484
- import { readFileSync as readFileSync24 } from "node:fs";
20977
+ import { readFileSync as readFileSync25 } from "node:fs";
20485
20978
  function readPendingSyncLog(watermark) {
20486
20979
  let raw;
20487
20980
  try {
20488
- raw = readFileSync24(cclensPath("daemon.log"), "utf8");
20981
+ raw = readFileSync25(cclensPath("daemon.log"), "utf8");
20489
20982
  } catch {
20490
20983
  return { lines: [] };
20491
20984
  }
@@ -20549,7 +21042,7 @@ __export(file_probe_exports, {
20549
21042
  probeArtifactSignals: () => probeArtifactSignals
20550
21043
  });
20551
21044
  import { createHash } from "node:crypto";
20552
- import { existsSync as existsSync23, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
21045
+ import { existsSync as existsSync24, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
20553
21046
  import { homedir as homedir8 } from "node:os";
20554
21047
  import { join as join17, relative, resolve as resolve3, sep } from "node:path";
20555
21048
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -20559,15 +21052,15 @@ function pathHash(relPath) {
20559
21052
  function defaultScanRoots(extraRoots = []) {
20560
21053
  const roots = [];
20561
21054
  const home = join17(homedir8(), ".claude");
20562
- if (existsSync23(home)) roots.push({ root: home, label: "user" });
21055
+ if (existsSync24(home)) roots.push({ root: home, label: "user" });
20563
21056
  for (const e of extraRoots) {
20564
21057
  const r = resolve3(e, ".claude");
20565
- if (existsSync23(r) && r !== home) roots.push({ root: r, label: "project" });
21058
+ if (existsSync24(r) && r !== home) roots.push({ root: r, label: "project" });
20566
21059
  }
20567
21060
  return roots;
20568
21061
  }
20569
21062
  function listMarkdownFiles(dir) {
20570
- if (!existsSync23(dir)) return [];
21063
+ if (!existsSync24(dir)) return [];
20571
21064
  const out = [];
20572
21065
  let entries2;
20573
21066
  try {
@@ -20637,7 +21130,7 @@ function scanAuthored(roots, subdir, day2) {
20637
21130
  const out = [];
20638
21131
  for (const r of roots) {
20639
21132
  const dir = join17(r.root, subdir);
20640
- if (!existsSync23(dir)) continue;
21133
+ if (!existsSync24(dir)) continue;
20641
21134
  for (const f of listMarkdownFiles(dir)) {
20642
21135
  const firstSeen = firstSeenDateOf(f);
20643
21136
  if (firstSeen !== day2) continue;
@@ -20651,7 +21144,7 @@ function scanEdited(roots, day2) {
20651
21144
  const out = [];
20652
21145
  for (const r of roots) {
20653
21146
  const dir = join17(r.root, "skills");
20654
- if (!existsSync23(dir)) continue;
21147
+ if (!existsSync24(dir)) continue;
20655
21148
  for (const f of listMarkdownFiles(dir)) {
20656
21149
  const firstSeen = firstSeenDateOf(f);
20657
21150
  if (firstSeen === day2) continue;
@@ -20674,10 +21167,10 @@ function claudemdLineDelta(extraRoots, authorEmail, day2) {
20674
21167
  const candidates = [];
20675
21168
  for (const e of extraRoots) {
20676
21169
  const f = resolve3(e, "CLAUDE.md");
20677
- if (existsSync23(f)) candidates.push(f);
21170
+ if (existsSync24(f)) candidates.push(f);
20678
21171
  }
20679
21172
  const homeClaudeMd = join17(homedir8(), "CLAUDE.md");
20680
- if (existsSync23(homeClaudeMd)) candidates.push(homeClaudeMd);
21173
+ if (existsSync24(homeClaudeMd)) candidates.push(homeClaudeMd);
20681
21174
  for (const file of candidates) {
20682
21175
  try {
20683
21176
  const out = execFileSync3(
@@ -21567,11 +22060,11 @@ var logs_exports = {};
21567
22060
  __export(logs_exports, {
21568
22061
  teamLogs: () => teamLogs
21569
22062
  });
21570
- import { readFileSync as readFileSync25 } from "node:fs";
22063
+ import { readFileSync as readFileSync26 } from "node:fs";
21571
22064
  async function teamLogs() {
21572
22065
  const logPath = cclensPath("daemon.log");
21573
22066
  try {
21574
- const lines = readFileSync25(logPath, "utf8").trim().split("\n");
22067
+ const lines = readFileSync26(logPath, "utf8").trim().split("\n");
21575
22068
  const teamLines = lines.filter((l) => l.includes("[sync] ") || l.includes("team "));
21576
22069
  if (teamLines.length === 0) {
21577
22070
  console.log("No team-related log entries found.");
@@ -21699,7 +22192,7 @@ __export(runs_exports, {
21699
22192
  runs: () => runs
21700
22193
  });
21701
22194
  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";
22195
+ import { existsSync as existsSync25, readFileSync as readFileSync27, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
21703
22196
  import { join as join18 } from "node:path";
21704
22197
  async function runs(args2) {
21705
22198
  const json = args2.includes("--json");
@@ -21784,8 +22277,8 @@ function listActiveRuns() {
21784
22277
  return runs2.sort((a, b) => a.elapsed_s - b.elapsed_s);
21785
22278
  }
21786
22279
  function readSpend(sinceMs) {
21787
- if (!existsSync24(SPEND_PATH)) return [];
21788
- const lines = readFileSync26(SPEND_PATH, "utf-8").split("\n").filter((l) => l.trim());
22280
+ if (!existsSync25(SPEND_PATH)) return [];
22281
+ const lines = readFileSync27(SPEND_PATH, "utf-8").split("\n").filter((l) => l.trim());
21789
22282
  const out = [];
21790
22283
  for (const line of lines) {
21791
22284
  try {
@@ -21906,7 +22399,7 @@ function ageLabel(ms) {
21906
22399
  return `${Math.floor(ms / 864e5)}d`;
21907
22400
  }
21908
22401
  function listTraceFiles() {
21909
- if (!existsSync24(RUNS_DIR2)) return [];
22402
+ if (!existsSync25(RUNS_DIR2)) return [];
21910
22403
  const entries2 = readdirSync6(RUNS_DIR2).filter((f) => f.endsWith(".jsonl"));
21911
22404
  return entries2.map((f) => {
21912
22405
  const path11 = join18(RUNS_DIR2, f);
@@ -21915,14 +22408,14 @@ function listTraceFiles() {
21915
22408
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
21916
22409
  }
21917
22410
  function resolveRunId(arg) {
21918
- if (!existsSync24(RUNS_DIR2)) return null;
22411
+ if (!existsSync25(RUNS_DIR2)) return null;
21919
22412
  if (arg === "latest") {
21920
22413
  const all2 = listTraceFiles();
21921
22414
  if (all2.length === 0) return null;
21922
22415
  return { runId: all2[0].runId, path: all2[0].path };
21923
22416
  }
21924
22417
  const exact = join18(RUNS_DIR2, `${arg}.jsonl`);
21925
- if (existsSync24(exact)) return { runId: arg, path: exact };
22418
+ if (existsSync25(exact)) return { runId: arg, path: exact };
21926
22419
  const all = listTraceFiles();
21927
22420
  const matches = all.filter((r) => r.runId.includes(arg));
21928
22421
  if (matches.length === 1) return { runId: matches[0].runId, path: matches[0].path };
@@ -21934,7 +22427,7 @@ function resolveRunId(arg) {
21934
22427
  return null;
21935
22428
  }
21936
22429
  function readTrace(path11) {
21937
- return readFileSync26(path11, "utf-8").split("\n").filter((l) => l.trim()).map((l) => {
22430
+ return readFileSync27(path11, "utf-8").split("\n").filter((l) => l.trim()).map((l) => {
21938
22431
  try {
21939
22432
  return JSON.parse(l);
21940
22433
  } catch {
@@ -21946,7 +22439,7 @@ async function inspect(arg, follow, json) {
21946
22439
  const r = resolveRunId(arg);
21947
22440
  if (!r) {
21948
22441
  console.error(`run not found: ${arg}`);
21949
- if (existsSync24(RUNS_DIR2)) {
22442
+ if (existsSync25(RUNS_DIR2)) {
21950
22443
  const all = listTraceFiles().slice(0, 5);
21951
22444
  if (all.length > 0) {
21952
22445
  console.error(`
@@ -22126,7 +22619,7 @@ async function main() {
22126
22619
  case "version":
22127
22620
  case "--version":
22128
22621
  case "-v":
22129
- console.log(`fleetlens ${"1.0.7"}`);
22622
+ console.log(`fleetlens ${"1.0.9"}`);
22130
22623
  break;
22131
22624
  case "help":
22132
22625
  case "--help":
@@ -22140,7 +22633,7 @@ Common:
22140
22633
  update Update to the latest version
22141
22634
 
22142
22635
  Terminal:
22143
- usage [--save] Plan utilization snapshot (5h/7d)
22636
+ usage [--agent KIND] [--watch] [--save] Plan utilization (all agents; --watch is live)
22144
22637
  usage --history [-s D] [--days N] Daily token/cost table
22145
22638
  entries [--day D|--session ID|--all] [--json] Perception-layer entries
22146
22639
  digest day [--date D|--yesterday|--today] [--json] Day digest