fleetlens 0.6.4 → 0.7.1

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 (226) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/app-path-routes-manifest.json +1 -0
  3. package/app/apps/web/.next/build-manifest.json +5 -5
  4. package/app/apps/web/.next/prerender-manifest.json +3 -3
  5. package/app/apps/web/.next/routes-manifest.json +6 -0
  6. package/app/apps/web/.next/server/app/_global-error/page/build-manifest.json +2 -2
  7. package/app/apps/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/app/apps/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/app/apps/web/.next/server/app/_not-found/page/build-manifest.json +2 -2
  17. package/app/apps/web/.next/server/app/_not-found/page.js +3 -3
  18. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/app/apps/web/.next/server/app/api/ask/route.js +2 -2
  21. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +3 -3
  23. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
  24. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +3 -3
  25. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
  26. package/app/apps/web/.next/server/app/api/digest/month-index/route.js +1 -1
  27. package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
  28. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +3 -3
  29. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
  30. package/app/apps/web/.next/server/app/api/digest/week-index/route.js +1 -1
  31. package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
  32. package/app/apps/web/.next/server/app/api/events/route.js +2 -2
  33. package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
  34. package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
  35. package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
  36. package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
  37. package/app/apps/web/.next/server/app/api/settings/route.js +3 -3
  38. package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  39. package/app/apps/web/.next/server/app/changelog/page/app-paths-manifest.json +3 -0
  40. package/app/apps/web/.next/server/app/changelog/page/build-manifest.json +17 -0
  41. package/app/apps/web/.next/server/app/changelog/page/next-font-manifest.json +6 -0
  42. package/app/apps/web/.next/server/app/changelog/page/react-loadable-manifest.json +1 -0
  43. package/app/apps/web/.next/server/app/changelog/page/server-reference-manifest.json +4 -0
  44. package/app/apps/web/.next/server/app/changelog/page.js +17 -0
  45. package/app/apps/web/.next/server/app/changelog/page.js.map +5 -0
  46. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -0
  47. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +3 -0
  48. package/app/apps/web/.next/server/app/day/[date]/page/build-manifest.json +2 -2
  49. package/app/apps/web/.next/server/app/day/[date]/page.js +4 -4
  50. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  51. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  52. package/app/apps/web/.next/server/app/day/page/build-manifest.json +2 -2
  53. package/app/apps/web/.next/server/app/day/page.js +3 -3
  54. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  55. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  56. package/app/apps/web/.next/server/app/digest/[date]/page/build-manifest.json +2 -2
  57. package/app/apps/web/.next/server/app/digest/[date]/page.js +3 -3
  58. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  59. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  60. package/app/apps/web/.next/server/app/digest/page/build-manifest.json +2 -2
  61. package/app/apps/web/.next/server/app/digest/page.js +3 -3
  62. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  63. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  64. package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +2 -2
  65. package/app/apps/web/.next/server/app/insights/[key]/page.js +3 -3
  66. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  67. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  68. package/app/apps/web/.next/server/app/insights/page/build-manifest.json +2 -2
  69. package/app/apps/web/.next/server/app/insights/page.js +3 -3
  70. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  71. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  72. package/app/apps/web/.next/server/app/page/build-manifest.json +2 -2
  73. package/app/apps/web/.next/server/app/page.js +4 -4
  74. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  75. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  76. package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +2 -2
  77. package/app/apps/web/.next/server/app/parallelism/page.js +3 -3
  78. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  79. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  80. package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +2 -2
  81. package/app/apps/web/.next/server/app/projects/[slug]/page.js +4 -4
  82. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  83. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  84. package/app/apps/web/.next/server/app/projects/page/build-manifest.json +2 -2
  85. package/app/apps/web/.next/server/app/projects/page.js +4 -4
  86. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/app/apps/web/.next/server/app/runs/page/build-manifest.json +2 -2
  89. package/app/apps/web/.next/server/app/runs/page.js +3 -3
  90. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  91. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  92. package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +2 -2
  93. package/app/apps/web/.next/server/app/sessions/[id]/page.js +4 -4
  94. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  95. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  96. package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +2 -2
  97. package/app/apps/web/.next/server/app/sessions/page.js +4 -4
  98. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  99. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  100. package/app/apps/web/.next/server/app/settings/page/build-manifest.json +2 -2
  101. package/app/apps/web/.next/server/app/settings/page.js +3 -3
  102. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  103. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  104. package/app/apps/web/.next/server/app/usage/page/build-manifest.json +2 -2
  105. package/app/apps/web/.next/server/app/usage/page.js +3 -3
  106. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  107. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  108. package/app/apps/web/.next/server/app-paths-manifest.json +1 -0
  109. package/app/apps/web/.next/server/chunks/{[externals]__0o43ejh._.js → [externals]__0fpbr8-._.js} +2 -2
  110. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0-vgrh1._.js +1 -1
  111. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0.z48ue._.js +3 -0
  112. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0dctov1._.js +5 -0
  113. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0e8n_x4._.js +1 -1
  114. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0jprdks._.js +2 -2
  115. package/app/apps/web/.next/server/chunks/node_modules_next_036my~r._.js +1 -1
  116. package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ck.78s.js +1 -1
  117. package/app/apps/web/.next/server/chunks/packages_09hmrqx._.js +3 -0
  118. package/app/apps/web/.next/server/chunks/packages_118y1lp._.js +3 -0
  119. package/app/apps/web/.next/server/chunks/packages_entries_dist_node_0bckr7-.js +26 -17
  120. package/app/apps/web/.next/server/chunks/packages_parser_dist_fs_122oe0-.js +3 -0
  121. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0vvlth5._.js → [root-of-the-server]__02~v50d._.js} +2 -2
  122. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__04eq6z2._.js +3 -0
  123. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__09rjz7_._.js +3 -0
  124. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0e.gxk3._.js +3 -0
  125. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0p1x6v2._.js +3 -0
  126. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0r4tks8._.js +1 -1
  127. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0rvzdnx._.js +1 -1
  128. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0sp6.jt._.js +3 -0
  129. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0tejjy_._.js +3 -0
  130. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0w52fbc._.js +3 -3
  131. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0wwa6gh._.js +1 -1
  132. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ytdqxt._.js +3 -0
  133. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0yv-a8z._.js +12 -0
  134. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__10~b4bf._.js +12 -0
  135. package/app/apps/web/.next/server/chunks/ssr/_02s1n-x._.js +1 -1
  136. package/app/apps/web/.next/server/chunks/ssr/_0woap-3._.js +1 -1
  137. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  138. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_changelog_page_actions_12_04gy.js +3 -0
  139. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_day_[date]_day-view_tsx_0wjoe1u._.js +1 -1
  140. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_settings_ai-features-form_tsx_01p699p._.js +1 -1
  141. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0ly1xes._.js +1 -1
  142. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +1 -1
  143. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_changelog-mark-read_tsx_0hm41sv._.js +3 -0
  144. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_0m67-xi._.js +3 -0
  145. package/app/apps/web/.next/server/chunks/ssr/{node_modules_0i2xw~e._.js → node_modules_0fx6-.x._.js} +2 -2
  146. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uwo3v3.js +4 -0
  147. package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
  148. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_10_oegc._.js +3 -0
  149. package/app/apps/web/.next/server/middleware-build-manifest.js +5 -5
  150. package/app/apps/web/.next/server/pages/500.html +1 -1
  151. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  152. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  153. package/app/apps/web/.next/static/chunks/0.3mqbj24kwtc.js +6 -0
  154. package/app/apps/web/.next/static/chunks/05b689hq3x49w.js +1 -0
  155. package/app/apps/web/.next/static/chunks/07-fq4im3pmjp.js +4 -0
  156. package/app/apps/web/.next/static/chunks/{0mykrrv810ra-.js → 0kjot8367asxe.js} +1 -1
  157. package/app/apps/web/.next/static/chunks/0n3-prt61k7pr.js +1 -0
  158. package/app/apps/web/.next/static/chunks/{0_7303jr0sf0w.js → 0q2vd~aj.1hpz.js} +3 -3
  159. package/app/apps/web/.next/static/chunks/0qmh5da6gfm56.js +1 -0
  160. package/app/apps/web/.next/static/chunks/{0k9pksi_i.odf.js → 111-bgx08j2nq.js} +1 -1
  161. package/app/apps/web/.next/static/chunks/179_t_igg8ghh.js +14 -0
  162. package/app/apps/web/.next/static/chunks/{turbopack-0_p36yduv.waj.js → turbopack-00w.w1u3c~d-6.js} +1 -1
  163. package/app/apps/web/app/api/ask/route.ts +1 -1
  164. package/app/apps/web/app/api/digest/week/[startDate]/route.ts +2 -1
  165. package/app/apps/web/app/api/settings/route.ts +1 -0
  166. package/app/apps/web/app/changelog/page.tsx +238 -0
  167. package/app/apps/web/app/insights/[key]/page.tsx +8 -2
  168. package/app/apps/web/app/layout.tsx +12 -7
  169. package/app/apps/web/app/page.tsx +19 -15
  170. package/app/apps/web/app/parallelism/gantt-chart.tsx +7 -18
  171. package/app/apps/web/app/projects/projects-view.tsx +3 -0
  172. package/app/apps/web/app/sessions/[id]/session-view.tsx +168 -41
  173. package/app/apps/web/app/sessions/page.tsx +22 -12
  174. package/app/apps/web/app/sessions/sessions-grid.tsx +82 -9
  175. package/app/apps/web/app/settings/ai-features-form.tsx +20 -1
  176. package/app/apps/web/app/settings/page.tsx +1 -0
  177. package/app/apps/web/app/usage/page.tsx +161 -52
  178. package/app/apps/web/components/agent-badge.tsx +43 -0
  179. package/app/apps/web/components/agent-icon.tsx +38 -0
  180. package/app/apps/web/components/agent-mix-chip.tsx +42 -0
  181. package/app/apps/web/components/{ask-claude.tsx → ask.tsx} +13 -12
  182. package/app/apps/web/components/changelog-icon.tsx +62 -0
  183. package/app/apps/web/components/changelog-mark-read.tsx +12 -0
  184. package/app/apps/web/components/day-digest.tsx +1 -1
  185. package/app/apps/web/components/live-sessions-widget.tsx +5 -1
  186. package/app/apps/web/components/outcome-pill.tsx +7 -2
  187. package/app/apps/web/components/sidebar.tsx +8 -1
  188. package/app/apps/web/components/week-digest.tsx +2 -0
  189. package/app/apps/web/lib/ai/session-summary.ts +1 -1
  190. package/app/apps/web/lib/calibration-data.ts +4 -64
  191. package/app/apps/web/lib/changelog-data.generated.ts +3 -0
  192. package/app/apps/web/lib/changelog.ts +67 -0
  193. package/app/apps/web/lib/cycle-peaks.test.ts +185 -0
  194. package/app/apps/web/lib/cycle-peaks.ts +104 -0
  195. package/app/apps/web/lib/data.ts +7 -8
  196. package/app/apps/web/lib/date-range.ts +9 -2
  197. package/app/apps/web/lib/usage-data.ts +24 -1
  198. package/app/apps/web/lib/validate-settings.ts +1 -0
  199. package/app/apps/web/package.json +1 -1
  200. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  201. package/dist/daemon-worker.js +3162 -2354
  202. package/dist/index.js +4419 -3742
  203. package/package.json +1 -1
  204. package/app/apps/web/.next/server/chunks/[externals]__0_xztrf._.js +0 -3
  205. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0455vu3._.js +0 -5
  206. package/app/apps/web/.next/server/chunks/packages_0e7q9u_._.js +0 -3
  207. package/app/apps/web/.next/server/chunks/packages_0n6p2ot._.js +0 -3
  208. package/app/apps/web/.next/server/chunks/packages_parser_dist_fs_0bc7ptm.js +0 -3
  209. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0-94ji~._.js +0 -3
  210. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0_6.inc._.js +0 -3
  211. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gio_ck._.js +0 -3
  212. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0jdyefm._.js +0 -12
  213. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ln9jsw._.js +0 -3
  214. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0u5d7de._.js +0 -12
  215. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~_8vff._.js +0 -3
  216. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__11v.eqw._.js +0 -3
  217. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_usage-data_ts_0xtv99n._.js +0 -3
  218. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0-jvt~a._.js +0 -3
  219. package/app/apps/web/.next/static/chunks/04m3slg_5ph02.js +0 -1
  220. package/app/apps/web/.next/static/chunks/0coa1800xqgfj.js +0 -1
  221. package/app/apps/web/.next/static/chunks/0k1psab.bybh9.js +0 -14
  222. package/app/apps/web/.next/static/chunks/0xfu09fn_358k.js +0 -4
  223. package/app/apps/web/.next/static/chunks/17u2ww8ya5dv1.js +0 -6
  224. /package/app/apps/web/.next/static/{2jcMG-92q2XKei8dN7BMd → tiK9aMHXkgnKLNmxj9J1P}/_buildManifest.js +0 -0
  225. /package/app/apps/web/.next/static/{2jcMG-92q2XKei8dN7BMd → tiK9aMHXkgnKLNmxj9J1P}/_clientMiddlewareManifest.js +0 -0
  226. /package/app/apps/web/.next/static/{2jcMG-92q2XKei8dN7BMd → tiK9aMHXkgnKLNmxj9J1P}/_ssgManifest.js +0 -0
