fleetlens 0.10.6 → 0.11.0

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 (45) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/build-manifest.json +3 -3
  3. package/app/apps/web/.next/prerender-manifest.json +3 -3
  4. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  5. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  6. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  13. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  14. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  15. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  18. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  19. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  20. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  21. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  22. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  23. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  24. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  25. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  26. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  27. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  28. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__100.ck7._.js +1 -1
  29. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  30. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_0af94gl._.js +1 -1
  31. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  32. package/app/apps/web/.next/server/pages/500.html +1 -1
  33. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  34. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  35. package/app/apps/web/.next/static/chunks/10wg31hnd6phg.js +1 -0
  36. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  37. package/app/apps/web/package.json +1 -1
  38. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  39. package/dist/daemon-worker.js +117 -5
  40. package/dist/index.js +125 -8
  41. package/package.json +1 -1
  42. package/app/apps/web/.next/static/chunks/0-9zfeumkln-p.js +0 -1
  43. /package/app/apps/web/.next/static/{VquuqMXKjRSN6LfR7rRSz → WHoyEWNo3d5LkDnYSmiG4}/_buildManifest.js +0 -0
  44. /package/app/apps/web/.next/static/{VquuqMXKjRSN6LfR7rRSz → WHoyEWNo3d5LkDnYSmiG4}/_clientMiddlewareManifest.js +0 -0
  45. /package/app/apps/web/.next/static/{VquuqMXKjRSN6LfR7rRSz → WHoyEWNo3d5LkDnYSmiG4}/_ssgManifest.js +0 -0
@@ -3976,6 +3976,9 @@ function nextIntervalMs(current, outcome) {
3976
3976
  return Math.min(Math.max(current, BASE_INTERVAL_MS) * 2, MAX_INTERVAL_MS);
3977
3977
  }
3978
3978
 
3979
+ // src/team/sync.ts
3980
+ import { randomUUID as randomUUID3 } from "node:crypto";
3981
+
3979
3982
  // src/team/config.ts
3980
3983
  init_fs();
3981
3984
 
@@ -4340,8 +4343,74 @@ function writeLastPushFailure(payload, error, dir) {
4340
4343
  write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: false, payload, error }, dir);
4341
4344
  }
4342
4345
 
4346
+ // src/team/commands.ts
4347
+ async function dispatchCommand(command, config, log3) {
4348
+ switch (command.type) {
4349
+ case "backfill-activity":
4350
+ return runActivityBackfill(command, config, log3);
4351
+ default:
4352
+ return {
4353
+ id: command.id,
4354
+ ok: false,
4355
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
4356
+ error: `Unknown command type: ${command.type}`
4357
+ };
4358
+ }
4359
+ }
4360
+ async function runActivityBackfill(command, config, log3) {
4361
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
4362
+ const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
4363
+ const days = command.params.days;
4364
+ const todayMs = Date.now();
4365
+ const targetDayMs = todayMs - (days - 1) * 24 * 60 * 60 * 1e3;
4366
+ const targetDay = toLocalDay3(targetDayMs);
4367
+ log3("info", `command ${command.id}: backfill-activity from ${targetDay} (${days} days)`);
4368
+ let sessions;
4369
+ try {
4370
+ sessions = await listSessions2({ limit: 1e4 });
4371
+ } catch (err) {
4372
+ return {
4373
+ id: command.id,
4374
+ ok: false,
4375
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
4376
+ error: `Failed to read sessions: ${err.message}`
4377
+ };
4378
+ }
4379
+ const rollups = buildRollupsForRange(sessions, targetDay);
4380
+ if (rollups.length === 0) {
4381
+ return {
4382
+ id: command.id,
4383
+ ok: true,
4384
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
4385
+ summary: { pushed: 0, fromDay: targetDay }
4386
+ };
4387
+ }
4388
+ let pushed = 0;
4389
+ for (const rollup of rollups) {
4390
+ const payload = buildIngestPayload(rollup);
4391
+ const result = await pushToTeamServer(config, payload);
4392
+ if (!result.ok) {
4393
+ return {
4394
+ id: command.id,
4395
+ ok: false,
4396
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
4397
+ error: `Push failed on ${rollup.day} (HTTP ${result.status}); pushed ${pushed}/${rollups.length} before failing`
4398
+ };
4399
+ }
4400
+ pushed++;
4401
+ }
4402
+ log3("info", `command ${command.id}: backfill-activity ok \u2014 pushed ${pushed} day${pushed === 1 ? "" : "s"}`);
4403
+ return {
4404
+ id: command.id,
4405
+ ok: true,
4406
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
4407
+ summary: { pushed, fromDay: targetDay }
4408
+ };
4409
+ }
4410
+
4343
4411
  // src/team/sync.ts
4344
4412
  init_fs();
4413
+ var inFlightCommands = /* @__PURE__ */ new Set();
4345
4414
  var USAGE_LOG2 = cclensPath("usage.jsonl");
4346
4415
  var PROFILE_CACHE2 = cclensPath("profile.json");
4347
4416
  var noopLog2 = () => {
@@ -4358,6 +4427,43 @@ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
4358
4427
  nextConfig = { ...nextConfig, ...patch };
4359
4428
  writeTeamConfig(nextConfig);
4360
4429
  };
4430
+ const collectedCommands = /* @__PURE__ */ new Map();
4431
+ const collectCommands = (commands) => {
4432
+ if (!commands) return;
4433
+ for (const cmd of commands) {
4434
+ if (!collectedCommands.has(cmd.id)) collectedCommands.set(cmd.id, cmd);
4435
+ }
4436
+ };
4437
+ const dispatchAndReport = async () => {
4438
+ try {
4439
+ if (collectedCommands.size === 0) return;
4440
+ const results = [];
4441
+ for (const cmd of collectedCommands.values()) {
4442
+ if (inFlightCommands.has(cmd.id)) continue;
4443
+ inFlightCommands.add(cmd.id);
4444
+ try {
4445
+ const result = await dispatchCommand(cmd, config, log3);
4446
+ results.push(result);
4447
+ } finally {
4448
+ inFlightCommands.delete(cmd.id);
4449
+ }
4450
+ }
4451
+ if (results.length === 0) return;
4452
+ const resultsPayload = {
4453
+ ingestId: randomUUID3(),
4454
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
4455
+ commandResults: results
4456
+ };
4457
+ const r = await pushToTeamServer(config, resultsPayload);
4458
+ if (!r.ok) {
4459
+ log3("warn", `team commandResults push failed (${r.status}); will retry on next sync`);
4460
+ } else {
4461
+ log3("info", `team commandResults push ok: ${results.length} result${results.length === 1 ? "" : "s"}`);
4462
+ }
4463
+ } catch (err) {
4464
+ log3("warn", `team command dispatch error: ${err.message}`);
4465
+ }
4466
+ };
4361
4467
  const usageBackfill = await runTeamBackfill(log3, USAGE_LOG2, config, {
4362
4468
  sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
4363
4469
  });
@@ -4385,6 +4491,7 @@ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
4385
4491
  return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
4386
4492
  }
4387
4493
  writeLastPushSuccess(payload);
4494
+ collectCommands(result.body?.commands);
4388
4495
  let queuedDrained2 = 0;
4389
4496
  const backlog = dequeuePayloads();
4390
4497
  for (let i = 0; i < backlog.length; i++) {
@@ -4393,9 +4500,11 @@ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
4393
4500
  for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
4394
4501
  break;
4395
4502
  }
4503
+ collectCommands(qResult.body?.commands);
4396
4504
  queuedDrained2++;
