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
@@ -8,6 +8,40 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/usage/storage.ts
12
+ import { appendFileSync, mkdirSync, readFileSync as readFileSync2, existsSync } from "node:fs";
13
+ import { dirname } from "node:path";
14
+ function appendSnapshot(filePath, snapshot) {
15
+ mkdirSync(dirname(filePath), { recursive: true });
16
+ appendFileSync(filePath, JSON.stringify(snapshot) + "\n", "utf8");
17
+ }
18
+ function readSnapshots(filePath) {
19
+ if (!existsSync(filePath)) return [];
20
+ const content = readFileSync2(filePath, "utf8");
21
+ const snapshots = [];
22
+ for (const line of content.split("\n")) {
23
+ if (!line.trim()) continue;
24
+ try {
25
+ snapshots.push(JSON.parse(line));
26
+ } catch {
27
+ }
28
+ }
29
+ return snapshots;
30
+ }
31
+ function latestClaudeCodeSnapshot(filePath) {
32
+ const all = readSnapshots(filePath);
33
+ for (let i = all.length - 1; i >= 0; i--) {
34
+ const s = all[i];
35
+ if (!s.agent || s.agent === "claude-code") return s;
36
+ }
37
+ return null;
38
+ }
39
+ var init_storage = __esm({
40
+ "src/usage/storage.ts"() {
41
+ "use strict";
42
+ }
43
+ });
44
+
11
45
  // ../parser/dist/analytics.js
12
46
  function toLocalDay(ms) {
13
47
  const d = new Date(ms);
@@ -3742,8 +3776,10 @@ var init_dist = __esm({
3742
3776
  });
3743
3777
 
3744
3778
  // src/daemon-worker.ts
3745
- import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync12 } from "node:fs";
3746
- import { dirname as dirname8 } from "node:path";
3779
+ import { spawn as spawn2 } from "node:child_process";
3780
+ import { appendFileSync as appendFileSync5, closeSync, existsSync as existsSync11, mkdirSync as mkdirSync12, openSync, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
3781
+ import { dirname as dirname8, join as join8 } from "node:path";
3782
+ import { fileURLToPath } from "node:url";
3747
3783
 
3748
3784
  // src/usage/token.ts
3749
3785
  import { execFileSync } from "node:child_process";
@@ -3883,36 +3919,8 @@ function normalizeExtra(raw) {
3883
3919
  };
3884
3920
  }
3885
3921
 
3886
- // src/usage/storage.ts
3887
- import { appendFileSync, mkdirSync, readFileSync as readFileSync2, existsSync } from "node:fs";
3888
- import { dirname } from "node:path";
3889
- function appendSnapshot(filePath, snapshot) {
3890
- mkdirSync(dirname(filePath), { recursive: true });
3891
- appendFileSync(filePath, JSON.stringify(snapshot) + "\n", "utf8");
3892
- }
3893
- function readSnapshots(filePath) {
3894
- if (!existsSync(filePath)) return [];
3895
- const content = readFileSync2(filePath, "utf8");
3896
- const snapshots = [];
3897
- for (const line of content.split("\n")) {
3898
- if (!line.trim()) continue;
3899
- try {
3900
- snapshots.push(JSON.parse(line));
3901
- } catch {
3902
- }
3903
- }
3904
- return snapshots;
3905
- }
3906
- function latestClaudeCodeSnapshot(filePath) {
3907
- const all = readSnapshots(filePath);
3908
- for (let i = all.length - 1; i >= 0; i--) {
3909
- const s = all[i];
3910
- if (!s.agent || s.agent === "claude-code") return s;
3911
- }
3912
- return null;
3913
- }
3914
-
3915
3922
  // src/daemon-worker.ts
3923
+ init_storage();
3916
3924
  init_fs();
3917
3925
 
3918
3926
  // src/usage/backoff.ts
