fleetlens 0.10.2 → 0.10.3

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 (63) 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.js.nft.json +1 -1
  12. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
  14. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  15. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  16. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  18. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  19. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  20. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  21. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  23. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  24. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  25. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  26. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  27. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  28. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  29. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  30. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  31. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  32. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  33. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  34. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  35. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  36. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  37. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  38. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  39. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  40. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  41. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  42. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  43. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  44. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  45. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0.za3_3._.js +3 -0
  46. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  47. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_0m67-xi._.js +1 -1
  48. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  49. package/app/apps/web/.next/server/pages/500.html +1 -1
  50. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  51. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  52. package/app/apps/web/.next/static/chunks/0o3uir-f-t73p.js +14 -0
  53. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  54. package/app/apps/web/package.json +1 -1
  55. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  56. package/dist/daemon-worker.js +367 -53
  57. package/dist/index.js +249 -184
  58. package/package.json +1 -1
  59. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0s4dg1y._.js +0 -3
  60. package/app/apps/web/.next/static/chunks/063r4x2.9csh8.js +0 -14
  61. /package/app/apps/web/.next/static/{XTns3Y6jHWmb7EYKzQi55 → dGL5ege3-_A21c65DU7Jq}/_buildManifest.js +0 -0
  62. /package/app/apps/web/.next/static/{XTns3Y6jHWmb7EYKzQi55 → dGL5ege3-_A21c65DU7Jq}/_clientMiddlewareManifest.js +0 -0
  63. /package/app/apps/web/.next/static/{XTns3Y6jHWmb7EYKzQi55 → dGL5ege3-_A21c65DU7Jq}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -3720,9 +3720,9 @@ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath
3720
3720
  console.warn(
3721
3721
  ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
3722
3722
  );
3723
- } else if (verify.installedVersion !== "0.10.2") {
3723
+ } else if (verify.installedVersion !== "0.10.3") {
3724
3724
  console.log(
3725
- ` \u2192 This process is still running ${"0.10.2"}. Next invocation will use ${verify.installedVersion}.`
3725
+ ` \u2192 This process is still running ${"0.10.3"}. Next invocation will use ${verify.installedVersion}.`
3726
3726
  );
3727
3727
  }
3728
3728
  }
@@ -3753,13 +3753,27 @@ function isDevMode() {
3753
3753
  const script = process.argv[1] ?? "";
3754
3754
  return script.includes("packages/cli/") || script.includes("packages\\cli\\");
3755
3755
  }