4397
4505
  }
4398
4506
  log3("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
4507
+ await dispatchAndReport();
4399
4508
  return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
4400
4509
  }
4401
4510
  let pushed = 0;
@@ -4424,6 +4533,7 @@ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
4424
4533
  break;
4425
4534
  }
4426
4535
  writeLastPushSuccess(payload);
4536
+ collectCommands(result.body?.commands);
4427
4537
  pushed++;
4428
4538
  lastPushedDay = rollup.day;
4429
4539
  }
@@ -4441,12 +4551,14 @@ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
4441
4551
  for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
4442
4552
  break;
4443
4553
  }
4554
+ collectCommands(qResult.body?.commands);
4444
4555
  queuedDrained++;
4445
4556
  }
4446
4557
  }
4447
4558
  if (pushed > 0) {
4448
4559
  log3("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
4449
4560
  }
4561
+ await dispatchAndReport();
4450
4562
  return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
4451
4563
  } catch (err) {
4452
4564
  const message = err.message;
@@ -9790,7 +9902,7 @@ init_fs();
9790
9902
  import { spawn } from "node:child_process";
9791
9903
  import { mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, writeFileSync as writeFileSync9, existsSync as existsSync7 } from "node:fs";
9792
9904
  import { join as join8 } from "node:path";
9793
- import { randomUUID as randomUUID3 } from "node:crypto";
9905
+ import { randomUUID as randomUUID4 } from "node:crypto";
9794
9906
 
9795
9907
  // ../entries/dist/tmux-runner.js
9796
9908
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, existsSync as existsSync6, readdirSync as readdirSync4, realpathSync, readFileSync as readFileSync10, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
@@ -10141,7 +10253,7 @@ function detectKindFromSystemPrompt(sp) {
10141
10253
  }
10142
10254
  function newRunId(kind) {
10143
10255
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
10144
- const short = randomUUID3().slice(0, 8);
10256
+ const short = randomUUID4().slice(0, 8);
10145
10257
  return `${ts}_${kind}_${short}`;
10146
10258
  }
10147
10259
  function tracePath(runId) {
@@ -13446,14 +13558,14 @@ async function getUpdateAvailability() {
13446
13558
  if (isDevMode()) return null;
13447
13559
  const latest = await fetchLatestVersion();
13448
13560
  if (latest === null) return {
13449
- current: "0.10.6",
13561
+ current: "0.11.0",
13450
13562
  latest: null,
13451
13563
  updateAvailable: false
13452
13564
  };
13453
13565
  return {
13454
- current: "0.10.6",
13566
+ current: "0.11.0",
13455
13567
  latest,
13456
- updateAvailable: shouldUpdate("0.10.6", latest)
13568
+ updateAvailable: shouldUpdate("0.11.0", latest)
13457
13569
  };
13458
13570
  }
13459
13571
 
package/dist/index.js CHANGED
@@ -3765,9 +3765,9 @@ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath
3765
3765
  console.warn(
3766
3766
  ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
3767
3767
  );
3768
- } else if (verify.installedVersion !== "0.10.6") {
3768
+ } else if (verify.installedVersion !== "0.11.0") {
3769
3769
  console.log(
3770
- ` \u2192 This process is still running ${"0.10.6"}. Next invocation will use ${verify.installedVersion}.`
3770
+ ` \u2192 This process is still running ${"0.11.0"}. Next invocation will use ${verify.installedVersion}.`
3771
3771
  );
3772
3772
  }
3773
3773
  }
@@ -3802,14 +3802,14 @@ async function getUpdateAvailability() {
3802
3802
  if (isDevMode()) return null;
3803
3803
  const latest = await fetchLatestVersion();
3804
3804
  if (latest === null) return {
3805
- current: "0.10.6",
3805
+ current: "0.11.0",
3806
3806
  latest: null,
3807
3807
  updateAvailable: false
3808
3808
  };
3809
3809
  return {
3810
- current: "0.10.6",
3810
+ current: "0.11.0",
3811
3811
  latest,
3812
- updateAvailable: shouldUpdate("0.10.6", latest)
3812
+ updateAvailable: shouldUpdate("0.11.0", latest)
3813
3813
  };
3814
3814
  }
3815
3815
  async function checkForUpdate() {
@@ -3899,7 +3899,7 @@ async function restartServices(state) {
3899
3899
  }
3900
3900
  async function forceUpdate() {
3901
3901
  const latest = await fetchLatestVersion();
3902
- const current = "0.10.6";
3902
+ const current = "0.11.0";
3903
3903
  if (latest === null) {
3904
3904
  console.error("Could not reach npm registry. Check your network.");
3905
3905
  process.exit(1);
@@ -15059,11 +15059,83 @@ var init_last_push = __esm({
15059
15059
  }
15060
15060
  });
15061
15061
 
15062
+ // src/team/commands.ts
15063
+ async function dispatchCommand(command2, config, log) {
15064
+ switch (command2.type) {
15065
+ case "backfill-activity":
15066
+ return runActivityBackfill(command2, config, log);
15067
+ default:
15068
+ return {
15069
+ id: command2.id,
15070
+ ok: false,
15071
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15072
+ error: `Unknown command type: ${command2.type}`
15073
+ };
15074
+ }
15075
+ }
15076
+ async function runActivityBackfill(command2, config, log) {
15077
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
15078
+ const { toLocalDay: toLocalDay4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
15079
+ const days = command2.params.days;
15080
+ const todayMs = Date.now();
15081
+ const targetDayMs = todayMs - (days - 1) * 24 * 60 * 60 * 1e3;
15082
+ const targetDay = toLocalDay4(targetDayMs);
15083
+ log("info", `command ${command2.id}: backfill-activity from ${targetDay} (${days} days)`);
15084
+ let sessions;
15085
+ try {
15086
+ sessions = await listSessions2({ limit: 1e4 });
15087
+ } catch (err) {
15088
+ return {
15089
+ id: command2.id,
15090
+ ok: false,
15091
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15092
+ error: `Failed to read sessions: ${err.message}`
15093
+ };
15094
+ }
15095
+ const rollups = buildRollupsForRange(sessions, targetDay);
15096
+ if (rollups.length === 0) {
15097
+ return {
15098
+ id: command2.id,
15099
+ ok: true,
15100
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15101
+ summary: { pushed: 0, fromDay: targetDay }
15102
+ };
15103
+ }
15104
+ let pushed = 0;
15105
+ for (const rollup of rollups) {
15106
+ const payload = buildIngestPayload(rollup);
15107
+ const result = await pushToTeamServer(config, payload);
15108
+ if (!result.ok) {
15109
+ return {
15110
+ id: command2.id,
15111
+ ok: false,
15112
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15113
+ error: `Push failed on ${rollup.day} (HTTP ${result.status}); pushed ${pushed}/${rollups.length} before failing`
15114
+ };
15115
+ }
15116
+ pushed++;
15117
+ }
15118
+ log("info", `command ${command2.id}: backfill-activity ok \u2014 pushed ${pushed} day${pushed === 1 ? "" : "s"}`);
15119
+ return {
15120
+ id: command2.id,
15121
+ ok: true,
15122
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15123
+ summary: { pushed, fromDay: targetDay }
15124
+ };
15125
+ }
15126
+ var init_commands = __esm({
15127
+ "src/team/commands.ts"() {
15128
+ "use strict";
15129
+ init_push();
15130
+ }
15131
+ });
15132
+
15062
15133
  // src/team/sync.ts
15063
15134
  var sync_exports = {};
15064
15135
  __export(sync_exports, {
15065
15136
  runTeamSync: () => runTeamSync
15066
15137
  });
15138
+ import { randomUUID as randomUUID4 } from "node:crypto";
15067
15139
  async function runTeamSync(log = noopLog2, configOverride, options = {}) {
15068
15140
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
15069
15141
  if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
@@ -15076,6 +15148,43 @@ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
15076
15148
  nextConfig = { ...nextConfig, ...patch };
15077
15149
  writeTeamConfig(nextConfig);
15078
15150
  };
15151
+ const collectedCommands = /* @__PURE__ */ new Map();
15152
+ const collectCommands = (commands) => {
15153
+ if (!commands) return;
15154
+ for (const cmd of commands) {
15155
+ if (!collectedCommands.has(cmd.id)) collectedCommands.set(cmd.id, cmd);
15156
+ }
15157
+ };
15158
+ const dispatchAndReport = async () => {
15159
+ try {
15160
+ if (collectedCommands.size === 0) return;
15161
+ const results = [];
15162
+ for (const cmd of collectedCommands.values()) {
15163
+ if (inFlightCommands.has(cmd.id)) continue;
15164
+ inFlightCommands.add(cmd.id);
15165
+ try {
15166
+ const result = await dispatchCommand(cmd, config, log);
15167
+ results.push(result);
15168
+ } finally {
15169
+ inFlightCommands.delete(cmd.id);
15170
+ }
15171
+ }
15172
+ if (results.length === 0) return;
15173
+ const resultsPayload = {
15174
+ ingestId: randomUUID4(),
15175
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
15176
+ commandResults: results
15177
+ };
15178
+ const r = await pushToTeamServer(config, resultsPayload);
15179
+ if (!r.ok) {
15180
+ log("warn", `team commandResults push failed (${r.status}); will retry on next sync`);
15181
+ } else {
15182
+ log("info", `team commandResults push ok: ${results.length} result${results.length === 1 ? "" : "s"}`);
15183
+ }
15184
+ } catch (err) {
15185
+ log("warn", `team command dispatch error: ${err.message}`);
15186
+ }
15187
+ };
15079
15188
  const usageBackfill = await runTeamBackfill(log, USAGE_LOG4, config, {
15080
15189
  sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
15081
15190
  });
@@ -15103,6 +15212,7 @@ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
15103
15212
  return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
15104
15213
  }
15105
15214
  writeLastPushSuccess(payload);
15215
+ collectCommands(result.body?.commands);
15106
15216
  let queuedDrained2 = 0;
15107
15217
  const backlog = dequeuePayloads();
15108
15218
  for (let i = 0; i < backlog.length; i++) {
@@ -15111,9 +15221,11 @@ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
15111
15221
  for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
15112
15222
  break;
15113
15223
  }
15224
+ collectCommands(qResult.body?.commands);
15114
15225
  queuedDrained2++;
15115
15226
  }
15116
15227
  log("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
15228
+ await dispatchAndReport();
15117
15229
  return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
15118
15230
  }
15119
15231
  let pushed = 0;
@@ -15142,6 +15254,7 @@ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
15142
15254
  break;
15143
15255
  }
