fleetlens 0.10.2 → 0.10.4

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 (67) 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/[root-of-the-server]__02px2mk._.js +1 -1
  46. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0vm_41a._.js +1 -1
  47. package/app/apps/web/.next/server/chunks/packages_entries_dist_node_0bckr7-.js +6 -6
  48. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0.za3_3._.js +3 -0
  49. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  50. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_0m67-xi._.js +1 -1
  51. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_12.endt._.js +1 -1
  52. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  53. package/app/apps/web/.next/server/pages/500.html +1 -1
  54. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  55. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  56. package/app/apps/web/.next/static/chunks/08nun7x9h-ro1.js +14 -0
  57. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  58. package/app/apps/web/package.json +1 -1
  59. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  60. package/dist/daemon-worker.js +387 -58
  61. package/dist/index.js +269 -189
  62. package/package.json +6 -1
  63. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0s4dg1y._.js +0 -3
  64. package/app/apps/web/.next/static/chunks/063r4x2.9csh8.js +0 -14
  65. /package/app/apps/web/.next/static/{XTns3Y6jHWmb7EYKzQi55 → _8He0IMRxt70EDQL1JOFe}/_buildManifest.js +0 -0
  66. /package/app/apps/web/.next/static/{XTns3Y6jHWmb7EYKzQi55 → _8He0IMRxt70EDQL1JOFe}/_clientMiddlewareManifest.js +0 -0
  67. /package/app/apps/web/.next/static/{XTns3Y6jHWmb7EYKzQi55 → _8He0IMRxt70EDQL1JOFe}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -1470,6 +1470,12 @@ function decodeProjectName(dir) {
1470
1470
  return dir;
1471
1471
  return "/" + dir.slice(1).replace(/-/g, "/");
1472
1472
  }