3756
+ async function getUpdateAvailability() {
3757
+ if (isDevMode()) return null;
3758
+ const latest = await fetchLatestVersion();
3759
+ if (latest === null) return {
3760
+ current: "0.10.3",
3761
+ latest: null,
3762
+ updateAvailable: false
3763
+ };
3764
+ return {
3765
+ current: "0.10.3",
3766
+ latest,
3767
+ updateAvailable: shouldUpdate("0.10.3", latest)
3768
+ };
3769
+ }
3756
3770
  async function checkForUpdate() {
3757
3771
  if (isDevMode()) return;
3758
3772
  if (process.env.__FLEETLENS_UPDATED === "1" || process.env.__CCLENS_UPDATED === "1") return;
3759
- const latest = await fetchLatestVersion();
3760
- if (latest === null) return;
3761
- const current = "0.10.2";
3762
- if (!shouldUpdate(current, latest)) return;
3773
+ const availability = await getUpdateAvailability();
3774
+ if (!availability?.latest) return;
3775
+ if (!availability.updateAvailable) return;
3776
+ const { current, latest } = availability;
3763
3777
  console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
3764
3778
  await stopRunningServices();
3765
3779
  const ok = runNpmInstall();
@@ -3840,7 +3854,7 @@ async function restartServices(state) {
3840
3854
  }
3841
3855
  async function forceUpdate() {
3842
3856
  const latest = await fetchLatestVersion();
3843
- const current = "0.10.2";
3857
+ const current = "0.10.3";
3844
3858
  if (latest === null) {
3845
3859
  console.error("Could not reach npm registry. Check your network.");
3846
3860
  process.exit(1);
@@ -14617,9 +14631,147 @@ var init_config = __esm({
14617
14631
  }
14618
14632
  });
14619
14633
 
14620
- // src/usage/profile.ts
14621
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
14634
+ // src/team/push.ts
14635
+ import { randomUUID as randomUUID2 } from "node:crypto";
14636
+ function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
14637
+ const raw = latestClaudeCodeSnapshot(filePath);
14638
+ if (!raw) return null;
14639
+ const capturedMs = Date.parse(raw.captured_at);
14640
+ if (Number.isNaN(capturedMs)) return null;
14641
+ if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
14642
+ const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
14643
+ return {
14644
+ capturedAt: raw.captured_at,
14645
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
14646
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
14647
+ sevenDayOpus: toWire(raw.seven_day_opus),
14648
+ sevenDaySonnet: toWire(raw.seven_day_sonnet),
14649
+ sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
14650
+ sevenDayCowork: toWire(raw.seven_day_cowork),
14651
+ extraUsage: raw.extra_usage ? {
14652
+ isEnabled: raw.extra_usage.is_enabled,
14653
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
14654
+ usedCreditsUsd: raw.extra_usage.used_credits,
14655
+ utilization: raw.extra_usage.utilization
14656
+ } : null
14657
+ };
14658
+ }
14659
+ function bucketToRollup(b) {
14660
+ return {
14661
+ day: b.date,
14662
+ agentTimeMs: b.airTimeMs,
14663
+ sessions: b.sessions,
14664
+ toolCalls: b.toolCalls,
14665
+ turns: b.turns,
14666
+ tokens: { ...b.tokens }
14667
+ };
14668
+ }
14669
+ function buildRollupsForRange(sessions, sinceDay) {
14670
+ const buckets = dailyActivity(sessions);
14671
+ const startCounts = /* @__PURE__ */ new Map();
14672
+ for (const s of sessions) {
14673
+ const d = sessionDay(s);
14674
+ if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
14675
+ }
14676
+ return buckets.filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({
14677
+ ...bucketToRollup(b),
14678
+ sessions: startCounts.get(b.date) ?? 0
14679
+ }));
14680
+ }
14681
+ function buildIngestPayload(rollup, usageSnapshot, planTier, cyclePeaks) {
14682
+ return {
14683
+ ingestId: randomUUID2(),
14684
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
14685
+ ...rollup ? { dailyRollup: rollup } : {},
14686
+ ...usageSnapshot ? { usageSnapshot } : {},
14687
+ ...planTier ? { planTier } : {},
14688
+ ...cyclePeaks ? { cyclePeaks } : {}
14689
+ };
14690
+ }
14691
+ async function pushToTeamServer(config, payload) {
14692
+ const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
14693
+ method: "POST",
14694
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS),
14695
+ headers: {
14696
+ "Content-Type": "application/json",
14697
+ Authorization: `Bearer ${config.bearerToken}`
14698
+ },
14699
+ body: JSON.stringify(payload)
14700
+ });
14701
+ const body = await res.json().catch(() => null);
14702
+ return { ok: res.ok, status: res.status, body };
14703
+ }
14704
+ var SNAPSHOT_FRESHNESS_MS, POST_TIMEOUT_MS;
14705
+ var init_push = __esm({
14706
+ "src/team/push.ts"() {
14707
+ "use strict";
14708
+ init_dist();
14709
+ init_storage();
14710
+ SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
14711
+ POST_TIMEOUT_MS = 15e3;
14712
+ }
14713
+ });
14714
+
14715
+ // src/team/queue.ts
14716
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync4, statSync as statSync3, mkdirSync as mkdirSync10 } from "node:fs";
14622
14717
  import { dirname as dirname9 } from "node:path";