15144
15256
  writeLastPushSuccess(payload);
15257
+ collectCommands(result.body?.commands);
15145
15258
  pushed++;
15146
15259
  lastPushedDay = rollup.day;
15147
15260
  }
@@ -15159,12 +15272,14 @@ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
15159
15272
  for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
15160
15273
  break;
15161
15274
  }
15275
+ collectCommands(qResult.body?.commands);
15162
15276
  queuedDrained++;
15163
15277
  }
15164
15278
  }
15165
15279
  if (pushed > 0) {
15166
15280
  log("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
15167
15281
  }
15282
+ await dispatchAndReport();
15168
15283
  return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
15169
15284
  } catch (err) {
15170
15285
  const message = err.message;
@@ -15236,7 +15351,7 @@ async function buildCyclePeaksForPush(planTier, loadCurve) {
15236
15351
  sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 12)
15237
15352
  };
15238
15353
  }
15239
- var USAGE_LOG4, PROFILE_CACHE2, noopLog2;
15354
+ var inFlightCommands, USAGE_LOG4, PROFILE_CACHE2, noopLog2;
15240
15355
  var init_sync = __esm({
15241
15356
  "src/team/sync.ts"() {
15242
15357
  "use strict";
@@ -15245,8 +15360,10 @@ var init_sync = __esm({
15245
15360
  init_queue2();
15246
15361
  init_backfill();
15247
15362
  init_last_push();
15363
+ init_commands();
15248
15364
  init_profile();
15249
15365
  init_fs();
15366
+ inFlightCommands = /* @__PURE__ */ new Set();
15250
15367
  USAGE_LOG4 = cclensPath("usage.jsonl");
15251
15368
  PROFILE_CACHE2 = cclensPath("profile.json");
15252
15369
  noopLog2 = () => {
@@ -15922,7 +16039,7 @@ async function main() {
15922
16039
  case "version":
15923
16040
  case "--version":
15924
16041
  case "-v":
15925
- console.log(`fleetlens ${"0.10.6"}`);
16042
+ console.log(`fleetlens ${"0.11.0"}`);
15926
16043
  break;
15927
16044
  case "help":
15928
16045
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetlens",
3
- "version": "0.10.6",
3
+ "version": "0.11.0",
4
4
  "description": "fleetlens — local-only dashboard and usage tracker for Claude Code sessions and agent fleets",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,98183,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r={assign:function(){return l},searchParamsToUrlQuery:function(){return o},urlQueryToSearchParams:function(){return i}};for(var a in r)Object.defineProperty(n,a,{enumerable:!0,get:r[a]});function o(e){let t={};for(let[n,r]of e.entries()){let e=t[n];void 0===e?t[n]=r:Array.isArray(e)?e.push(r):t[n]=[e,r]}return t}function s(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function i(e){let t=new URLSearchParams;for(let[n,r]of Object.entries(e))if(Array.isArray(r))for(let e of r)t.append(n,s(e));else t.set(n,s(r));return t}function l(e,...t){for(let n of t){for(let t of n.keys())e.delete(t);for(let[t,r]of n.entries())e.append(t,r)}return e}},18967,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r={DecodeError:function(){return y},MiddlewareNotFoundError:function(){return k},MissingStaticPage:function(){return v},NormalizeError:function(){return b},PageNotFoundError:function(){return w},SP:function(){return g},ST:function(){return m},WEB_VITALS:function(){return o},execOnce:function(){return s},getDisplayName:function(){return u},getLocationOrigin:function(){return c},getURL:function(){return d},isAbsoluteUrl:function(){return l},isResSent:function(){return h},loadGetInitialProps:function(){return f},normalizeRepeatedSlashes:function(){return p},stringifyError:function(){return x}};for(var a in r)Object.defineProperty(n,a,{enumerable:!0,get:r[a]});let o=["CLS","FCP","FID","INP","LCP","TTFB"];function s(e){let t,n=!1;return(...r)=>(n||(n=!0,t=e(...r)),t)}let i=/^[a-zA-Z][a-zA-Z\d+\-.]*?:/,l=e=>i.test(e);function c(){let{protocol:e,hostname:t,port:n}=window.location;return`${e}//${t}${n?":"+n:""}`}function d(){let{href:e}=window.location,t=c();return e.substring(t.length)}function u(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function h(e){return e.finished||e.headersSent}function p(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?`?${t.slice(1).join("?")}`:"")}async function f(e,t){let n=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await f(t.Component,t.ctx)}:{};let r=await e.getInitialProps(t);if(n&&h(n))return r;if(!r)throw Object.defineProperty(Error(`"${u(e)}.getInitialProps()" should resolve to an object. But found "${r}" instead.`),"__NEXT_ERROR_CODE",{value:"E1025",enumerable:!1,configurable:!0});return r}let g="u">typeof performance,m=g&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class y extends Error{}class b extends Error{}class w extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message=`Cannot find module for page: ${e}`}}class v extends Error{constructor(e,t){super(),this.message=`Failed to load static file for page: ${e} ${t}`}}class k extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function x(e){return JSON.stringify({message:e.message,stack:e.stack})}},33525,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"warnOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},69198,e=>{"use strict";var t=e.i(18050);e.i(74472);var n=e.i(33815);e.s(["AgentBadge",0,function({agent:e}){if(!e||"claude-code"===e)return null;let r=(0,n.getAgentMetadata)(e);return r?(0,t.jsx)("span",{style:{display:"inline-flex",alignItems:"center",borderRadius:4,padding:"2px 6px",fontSize:10,letterSpacing:"0.04em",textTransform:"uppercase",whiteSpace:"nowrap",fontWeight:600,color:r.accentColor,background:`color-mix(in srgb, ${r.accentColor} 12%, transparent)`,border:`1px solid color-mix(in srgb, ${r.accentColor} 30%, transparent)`},title:`Source: ${r.displayName}`,children:r.shortLabel}):null}])},43531,e=>{"use strict";let t=(0,e.i(75254).default)("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]);e.s(["Check",0,t],43531)},88137,e=>{"use strict";function t(e){return e<-15?{tone:"danger",label:"may exhaust early"}:e>15?{tone:"warning",label:"below pace"}:{tone:"success",label:"on pace"}}e.s(["paceLabel",0,t,"paceToneForCycle",0,function(e,n,r,a=Date.now()){return t(100*Math.max(0,Math.min(1,(a-(n-r))/r))-e).tone},"toneVar",0,function(e){switch(e){case"success":return"var(--af-success)";case"warning":return"#b58400";case"danger":return"var(--af-danger)"}}])},3116,e=>{"use strict";let t=(0,e.i(75254).default)("Clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);e.s(["Clock",0,t],3116)},41240,63639,e=>{"use strict";var t=e.i(75254);let n=(0,t.default)("Lightbulb",[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",key:"1gvzjb"}],["path",{d:"M9 18h6",key:"x1upvd"}],["path",{d:"M10 22h4",key:"ceow96"}]]);e.s(["Lightbulb",0,n],41240);let r=(0,t.default)("Radio",[["path",{d:"M4.9 19.1C1 15.2 1 8.8 4.9 4.9",key:"1vaf9d"}],["path",{d:"M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5",key:"u1ii0m"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5",key:"1j5fej"}],["path",{d:"M19.1 4.9C23 8.8 23 15.1 19.1 19",key:"10b0cb"}]]);e.s(["Radio",0,r],63639)},31278,e=>{"use strict";let t=(0,e.i(75254).default)("LoaderCircle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);e.s(["Loader2",0,t],31278)},54611,e=>{"use strict";let t=/^##\s+\[([^\]]+)\](?:\s+[—\-–]\s+(\S+))?\s*$/,n=/^###\s+(.+?)\s*$/,r=/^[-*]\s+(.+?)\s*$/,a=function(e){let a=e.split(/\r?\n/),o=[],s=null,i=null,l=[];function c(){i&&l.length>0&&(i.bullets.push(l.join(" ").trim()),l=[])}for(let e of a){let a=t.exec(e);if(a){c(),i=null,s={version:a[1].trim(),date:a[2]?.trim()??null,sections:[]},o.push(s);continue}if(!s)continue;let d=n.exec(e);if(d){c(),i={kind:d[1].trim(),bullets:[]},s.sections.push(i);continue}let u=r.exec(e);if(u){c(),l=[u[1]];continue}l.length>0&&e.trim().length>0&&l.push(e.trim())}return c(),o}('# Changelog\n\nAll notable user-facing changes to the Fleetlens CLI (`fleetlens` on npm) are\nrecorded here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\nThe team-server has its own log at `packages/team-server/CHANGELOG.md`.\n\n## [0.10.6] — 2026-05-21\n\n### Added\n- **Team-pairing visibility in the Personal Edition dashboard.** When you\'ve paired with a Team Edition server (`fleetlens team join …`), the dashboard now surfaces what\'s syncing without you having to open a terminal. A sidebar chip with a health dot (green / amber / red — re-derived client-side every 30 s so it ages correctly even when the daemon stops) and a live-aging "synced N ago" label. A `/settings → Team connection` panel showing team metadata, a labeled last-push preview (Agent time / Session count / Tool call count / Turn count / Token total / Plan tier), an explicit "What does NOT leave your machine" block, and a "Sync now" button that spawns `fleetlens team sync` as a subprocess and renders its output inline. A first-run welcome banner on the overview after pairing, dismissable per pairing. The preview also includes a "Show raw JSON payload" disclosure that displays the literal `IngestPayload` from disk — verify byte-for-byte that no transcripts, prompts, or project content leave your machine. Solo users see no change.\n\n### Changed\n- **Wire-format types now live in `@claude-lens/parser`.** `IngestPayload`, `DailyRollup`, `LastPushRecord`, and the rest of the daemon→server wire shape moved out of `packages/cli/src/team/push.ts` so the CLI and the dashboard share a single canonical definition — no drift risk on future schema changes. The CLI\'s `team-config` module also moved to the parser (`@claude-lens/parser/fs`) for the same reason.\n\n## [0.10.5] — 2026-05-21\n\n### Changed\n- **Team backfill now rides the consolidated `/api/ingest/metrics` path.** Same endpoint as the daemon\'s regular 5-min push, just with the optional `snapshotHistory` field set. Removes the need for a separate path-allowlist entry on deployments fronted by a proxy (IAP, WAF, Cloud Run / GLB path matcher), and as a side-effect the old `/api/ingest/usage-history` route is now a thin deprecation shim. Requires `team-server v0.8.4+` — older servers will return 200 without a `snapshotHistory` result block, and the CLI aborts the backfill loudly rather than silently advancing the high-water mark.\n\n### Fixed\n- **Backfill no longer silently advances the high-water mark on an older team-server.** Previously, a server that didn\'t recognize `snapshotHistory` would return 200 (zod `passthrough()` swallows unknown fields), `runTeamSync` would persist `lastSyncedUsageSnapshotAt` as if all rows landed, and the dropped rows would never be retried after the server upgraded. Backfill now requires an explicit `snapshotHistory` result block in the response.\n\n## [0.10.4] — 2026-05-21\n\n### Fixed\n- **Fleetlens\'s own LLM runs no longer show up in the dashboard.** The tmux-driven runner records its enrichment / digest / `/ask` calls as real Claude Code transcripts under `~/.claude/projects/` (cwd `~/.cclens/runtime`), since tailing that transcript is how it reads the model\'s reply. Those self-generated sessions were surfacing as a `.cclens/runtime` project and inflating session, project, and token-calibration rollups. The tmux runner now deletes each transcript once the response is captured (kill-session before unlink so claude can\'t rewrite it, gated by `FLEETLENS_TMUX_KEEP_WRAPPER`), and the parser excludes the runtime project dir at the scan chokepoint as a safety net for any transcript that outlives cleanup.\n\n## [0.10.3] — 2026-05-16\n\n### Added\n- **Daemon auto-update every 6 hours.** The local daemon now hourly-checks `registry.npmjs.org/fleetlens/latest` and spawns a detached `fleetlens update` child when a newer version exists. Timestamp persisted in `~/.cclens/daemon-update.json` so closing the laptop lid for days and reopening triggers an immediate catch-up check instead of waiting out the interval. Opt-out via `FLEETLENS_DAEMON_AUTO_UPDATE=0`.\n\n### Changed\n- **Unified team sync.** `runTeamSync` now drives history backfill, daily activity, live utilization, queue drain, and tier propagation through a single path. `team join` and the daemon\'s 5-min tick both call the same function — so a user disconnected from the team server for days will autonomously catch up on next sync, no manual `team backfill` needed.\n- New `lastSyncedUsageSnapshotAt` high-water mark in `~/.cclens/team.json` means incremental syncs only ship eligible new snapshots. Cold-start full backfill is bypass-able via `fleetlens team backfill --force` (and still runs unconditionally on first pair).\n- Team sync cadence is now independent of Claude OAuth polling, so an expired Claude token can no longer turn team sync into a 5-second retry storm.\n\n### Fixed\n- `lastSyncedDay` no longer advances past a failed day on partial outage — previously a transient mid-stream 5xx left a permanent hole in the team-server\'s daily-activity coverage.\n- Both `/api/ingest/metrics` and `/api/ingest/usage-history` POSTs now have a 15-second `AbortSignal.timeout` to bound hangs on network stalls.\n\n## [0.10.2] — 2026-05-15\n\n### Fixed\n- **Session-detail minimap no longer paints subagent runtime as idle.** Long agent runs that dispatched many subagents were reading as wall-to-wall "Session idle" stripes because the parent\'s tool_use → tool_result gap (the subagent\'s actual runtime) was classified as in-turn idle. `rawIdleBands` now carves subagent run spans out of every candidate gap; for in-turn gaps that overlap any subagent run the entire gap is dropped, and between-turn gaps with background subagent activity keep only the genuinely-unwatched residue.\n- **First-response thinking stops registering as idle.** Anchors now include every timestamped event (agent-thinking, meta, etc.), and the loop tracks an "awaiting first response" phase across intermediate thinking anchors. A `user → thinking → thinking → agent` sequence where the model takes a while to compose its first reply is now treated as response latency rather than painted as a stripe.\n- **Turn duration no longer shows `0ms` for single-row turns.** `buildMegaRows` now anchors a turn\'s `tOffsetMs` and `durationMs` at the originating user message rather than at the first agent row. Single-row turns show the actual user → agent latency, and the visual span of the turn rectangle on the minimap covers the whole arc.\n- **Single `Session idle` divider per band.** A user row and the turn-collapsed row that follows it now share a `tOffsetMs`, so the body\'s `IdleDivider` emitter dedupes per band to stop two consecutive identical idle markers around the same boundary.\n\n### Changed\n- Minimap idle threshold raised from 10s to 30s. Ordinary tool latency (slow Bash, big-file Read, between-anchor model thinking) no longer registers as idle.\n\n## [0.10.1] — 2026-05-14\n\n### Added\n- **Alternative claude runtime via tmux.** When `tmux` is on PATH, both the LLM pipeline (digest synth, entry enrichment, top-session perception) and the `/ask` feature now drive `claude` through a detached tmux session instead of `claude -p`. Sessions produced this way carry `entrypoint: cli` in their JSONL, which is also extracted by the parser and surfaced as a small badge on `/sessions/<id>` next to the model chip — green for `cli` / `claude-desktop`, amber for `sdk-*`. The path falls back to `claude -p` whenever tmux is unavailable or the run errors; set `FLEETLENS_FORCE_PRINT_MODE=1` to disable the tmux path entirely.\n- **`CCLENS_HOME` env override.** All `~/.cclens/` state (pid file, daemon log, perception state, usage snapshot log, llm-runs traces) is now reached through a shared `cclensHome()` helper that honors `CCLENS_HOME`. Lets multiple installs / workspaces run side-by-side without stomping on each other\'s state.\n\n### Fixed\n- `/api/runs` rendered every successful tmux-driven run as `status=error`. Tmux runs have no subprocess exit code (the session is killed by cleanup), and the runs viewer was treating a missing code as failure. End records now stamp `exit_code: 0` on success and the active-process filter no longer requires the literal `claude -p` flag, so tmux-driven processes also show up in the "active" panel while they\'re running.\n- `JobQueueWidget`\'s `/api/jobs` polling effect listed `jobs` in its deps, so each tick re-armed the effect on the freshly-set state ref — effective cadence collapsed to a 1–4 ms refetch loop. Now tracks `hasActive` in a closure-local var and runs the effect once on mount.\n\n### Conductor\n- New `conductor.json` + `scripts/conductor-{setup,run,archive}.sh` so each Conductor workspace allocates its own 3-port band (web / team-server / postgres) and boots its own `fleetlens-<workspace>` Compose stack against `.harness/cclens-state`. Replaces ~30 hard-coded `join(homedir(), ".cclens", …)` call sites with the shared `cclensHome()/cclensPath()` helper so a second workspace can boot without colliding with the user\'s primary install.\n\n## [0.10.0] — 2026-05-08\n\n### Changed\n- **Plan utilization reframed.** High utilization is now "getting plan value" (green); low utilization is "paying for unused headroom" (red). Mirrors how an operator actually looks at the dashboard: the wasteful outcome is paying for a plan and barely touching it, not running near the cap. Applied across `/usage` burndown, gauges, sidebar, and previous-cycles strip.\n- **In-progress cycles colored by pace, not peak.** A 50% peak halfway through a 7-day cycle reads on-pace green, not amber — the chart no longer cries wolf on a fresh cycle that just hasn\'t burnt yet. Completed cycles still use peak tone.\n- **Pace label ±15pp band.** "On pace" now covers a ±15pp window around ideal; outside that, label is "below pace" (under-burning) or "may exhaust early" (over-burning). The previous +5/-50 thresholds flipped amber every time the user took a night off.\n- **Pace label collapses to three states.** "May exhaust early" / "below pace" / "on pace" — same color tones, one fewer qualifier to read.\n- **Wording softened across the utilization views** to describe the pattern rather than judge the user. "Behind schedule" → "below pace"; "on track (+x%)" → "on pace (+xpp vs ideal)".\n\n### Added\n- **Conductor multi-agent workspace support.** Sessions inside Conductor\'s worktree-style agent workspaces (`~/conductor/workspaces/<repo>/<agent>/...`) now group correctly under their parent project, with each agent shown as a separate worktree. Mirrors the existing `.worktrees/` handling.\n\n### Fixed\n- Hydration mismatch on `/usage` when pace labels rendered before client mount.\n\n### Notes for operators\n- The CLI\'s team push now ships **only Claude Code usage snapshots** to the team server. Codex snapshots stay local until `plan_utilization` gets a per-agent partition — without one, the team-server\'s "latest snapshot" pick flips between agents and miscolors the in-progress cycle. Existing rows in `plan_utilization` stay; new pushes are claude-code only.\n\n## [0.9.0] — 2026-05-07\n\n### Added\n- **Gemini CLI** as the third agent source. Sessions written to `~/.gemini/tmp/<slug>/chats/session-*.jsonl` are now read, parsed, and rendered alongside Claude Code and Codex. Tool calls (`read_file`, `list_directory`, `glob`, `run_shell_command`, `update_topic`, …) and their results show in the session timeline; thinking blocks, multi-write status replay, `$set` / `$rewindTo` semantics, and the legacy single-JSON file shape all handled. Slug → cwd inversion via `~/.gemini/projects.json`. `✺` glyph + purple accent in the UI.\n\n### Changed\n- Session detail timeline drops in-turn idle hatching. Within a turn, every wait — model loading, tool execution, network — is the agent\'s active time. Only between-turn idle (user reading + composing the next message) renders as hatched.\n- Gemini parser anchors `firstTimestamp`, `lastTimestamp`, and `activeSegments` on conversational events only. Login/OAuth `info` records and `metadata.startTime` (file-creation moment) no longer stretch the session bar back to before the first user message.\n\n## [0.7.1] — 2026-05-07\n\n### Fixed\n- Cycle-peaks trend no longer renders two bars labeled the same day. When Anthropic\'s rolling 7-day window slid its anchor between consecutive polls, the daemon\'s bucketer (hour-rounded) recorded both reset boundaries as distinct cycles. They\'re now merged within a 12 h tolerance — same fix in `apps/web/lib/cycle-peaks.ts` (used by `/usage`) and in `packages/cli/src/team/sync.ts` (the daemon\'s team-server push), so both the personal `/usage` chart and the team-server\'s plan-utilization view stay consistent. 5-hour cycles keep no merge tolerance (their anchor is stable; merging would collapse legitimate distinct cycles).\n\n## [0.6.4] — 2026-05-04\n\n### Fixed\n- Cached calibration events per-file so `/usage` warm renders complete in well under a second on large session histories.\n\n## [0.6.3] — 2026-05-04\n\n### Fixed\n- Dropped poisoned auto-week locks left behind by interrupted runs; backfill now gates on the live pipeline lock instead.\n\n## [0.6.2] — 2026-05-04\n\n### Added\n- Last week\'s digest auto-generates on launch when AI features are on. Disable in Settings → AI features.\n\n## [0.6.1] — 2026-05-04\n\n### Changed\n- Smaller install size — published bundle no longer drags in unused workspace metadata.\n\n## [0.6.0] — 2026-04-30\n\n### Added\n- V2 perception layer. Daily, weekly, and monthly digests synthesized from per-session entries via a strict hierarchical pipeline (entry → day → week → month). Past-period digests are immutable on disk; current-period digests use a 10-min in-memory TTL.\n- `/insights` route with on-demand week and month digest generation, picker history, and force-regen.\n- `fleetlens digest day | week | month [--json]` — CLI surface that reproduces the same digests served at `/insights`.\n- `fleetlens entries [--day | --session | --all] [--json]` — inspect the per-session entry primitive.\n\n### Changed\n- Renamed "active time" to "agent time" across the dashboard and CLI for clarity.\n\n## [0.4.1] — 2026-04-23\n\n### Added\n- Concurrency view on `/parallelism` page. Bursts colored teal for same-project and purple for cross-project (the genuinely interesting fleet-work signal).\n- Plan utilization sparkline on the sidebar, fed by the usage daemon\'s 5-min snapshots.\n\n### Fixed\n- Auto-update no longer leaves a zombie web server on the old binary; both server and daemon are torn down before re-exec.\n\n## [0.4.0] — 2026-04-18\n\n### Added\n- Usage daemon (`fleetlens daemon …`) polls `/api/oauth/usage` every 5 min and appends snapshots to `~/.cclens/usage.jsonl`. `fleetlens start` and `fleetlens stop` manage server + daemon together.\n- `fleetlens usage --history` for inspecting past plan utilization.\n\n## [0.3.0] — 2026-04-10\n\n### Added\n- Project rollups with worktree aggregation. Sessions inside `<project>/.worktrees/<name>` roll up under their parent canonical project with a `+N wt` badge.\n- Pinned projects in the sidebar (per-browser via localStorage).\n\n## [0.2.0] — 2026-04-01\n\n### Changed\n- Active-segment computation now uses all timestamped events (system + summary + sidechain + conversational) instead of conversational-only. Earlier filter undercounted by up to 100x on tool-heavy sessions.\n\n## [0.1.0] — 2026-03-15\n\n### Added\n- Initial release. Local-only dashboard for Claude Code sessions: timeline, projects, daily activity bucketing, parallelism detection, per-model cost estimation. Reads JSONL transcripts from `~/.claude/projects/`.\n');a[0]?.version,e.s(["STORAGE_KEY",0,"cclens:changelog-last-seen"],54611)},86619,e=>{"use strict";var t=e.i(71645);e.s(["usePersistentBoolean",0,function(e,n){let[r,a]=(0,t.useState)(n),[o,s]=(0,t.useState)(!1);return(0,t.useEffect)(()=>{try{let t=window.localStorage.getItem(e);"1"===t?a(!0):"0"===t&&a(!1)}catch{}s(!0)},[e]),(0,t.useEffect)(()=>{let t=t=>{let n=t.detail;n?.key===e&&"boolean"==typeof n.value&&a(n.value)},n=t=>{t.key===e&&("1"===t.newValue?a(!0):"0"===t.newValue&&a(!1))};return window.addEventListener("cclens:persistent-boolean",t),window.addEventListener("storage",n),()=>{window.removeEventListener("cclens:persistent-boolean",t),window.removeEventListener("storage",n)}},[e]),[r,(0,t.useCallback)(t=>{a(t);try{window.localStorage.setItem(e,t?"1":"0")}catch{}window.dispatchEvent(new CustomEvent("cclens:persistent-boolean",{detail:{key:e,value:t}}))},[e]),o]}])},18566,(e,t,n)=>{t.exports=e.r(76562)},95057,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r={formatUrl:function(){return i},formatWithValidation:function(){return c},urlObjectKeys:function(){return l}};for(var a in r)Object.defineProperty(n,a,{enumerable:!0,get:r[a]});let o=e.r(90809)._(e.r(98183)),s=/https?|ftp|gopher|file/;function i(e){let{auth:t,hostname:n}=e,r=e.protocol||"",a=e.pathname||"",i=e.hash||"",l=e.query||"",c=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?c=t+e.host:n&&(c=t+(~n.indexOf(":")?`[${n}]`:n),e.port&&(c+=":"+e.port)),l&&"object"==typeof l&&(l=String(o.urlQueryToSearchParams(l)));let d=e.search||l&&`?${l}`||"";return r&&!r.endsWith(":")&&(r+=":"),e.slashes||(!r||s.test(r))&&!1!==c?(c="//"+(c||""),a&&"/"!==a[0]&&(a="/"+a)):c||(c=""),i&&"#"!==i[0]&&(i="#"+i),d&&"?"!==d[0]&&(d="?"+d),a=a.replace(/[?#]/g,encodeURIComponent),d=d.replace("#","%23"),`${r}${c}${a}${d}${i}`}let l=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function c(e){return i(e)}},18581,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"useMergedRef",{enumerable:!0,get:function(){return a}});let r=e.r(71645);function a(e,t){let n=(0,r.useRef)(null),a=(0,r.useRef)(null);return(0,r.useCallback)(r=>{if(null===r){let e=n.current;e&&(n.current=null,e());let t=a.current;t&&(a.current=null,t())}else e&&(n.current=o(e,r)),t&&(a.current=o(t,r))},[e,t])}function o(e,t){if("function"!=typeof e)return e.current=t,()=>{e.current=null};{let n=e(t);return"function"==typeof n?n:()=>e(null)}}("function"==typeof n.default||"object"==typeof n.default&&null!==n.default)&&void 0===n.default.__esModule&&(Object.defineProperty(n.default,"__esModule",{value:!0}),Object.assign(n.default,n),t.exports=n.default)},73668,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"isLocalURL",{enumerable:!0,get:function(){return o}});let r=e.r(18967),a=e.r(52817);function o(e){if(!(0,r.isAbsoluteUrl)(e))return!0;try{let t=(0,r.getLocationOrigin)(),n=new URL(e,t);return n.origin===t&&(0,a.hasBasePath)(n.pathname)}catch(e){return!1}}},84508,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"errorOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},22016,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r={default:function(){return y},useLinkStatus:function(){return w}};for(var a in r)Object.defineProperty(n,a,{enumerable:!0,get:r[a]});let o=e.r(90809),s=e.r(18050),i=o._(e.r(71645)),l=e.r(95057),c=e.r(8372),d=e.r(18581),u=e.r(18967),h=e.r(5550);e.r(33525);let p=e.r(88540),f=e.r(91949),g=e.r(73668),m=e.r(9396);function y(t){var n,r;let a,o,y,[w,v]=(0,i.useOptimistic)(f.IDLE_LINK_STATUS),k=(0,i.useRef)(null),{href:x,as:C,children:S,prefetch:j=null,passHref:T,replace:E,shallow:L,scroll:P,onClick:A,onMouseEnter:$,onTouchStart:O,legacyBehavior:M=!1,onNavigate:_,transitionTypes:N,ref:R,unstable_dynamicOnHover:I,...F}=t;a=S,M&&("string"==typeof a||"number"==typeof a)&&(a=(0,s.jsx)("a",{children:a}));let z=i.default.useContext(c.AppRouterContext),D=!1!==j,U=!1!==j?null===(r=j)||"auto"===r?m.FetchStrategy.PPR:m.FetchStrategy.Full:m.FetchStrategy.PPR,W="string"==typeof(n=C||x)?n:(0,l.formatUrl)(n);if(M){if(a?.$$typeof===Symbol.for("react.lazy"))throw Object.defineProperty(Error("`<Link legacyBehavior>` received a direct child that is either a Server Component, or JSX that was loaded with React.lazy(). This is not supported. Either remove legacyBehavior, or make the direct child a Client Component that renders the Link's `<a>` tag."),"__NEXT_ERROR_CODE",{value:"E863",enumerable:!1,configurable:!0});o=i.default.Children.only(a)}let B=M?o&&"object"==typeof o&&o.ref:R,G=i.default.useCallback(e=>(null!==z&&(k.current=(0,f.mountLinkInstance)(e,W,z,U,D,v)),()=>{k.current&&((0,f.unmountLinkForCurrentNavigation)(k.current),k.current=null),(0,f.unmountPrefetchableInstance)(e)}),[D,W,z,U,v]),H={ref:(0,d.useMergedRef)(G,B),onClick(t){M||"function"!=typeof A||A(t),M&&o.props&&"function"==typeof o.props.onClick&&o.props.onClick(t),!z||t.defaultPrevented||function(t,n,r,a,o,s,l){if("u">typeof window){let c,{nodeName:d}=t.currentTarget;if("A"===d.toUpperCase()&&((c=t.currentTarget.getAttribute("target"))&&"_self"!==c||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.nativeEvent&&2===t.nativeEvent.which)||t.currentTarget.hasAttribute("download"))return;if(!(0,g.isLocalURL)(n)){a&&(t.preventDefault(),location.replace(n));return}if(t.preventDefault(),s){let e=!1;if(s({preventDefault:()=>{e=!0}}),e)return}let{dispatchNavigateAction:u}=e.r(99781);i.default.startTransition(()=>{u(n,a?"replace":"push",!1===o?p.ScrollBehavior.NoScroll:p.ScrollBehavior.Default,r.current,l)})}}(t,W,k,E,P,_,N)},onMouseEnter(e){M||"function"!=typeof $||$(e),M&&o.props&&"function"==typeof o.props.onMouseEnter&&o.props.onMouseEnter(e),z&&D&&(0,f.onNavigationIntent)(e.currentTarget,!0===I)},onTouchStart:function(e){M||"function"!=typeof O||O(e),M&&o.props&&"function"==typeof o.props.onTouchStart&&o.props.onTouchStart(e),z&&D&&(0,f.onNavigationIntent)(e.currentTarget,!0===I)}};return(0,u.isAbsoluteUrl)(W)?H.href=W:M&&!T&&("a"!==o.type||"href"in o.props)||(H.href=(0,h.addBasePath)(W)),y=M?i.default.cloneElement(o,H):(0,s.jsx)("a",{...F,...H,children:a}),(0,s.jsx)(b.Provider,{value:w,children:y})}e.r(84508);let b=(0,i.createContext)(f.IDLE_LINK_STATUS),w=()=>(0,i.useContext)(b);("function"==typeof n.default||"object"==typeof n.default&&null!==n.default)&&void 0===n.default.__esModule&&(Object.defineProperty(n.default,"__esModule",{value:!0}),Object.assign(n.default,n),t.exports=n.default)},75254,e=>{"use strict";var t=e.i(71645);let n=(...e)=>e.filter((e,t,n)=>!!e&&""!==e.trim()&&n.indexOf(e)===t).join(" ").trim();var r={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let a=(0,t.forwardRef)(({color:e="currentColor",size:a=24,strokeWidth:o=2,absoluteStrokeWidth:s,className:i="",children:l,iconNode:c,...d},u)=>(0,t.createElement)("svg",{ref:u,...r,width:a,height:a,stroke:e,strokeWidth:s?24*Number(o)/Number(a):o,className:n("lucide",i),...d},[...c.map(([e,n])=>(0,t.createElement)(e,n)),...Array.isArray(l)?l:[l]]));e.s(["default",0,(e,r)=>{let o=(0,t.forwardRef)(({className:o,...s},i)=>(0,t.createElement)(a,{ref:i,iconNode:r,className:n(`lucide-${e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}`,o),...s}));return o.displayName=`${e}`,o}],75254)},74472,33815,e=>{"use strict";let t=[{kind:"claude-code",displayName:"Claude Code",shortLabel:"Claude",accentColor:"var(--af-accent)",iconChar:"✦"},{kind:"codex",displayName:"Codex (OpenAI)",shortLabel:"Codex",accentColor:"rgb(16, 163, 127)",iconChar:"◆"},{kind:"gemini",displayName:"Gemini CLI",shortLabel:"Gemini",accentColor:"rgb(124, 92, 246)",iconChar:"✺"}],n=new Set(t.map(e=>e.kind));e.s(["getAgentMetadata",0,function(e){return t.find(t=>t.kind===e)},"isAgentKind",0,function(e){return"string"==typeof e&&n.has(e)}],33815),e.s([],74472)},23614,41783,e=>{"use strict";var t=e.i(18050),n=e.i(22016);e.i(74472);var r=e.i(33815);function a({agent:e,size:n=11,title:o}){if(!e)return null;let s=(0,r.getAgentMetadata)(e);return s?(0,t.jsx)("span",{"aria-hidden":!0,title:o??s.displayName,style:{display:"inline-flex",alignItems:"center",justifyContent:"center",flexShrink:0,width:n+2,height:n+2,fontSize:n,lineHeight:1,color:s.accentColor},children:s.iconChar}):null}e.s(["AgentIcon",0,a],41783);let o={shipped:{bg:"rgba(72, 187, 120, 0.12)",fg:"#48bb78",label:"Shipped",icon:"🚀"},partial:{bg:"rgba(66, 153, 225, 0.12)",fg:"#4299e1",label:"Partial",icon:"🔨"},blocked:{bg:"rgba(245, 101, 101, 0.12)",fg:"#f56565",label:"Blocked",icon:"🚧"},exploratory:{bg:"rgba(160, 174, 192, 0.12)",fg:"#a0aec0",label:"Exploratory",icon:"🧭"},trivial:{bg:"rgba(203, 213, 224, 0.12)",fg:"#a0aec0",label:"Warmup",icon:"💤"},idle:{bg:"rgba(203, 213, 224, 0.12)",fg:"#a0aec0",label:"Idle",icon:"—"}},s={sm:{fontSize:9,padding:"1px 6px",iconGap:3,borderRadius:99},md:{fontSize:10,padding:"2px 8px",iconGap:4,borderRadius:99},lg:{fontSize:11,padding:"3px 10px",iconGap:5,borderRadius:99}},i={shipped:6,partial:5,blocked:4,exploratory:3,trivial:2,idle:1};e.s(["OUTCOME_STYLES",0,o,"OutcomePill",0,function(e){let r=s[e.size??"md"],i=r.fontSize+1;if("pending"in e&&e.pending){let o={display:"inline-flex",alignItems:"center",gap:r.iconGap,padding:r.padding,borderRadius:r.borderRadius,fontSize:r.fontSize,fontWeight:600,background:"rgba(160, 174, 192, 0.08)",color:"var(--af-text-tertiary)",border:"1px dashed var(--af-border-subtle)",textDecoration:"none",whiteSpace:"nowrap"};return(0,t.jsxs)(n.default,{href:`/digest/${e.localDay}`,style:o,title:`Generate ${e.localDay} digest →`,children:[e.agent&&(0,t.jsx)(a,{agent:e.agent,size:i}),(0,t.jsx)("span",{"aria-hidden":!0,style:{lineHeight:1},children:"⋯"}),(0,t.jsx)("span",{children:"pending"})]})}let l=o[e.outcome],c=e.label??"both",d={display:"inline-flex",alignItems:"center",gap:r.iconGap,padding:r.padding,borderRadius:r.borderRadius,fontSize:r.fontSize,fontWeight:600,background:l.bg,color:l.fg,whiteSpace:"nowrap"};return(0,t.jsxs)("span",{style:d,title:l.label,children:[e.agent&&(0,t.jsx)(a,{agent:e.agent,size:i}),("icon"===c||"both"===c)&&(0,t.jsx)("span",{"aria-hidden":!0,style:{lineHeight:1},children:l.icon}),("text"===c||"both"===c)&&(0,t.jsx)("span",{children:l.label})]})},"outcomePriority",0,function(e){return e?i[e]:0}],23614)},56324,e=>{"use strict";let t={input:1,output:5,cacheRead:.1,cacheWrite:1.25},n={input:3,output:15,cacheRead:.3,cacheWrite:3.75},r={input:5,output:25,cacheRead:.5,cacheWrite:6.25};e.s(["estimateCost",0,function(e,a){let o=function(e){if(!e)return r;let a=e.toLowerCase();return a.includes("haiku")?t:a.includes("sonnet")?n:r}(a);return e.input/1e6*o.input+e.output/1e6*o.output+e.cacheRead/1e6*o.cacheRead+e.cacheWrite/1e6*o.cacheWrite},"formatCost",0,function(e){return e<.01?"<$0.01":e<1||e<100?`$${e.toFixed(2)}`:e<1e3?`$${e.toFixed(1)}`:`$${e.toLocaleString(void 0,{maximumFractionDigits:0})}`},"formatDuration",0,function(e){if(void 0===e)return"—";if(e<1e3)return`${e}ms`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60),r=t%60;if(n<60)return r?`${n}m ${r}s`:`${n}m`;let a=Math.floor(n/60),o=n%60;return o?`${a}h ${o}m`:`${a}h`},"formatGap",0,function(e){if(e<1e3)return`${e}ms`;let t=e/1e3;if(t<60)return`${t.toFixed(1)}s`;let n=Math.floor(t/60);if(n<60){let e=Math.round(t%60);return`${n}m ${e}s`}let r=Math.floor(n/60),a=n%60;if(r<24)return a?`${r}h ${a}m`:`${r}h`;let o=Math.floor(r/24),s=r%24;return s?`${o}d ${s}h`:`${o}d`},"formatOffset",0,function(e){if(void 0===e||e<0)return"—";let t=Math.floor(e/1e3),n=Math.floor(t/3600),r=String(Math.floor(t%3600/60)).padStart(2,"0"),a=String(t%60).padStart(2,"0");return`${n}:${r}:${a}`},"formatRelative",0,function(e){let t=Date.parse(e);if(Number.isNaN(t))return e;let n=Math.round((Date.now()-t)/1e3);if(n<60)return`${n}s ago`;let r=Math.round(n/60);if(r<60)return`${r}m ago`;let a=Math.round(r/60);if(a<24)return`${a}h ago`;let o=Math.round(a/24);return`${o}d ago`},"formatTokens",0,function(e){return e<1e3?String(e):e<1e4?(e/1e3).toFixed(1)+"k":e<1e6?Math.round(e/1e3)+"k":e<1e7?(e/1e6).toFixed(2)+"M":(e/1e6).toFixed(1)+"M"},"prettyProjectName",0,function(e,t=35){let n=e.split("/").filter(Boolean),r=n.lastIndexOf(".worktrees");if(r>0&&r<n.length-1){let e=n[r-1],a=n[r+1],o=`${e}/${a}`;return o.length<=t?o:"…"+o.slice(o.length-t+1)}let a=n.length>=2?`${n[n.length-2]}/${n[n.length-1]}`:n[n.length-1]??e;return a.length<=t?a:"…"+a.slice(a.length-t+1)},"shortId",0,function(e){return e.length<=10?e:e.slice(0,6)+"…"+e.slice(-4)}])},55436,e=>{"use strict";let t=(0,e.i(75254).default)("Search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);e.s(["Search",0,t],55436)},56004,e=>{"use strict";var t=e.i(18050),n=e.i(22016);let r={display:"inline-flex",alignItems:"center",borderRadius:4,padding:"2px 6px",fontSize:10,letterSpacing:"0.04em",textTransform:"uppercase",whiteSpace:"nowrap"};e.s(["TeamBadge",0,function({session:e,linkable:a=!0}){if(!e.teamName)return null;let o=!0===e.isTeamLead,s=void 0!==e.agentName;return o||s?o&&a?(0,t.jsx)(n.default,{href:`/sessions/${e.id}`,style:{...r,fontWeight:600,background:"var(--af-warning-subtle)",color:"var(--af-warning)",border:"1px solid var(--af-warning-subtle)",textDecoration:"none"},title:`Team lead — ${e.teamName}`,children:"Team Lead"}):o?(0,t.jsx)("span",{style:{...r,fontWeight:600,background:"var(--af-warning-subtle)",color:"var(--af-warning)",border:"1px solid var(--af-warning-subtle)"},title:`Team lead — ${e.teamName}`,children:"Team Lead"}):(0,t.jsx)("span",{style:{...r,fontWeight:500,background:"var(--af-surface-hover)",color:"var(--af-text-tertiary)",border:"1px solid var(--af-border-subtle)"},title:`Team member — ${e.teamName} \xb7 ${e.agentName}`,children:"Team Member"}):null}])}]);