@@ -3945,8 +3953,10 @@ function writeTeamConfig(config, dir) {
3945
3953
 
3946
3954
  // src/team/push.ts
3947
3955
  init_dist();
3956
+ init_storage();
3948
3957
  import { randomUUID } from "node:crypto";
3949
3958
  var SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
3959
+ var POST_TIMEOUT_MS = 15e3;
3950
3960
  function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
3951
3961
  const raw = latestClaudeCodeSnapshot(filePath);
3952
3962
  if (!raw) return null;
@@ -4005,6 +4015,7 @@ function buildIngestPayload(rollup, usageSnapshot, planTier, cyclePeaks) {
4005
4015
  async function pushToTeamServer(config, payload) {
4006
4016
  const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
4007
4017
  method: "POST",
4018
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS),
4008
4019
  headers: {
4009
4020
  "Content-Type": "application/json",
4010
4021
  Authorization: `Bearer ${config.bearerToken}`
@@ -4066,6 +4077,9 @@ function pruneOldest(queuePath) {
4066
4077
  writeFileSync2(queuePath, keep.join("\n") + "\n", "utf8");
4067
4078
  }
4068
4079
 
4080
+ // src/team/backfill.ts
4081
+ init_storage();
4082
+
4069
4083
  // src/usage/profile.ts
4070
4084
  import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
4071
4085
  import { dirname as dirname3 } from "node:path";
@@ -4141,36 +4155,184 @@ async function getPlanTier(cachePath, nowMs = Date.now(), fetcher = fetchProfile
4141
4155
  return fresh.planTier;
4142
4156
  }
4143
4157
 
4144
- // src/team/sync.ts
4158
+ // src/team/backfill.ts
4145
4159
  init_fs();
4146
4160
  var USAGE_LOG = cclensPath("usage.jsonl");
4147
4161
  var PROFILE_CACHE = cclensPath("profile.json");
4162
+ var BATCH_SIZE = 500;
4163
+ var POST_TIMEOUT_MS2 = 15e3;
4148
4164
  var noopLog = () => {
4149
4165
  };
4150
- async function runTeamSync(log3 = noopLog, configOverride) {
4166
+ function rawToWire(raw) {
4167
+ const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
4168
+ return {
4169
+ capturedAt: raw.captured_at,
4170
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
4171
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
4172
+ sevenDayOpus: win(raw.seven_day_opus),
4173
+ sevenDaySonnet: win(raw.seven_day_sonnet),
4174
+ sevenDayOauthApps: win(raw.seven_day_oauth_apps),
4175
+ sevenDayCowork: win(raw.seven_day_cowork),
4176
+ extraUsage: raw.extra_usage ? {
4177
+ isEnabled: raw.extra_usage.is_enabled,
4178
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
4179
+ usedCreditsUsd: raw.extra_usage.used_credits,
4180
+ utilization: raw.extra_usage.utilization
4181
+ } : null
4182
+ };
4183
+ }
4184
+ function chunk(items, size) {
4185
+ if (size <= 0) return [items];
4186
+ const out = [];
4187
+ for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
4188
+ return out;
4189
+ }
4190
+ async function postBatch(config, snapshots, planTier) {
4191
+ const res = await fetch(`${config.serverUrl}/api/ingest/usage-history`, {
4192
+ method: "POST",
4193
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS2),
4194
+ headers: {
4195
+ "Content-Type": "application/json",
4196
+ Authorization: `Bearer ${config.bearerToken}`
4197
+ },
4198
+ body: JSON.stringify({ snapshots, ...planTier ? { planTier } : {} })
4199
+ });
4200
+ if (!res.ok) {
4201
+ return { inserted: 0, skipped: snapshots.length, status: res.status };
4202
+ }
4203
+ const body = await res.json().catch(() => ({}));
4204
+ return {
4205
+ inserted: body.inserted ?? 0,
4206
+ skipped: body.skipped ?? 0,
4207
+ status: res.status
4208
+ };
4209
+ }
4210
+ function isAfterSince(snapshot, sinceCapturedAt) {
4211
+ if (!sinceCapturedAt) return true;
4212
+ const sinceMs = Date.parse(sinceCapturedAt);
4213
+ if (Number.isNaN(sinceMs)) return true;
4214
+ const capturedMs = Date.parse(snapshot.captured_at);
4215
+ return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
4216
+ }
4217
+ function capturedAtMs(snapshot) {
4218
+ const ms = Date.parse(snapshot.captured_at);
4219
+ return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
4220
+ }
4221
+ function latestCapturedAt(snapshots) {
4222
+ let latest = null;
4223
+ for (const s of snapshots) {
4224
+ const ms = Date.parse(s.capturedAt);
4225
+ if (Number.isNaN(ms)) continue;
4226
+ if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
4227
+ }
4228
+ return latest?.at;
4229
+ }
4230
+ async function runTeamBackfill(log3 = noopLog, filePath = USAGE_LOG, configOverride, options = {}) {
4231
+ const config = configOverride === void 0 ? readTeamConfig() : configOverride;
4232
+ if (!config) {
4233
+ return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
4234
+ }
4235
+ const raw = readSnapshots(filePath);
4236
+ const claudeOnly = raw.filter(
4237
+ (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
4238
+ ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
4239
+ if (claudeOnly.length === 0) {
4240
+ log3("info", "team backfill: no usage snapshots to send");
4241
+ return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
4242
+ }
4243
+ const wire = claudeOnly.map(rawToWire);
4244
+ const batches = chunk(wire, BATCH_SIZE);
4245
+ const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
4246
+ let inserted = 0;
4247
+ let skipped = 0;
4248
+ let lastSnapshotAt;
4249
+ for (let i = 0; i < batches.length; i++) {
4250
+ const batch = batches[i];
4251
+ try {
4252
+ const result = await postBatch(config, batch, planTier);
4253
+ if (result.status >= 400) {
4254
+ log3("warn", `team backfill: batch ${i + 1}/${batches.length} failed (${result.status})`);
4255
+ return {
4256
+ paired: true,
4257
+ sentSnapshots: wire.length,
4258
+ insertedSnapshots: inserted,
4259
+ skippedSnapshots: skipped,
4260
+ batches: i,
4261
+ lastSnapshotAt,
4262
+ error: `HTTP ${result.status}`
4263
+ };
4264
+ }
4265
+ inserted += result.inserted;
4266
+ skipped += result.skipped;
4267
+ lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
4268
+ } catch (err) {
4269
+ log3("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
4270
+ return {
4271
+ paired: true,
4272
+ sentSnapshots: wire.length,
4273
+ insertedSnapshots: inserted,
4274
+ skippedSnapshots: skipped,
4275
+ batches: i,
4276
+ lastSnapshotAt,
4277
+ error: err.message
4278
+ };
4279
+ }
4280
+ }
4281
+ log3(
4282
+ "info",
4283
+ `team backfill: ${inserted} new, ${skipped} already-known across ${batches.length} batch${batches.length === 1 ? "" : "es"}`
4284
+ );
4285
+ return {
4286
+ paired: true,
4287
+ sentSnapshots: wire.length,
4288
+ insertedSnapshots: inserted,
4289
+ skippedSnapshots: skipped,
4290
+ batches: batches.length,
4291
+ lastSnapshotAt
4292
+ };
4293
+ }
4294
+
4295
+ // src/team/sync.ts
4296
+ init_fs();
4297
+ var USAGE_LOG2 = cclensPath("usage.jsonl");
4298
+ var PROFILE_CACHE2 = cclensPath("profile.json");
4299
+ var noopLog2 = () => {
4300
+ };
4301
+ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
4151
4302
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
4152
4303
  if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
4153
4304
  try {
4154
4305
  const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
4155
4306
  const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
4156
4307
  const today = toLocalDay3(Date.now());
4308
+ let nextConfig = { ...config };
4309
+ const persistConfig = (patch) => {
4310
+ nextConfig = { ...nextConfig, ...patch };
4311
+ writeTeamConfig(nextConfig);
4312
+ };
4313
+ const usageBackfill = await runTeamBackfill(log3, USAGE_LOG2, config, {
4314
+ sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
4315
+ });
4316
+ if (usageBackfill.lastSnapshotAt) {
4317
+ persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
4318
+ }
4157
4319
  const sessions = await listSessions2({ limit: 1e4 });
4158
4320
  const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
4159
- const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG) ?? void 0;
4160
- const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
4321
+ const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG2) ?? void 0;
4322
+ const planTier = await getPlanTier(PROFILE_CACHE2).catch(() => null) ?? void 0;
4161
4323
  const cyclePeaks = await buildCyclePeaksForPush(planTier, loadCalibrationCurve2);
4162
4324
  if (rollups.length === 0) {
4163
4325
  const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
4164
4326
  if (!hasLiveData) {
4165
- log3("info", "team push: nothing to sync");
4166
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0 };
4327
+ if (usageBackfill.sentSnapshots === 0) log3("info", "team push: nothing to sync");
4328
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
4167
4329
  }
4168
4330
  const payload = buildIngestPayload(void 0, usageSnapshot, planTier, cyclePeaks);
4169
4331
  const result = await pushToTeamServer(config, payload);
4170
4332
  if (!result.ok) {
4171
4333
  log3("warn", `team push (live-only) failed (${result.status}); queueing`);
4172
4334
  enqueuePayload(payload);
4173
- return { paired: true, pushed: 0, queued: 1, queuedDrained: 0 };
4335
+ return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
4174
4336
  }
4175
4337
  let queuedDrained2 = 0;
4176
4338
  const backlog = dequeuePayloads();
@@ -4183,11 +4345,12 @@ async function runTeamSync(log3 = noopLog, configOverride) {
4183
4345
  queuedDrained2++;
4184
4346
  }
4185
4347
  log3("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
4186
- return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2 };
4348
+ return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
4187
4349
  }
4188
4350
  let pushed = 0;
4189
4351
  let queued = 0;
4190
4352
  let failedDay;
4353
+ let lastPushedDay;
4191
4354
  for (let i = 0; i < rollups.length; i++) {
4192
4355
  const rollup = rollups[i];
4193
4356
  const isLatest = i === rollups.length - 1;
@@ -4208,9 +4371,13 @@ async function runTeamSync(log3 = noopLog, configOverride) {
4208
4371
  break;
4209
4372
  }
4210
4373
  pushed++;
4374
+ lastPushedDay = rollup.day;
4375
+ }
4376
+ if (failedDay) {
4377
+ if (lastPushedDay) persistConfig({ lastSyncedDay: lastPushedDay });
4378
+ } else {
4379
+ persistConfig({ lastSyncedDay: today });
4211
4380
  }
4212
- const nextConfig = { ...config, lastSyncedDay: today };
4213
- if (pushed > 0 || !failedDay) writeTeamConfig(nextConfig);
4214
4381
  let queuedDrained = 0;
4215
4382
  if (!failedDay) {
4216
4383
  const backlog = dequeuePayloads();
@@ -4226,7 +4393,7 @@ async function runTeamSync(log3 = noopLog, configOverride) {
4226
4393
  if (pushed > 0) {
4227
4394
  log3("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
4228
4395
  }
4229
- return { paired: true, pushed, queued, queuedDrained, failedDay };
4396
+ return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
4230
4397
  } catch (err) {
4231
4398
  const message = err.message;
4232
4399
  log3("warn", `team push error: ${message}`);
@@ -9671,10 +9838,10 @@ exec ${shellEscape(claudeBin)} \\
9671
9838
  "$USR"
9672
9839
  `, { mode: 493 });
9673
9840
  const existingBefore = snapshotAllTranscripts();
9674
- const spawn2 = spawnSync(tmuxBin, ["new-session", "-d", "-s", sessionName, "-x", "140", "-y", "50", "-c", cwd, wrapper], { encoding: "utf8" });
9675
- if (spawn2.status !== 0) {
9841
+ const spawn3 = spawnSync(tmuxBin, ["new-session", "-d", "-s", sessionName, "-x", "140", "-y", "50", "-c", cwd, wrapper], { encoding: "utf8" });
9842
+ if (spawn3.status !== 0) {
9676
9843
  cleanup(cwd, runId, tmuxBin, sessionName);
9677
- throw new TmuxRunError(`tmux new-session failed: ${spawn2.stderr.trim()}`);
9844
+ throw new TmuxRunError(`tmux new-session failed: ${spawn3.stderr.trim()}`);
9678
9845
  }
9679
9846
  const onAbort = () => {
9680
9847
  try {
@@ -9701,10 +9868,10 @@ exec ${shellEscape(claudeBin)} \\
9701
9868
  }
9702
9869
  }
9703
9870
  if (args.onDelta && assembled.length > lastEmittedLength) {
9704
- const chunk = assembled.slice(lastEmittedLength);
9871
+ const chunk2 = assembled.slice(lastEmittedLength);
9705
9872
  lastEmittedLength = assembled.length;
9706
9873
  try {
9707
- args.onDelta(chunk);
9874
+ args.onDelta(chunk2);
9708
9875
  } catch {
9709
9876
  }
9710
9877
  }
@@ -10068,8 +10235,8 @@ ${args.reminder}` : args.userPrompt;
10068
10235
  let buffer = "", inputTokens = 0, outputTokens = 0, modelUsed = args.model, stderr = "";
10069
10236
  const startMs = Date.now();
10070
10237
  let lastReportedKb = -1;
10071
- proc.stdout.on("data", (chunk) => {
10072
- for (const line of chunk.toString("utf8").split("\n")) {
10238
+ proc.stdout.on("data", (chunk2) => {
10239
+ for (const line of chunk2.toString("utf8").split("\n")) {
10073
10240
  const t = line.trim();
10074
10241
  if (!t)
10075
10242
  continue;
@@ -13179,11 +13346,59 @@ async function backfillYesterdayDigest(opts = {}) {
13179
13346
  return { fired: true, reason: "ok", key: yesterday };
13180
13347
  }
13181
13348
 
13349
+ // src/updater.ts
13350
+ var PACKAGE_NAME = "fleetlens";
13351
+ var CHECK_TIMEOUT_MS = 3e3;
13352
+ function shouldUpdate(local, remote) {
13353
+ const l = local.split(".").map(Number);
13354
+ const r = remote.split(".").map(Number);
13355
+ for (let i = 0; i < 3; i++) {
13356
+ if ((r[i] ?? 0) > (l[i] ?? 0)) return true;
13357
+ if ((r[i] ?? 0) < (l[i] ?? 0)) return false;
13358
+ }
13359
+ return false;
13360
+ }
13361
+ async function fetchLatestVersion() {
13362
+ try {
13363
+ const controller = new AbortController();
13364
+ const timer = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
13365
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
13366
+ signal: controller.signal
13367
+ });
13368
+ clearTimeout(timer);
13369
+ if (!res.ok) return null;
13370
+ const data = await res.json();
13371
+ return data.version ?? null;
13372
+ } catch {
13373
+ return null;
13374
+ }
13375
+ }
13376
+ function isDevMode() {
13377
+ const script = process.argv[1] ?? "";
13378
+ return script.includes("packages/cli/") || script.includes("packages\\cli\\");
13379
+ }
13380
+ async function getUpdateAvailability() {
13381
+ if (isDevMode()) return null;
13382
+ const latest = await fetchLatestVersion();
13383
+ if (latest === null) return {
13384
+ current: "0.10.3",
13385
+ latest: null,
13386
+ updateAvailable: false
13387
+ };
13388
+ return {
13389
+ current: "0.10.3",
13390
+ latest,
13391
+ updateAvailable: shouldUpdate("0.10.3", latest)
13392
+ };
13393
+ }
13394
+
13182
13395
  // src/daemon-worker.ts
13183
- var USAGE_LOG2 = cclensPath("usage.jsonl");
13396
+ var USAGE_LOG3 = cclensPath("usage.jsonl");
13184
13397
  var DAEMON_LOG = cclensPath("daemon.log");
13398
+ var UPDATE_STATE = cclensPath("daemon-update.json");
13185
13399
  var WATCHDOG_INTERVAL_MS = 5 * 1e3;
13186
- mkdirSync12(dirname8(USAGE_LOG2), { recursive: true });
13400
+ var AUTO_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
13401
+ mkdirSync12(dirname8(USAGE_LOG3), { recursive: true });
13187
13402
  function log2(level, message) {
13188
13403
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${message}
13189
13404
  `;
@@ -13193,8 +13408,10 @@ function log2(level, message) {
13193
13408
  }
13194
13409
  }
13195
13410
  var nextPollAtMs = 0;
13411
+ var nextTeamSyncAtMs = 0;
13196
13412
  var currentIntervalMs = BASE_INTERVAL_MS;
13197
13413
  var waitingForRefresh = false;
13414
+ var updateCheckInFlight = false;
13198
13415
  var PERCEPTION_INTERVAL_MS = 5 * 60 * 1e3;
13199
13416
  var perceptionInFlight = false;
13200
13417
  var backfillAttempted = false;
@@ -13221,6 +13438,96 @@ var perceptionHandle = setInterval(async () => {
13221
13438
  perceptionInFlight = false;
13222
13439
  }
13223
13440
  }, PERCEPTION_INTERVAL_MS);
13441
+ function readUpdateState() {
13442
+ try {
13443
+ return JSON.parse(readFileSync14(UPDATE_STATE, "utf8"));
13444
+ } catch {
13445
+ return null;
13446
+ }
13447
+ }
13448
+ function writeUpdateState(state) {
13449
+ try {
13450
+ writeFileSync11(UPDATE_STATE, JSON.stringify(state, null, 2), { mode: 384 });
13451
+ } catch {
13452
+ }
13453
+ }
13454
+ function nextUpdateCheckFromState(nowMs) {
13455
+ const lastCheckedAt = readUpdateState()?.lastCheckedAt;
13456
+ if (!lastCheckedAt) return 0;
13457
+ const lastMs = Date.parse(lastCheckedAt);
13458
+ if (Number.isNaN(lastMs)) return 0;
13459
+ return nowMs - lastMs >= AUTO_UPDATE_INTERVAL_MS ? 0 : lastMs + AUTO_UPDATE_INTERVAL_MS;
13460
+ }
13461
+ var nextUpdateCheckAtMs = nextUpdateCheckFromState(Date.now());
13462
+ function cliEntryPath() {
13463
+ return join8(dirname8(fileURLToPath(import.meta.url)), "index.js");
13464
+ }
13465
+ async function runDaemonUpdateCheck() {
13466
+ if (process.env.FLEETLENS_DAEMON_AUTO_UPDATE === "0") {
13467
+ nextUpdateCheckAtMs = Date.now() + AUTO_UPDATE_INTERVAL_MS;
13468
+ return;
13469
+ }
13470
+ if (updateCheckInFlight) return;
13471
+ updateCheckInFlight = true;
13472
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
13473
+ try {
13474
+ const availability = await getUpdateAvailability();
13475
+ if (!availability) return;
13476
+ const state = {
13477
+ lastCheckedAt: checkedAt,
13478
+ current: availability.current,
13479
+ latest: availability.latest,
13480
+ updateAvailable: availability.updateAvailable
13481
+ };
13482
+ writeUpdateState(state);
13483
+ if (!availability.latest) {
13484
+ log2("warn", "update check skipped: npm registry unreachable");
13485
+ return;
13486
+ }
13487
+ if (!availability.updateAvailable) {
13488
+ log2("info", `update check ok: ${availability.current} is current`);
13489
+ return;
13490
+ }
13491
+ const entry = cliEntryPath();
13492
+ if (!existsSync11(entry)) {
13493
+ log2("warn", `update check found ${availability.latest}, but CLI entry is missing at ${entry}`);
13494
+ return;
13495
+ }
13496
+ let logFd = "ignore";
13497
+ try {
13498
+ logFd = openSync(DAEMON_LOG, "a");
13499
+ } catch {
13500
+ }
13501
+ const child = spawn2(process.execPath, [entry, "update"], {
13502
+ detached: true,
13503
+ stdio: ["ignore", logFd, logFd],
13504
+ env: { ...process.env, FLEETLENS_UPDATE_SOURCE: "daemon" }
13505
+ });
13506
+ if (typeof logFd === "number") {
13507
+ child.on("exit", () => {
13508
+ try {
13509
+ closeSync(logFd);
13510
+ } catch {
13511
+ }
13512
+ });
13513
+ }
13514
+ child.unref();
13515
+ writeUpdateState({
13516
+ ...state,
13517
+ updateSpawnedAt: (/* @__PURE__ */ new Date()).toISOString()
13518
+ });
13519
+ log2(
13520
+ "info",
13521
+ `update check found ${availability.latest}; spawned updater pid=${child.pid ?? "unknown"}`
13522
+ );
13523
+ } catch (err) {
13524
+ writeUpdateState({ lastCheckedAt: checkedAt, error: err.message });
13525
+ log2("warn", `update check failed: ${err.message}`);
13526
+ } finally {
13527
+ updateCheckInFlight = false;
13528
+ nextUpdateCheckAtMs = Date.now() + AUTO_UPDATE_INTERVAL_MS;
13529
+ }
13530
+ }
13224
13531
  async function tick() {
13225
13532
  for (const source of agentSources) {
13226
13533
  if (source.kind === "claude-code") continue;
@@ -13236,7 +13543,7 @@ async function tick() {
13236
13543
  seven_day_cowork: null,
13237
13544
  extra_usage: null
13238
13545
  };
13239
- appendSnapshot(USAGE_LOG2, snapshot);
13546
+ appendSnapshot(USAGE_LOG3, snapshot);
13240
13547
  log2(
13241
13548
  "info",
13242
13549
  `${source.kind} snapshot 5h=${snapshot.five_hour.utilization}% 7d=${snapshot.seven_day.utilization}%`
@@ -13247,7 +13554,7 @@ async function tick() {
13247
13554
  }
13248
13555
  try {
13249
13556
  const snapshot = await fetchUsage();
13250
- appendSnapshot(USAGE_LOG2, snapshot);
13557
+ appendSnapshot(USAGE_LOG3, snapshot);
13251
13558
  log2(
13252
13559
  "info",
13253
13560
  `claude-code snapshot 5h=${snapshot.five_hour.utilization}% 7d=${snapshot.seven_day.utilization}%`
@@ -13304,7 +13611,14 @@ async function runLoop() {
13304
13611
  const outcome = await tick();
13305
13612
  scheduleAfter(Date.now(), outcome);
13306
13613
  }
13614
+ }
13615
+ const teamNow = Date.now();
13616
+ if (teamNow >= nextTeamSyncAtMs) {
13307
13617
  await runTeamSync(log2);
13618
+ nextTeamSyncAtMs = Date.now() + BASE_INTERVAL_MS;
13619
+ }
13620
+ if (Date.now() >= nextUpdateCheckAtMs) {
13621
+ await runDaemonUpdateCheck();
13308
13622
  }
13309
13623
  await sleep2(WATCHDOG_INTERVAL_MS);
13310
13624
  }