14718
+ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
14719
+ mkdirSync10(dirname9(queuePath), { recursive: true });
14720
+ const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
14721
+ appendFileSync4(queuePath, JSON.stringify(entry) + "\n", "utf8");
14722
+ try {
14723
+ const { size } = statSync3(queuePath);
14724
+ if (size > MAX_SIZE_BYTES) {
14725
+ pruneOldest(queuePath);
14726
+ }
14727
+ } catch {
14728
+ }
14729
+ }
14730
+ function dequeuePayloads(queuePath = QUEUE_PATH) {
14731
+ let lines;
14732
+ try {
14733
+ lines = readFileSync13(queuePath, "utf8").trim().split("\n").filter(Boolean);
14734
+ } catch {
14735
+ return [];
14736
+ }
14737
+ const now = Date.now();
14738
+ const valid = [];
14739
+ for (const line of lines) {
14740
+ try {
14741
+ const entry = JSON.parse(line);
14742
+ if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
14743
+ valid.push(entry);
14744
+ }
14745
+ } catch {
14746
+ }
14747
+ }
14748
+ writeFileSync9(queuePath, "", "utf8");
14749
+ return valid.map((e) => e.payload);
14750
+ }
14751
+ function pruneOldest(queuePath) {
14752
+ let lines;
14753
+ try {
14754
+ lines = readFileSync13(queuePath, "utf8").trim().split("\n").filter(Boolean);
14755
+ } catch {
14756
+ return;
14757
+ }
14758
+ const keep = lines.slice(Math.floor(lines.length / 2));
14759
+ writeFileSync9(queuePath, keep.join("\n") + "\n", "utf8");
14760
+ }
14761
+ var QUEUE_PATH, MAX_SIZE_BYTES, MAX_AGE_MS;
14762
+ var init_queue2 = __esm({
14763
+ "src/team/queue.ts"() {
14764
+ "use strict";
14765
+ init_fs();
14766
+ QUEUE_PATH = cclensPath("ingest-queue.jsonl");
14767
+ MAX_SIZE_BYTES = 10 * 1024 * 1024;
14768
+ MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
14769
+ }
14770
+ });
14771
+
14772
+ // src/usage/profile.ts
14773
+ import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
14774
+ import { dirname as dirname10 } from "node:path";
14623
14775
  function mapRateLimitTier(rateLimitTier) {
14624
14776
  switch (rateLimitTier) {
14625
14777
  case "default_claude_pro":
@@ -14661,7 +14813,7 @@ async function fetchProfile(nowMs = Date.now()) {
14661
14813
  function readCachedProfile(cachePath) {
14662
14814
  if (!existsSync12(cachePath)) return null;
14663
14815
  try {
14664
- const raw = JSON.parse(readFileSync13(cachePath, "utf8"));
14816
+ const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
14665
14817
  if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
14666
14818
  return raw;
14667
14819
  } catch {
@@ -14669,8 +14821,8 @@ function readCachedProfile(cachePath) {
14669
14821
  }
14670
14822
  }
14671
14823
  function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
14672
- mkdirSync10(dirname9(cachePath), { recursive: true });
14673
- writeFileSync9(
14824
+ mkdirSync11(dirname10(cachePath), { recursive: true });
14825
+ writeFileSync10(
14674
14826
  cachePath,
14675
14827
  JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
14676
14828
  { mode: 384 }
@@ -14700,11 +14852,6 @@ var init_profile = __esm({
14700
14852
  });
14701
14853
 
14702
14854
  // src/team/backfill.ts
14703
- var backfill_exports = {};
14704
- __export(backfill_exports, {
14705
- chunk: () => chunk,
14706
- runTeamBackfill: () => runTeamBackfill
14707
- });
14708
14855
  function rawToWire(raw) {
14709
14856
  const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
14710
14857
  return {
@@ -14732,6 +14879,7 @@ function chunk(items, size) {
14732
14879
  async function postBatch(config, snapshots, planTier) {
14733
14880
  const res = await fetch(`${config.serverUrl}/api/ingest/usage-history`, {
14734
14881
  method: "POST",
14882
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS2),
14735
14883
  headers: {
14736
14884
  "Content-Type": "application/json",
14737
14885
  Authorization: `Bearer ${config.bearerToken}`
@@ -14748,13 +14896,35 @@ async function postBatch(config, snapshots, planTier) {
14748
14896
  status: res.status
14749
14897
  };
14750
14898
  }
14751
- async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverride) {
14899
+ function isAfterSince(snapshot, sinceCapturedAt) {
14900
+ if (!sinceCapturedAt) return true;
14901
+ const sinceMs = Date.parse(sinceCapturedAt);
14902
+ if (Number.isNaN(sinceMs)) return true;
14903
+ const capturedMs = Date.parse(snapshot.captured_at);
14904
+ return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
14905
+ }
14906
+ function capturedAtMs(snapshot) {
14907
+ const ms = Date.parse(snapshot.captured_at);
14908
+ return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
14909
+ }
14910
+ function latestCapturedAt(snapshots) {
14911
+ let latest = null;
14912
+ for (const s of snapshots) {
14913
+ const ms = Date.parse(s.capturedAt);
14914
+ if (Number.isNaN(ms)) continue;
14915
+ if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
14916
+ }
14917
+ return latest?.at;
14918
+ }
14919
+ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverride, options = {}) {
14752
14920
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
14753
14921
  if (!config) {
14754
14922
  return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
14755
14923
  }
14756
14924
  const raw = readSnapshots(filePath);
14757
- const claudeOnly = raw.filter((s) => !s.agent || s.agent === "claude-code");
14925
+ const claudeOnly = raw.filter(
14926
+ (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
14927
+ ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
14758
14928
  if (claudeOnly.length === 0) {
14759
14929
  log("info", "team backfill: no usage snapshots to send");
14760
14930
  return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
@@ -14764,6 +14934,7 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14764
14934
  const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
14765
14935
  let inserted = 0;
14766
14936
  let skipped = 0;
14937
+ let lastSnapshotAt;
14767
14938
  for (let i = 0; i < batches.length; i++) {
14768
14939
  const batch = batches[i];
14769
14940
  try {
@@ -14776,11 +14947,13 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14776
14947
  insertedSnapshots: inserted,
14777
14948
  skippedSnapshots: skipped,
14778
14949
  batches: i,
14950
+ lastSnapshotAt,
14779
14951
  error: `HTTP ${result.status}`
14780
14952
  };
14781
14953
  }
14782
14954
  inserted += result.inserted;
14783
14955
  skipped += result.skipped;
14956
+ lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
14784
14957
  } catch (err) {
14785
14958
  log("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
14786
14959
  return {
@@ -14789,6 +14962,7 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14789
14962
  insertedSnapshots: inserted,
14790
14963
  skippedSnapshots: skipped,
14791
14964
  batches: i,
14965
+ lastSnapshotAt,
14792
14966
  error: err.message
14793
14967
  };
14794
14968
  }
@@ -14802,10 +14976,11 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14802
14976
  sentSnapshots: wire.length,
14803
14977
  insertedSnapshots: inserted,
14804
14978
  skippedSnapshots: skipped,
14805
- batches: batches.length
14979
+ batches: batches.length,
14980
+ lastSnapshotAt
14806
14981
  };
14807
14982
  }
14808
- var USAGE_LOG3, PROFILE_CACHE, BATCH_SIZE, noopLog;
14983
+ var USAGE_LOG3, PROFILE_CACHE, BATCH_SIZE, POST_TIMEOUT_MS2, noopLog;
14809
14984
  var init_backfill = __esm({
14810
14985
  "src/team/backfill.ts"() {
14811
14986
  "use strict";
@@ -14816,159 +14991,35 @@ var init_backfill = __esm({
14816
14991
  USAGE_LOG3 = cclensPath("usage.jsonl");
14817
14992
  PROFILE_CACHE = cclensPath("profile.json");
14818
14993
  BATCH_SIZE = 500;
14994
+ POST_TIMEOUT_MS2 = 15e3;
14819
14995
  noopLog = () => {
14820
14996
  };
14821
14997
  }
14822
14998
  });
14823
14999
 
14824
- // src/team/push.ts
14825
- import { randomUUID as randomUUID2 } from "node:crypto";
14826
- function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
14827
- const raw = latestClaudeCodeSnapshot(filePath);
14828
- if (!raw) return null;
14829
- const capturedMs = Date.parse(raw.captured_at);
14830
- if (Number.isNaN(capturedMs)) return null;
14831
- if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
14832
- const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
14833
- return {
14834
- capturedAt: raw.captured_at,
14835
- fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
14836
- sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
14837
- sevenDayOpus: toWire(raw.seven_day_opus),
14838
- sevenDaySonnet: toWire(raw.seven_day_sonnet),
14839
- sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
14840
- sevenDayCowork: toWire(raw.seven_day_cowork),
14841
- extraUsage: raw.extra_usage ? {
14842
- isEnabled: raw.extra_usage.is_enabled,
14843
- monthlyLimitUsd: raw.extra_usage.monthly_limit,
14844
- usedCreditsUsd: raw.extra_usage.used_credits,
14845
- utilization: raw.extra_usage.utilization
14846
- } : null
14847
- };
14848
- }
14849
- function bucketToRollup(b) {
14850
- return {
14851
- day: b.date,
14852
- agentTimeMs: b.airTimeMs,
14853
- sessions: b.sessions,
14854
- toolCalls: b.toolCalls,
14855
- turns: b.turns,
14856
- tokens: { ...b.tokens }
14857
- };
14858
- }
14859
- function buildRollupsForRange(sessions, sinceDay) {
14860
- const buckets = dailyActivity(sessions);
14861
- const startCounts = /* @__PURE__ */ new Map();
14862
- for (const s of sessions) {
14863
- const d = sessionDay(s);
14864
- if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
14865
- }
14866
- return buckets.filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({
14867
- ...bucketToRollup(b),
14868
- sessions: startCounts.get(b.date) ?? 0
14869
- }));
14870
- }
14871
- function buildIngestPayload(rollup, usageSnapshot, planTier, cyclePeaks) {
14872
- return {
14873
- ingestId: randomUUID2(),
14874
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
14875
- ...rollup ? { dailyRollup: rollup } : {},
14876
- ...usageSnapshot ? { usageSnapshot } : {},
14877
- ...planTier ? { planTier } : {},
14878
- ...cyclePeaks ? { cyclePeaks } : {}
14879
- };
14880
- }
14881
- async function pushToTeamServer(config, payload) {
14882
- const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
14883
- method: "POST",
14884
- headers: {
14885
- "Content-Type": "application/json",
14886
- Authorization: `Bearer ${config.bearerToken}`
14887
- },
14888
- body: JSON.stringify(payload)
14889
- });
14890
- const body = await res.json().catch(() => null);
14891
- return { ok: res.ok, status: res.status, body };
14892
- }
14893
- var SNAPSHOT_FRESHNESS_MS;
14894
- var init_push = __esm({
14895
- "src/team/push.ts"() {
14896
- "use strict";
14897
- init_dist();
14898
- init_storage();
14899
- SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
14900
- }
14901
- });
14902
-
14903
- // src/team/queue.ts
14904
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, appendFileSync as appendFileSync4, statSync as statSync3, mkdirSync as mkdirSync11 } from "node:fs";
14905
- import { dirname as dirname10 } from "node:path";
14906
- function enqueuePayload(payload, queuePath = QUEUE_PATH) {
14907
- mkdirSync11(dirname10(queuePath), { recursive: true });
14908
- const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
14909
- appendFileSync4(queuePath, JSON.stringify(entry) + "\n", "utf8");
14910
- try {
14911
- const { size } = statSync3(queuePath);
14912
- if (size > MAX_SIZE_BYTES) {
14913
- pruneOldest(queuePath);
14914
- }
14915
- } catch {
14916
- }
14917
- }
14918
- function dequeuePayloads(queuePath = QUEUE_PATH) {
14919
- let lines;
14920
- try {
14921
- lines = readFileSync14(queuePath, "utf8").trim().split("\n").filter(Boolean);
14922
- } catch {
14923
- return [];
14924
- }
14925
- const now = Date.now();
14926
- const valid = [];
14927
- for (const line of lines) {
14928
- try {
14929
- const entry = JSON.parse(line);
14930
- if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
14931
- valid.push(entry);
14932
- }
14933
- } catch {
14934
- }
14935
- }
14936
- writeFileSync10(queuePath, "", "utf8");
14937
- return valid.map((e) => e.payload);
14938
- }
14939
- function pruneOldest(queuePath) {
14940
- let lines;
14941
- try {
14942
- lines = readFileSync14(queuePath, "utf8").trim().split("\n").filter(Boolean);
14943
- } catch {
14944
- return;
14945
- }
14946
- const keep = lines.slice(Math.floor(lines.length / 2));
14947
- writeFileSync10(queuePath, keep.join("\n") + "\n", "utf8");
14948
- }
14949
- var QUEUE_PATH, MAX_SIZE_BYTES, MAX_AGE_MS;
14950
- var init_queue2 = __esm({
14951
- "src/team/queue.ts"() {
14952
- "use strict";
14953
- init_fs();
14954
- QUEUE_PATH = cclensPath("ingest-queue.jsonl");
14955
- MAX_SIZE_BYTES = 10 * 1024 * 1024;
14956
- MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
14957
- }
14958
- });
14959
-
14960
15000
  // src/team/sync.ts
14961
15001
  var sync_exports = {};
14962
15002
  __export(sync_exports, {
14963
15003
  runTeamSync: () => runTeamSync
14964
15004
  });
14965
- async function runTeamSync(log = noopLog2, configOverride) {
15005
+ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
14966
15006
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
14967
15007
  if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
14968
15008
  try {
14969
15009
  const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
14970
15010
  const { toLocalDay: toLocalDay4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
14971
15011
  const today = toLocalDay4(Date.now());
15012
+ let nextConfig = { ...config };
15013
+ const persistConfig = (patch) => {
15014
+ nextConfig = { ...nextConfig, ...patch };
15015
+ writeTeamConfig(nextConfig);
15016
+ };
15017
+ const usageBackfill = await runTeamBackfill(log, USAGE_LOG4, config, {
15018
+ sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
15019
+ });
15020
+ if (usageBackfill.lastSnapshotAt) {
15021
+ persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
15022
+ }
14972
15023
  const sessions = await listSessions2({ limit: 1e4 });
14973
15024
  const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
14974
15025
  const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG4) ?? void 0;
@@ -14977,15 +15028,15 @@ async function runTeamSync(log = noopLog2, configOverride) {
14977
15028
  if (rollups.length === 0) {
14978
15029
  const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
14979
15030
  if (!hasLiveData) {
14980
- log("info", "team push: nothing to sync");
14981
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0 };
15031
+ if (usageBackfill.sentSnapshots === 0) log("info", "team push: nothing to sync");
15032
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
14982
15033
  }
14983
15034
  const payload = buildIngestPayload(void 0, usageSnapshot, planTier, cyclePeaks);
14984
15035
  const result = await pushToTeamServer(config, payload);
14985
15036
  if (!result.ok) {
14986
15037
  log("warn", `team push (live-only) failed (${result.status}); queueing`);
14987
15038
  enqueuePayload(payload);
14988
- return { paired: true, pushed: 0, queued: 1, queuedDrained: 0 };
15039
+ return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
14989
15040
  }
14990
15041
  let queuedDrained2 = 0;
14991
15042
  const backlog = dequeuePayloads();
@@ -14998,11 +15049,12 @@ async function runTeamSync(log = noopLog2, configOverride) {
14998
15049
  queuedDrained2++;
14999
15050
  }
15000
15051
  log("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
15001
- return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2 };
15052
+ return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
15002
15053
  }
15003
15054
  let pushed = 0;
15004
15055
  let queued = 0;
15005
15056
  let failedDay;
15057
+ let lastPushedDay;
15006
15058
  for (let i = 0; i < rollups.length; i++) {
15007
15059
  const rollup = rollups[i];
15008
15060
  const isLatest = i === rollups.length - 1;
@@ -15023,9 +15075,13 @@ async function runTeamSync(log = noopLog2, configOverride) {
15023
15075
  break;
15024
15076
  }
15025
15077
  pushed++;
15078
+ lastPushedDay = rollup.day;
15079
+ }
15080
+ if (failedDay) {
15081
+ if (lastPushedDay) persistConfig({ lastSyncedDay: lastPushedDay });
15082
+ } else {
15083
+ persistConfig({ lastSyncedDay: today });
15026
15084
  }
15027
- const nextConfig = { ...config, lastSyncedDay: today };
15028
- if (pushed > 0 || !failedDay) writeTeamConfig(nextConfig);
15029
15085
  let queuedDrained = 0;
15030
15086
  if (!failedDay) {
15031
15087
  const backlog = dequeuePayloads();
@@ -15041,7 +15097,7 @@ async function runTeamSync(log = noopLog2, configOverride) {
15041
15097
  if (pushed > 0) {
15042
15098
  log("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
15043
15099
  }
15044
- return { paired: true, pushed, queued, queuedDrained, failedDay };
15100
+ return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
15045
15101
  } catch (err) {
15046
15102
  const message = err.message;
15047
15103
  log("warn", `team push error: ${message}`);
@@ -15115,6 +15171,7 @@ var init_sync = __esm({
15115
15171
  init_config();
15116
15172
  init_push();
15117
15173
  init_queue2();
15174
+ init_backfill();
15118
15175
  init_profile();
15119
15176
  init_fs();
15120
15177
  USAGE_LOG4 = cclensPath("usage.jsonl");
@@ -15158,24 +15215,24 @@ async function joinTeam(args2) {
15158
15215
  writeTeamConfig(config);
15159
15216
  console.log(`Paired with "${data.team.name}" as ${data.user.displayName || data.user.email}`);
15160
15217
  console.log(` role: ${data.membership.role}`);
15161
- console.log(" Pushing local history\u2026");
15162
- const backfill = await runTeamBackfill(void 0, void 0, config);
15163
- if (backfill.error) {
15164
- console.log(` \u26A0 Backfill failed: ${backfill.error} \u2014 run 'fleetlens team backfill' to retry.`);
15165
- } else if (backfill.insertedSnapshots > 0) {
15218
+ console.log(" Syncing local history\u2026");
15219
+ const sync = await runTeamSync(void 0, config, { forceUsageBackfill: true });
15220
+ const backfill = sync.usageBackfill;
15221
+ if (backfill?.error) {
15222
+ console.log(` \u26A0 Usage history sync failed: ${backfill.error} \u2014 daemon will retry automatically.`);
15223
+ } else if ((backfill?.insertedSnapshots ?? 0) > 0) {
15166
15224
  console.log(
15167
- ` \u2713 Backfilled ${backfill.insertedSnapshots} usage snapshot${backfill.insertedSnapshots === 1 ? "" : "s"} from local history.`
15225
+ ` \u2713 Synced ${backfill.insertedSnapshots} usage snapshot${backfill.insertedSnapshots === 1 ? "" : "s"} from local history.`
15168
15226
  );
15169
- } else if (backfill.sentSnapshots > 0) {
15170
- console.log(` \u2713 ${backfill.sentSnapshots} snapshots already on server (deduped on capture time).`);
15227
+ } else if ((backfill?.sentSnapshots ?? 0) > 0) {
15228
+ console.log(` \u2713 ${backfill.sentSnapshots} usage snapshots already on server (deduped on capture time).`);
15171
15229
  } else {
15172
15230
  console.log(" \xB7 No local usage snapshots yet \u2014 daemon will start collecting.");
15173
15231
  }
15174
- const sync = await runTeamSync(void 0, config);
15175
15232
  if (sync.error) {
15176
- console.log(` \u26A0 Activity sync failed: ${sync.error} \u2014 will retry on next daemon cycle.`);
15233
+ console.log(` \u26A0 Team sync failed: ${sync.error} \u2014 will retry on next daemon cycle.`);
15177
15234
  } else if (sync.pushed > 0) {
15178
- console.log(` \u2713 Synced ${sync.pushed} day${sync.pushed === 1 ? "" : "s"} of session activity.`);
15235
+ console.log(` \u2713 Synced ${sync.pushed} activity payload${sync.pushed === 1 ? "" : "s"}.`);
15179
15236
  } else {
15180
15237
  console.log(" \xB7 No new session activity to push.");
15181
15238
  }
@@ -15185,7 +15242,6 @@ var init_join = __esm({
15185
15242
  "src/team/join.ts"() {
15186
15243
  "use strict";
15187
15244
  init_config();
15188
- init_backfill();
15189
15245
  init_sync();
15190
15246
  }
15191
15247
  });
@@ -15322,22 +15378,31 @@ async function team(args2) {
15322
15378
  console.error("Not paired. Run 'fleetlens team join <url> <device-token>' first.");
15323
15379
  process.exit(1);
15324
15380
  }
15325
- if (outcome.error) process.exit(1);
15381
+ const inserted = outcome.usageBackfill?.insertedSnapshots ?? 0;
15382
+ const madeProgress = outcome.pushed > 0 || outcome.queuedDrained > 0 || inserted > 0;
15383
+ if (outcome.error || outcome.usageBackfill?.error && !madeProgress) process.exit(1);
15384
+ const sent = outcome.usageBackfill?.sentSnapshots ?? 0;
15385
+ const usageChecked = sent > 0 ? `, ${sent} usage snapshot${sent === 1 ? "" : "s"} checked` : "";
15326
15386
  console.log(
15327
- `\u2713 ${outcome.pushed} day${outcome.pushed === 1 ? "" : "s"} pushed` + (outcome.queuedDrained ? `, ${outcome.queuedDrained} queued retried` : "") + (outcome.queued ? `, ${outcome.queued} queued for retry (will fire next cycle)` : "")
15387
+ `\u2713 ${outcome.pushed} activity payload${outcome.pushed === 1 ? "" : "s"} pushed` + usageChecked + (outcome.queuedDrained ? `, ${outcome.queuedDrained} queued retried` : "") + (outcome.queued ? `, ${outcome.queued} queued for retry (will fire next cycle)` : "")
15328
15388
  );
15329
15389
  break;
15330
15390
  }
15331
15391
  case "backfill": {
15332
- const { runTeamBackfill: runTeamBackfill2 } = await Promise.resolve().then(() => (init_backfill(), backfill_exports));
15333
- const outcome = await runTeamBackfill2((level, msg) => console.log(`[${level}] ${msg}`));
15392
+ const { runTeamSync: runTeamSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
15393
+ const outcome = await runTeamSync2(
15394
+ (level, msg) => console.log(`[${level}] ${msg}`),
15395
+ void 0,
15396
+ { forceUsageBackfill: true }
15397
+ );
15334
15398
  if (!outcome.paired) {
15335
15399
  console.error("Not paired. Run 'fleetlens team join <url> <device-token>' first.");
15336
15400
  process.exit(1);
15337
15401
  }
15338
- if (outcome.error) process.exit(1);
15402
+ if (outcome.error || outcome.usageBackfill?.error) process.exit(1);
15403
+ const inserted = outcome.usageBackfill?.insertedSnapshots ?? 0;
15339
15404
  console.log(
15340
- `\u2713 ${outcome.insertedSnapshots} new snapshot${outcome.insertedSnapshots === 1 ? "" : "s"} loaded` + (outcome.skippedSnapshots ? `, ${outcome.skippedSnapshots} already-known` : "")
15405
+ `\u2713 ${inserted} new snapshot${inserted === 1 ? "" : "s"} loaded` + (outcome.usageBackfill?.skippedSnapshots ? `, ${outcome.usageBackfill.skippedSnapshots} already-known` : "") + (outcome.pushed ? `, ${outcome.pushed} activity payload${outcome.pushed === 1 ? "" : "s"} pushed` : "")
15341
15406
  );
15342
15407
  break;
15343
15408
  }
@@ -15781,7 +15846,7 @@ async function main() {
15781
15846
  case "version":
15782
15847
  case "--version":
15783
15848
  case "-v":
15784
- console.log(`fleetlens ${"0.10.2"}`);
15849
+ console.log(`fleetlens ${"0.10.3"}`);
15785
15850
  break;
15786
15851
  case "help":
15787
15852
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetlens",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "fleetlens — local-only dashboard and usage tracker for Claude Code sessions and agent fleets",
5
5
  "license": "MIT",
6
6
  "type": "module",