@@ -21,6 +21,7 @@ import {
21
21
  import { formatRelative, prettyProjectName } from "@/lib/format";
22
22
  import { ThemeToggle } from "@/components/theme-toggle";
23
23
  import { UsageSidebar } from "@/components/usage-sidebar";
24
+ import { ChangelogIcon } from "@/components/changelog-icon";
24
25
  import type { UsageSnapshot } from "@/lib/usage-data";
25
26
 
26
27
  export type ProjectRef = {
@@ -59,11 +60,13 @@ export function Sidebar({
59
60
  totalSessions,
60
61
  currentUsage,
61
62
  version,
63
+ latestChangelogVersion,
62
64
  }: {
63
65
  projects: ProjectRef[];
64
66
  totalSessions: number;
65
67
  currentUsage: UsageSnapshot | null;
66
68
  version: string;
69
+ latestChangelogVersion: string | null;
67
70
  }) {
68
71
  const pathname = usePathname();
69
72
  const [pins, setPins] = useState<Set<string>>(new Set());
@@ -156,7 +159,7 @@ export function Sidebar({
156
159
  </span>
157
160
  </div>
158
161
  <div style={{ fontSize: 11, color: "var(--af-text-tertiary)" }}>
159
- Claude Code fleet analytics
162
+ Multi-agent fleet analytics
160
163
  </div>
161
164
  </div>
162
165
  </div>
@@ -283,6 +286,10 @@ export function Sidebar({
283
286
  <span style={{ flex: 1, minWidth: 0 }}>
284
287
  {totalSessions} sessions · {projects.length} projects
285
288
  </span>
289
+ <ChangelogIcon
290
+ latestVersion={latestChangelogVersion}
291
+ active={pathname === "/changelog"}
292
+ />
286
293
  <Link
287
294
  href="/settings"
288
295
  aria-label="Settings"
@@ -6,6 +6,7 @@ import { Check, Copy } from "lucide-react";
6
6
  import type { WeekDigest as WeekDigestType, DayHelpfulness, WeekTopSession, SessionPin } from "@claude-lens/entries";
7
7
  import { renderWithFlagChips } from "./flag-chip";
8
8
  import { GoalBar } from "./goal-bar";
9
+ import { AgentBadge } from "./agent-badge";
9
10
 
10
11
  const HELP_COLORS: Record<NonNullable<DayHelpfulness>, string> = {
11
12
  essential: "#48bb78",
@@ -1742,6 +1743,7 @@ function TopSessionCard({ session }: { session: WeekTopSession }) {
1742
1743
  >
1743
1744
  {session.project_display}
1744
1745
  </Link>
1746
+ <AgentBadge agent={session.agent} />
1745
1747
  <Link
1746
1748
  href={`/digest/${session.date}`}
1747
1749
  style={{
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Compact text summary of a parsed session, suitable as context for
3
- * the Ask Claude feature. Target: well under 8k tokens so Haiku has
3
+ * the Ask feature. Target: well under 8k tokens so Haiku has
4
4
  * room for an answer.
5
5
  *
6
6
  * Shape:
@@ -47,70 +47,10 @@ export type PredictedSeriesByKey = {
47
47
  seven_day_sonnet: { capturedAt: number; util: number }[];
48
48
  };
49
49
 
50
- // History of recent cycles for trend visuals. Each entry is one full cycle
51
- // the user lived through — `current: true` flags the live (in-progress)
52
- // cycle, the rest are completed. `source: "real"` = daemon observed the
53
- // peak, `source: "predicted"` = cycle predates daemon data and we fall
54
- // back to the JSONL-derived prediction (cold-start path).
55
- export type CyclePeak = {
56
- endsAt: string;
57
- peakPct: number;
58
- source: "real" | "predicted";
59
- current: boolean;
60
- };
61
-
62
- // Per-cycle peak history for a single window (5h or 7d). Used by the
63
- // dashboard's "previous cycles" trend strip — humans understand "last 4
64
- // weeks: 86%, 92%, 44%, 34% (current)" much better than a single number.
65
- // Limited to the last `maxCycles` (most recent) so the visual stays compact.
66
- export function previousCyclesTrend(
67
- dump: CalibrationDump | null,
68
- window: "5h" | "7d",
69
- maxCycles = 6,
70
- ): CyclePeak[] {
71
- if (!dump || dump.curve.length < 2) return [];
72
- const HOUR = 3_600_000;
73
- const cycleKey: "cycle_end_5h" | "cycle_end_7d" = window === "5h" ? "cycle_end_5h" : "cycle_end_7d";
74
- const predKey: "pred_5h" | "pred_7d" = window === "5h" ? "pred_5h" : "pred_7d";
75
- const realKey: "real_5h" | "real_7d" = window === "5h" ? "real_5h" : "real_7d";
76
-
77
- // Hour-round the cycle key so millisecond-jittered resets collapse.
78
- const byCycle = new Map<number, typeof dump.curve>();
79
- for (const p of dump.curve) {
80
- const k = p[cycleKey];
81
- if (!k) continue;
82
- const ms = Date.parse(k);
83
- if (Number.isNaN(ms)) continue;
84
- const bucket = Math.round(ms / HOUR) * HOUR;
85
- const arr = byCycle.get(bucket) ?? [];
86
- arr.push(p);
87
- byCycle.set(bucket, arr);
88
- }
89
-
90
- const nowMs = Date.now();
91
- const cycles: CyclePeak[] = [];
92
- for (const [endMs, points] of Array.from(byCycle.entries()).sort((a, b) => a[0] - b[0])) {
93
- // Take the max across BOTH real and predicted — when the daemon goes
94
- // dark before cycle close, predicted forward-extrapolation often
95
- // exceeds the last observed value, and the cycle's true peak is the
96
- // predicted close, not the last poll. `source` reflects which side won.
97
- let peak = 0;
98
- let source: "real" | "predicted" = "predicted";
99
- for (const p of points) {
100
- const r = p[realKey];
101
- if (typeof r === "number" && r > peak) { peak = r; source = "real"; }
102
- const v = p[predKey] ?? 0;
103
- if (v > peak) { peak = v; source = "predicted"; }
104
- }
105
- cycles.push({
106
- endsAt: new Date(endMs).toISOString(),
107
- peakPct: Math.round(peak * 10) / 10,
108
- source,
109
- current: endMs > nowMs,
110
- });
111
- }
112
- return cycles.slice(-maxCycles);
113
- }
50
+ // Re-export the pure cycle-peak helpers so existing call sites keep
51
+ // working. New tests import directly from ./cycle-peaks (no server-only).
52
+ export { previousCyclesTrend } from "./cycle-peaks";
53
+ export type { CyclePeak } from "./cycle-peaks";
114
54
 
115
55
  // Converts the calibration dump into the same shape UsageChart expects to
116
56
  // overlay on each burndown plot. seven_day_sonnet has no predictor today
@@ -0,0 +1,3 @@
1
+ // AUTOGENERATED by scripts/build-changelog.mjs from CHANGELOG.md.
2
+ // Do not edit by hand — run `pnpm changelog:sync`.
3
+ export const RAW_CHANGELOG = "# 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.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";
@@ -0,0 +1,67 @@
1
+ import { RAW_CHANGELOG } from "./changelog-data.generated";
2
+
3
+ export type ChangelogSection = {
4
+ /** "Added" | "Changed" | "Fixed" | etc. — verbatim from `### <kind>` heading. */
5
+ kind: string;
6
+ bullets: string[];
7
+ };
8
+
9
+ export type ChangelogEntry = {
10
+ version: string;
11
+ date: string | null;
12
+ sections: ChangelogSection[];
13
+ };
14
+
15
+ export const STORAGE_KEY = "cclens:changelog-last-seen";
16
+
17
+ const VERSION_HEADING = /^##\s+\[([^\]]+)\](?:\s+[—\-–]\s+(\S+))?\s*$/;
18
+ const SECTION_HEADING = /^###\s+(.+?)\s*$/;
19
+ const BULLET = /^[-*]\s+(.+?)\s*$/;
20
+
21
+ export function parseChangelog(raw: string): ChangelogEntry[] {
22
+ const lines = raw.split(/\r?\n/);
23
+ const entries: ChangelogEntry[] = [];
24
+ let current: ChangelogEntry | null = null;
25
+ let section: ChangelogSection | null = null;
26
+ let bulletBuf: string[] = [];
27
+
28
+ function flushBullet(): void {
29
+ if (section && bulletBuf.length > 0) {
30
+ section.bullets.push(bulletBuf.join(" ").trim());
31
+ bulletBuf = [];
32
+ }
33
+ }
34
+
35
+ for (const line of lines) {
36
+ const v = VERSION_HEADING.exec(line);
37
+ if (v) {
38
+ flushBullet();
39
+ section = null;
40
+ current = { version: v[1].trim(), date: v[2]?.trim() ?? null, sections: [] };
41
+ entries.push(current);
42
+ continue;
43
+ }
44
+ if (!current) continue;
45
+ const s = SECTION_HEADING.exec(line);
46
+ if (s) {
47
+ flushBullet();
48
+ section = { kind: s[1].trim(), bullets: [] };
49
+ current.sections.push(section);
50
+ continue;
51
+ }
52
+ const b = BULLET.exec(line);
53
+ if (b) {
54
+ flushBullet();
55
+ bulletBuf = [b[1]];
56
+ continue;
57
+ }
58
+ if (bulletBuf.length > 0 && line.trim().length > 0) {
59
+ bulletBuf.push(line.trim());
60
+ }
61
+ }
62
+ flushBullet();
63
+ return entries;
64
+ }
65
+
66
+ export const ENTRIES: ChangelogEntry[] = parseChangelog(RAW_CHANGELOG);
67
+ export const LATEST_VERSION: string | null = ENTRIES[0]?.version ?? null;
@@ -0,0 +1,185 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ previousCyclesTrend,
4
+ type CalibrationCurvePoint,
5
+ type CalibrationLike,
6
+ } from "./cycle-peaks";
7
+
8
+ function dump(curve: CalibrationCurvePoint[]): CalibrationLike {
9
+ return { curve };
10
+ }
11
+
12
+ function point(overrides: Partial<CalibrationCurvePoint>): CalibrationCurvePoint {
13
+ return {
14
+ ts: "2026-05-01T00:00:00.000Z",
15
+ real_5h: null,
16
+ pred_5h: 0,
17
+ real_7d: null,
18
+ pred_7d: 0,
19
+ cycle_end_5h: null,
20
+ cycle_end_7d: null,
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ const NOW = Date.parse("2026-05-08T00:00:00.000Z");
26
+
27
+ describe("previousCyclesTrend (7d)", () => {
28
+ it("merges two cycle ends within 12 h into a single cycle, latest endsAt + max peak", () => {
29
+ // Regression: Anthropic's rolling 7-day window slid its anchor between
30
+ // two polls on the same day, producing two distinct hour-buckets.
31
+ // Without the merge, /usage and the team-server both rendered two
32
+ // bars labeled the same day.
33
+ const result = previousCyclesTrend(
34
+ dump([
35
+ point({ cycle_end_7d: "2026-05-07T07:00:00.000Z", real_7d: 6 }),
36
+ point({ cycle_end_7d: "2026-05-07T09:00:00.000Z", real_7d: 91 }),
37
+ ]),
38
+ "7d",
39
+ 6,
40
+ NOW,
41
+ );
42
+ expect(result).toHaveLength(1);
43
+ expect(result[0].endsAt).toBe("2026-05-07T09:00:00.000Z");
44
+ expect(result[0].peakPct).toBe(91);
45
+ expect(result[0].source).toBe("real");
46
+ });
47
+
48
+ it("keeps cycles 7 d apart as distinct entries", () => {
49
+ const result = previousCyclesTrend(
50
+ dump([
51
+ point({ cycle_end_7d: "2026-04-30T12:00:00.000Z", real_7d: 50 }),
52
+ point({ cycle_end_7d: "2026-05-07T12:00:00.000Z", real_7d: 80 }),
53
+ ]),
54
+ "7d",
55
+ 6,
56
+ NOW,
57
+ );
58
+ expect(result).toHaveLength(2);
59
+ expect(result.map((r) => r.endsAt)).toEqual([
60
+ "2026-04-30T12:00:00.000Z",
61
+ "2026-05-07T12:00:00.000Z",
62
+ ]);
63
+ });
64
+
65
+ it("collapses millisecond-jitter on the same reset boundary", () => {
66
+ const result = previousCyclesTrend(
67
+ dump([
68
+ point({ cycle_end_7d: "2026-05-07T12:00:00.123Z", real_7d: 40 }),
69
+ point({ cycle_end_7d: "2026-05-07T12:00:00.999Z", real_7d: 60 }),
70
+ ]),
71
+ "7d",
72
+ 6,
73
+ NOW,
74
+ );
75
+ expect(result).toHaveLength(1);
76
+ expect(result[0].peakPct).toBe(60);
77
+ });
78
+
79
+ it("takes max across real AND predicted, preferring real when it wins", () => {
80
+ const result = previousCyclesTrend(
81
+ dump([
82
+ point({ cycle_end_7d: "2026-05-07T12:00:00.000Z", real_7d: 30, pred_7d: 20 }),
83
+ point({ cycle_end_7d: "2026-05-07T12:00:00.000Z", real_7d: 50, pred_7d: 40 }),
84
+ ]),
85
+ "7d",
86
+ 6,
87
+ NOW,
88
+ );
89
+ expect(result[0].peakPct).toBe(50);
90
+ expect(result[0].source).toBe("real");
91
+ });
92
+
93
+ it("falls back to predicted when no real value is recorded", () => {
94
+ const result = previousCyclesTrend(
95
+ dump([
96
+ point({ cycle_end_7d: "2026-05-07T12:00:00.000Z", pred_7d: 70 }),
97
+ point({ cycle_end_7d: "2026-05-07T12:00:00.000Z", pred_7d: 40 }),
98
+ ]),
99
+ "7d",
100
+ 6,
101
+ NOW,
102
+ );
103
+ expect(result[0].peakPct).toBe(70);
104
+ expect(result[0].source).toBe("predicted");
105
+ });
106
+
107
+ it("respects maxCycles by returning the most recent N", () => {
108
+ const result = previousCyclesTrend(
109
+ dump([
110
+ point({ cycle_end_7d: "2026-04-02T00:00:00.000Z", real_7d: 10 }),
111
+ point({ cycle_end_7d: "2026-04-09T00:00:00.000Z", real_7d: 20 }),
112
+ point({ cycle_end_7d: "2026-04-16T00:00:00.000Z", real_7d: 0.3 }),
113
+ point({ cycle_end_7d: "2026-04-23T00:00:00.000Z", real_7d: 40 }),
114
+ ]),
115
+ "7d",
116
+ 2,
117
+ NOW,
118
+ );
119
+ expect(result).toHaveLength(2);
120
+ expect(result.map((r) => r.endsAt)).toEqual([
121
+ "2026-04-16T00:00:00.000Z",
122
+ "2026-04-23T00:00:00.000Z",
123
+ ]);
124
+ });
125
+
126
+ it("flags the future-ending cycle as current", () => {
127
+ const result = previousCyclesTrend(
128
+ dump([
129
+ point({ cycle_end_7d: "2026-05-01T00:00:00.000Z", real_7d: 50 }),
130
+ point({ cycle_end_7d: "2026-05-09T00:00:00.000Z", real_7d: 0.7 }),
131
+ ]),
132
+ "7d",
133
+ 6,
134
+ NOW,
135
+ );
136
+ expect(result.find((r) => r.endsAt.startsWith("2026-05-01"))?.current).toBe(false);
137
+ expect(result.find((r) => r.endsAt.startsWith("2026-05-09"))?.current).toBe(true);
138
+ });
139
+
140
+ it("returns empty when the dump is null or has fewer than two points", () => {
141
+ expect(previousCyclesTrend(null, "7d", 6, NOW)).toEqual([]);
142
+ expect(
143
+ previousCyclesTrend(
144
+ dump([point({ cycle_end_7d: "2026-05-07T12:00:00.000Z" })]),
145
+ "7d",
146
+ 6,
147
+ NOW,
148
+ ),
149
+ ).toEqual([]);
150
+ });
151
+
152
+ it("skips points missing the cycle key", () => {
153
+ const result = previousCyclesTrend(
154
+ dump([
155
+ point({ cycle_end_7d: null, real_7d: 50 }),
156
+ point({ cycle_end_7d: "2026-05-07T12:00:00.000Z", real_7d: 40 }),
157
+ ]),
158
+ "7d",
159
+ 6,
160
+ NOW,
161
+ );
162
+ expect(result).toHaveLength(1);
163
+ expect(result[0].peakPct).toBe(40);
164
+ });
165
+ });
166
+
167
+ describe("previousCyclesTrend (5h)", () => {
168
+ it("keeps two 5h cycle ends as distinct entries (no merge tolerance for 5h)", () => {
169
+ const result = previousCyclesTrend(
170
+ dump([
171
+ point({ cycle_end_5h: "2026-05-07T10:00:00.000Z", real_5h: 30 }),
172
+ point({ cycle_end_5h: "2026-05-07T15:00:00.000Z", real_5h: 70 }),
173
+ ]),
174
+ "5h",
175
+ 6,
176
+ NOW,
177
+ );
178
+ expect(result).toHaveLength(2);
179
+ expect(result.map((r) => r.endsAt)).toEqual([
180
+ "2026-05-07T10:00:00.000Z",
181
+ "2026-05-07T15:00:00.000Z",
182
+ ]);
183
+ expect(result.map((r) => r.peakPct)).toEqual([30, 70]);
184
+ });
185
+ });
@@ -0,0 +1,104 @@
1
+ // Pure helpers for deriving per-cycle peak utilization from the daemon's
2
+ // calibration curve. Lives separately from calibration-data.ts so it can
3
+ // be imported in unit tests (calibration-data.ts pulls in `server-only`
4
+ // for its fs-touching helpers).
5
+
6
+ export type CalibrationCurvePoint = {
7
+ ts: string;
8
+ real_5h: number | null;
9
+ pred_5h: number;
10
+ real_7d: number | null;
11
+ pred_7d: number;
12
+ cycle_end_5h?: string | null;
13
+ cycle_end_7d?: string | null;
14
+ };
15
+
16
+ export type CalibrationLike = {
17
+ curve: CalibrationCurvePoint[];
18
+ };
19
+
20
+ export type CyclePeak = {
21
+ endsAt: string;
22
+ peakPct: number;
23
+ source: "real" | "predicted";
24
+ current: boolean;
25
+ };
26
+
27
+ const HOUR = 3_600_000;
28
+
29
+ /** History of recent cycles for trend visuals. Each entry is one full
30
+ * cycle — `current: true` for the in-progress cycle, completed otherwise.
31
+ * Limited to the last `maxCycles` so the visual stays compact. */
32
+ export function previousCyclesTrend(
33
+ dump: CalibrationLike | null,
34
+ window: "5h" | "7d",
35
+ maxCycles = 6,
36
+ nowMs: number = Date.now(),
37
+ ): CyclePeak[] {
38
+ if (!dump || dump.curve.length < 2) return [];
39
+ const cycleKey: "cycle_end_5h" | "cycle_end_7d" = window === "5h" ? "cycle_end_5h" : "cycle_end_7d";
40
+ const predKey: "pred_5h" | "pred_7d" = window === "5h" ? "pred_5h" : "pred_7d";
41
+ const realKey: "real_5h" | "real_7d" = window === "5h" ? "real_5h" : "real_7d";
42
+
43
+ // Hour-round the cycle key so millisecond-jittered resets collapse, then
44
+ // merge adjacent buckets within a window-specific tolerance. Anthropic's
45
+ // rolling 7-day window can slide its anchor by a few hours within the
46
+ // SAME cycle (different polls report different `seven_day.resets_at`
47
+ // values); without this merge, a cycle that slid would render as two
48
+ // bars labeled the same day. The 5-hour cycle's anchor is stable, so we
49
+ // don't merge there beyond hour-rounding — distinct 5h cycles would
50
+ // otherwise collapse.
51
+ const TOLERANCE_MS = window === "7d" ? 12 * HOUR : 0;
52
+ const byCycle = new Map<number, CalibrationCurvePoint[]>();
53
+ for (const p of dump.curve) {
54
+ const k = p[cycleKey];
55
+ if (!k) continue;
56
+ const ms = Date.parse(k);
57
+ if (Number.isNaN(ms)) continue;
58
+ const bucket = Math.round(ms / HOUR) * HOUR;
59
+ const arr = byCycle.get(bucket) ?? [];
60
+ arr.push(p);
61
+ byCycle.set(bucket, arr);
62
+ }
63
+ const merged = mergeAdjacentBuckets(byCycle, TOLERANCE_MS);
64
+
65
+ const cycles: CyclePeak[] = [];
66
+ for (const { endMs, points } of merged) {
67
+ let peak = 0;
68
+ let source: "real" | "predicted" = "predicted";
69
+ for (const p of points) {
70
+ const r = p[realKey];
71
+ if (typeof r === "number" && r > peak) { peak = r; source = "real"; }
72
+ const v = p[predKey] ?? 0;
73
+ if (v > peak) { peak = v; source = "predicted"; }
74
+ }
75
+ cycles.push({
76
+ endsAt: new Date(endMs).toISOString(),
77
+ peakPct: Math.round(peak * 10) / 10,
78
+ source,
79
+ current: endMs > nowMs,
80
+ });
81
+ }
82
+ return cycles.slice(-maxCycles);
83
+ }
84
+
85
+ /** Merge buckets whose end-times are within `toleranceMs` of each other.
86
+ * Output sorted ascending by `endMs`. Each merged bucket adopts the
87
+ * LATEST endMs (so the rendered label reflects the most recent reset
88
+ * boundary) and the union of points from all merged buckets. */
89
+ export function mergeAdjacentBuckets<P>(
90
+ byCycle: Map<number, P[]>,
91
+ toleranceMs: number,
92
+ ): Array<{ endMs: number; points: P[] }> {
93
+ const merged: Array<{ endMs: number; points: P[] }> = [];
94
+ for (const [endMs, points] of Array.from(byCycle.entries()).sort((a, b) => a[0] - b[0])) {
95
+ const last = merged[merged.length - 1];
96
+ if (last && endMs - last.endMs <= toleranceMs) {
97
+ last.endMs = endMs;
98
+ last.points.push(...points);
99
+ } else {
100
+ merged.push({ endMs, points: [...points] });
101
+ }
102
+ }
103
+ return merged;
104
+ }
@@ -1,21 +1,20 @@
1
1
  /**
2
2
  * Server-only data access layer.
3
3
  *
4
- * Wraps @claude-lens/parser/fs with a per-request cache so that a
5
- * single RSC render doesn't re-scan ~/.claude/projects for every page
6
- * component that needs the session list.
4
+ * Wraps @claude-lens/parser/fs with a per-request cache so a single RSC
5
+ * render doesn't re-scan the agent disk roots for every page that needs
6
+ * the session list. Multi-agent: iterates the parser's AgentSource
7
+ * registry, so adding a new agent means adding one source — no edits here.
7
8
  */
8
9
 
9
10
  import "server-only";
10
11
  import { cache } from "react";
11
- import { listSessions as rawListSessions, getSession as rawGetSession } from "@claude-lens/parser/fs";
12
-
13
- const dataRoot = process.env.CCLENS_DATA_DIR || undefined;
12
+ import { listAllSessions, getAnySession } from "@claude-lens/parser/fs";
14
13
 
15
14
  export const listSessions = cache(async () => {
16
- return rawListSessions({ limit: 1000, root: dataRoot });
15
+ return listAllSessions({ limit: 1000 });
17
16
  });
18
17
 
19
18
  export const getSession = cache(async (id: string) => {
20
- return rawGetSession(id, { root: dataRoot });
19
+ return getAnySession(id);
21
20
  });
@@ -7,11 +7,18 @@
7
7
 
8
8
  export type RangeKey = "7d" | "30d" | "90d" | "all";
9
9
 
10
- /** Parse a search-param value into a RangeKey (default: "all"). */
10
+ /**
11
+ * Parse a search-param value into a RangeKey (default: "90d").
12
+ *
13
+ * 90d is chosen as a sensible recent window for the heatmap and activity
14
+ * chart — large enough to show trends, small enough that one stale agent
15
+ * (Codex sessions back to last September, say) doesn't stretch the x-axis
16
+ * across months of empty days. Users can still pick "all" explicitly.
17
+ */
11
18
  export function parseRange(value: string | string[] | undefined): RangeKey {
12
19
  const v = Array.isArray(value) ? value[0] : value;
13
20
  if (v === "7d" || v === "30d" || v === "90d" || v === "all") return v;
14
- return "all";
21
+ return "90d";
15
22
  }
16
23
 
17
24
  /**
@@ -9,6 +9,7 @@ import { cache } from "react";
9
9
  import { existsSync, readFileSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { homedir } from "node:os";
12
+ import type { AgentKind } from "@claude-lens/parser";
12
13
 
13
14
  export type UsageWindow = {
14
15
  utilization: number | null;
@@ -17,6 +18,9 @@ export type UsageWindow = {
17
18
 
18
19
  export type UsageSnapshot = {
19
20
  captured_at: string;
21
+ /** Source agent. Absent on legacy snapshots written before multi-agent
22
+ * support — readers MUST treat undefined as "claude-code". */
23
+ agent?: AgentKind;
20
24
  five_hour: UsageWindow;
21
25
  seven_day: UsageWindow;
22
26
  seven_day_opus: UsageWindow | null;
@@ -29,6 +33,7 @@ export type UsageSnapshot = {
29
33
  used_credits: number | null;
30
34
  utilization: number | null;
31
35
  } | null;
36
+ plan_type?: string | null;
32
37
  };
33
38
 
34
39
  function usageLogPath(): string {
@@ -53,9 +58,27 @@ export const readUsageSnapshots = cache((): UsageSnapshot[] => {
53
58
 
54
59
  export const latestUsageSnapshot = cache((): UsageSnapshot | null => {
55
60
  const all = readUsageSnapshots();
56
- return all.length > 0 ? all[all.length - 1]! : null;
61
+ // Sidebar's "current usage" widget is Claude-tier specific (extra_usage,
62
+ // per-model 7d windows). Filter to Claude only so a Codex snapshot doesn't
63
+ // accidentally end up as the latest source.
64
+ const claude = all.filter((s) => (s.agent ?? "claude-code") === "claude-code");
65
+ return claude.length > 0 ? claude[claude.length - 1]! : null;
57
66
  });
58
67
 
68
+ export const latestUsageSnapshotByAgent = cache(
69
+ (agent: AgentKind): UsageSnapshot | null => {
70
+ const all = readUsageSnapshots();
71
+ const filtered = all.filter((s) => (s.agent ?? "claude-code") === agent);
72
+ return filtered.length > 0 ? filtered[filtered.length - 1]! : null;
73
+ },
74
+ );
75
+
76
+ export const readUsageSnapshotsByAgent = cache(
77
+ (agent: AgentKind): UsageSnapshot[] => {
78
+ return readUsageSnapshots().filter((s) => (s.agent ?? "claude-code") === agent);
79
+ },
80
+ );
81
+
59
82
  // The CLI's profile cache mirrors what we report to a paired team-server.
60
83
  // Reading it here keeps personal and team editions consistent — both show
61
84
  // the same "what tier are we?" answer.
@@ -4,6 +4,7 @@ export const SettingsUpdateSchema = z.object({
4
4
  ai_features: z.object({
5
5
  enabled: z.boolean().optional(),
6
6
  autoBackfillLastWeek: z.boolean().optional(),
7
+ autoBackfillYesterday: z.boolean().optional(),
7
8
  }).refine((v) => Object.keys(v).length > 0, {
8
9
  message: "ai_features must include at least one field",
9
10
  }),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-lens/web",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "private": true,
5
5
  "description": "Local-only dashboard for Claude Code sessions. Reads ~/.claude/projects/*.jsonl and visualizes agent activity.",
6
6
  "license": "MIT",