1473
+ function encodeProjectDir(cwd) {
1474
+ return cwd.replace(/[/.]/g, "-");
1475
+ }
1476
+ function isFleetlensRuntimeDir(projectDir) {
1477
+ return projectDir === FLEETLENS_RUNTIME_PROJECT_DIR;
1478
+ }
1473
1479
  async function readJsonlFile(filePath) {
1474
1480
  const raw = await fs.readFile(filePath, "utf8");
1475
1481
  const out = [];
@@ -1516,7 +1522,7 @@ async function walkJsonlFiles(root = DEFAULT_ROOT) {
1516
1522
  } catch {
1517
1523
  return [];
1518
1524
  }
1519
- const projectDirs = topEntries.filter((e) => e.isDirectory()).map((e) => e.name);
1525
+ const projectDirs = topEntries.filter((e) => e.isDirectory() && !isFleetlensRuntimeDir(e.name)).map((e) => e.name);
1520
1526
  const perProject = await Promise.all(projectDirs.map(async (projectDir) => {
1521
1527
  const projectPath = path.join(root, projectDir);
1522
1528
  let files;
@@ -1997,7 +2003,7 @@ async function findTeamLead(sessionId, opts = {}) {
1997
2003
  agentName: self.agentName
1998
2004
  };
1999
2005
  }
2000
- var DEFAULT_ROOT, metaCache, detailCache, calibrationEventsCache, fileListCache, FILE_LIST_TTL_MS, BLANK_USAGE3, claudeCodeSource;
2006
+ var DEFAULT_ROOT, FLEETLENS_RUNTIME_PROJECT_DIR, metaCache, detailCache, calibrationEventsCache, fileListCache, FILE_LIST_TTL_MS, BLANK_USAGE3, claudeCodeSource;
2001
2007
  var init_claude_code = __esm({
2002
2008
  "../parser/dist/claude-code.js"() {
2003
2009
  "use strict";
@@ -2007,6 +2013,7 @@ var init_claude_code = __esm({
2007
2013
  init_agent_metadata();
2008
2014
  init_calibration();
2009
2015
  DEFAULT_ROOT = path.join(os.homedir(), ".claude", "projects");
2016
+ FLEETLENS_RUNTIME_PROJECT_DIR = encodeProjectDir(path.join(os.homedir(), ".cclens", "runtime"));
2010
2017
  metaCache = /* @__PURE__ */ new Map();
2011
2018
  detailCache = /* @__PURE__ */ new Map();
2012
2019
  calibrationEventsCache = /* @__PURE__ */ new Map();
@@ -3006,6 +3013,7 @@ __export(fs_exports, {
3006
3013
  getLatestCodexUsage: () => getLatestCodexUsage,
3007
3014
  getSession: () => getSession,
3008
3015
  invalidateFile: () => invalidateFile,
3016
+ isFleetlensRuntimeDir: () => isFleetlensRuntimeDir,
3009
3017
  listAllSessions: () => listAllSessions,
3010
3018
  listCodexSessions: () => listCodexSessions,
3011
3019
  listGeminiSessions: () => listGeminiSessions,
@@ -3720,9 +3728,9 @@ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath
3720
3728
  console.warn(
3721
3729
  ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
3722
3730
  );
3723
- } else if (verify.installedVersion !== "0.10.2") {
3731
+ } else if (verify.installedVersion !== "0.10.4") {
3724
3732
  console.log(
3725
- ` \u2192 This process is still running ${"0.10.2"}. Next invocation will use ${verify.installedVersion}.`
3733
+ ` \u2192 This process is still running ${"0.10.4"}. Next invocation will use ${verify.installedVersion}.`
3726
3734
  );
3727
3735
  }
3728
3736
  }
@@ -3753,13 +3761,27 @@ function isDevMode() {
3753
3761
  const script = process.argv[1] ?? "";
3754
3762
  return script.includes("packages/cli/") || script.includes("packages\\cli\\");
3755
3763
  }
3764
+ async function getUpdateAvailability() {
3765
+ if (isDevMode()) return null;
3766
+ const latest = await fetchLatestVersion();
3767
+ if (latest === null) return {
3768
+ current: "0.10.4",
3769
+ latest: null,
3770
+ updateAvailable: false
3771
+ };
3772
+ return {
3773
+ current: "0.10.4",
3774
+ latest,
3775
+ updateAvailable: shouldUpdate("0.10.4", latest)
3776
+ };
3777
+ }
3756
3778
  async function checkForUpdate() {
3757
3779
  if (isDevMode()) return;
3758
3780
  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;
3781
+ const availability = await getUpdateAvailability();
3782
+ if (!availability?.latest) return;
3783
+ if (!availability.updateAvailable) return;
3784
+ const { current, latest } = availability;
3763
3785
  console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
3764
3786
  await stopRunningServices();
3765
3787
  const ok = runNpmInstall();
@@ -3840,7 +3862,7 @@ async function restartServices(state) {
3840
3862
  }
3841
3863
  async function forceUpdate() {
3842
3864
  const latest = await fetchLatestVersion();
3843
- const current = "0.10.2";
3865
+ const current = "0.10.4";
3844
3866
  if (latest === null) {
3845
3867
  console.error("Could not reach npm registry. Check your network.");
3846
3868
  process.exit(1);
@@ -9935,8 +9957,9 @@ exec ${shellEscape(claudeBin)} \\
9935
9957
  const timeoutMs = args2.timeoutMs ?? 12e4;
9936
9958
  let lastReportedKb = -1;
9937
9959
  let lastEmittedLength = 0;
9960
+ let transcriptPath;
9938
9961
  try {
9939
- const transcriptPath = await discoverTranscript(cwd, existingBefore, startMs, timeoutMs, args2.signal);
9962
+ transcriptPath = await discoverTranscript(cwd, existingBefore, startMs, timeoutMs, args2.signal);
9940
9963
  while (Date.now() - startMs < timeoutMs) {
9941
9964
  if (args2.signal?.aborted)
9942
9965
  throw new TmuxRunError("aborted");
@@ -9969,7 +9992,7 @@ exec ${shellEscape(claudeBin)} \\
9969
9992
  throw new TmuxRunError(`timeout after ${timeoutMs}ms waiting for assistant turn`);
9970
9993
  } finally {
9971
9994
  args2.signal?.removeEventListener("abort", onAbort);
9972
- cleanup(cwd, runId, tmuxBin, sessionName);
9995
+ cleanup(cwd, runId, tmuxBin, sessionName, transcriptPath);
9973
9996
  }
9974
9997
  }
9975
9998
  function num(v) {
@@ -10108,12 +10131,18 @@ function parseTranscript2(path5, defaultModel) {
10108
10131
  }
10109
10132
  return { assembled, inputTokens, outputTokens, modelUsed, done };
10110
10133
  }
10111
- function cleanup(cwd, runId, tmuxBin, sessionName) {
10134
+ function cleanup(cwd, runId, tmuxBin, sessionName, transcriptPath) {
10112
10135
  try {
10113
10136
  spawnSync2(tmuxBin, ["kill-session", "-t", sessionName], { stdio: "ignore" });
10114
10137
  } catch {
10115
10138
  }
10116
10139
  if (process.env.FLEETLENS_TMUX_KEEP_WRAPPER !== "1") {
10140
+ if (transcriptPath) {
10141
+ try {
10142
+ unlinkSync2(transcriptPath);
10143
+ } catch {
10144
+ }
10145
+ }
10117
10146
  for (const f of [`system-${runId}.txt`, `user-${runId}.txt`, `run-${runId}.sh`]) {
10118
10147
  try {
10119
10148
  unlinkSync2(join7(cwd, f));
@@ -14617,9 +14646,147 @@ var init_config = __esm({
14617
14646
  }
14618
14647
  });
14619
14648
 
14620
- // src/usage/profile.ts
14621
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
14649
+ // src/team/push.ts
14650
+ import { randomUUID as randomUUID2 } from "node:crypto";
14651
+ function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
14652
+ const raw = latestClaudeCodeSnapshot(filePath);
14653
+ if (!raw) return null;
14654
+ const capturedMs = Date.parse(raw.captured_at);
14655
+ if (Number.isNaN(capturedMs)) return null;
14656
+ if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
14657
+ const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
14658
+ return {
14659
+ capturedAt: raw.captured_at,
14660
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
14661
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
14662
+ sevenDayOpus: toWire(raw.seven_day_opus),
14663
+ sevenDaySonnet: toWire(raw.seven_day_sonnet),
14664
+ sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
14665
+ sevenDayCowork: toWire(raw.seven_day_cowork),
14666
+ extraUsage: raw.extra_usage ? {
14667
+ isEnabled: raw.extra_usage.is_enabled,
14668
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
14669
+ usedCreditsUsd: raw.extra_usage.used_credits,
14670
+ utilization: raw.extra_usage.utilization
14671
+ } : null
14672
+ };
14673
+ }
14674
+ function bucketToRollup(b) {
14675
+ return {
14676
+ day: b.date,
14677
+ agentTimeMs: b.airTimeMs,
14678
+ sessions: b.sessions,
14679
+ toolCalls: b.toolCalls,
14680
+ turns: b.turns,
14681
+ tokens: { ...b.tokens }
14682
+ };
14683
+ }
14684
+ function buildRollupsForRange(sessions, sinceDay) {
14685
+ const buckets = dailyActivity(sessions);
14686
+ const startCounts = /* @__PURE__ */ new Map();
14687
+ for (const s of sessions) {
14688
+ const d = sessionDay(s);
14689
+ if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
14690
+ }
14691
+ return buckets.filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({
14692
+ ...bucketToRollup(b),
14693
+ sessions: startCounts.get(b.date) ?? 0
14694
+ }));
14695
+ }
14696
+ function buildIngestPayload(rollup, usageSnapshot, planTier, cyclePeaks) {
14697
+ return {
14698
+ ingestId: randomUUID2(),
14699
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
14700
+ ...rollup ? { dailyRollup: rollup } : {},
14701
+ ...usageSnapshot ? { usageSnapshot } : {},
14702
+ ...planTier ? { planTier } : {},
14703
+ ...cyclePeaks ? { cyclePeaks } : {}
14704
+ };
14705
+ }
14706
+ async function pushToTeamServer(config, payload) {
14707
+ const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
14708
+ method: "POST",
14709
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS),
14710
+ headers: {
14711
+ "Content-Type": "application/json",
14712
+ Authorization: `Bearer ${config.bearerToken}`
14713
+ },
14714
+ body: JSON.stringify(payload)
14715
+ });
14716
+ const body = await res.json().catch(() => null);
14717
+ return { ok: res.ok, status: res.status, body };
14718
+ }
14719
+ var SNAPSHOT_FRESHNESS_MS, POST_TIMEOUT_MS;
14720
+ var init_push = __esm({
14721
+ "src/team/push.ts"() {
14722
+ "use strict";
14723
+ init_dist();
14724
+ init_storage();
14725
+ SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
14726
+ POST_TIMEOUT_MS = 15e3;
14727
+ }
14728
+ });
14729
+
14730
+ // src/team/queue.ts
14731
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync4, statSync as statSync3, mkdirSync as mkdirSync10 } from "node:fs";
14622
14732
  import { dirname as dirname9 } from "node:path";
14733
+ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
14734
+ mkdirSync10(dirname9(queuePath), { recursive: true });
14735
+ const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
14736
+ appendFileSync4(queuePath, JSON.stringify(entry) + "\n", "utf8");
14737
+ try {
14738
+ const { size } = statSync3(queuePath);
14739
+ if (size > MAX_SIZE_BYTES) {
14740
+ pruneOldest(queuePath);
14741
+ }
14742
+ } catch {
14743
+ }
14744
+ }
14745
+ function dequeuePayloads(queuePath = QUEUE_PATH) {
14746
+ let lines;
14747
+ try {
14748
+ lines = readFileSync13(queuePath, "utf8").trim().split("\n").filter(Boolean);
14749
+ } catch {
14750
+ return [];
14751
+ }
14752
+ const now = Date.now();
14753
+ const valid = [];
14754
+ for (const line of lines) {
14755
+ try {
14756
+ const entry = JSON.parse(line);
14757
+ if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
14758
+ valid.push(entry);
14759
+ }
14760
+ } catch {
14761
+ }
14762
+ }
14763
+ writeFileSync9(queuePath, "", "utf8");
14764
+ return valid.map((e) => e.payload);
14765
+ }
14766
+ function pruneOldest(queuePath) {
14767
+ let lines;
14768
+ try {
14769
+ lines = readFileSync13(queuePath, "utf8").trim().split("\n").filter(Boolean);
14770
+ } catch {
14771
+ return;
14772
+ }
14773
+ const keep = lines.slice(Math.floor(lines.length / 2));
14774
+ writeFileSync9(queuePath, keep.join("\n") + "\n", "utf8");
14775
+ }
14776
+ var QUEUE_PATH, MAX_SIZE_BYTES, MAX_AGE_MS;
14777
+ var init_queue2 = __esm({
14778
+ "src/team/queue.ts"() {
14779
+ "use strict";
14780
+ init_fs();
14781
+ QUEUE_PATH = cclensPath("ingest-queue.jsonl");
14782
+ MAX_SIZE_BYTES = 10 * 1024 * 1024;
14783
+ MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
14784
+ }
14785
+ });
14786
+
14787
+ // src/usage/profile.ts
14788
+ import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
14789
+ import { dirname as dirname10 } from "node:path";
14623
14790
  function mapRateLimitTier(rateLimitTier) {
14624
14791
  switch (rateLimitTier) {
14625
14792
  case "default_claude_pro":
@@ -14661,7 +14828,7 @@ async function fetchProfile(nowMs = Date.now()) {
14661
14828
  function readCachedProfile(cachePath) {
14662
14829
  if (!existsSync12(cachePath)) return null;
14663
14830
  try {
14664
- const raw = JSON.parse(readFileSync13(cachePath, "utf8"));
14831
+ const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
14665
14832
  if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
14666
14833
  return raw;
14667
14834
  } catch {
@@ -14669,8 +14836,8 @@ function readCachedProfile(cachePath) {
14669
14836
  }
14670
14837
  }
14671
14838
  function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
14672
- mkdirSync10(dirname9(cachePath), { recursive: true });
14673
- writeFileSync9(
14839
+ mkdirSync11(dirname10(cachePath), { recursive: true });
14840
+ writeFileSync10(
14674
14841
  cachePath,
14675
14842
  JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
14676
14843
  { mode: 384 }
@@ -14700,11 +14867,6 @@ var init_profile = __esm({
14700
14867
  });
14701
14868
 
14702
14869
  // src/team/backfill.ts
14703
- var backfill_exports = {};
14704
- __export(backfill_exports, {
14705
- chunk: () => chunk,
14706
- runTeamBackfill: () => runTeamBackfill
14707
- });
14708
14870
  function rawToWire(raw) {
14709
14871
  const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
14710
14872
  return {
@@ -14732,6 +14894,7 @@ function chunk(items, size) {
14732
14894
  async function postBatch(config, snapshots, planTier) {
14733
14895
  const res = await fetch(`${config.serverUrl}/api/ingest/usage-history`, {
14734
14896
  method: "POST",
14897
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS2),
14735
14898
  headers: {
14736
14899
  "Content-Type": "application/json",
14737
14900
  Authorization: `Bearer ${config.bearerToken}`
@@ -14748,13 +14911,35 @@ async function postBatch(config, snapshots, planTier) {
14748
14911
  status: res.status
14749
14912
  };
14750
14913
  }
14751
- async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverride) {
14914
+ function isAfterSince(snapshot, sinceCapturedAt) {
14915
+ if (!sinceCapturedAt) return true;
14916
+ const sinceMs = Date.parse(sinceCapturedAt);
14917
+ if (Number.isNaN(sinceMs)) return true;
14918
+ const capturedMs = Date.parse(snapshot.captured_at);
14919
+ return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
14920
+ }
14921
+ function capturedAtMs(snapshot) {
14922
+ const ms = Date.parse(snapshot.captured_at);
14923
+ return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
14924
+ }
14925
+ function latestCapturedAt(snapshots) {
14926
+ let latest = null;
14927
+ for (const s of snapshots) {
14928
+ const ms = Date.parse(s.capturedAt);
14929
+ if (Number.isNaN(ms)) continue;
14930
+ if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
14931
+ }
14932
+ return latest?.at;
14933
+ }
14934
+ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverride, options = {}) {
14752
14935
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
14753
14936
  if (!config) {
14754
14937
  return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
14755
14938
  }
14756
14939
  const raw = readSnapshots(filePath);
14757
- const claudeOnly = raw.filter((s) => !s.agent || s.agent === "claude-code");
14940
+ const claudeOnly = raw.filter(
14941
+ (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
14942
+ ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
14758
14943
  if (claudeOnly.length === 0) {
14759
14944
  log("info", "team backfill: no usage snapshots to send");
14760
14945
  return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
@@ -14764,6 +14949,7 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14764
14949
  const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
14765
14950
  let inserted = 0;
14766
14951
  let skipped = 0;
14952
+ let lastSnapshotAt;
14767
14953
  for (let i = 0; i < batches.length; i++) {
14768
14954
  const batch = batches[i];
14769
14955
  try {
@@ -14776,11 +14962,13 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14776
14962
  insertedSnapshots: inserted,
14777
14963
  skippedSnapshots: skipped,
14778
14964
  batches: i,
14965
+ lastSnapshotAt,
14779
14966
  error: `HTTP ${result.status}`
14780
14967
  };
14781
14968
  }
14782
14969
  inserted += result.inserted;
14783
14970
  skipped += result.skipped;
14971
+ lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
14784
14972
  } catch (err) {
14785
14973
  log("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
14786
14974
  return {
@@ -14789,6 +14977,7 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14789
14977
  insertedSnapshots: inserted,
14790
14978
  skippedSnapshots: skipped,
14791
14979
  batches: i,
14980
+ lastSnapshotAt,
14792
14981
  error: err.message
14793
14982
  };
14794
14983
  }
@@ -14802,10 +14991,11 @@ async function runTeamBackfill(log = noopLog, filePath = USAGE_LOG3, configOverr
14802
14991
  sentSnapshots: wire.length,
14803
14992
  insertedSnapshots: inserted,
14804
14993
  skippedSnapshots: skipped,
14805
- batches: batches.length
14994
+ batches: batches.length,
14995
+ lastSnapshotAt
14806
14996
  };
14807
14997
  }
14808
- var USAGE_LOG3, PROFILE_CACHE, BATCH_SIZE, noopLog;
14998
+ var USAGE_LOG3, PROFILE_CACHE, BATCH_SIZE, POST_TIMEOUT_MS2, noopLog;
14809
14999
  var init_backfill = __esm({
14810
15000
  "src/team/backfill.ts"() {
14811
15001
  "use strict";
@@ -14816,159 +15006,35 @@ var init_backfill = __esm({
14816
15006
  USAGE_LOG3 = cclensPath("usage.jsonl");
14817
15007
  PROFILE_CACHE = cclensPath("profile.json");
14818
15008
  BATCH_SIZE = 500;
15009
+ POST_TIMEOUT_MS2 = 15e3;
14819
15010
  noopLog = () => {
14820
15011
  };
14821
15012
  }
14822
15013
  });
14823
15014
 
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
15015
  // src/team/sync.ts
14961
15016
  var sync_exports = {};
14962
15017
  __export(sync_exports, {
14963
15018
  runTeamSync: () => runTeamSync
14964
15019
  });
14965
- async function runTeamSync(log = noopLog2, configOverride) {
15020
+ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
14966
15021
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
14967
15022
  if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
14968
15023
  try {
14969
15024
  const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
14970
15025
  const { toLocalDay: toLocalDay4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
14971
15026
  const today = toLocalDay4(Date.now());
15027
+ let nextConfig = { ...config };
15028
+ const persistConfig = (patch) => {
15029
+ nextConfig = { ...nextConfig, ...patch };
15030
+ writeTeamConfig(nextConfig);
15031
+ };
15032
+ const usageBackfill = await runTeamBackfill(log, USAGE_LOG4, config, {
15033
+ sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
15034
+ });
15035
+ if (usageBackfill.lastSnapshotAt) {
15036
+ persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
15037
+ }
14972
15038
  const sessions = await listSessions2({ limit: 1e4 });
14973
15039
  const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
14974
15040
  const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG4) ?? void 0;
@@ -14977,15 +15043,15 @@ async function runTeamSync(log = noopLog2, configOverride) {
14977
15043
  if (rollups.length === 0) {
14978
15044
  const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
14979
15045
  if (!hasLiveData) {
14980
- log("info", "team push: nothing to sync");
14981
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0 };
15046
+ if (usageBackfill.sentSnapshots === 0) log("info", "team push: nothing to sync");
15047
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
14982
15048
  }
14983
15049
  const payload = buildIngestPayload(void 0, usageSnapshot, planTier, cyclePeaks);
14984
15050
  const result = await pushToTeamServer(config, payload);
14985
15051
  if (!result.ok) {
14986
15052
  log("warn", `team push (live-only) failed (${result.status}); queueing`);
14987
15053
  enqueuePayload(payload);
14988
- return { paired: true, pushed: 0, queued: 1, queuedDrained: 0 };
15054
+ return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
14989
15055
  }
14990
15056
  let queuedDrained2 = 0;
14991
15057
  const backlog = dequeuePayloads();
@@ -14998,11 +15064,12 @@ async function runTeamSync(log = noopLog2, configOverride) {
14998
15064
  queuedDrained2++;
14999
15065
  }
15000
15066
  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 };
15067
+ return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
15002
15068
  }
15003
15069
  let pushed = 0;
15004
15070
  let queued = 0;
15005
15071
  let failedDay;
15072
+ let lastPushedDay;
15006
15073
  for (let i = 0; i < rollups.length; i++) {
15007
15074
  const rollup = rollups[i];
15008
15075
  const isLatest = i === rollups.length - 1;
@@ -15023,9 +15090,13 @@ async function runTeamSync(log = noopLog2, configOverride) {
15023
15090
  break;
15024
15091
  }
15025
15092
  pushed++;
15093
+ lastPushedDay = rollup.day;
15094
+ }
15095
+ if (failedDay) {
15096
+ if (lastPushedDay) persistConfig({ lastSyncedDay: lastPushedDay });
15097
+ } else {
15098
+ persistConfig({ lastSyncedDay: today });
15026
15099
  }
15027
- const nextConfig = { ...config, lastSyncedDay: today };
15028
- if (pushed > 0 || !failedDay) writeTeamConfig(nextConfig);
15029
15100
  let queuedDrained = 0;
15030
15101
  if (!failedDay) {
15031
15102
  const backlog = dequeuePayloads();
@@ -15041,7 +15112,7 @@ async function runTeamSync(log = noopLog2, configOverride) {
15041
15112
  if (pushed > 0) {
15042
15113
  log("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
15043
15114
  }
15044
- return { paired: true, pushed, queued, queuedDrained, failedDay };
15115
+ return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
15045
15116
  } catch (err) {
15046
15117
  const message = err.message;
15047
15118
  log("warn", `team push error: ${message}`);
@@ -15115,6 +15186,7 @@ var init_sync = __esm({
15115
15186
  init_config();
15116
15187
  init_push();
15117
15188
  init_queue2();
15189
+ init_backfill();
15118
15190
  init_profile();
15119
15191
  init_fs();
15120
15192
  USAGE_LOG4 = cclensPath("usage.jsonl");
@@ -15158,24 +15230,24 @@ async function joinTeam(args2) {
15158
15230
  writeTeamConfig(config);
15159
15231
  console.log(`Paired with "${data.team.name}" as ${data.user.displayName || data.user.email}`);
15160
15232
  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) {
15233
+ console.log(" Syncing local history\u2026");
15234
+ const sync = await runTeamSync(void 0, config, { forceUsageBackfill: true });
15235
+ const backfill = sync.usageBackfill;
15236
+ if (backfill?.error) {
15237
+ console.log(` \u26A0 Usage history sync failed: ${backfill.error} \u2014 daemon will retry automatically.`);
15238
+ } else if ((backfill?.insertedSnapshots ?? 0) > 0) {
15166
15239
  console.log(
15167
- ` \u2713 Backfilled ${backfill.insertedSnapshots} usage snapshot${backfill.insertedSnapshots === 1 ? "" : "s"} from local history.`
15240
+ ` \u2713 Synced ${backfill.insertedSnapshots} usage snapshot${backfill.insertedSnapshots === 1 ? "" : "s"} from local history.`
15168
15241
  );
15169
- } else if (backfill.sentSnapshots > 0) {
15170
- console.log(` \u2713 ${backfill.sentSnapshots} snapshots already on server (deduped on capture time).`);
15242
+ } else if ((backfill?.sentSnapshots ?? 0) > 0) {
15243
+ console.log(` \u2713 ${backfill.sentSnapshots} usage snapshots already on server (deduped on capture time).`);
15171
15244
  } else {
15172
15245
  console.log(" \xB7 No local usage snapshots yet \u2014 daemon will start collecting.");
15173
15246
  }
15174
- const sync = await runTeamSync(void 0, config);
15175
15247
  if (sync.error) {
15176
- console.log(` \u26A0 Activity sync failed: ${sync.error} \u2014 will retry on next daemon cycle.`);
15248
+ console.log(` \u26A0 Team sync failed: ${sync.error} \u2014 will retry on next daemon cycle.`);
15177
15249
  } else if (sync.pushed > 0) {
15178
- console.log(` \u2713 Synced ${sync.pushed} day${sync.pushed === 1 ? "" : "s"} of session activity.`);
15250
+ console.log(` \u2713 Synced ${sync.pushed} activity payload${sync.pushed === 1 ? "" : "s"}.`);
15179
15251
  } else {
15180
15252
  console.log(" \xB7 No new session activity to push.");
15181
15253
  }
@@ -15185,7 +15257,6 @@ var init_join = __esm({
15185
15257
  "src/team/join.ts"() {
15186
15258
  "use strict";
15187
15259
  init_config();
15188
- init_backfill();
15189
15260
  init_sync();
15190
15261
  }
15191
15262
  });
@@ -15322,22 +15393,31 @@ async function team(args2) {
15322
15393
  console.error("Not paired. Run 'fleetlens team join <url> <device-token>' first.");
15323
15394
  process.exit(1);
15324
15395
  }
15325
- if (outcome.error) process.exit(1);
15396
+ const inserted = outcome.usageBackfill?.insertedSnapshots ?? 0;
15397
+ const madeProgress = outcome.pushed > 0 || outcome.queuedDrained > 0 || inserted > 0;
15398
+ if (outcome.error || outcome.usageBackfill?.error && !madeProgress) process.exit(1);
15399
+ const sent = outcome.usageBackfill?.sentSnapshots ?? 0;
15400
+ const usageChecked = sent > 0 ? `, ${sent} usage snapshot${sent === 1 ? "" : "s"} checked` : "";
15326
15401
  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)` : "")
15402
+ `\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
15403
  );
15329
15404
  break;
15330
15405
  }
15331
15406
  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}`));
15407
+ const { runTeamSync: runTeamSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
15408
+ const outcome = await runTeamSync2(
15409
+ (level, msg) => console.log(`[${level}] ${msg}`),
15410
+ void 0,
15411
+ { forceUsageBackfill: true }
15412
+ );
15334
15413
  if (!outcome.paired) {
15335
15414
  console.error("Not paired. Run 'fleetlens team join <url> <device-token>' first.");
15336
15415
  process.exit(1);
15337
15416
  }
15338
- if (outcome.error) process.exit(1);
15417
+ if (outcome.error || outcome.usageBackfill?.error) process.exit(1);
15418
+ const inserted = outcome.usageBackfill?.insertedSnapshots ?? 0;
15339
15419
  console.log(
15340
- `\u2713 ${outcome.insertedSnapshots} new snapshot${outcome.insertedSnapshots === 1 ? "" : "s"} loaded` + (outcome.skippedSnapshots ? `, ${outcome.skippedSnapshots} already-known` : "")
15420
+ `\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
15421
  );
15342
15422
  break;
15343
15423
  }
@@ -15781,7 +15861,7 @@ async function main() {
15781
15861
  case "version":
15782
15862
  case "--version":
15783
15863
  case "-v":
15784
- console.log(`fleetlens ${"0.10.2"}`);
15864
+ console.log(`fleetlens ${"0.10.4"}`);
15785
15865
  break;
15786
15866
  case "help":
15787
15867
  case "--help":