fleetlens 0.3.0 → 0.4.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 (210) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/app-path-routes-manifest.json +9 -0
  3. package/app/apps/web/.next/build-manifest.json +6 -6
  4. package/app/apps/web/.next/prerender-manifest.json +3 -3
  5. package/app/apps/web/.next/routes-manifest.json +62 -0
  6. package/app/apps/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
  7. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/app/apps/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
  15. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next/server/app/api/ask/route.js +3 -2
  18. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  19. package/app/apps/web/.next/server/app/api/events/route.js +1 -0
  20. package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
  21. package/app/apps/web/.next/server/app/api/insights/months-index/route/app-paths-manifest.json +3 -0
  22. package/app/apps/web/.next/server/app/api/insights/months-index/route/build-manifest.json +9 -0
  23. package/app/apps/web/.next/server/app/api/insights/months-index/route/server-reference-manifest.json +4 -0
  24. package/app/apps/web/.next/server/app/api/insights/months-index/route.js +7 -0
  25. package/app/apps/web/.next/server/app/api/insights/months-index/route.js.map +5 -0
  26. package/app/apps/web/.next/server/app/api/insights/months-index/route.js.nft.json +1 -0
  27. package/app/apps/web/.next/server/app/api/insights/months-index/route_client-reference-manifest.js +3 -0
  28. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/app-paths-manifest.json +3 -0
  29. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/build-manifest.json +9 -0
  30. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/server-reference-manifest.json +4 -0
  31. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js +6 -0
  32. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.map +5 -0
  33. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.nft.json +1 -0
  34. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route_client-reference-manifest.js +3 -0
  35. package/app/apps/web/.next/server/app/api/insights/route/app-paths-manifest.json +3 -0
  36. package/app/apps/web/.next/server/app/api/insights/route/build-manifest.json +9 -0
  37. package/app/apps/web/.next/server/app/api/insights/route/server-reference-manifest.json +4 -0
  38. package/app/apps/web/.next/server/app/api/insights/route.js +8 -0
  39. package/app/apps/web/.next/server/app/api/insights/route.js.map +5 -0
  40. package/app/apps/web/.next/server/app/api/insights/route.js.nft.json +1 -0
  41. package/app/apps/web/.next/server/app/api/insights/route_client-reference-manifest.js +3 -0
  42. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/app-paths-manifest.json +3 -0
  43. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/build-manifest.json +9 -0
  44. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/server-reference-manifest.json +4 -0
  45. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js +6 -0
  46. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.map +5 -0
  47. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.nft.json +1 -0
  48. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route_client-reference-manifest.js +3 -0
  49. package/app/apps/web/.next/server/app/api/insights/saved/route/app-paths-manifest.json +3 -0
  50. package/app/apps/web/.next/server/app/api/insights/saved/route/build-manifest.json +9 -0
  51. package/app/apps/web/.next/server/app/api/insights/saved/route/server-reference-manifest.json +4 -0
  52. package/app/apps/web/.next/server/app/api/insights/saved/route.js +6 -0
  53. package/app/apps/web/.next/server/app/api/insights/saved/route.js.map +5 -0
  54. package/app/apps/web/.next/server/app/api/insights/saved/route.js.nft.json +1 -0
  55. package/app/apps/web/.next/server/app/api/insights/saved/route_client-reference-manifest.js +3 -0
  56. package/app/apps/web/.next/server/app/api/insights/weeks-index/route/app-paths-manifest.json +3 -0
  57. package/app/apps/web/.next/server/app/api/insights/weeks-index/route/build-manifest.json +9 -0
  58. package/app/apps/web/.next/server/app/api/insights/weeks-index/route/server-reference-manifest.json +4 -0
  59. package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js +7 -0
  60. package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.map +5 -0
  61. package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.nft.json +1 -0
  62. package/app/apps/web/.next/server/app/api/insights/weeks-index/route_client-reference-manifest.js +3 -0
  63. package/app/apps/web/.next/server/app/insights/[key]/page/app-paths-manifest.json +3 -0
  64. package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +17 -0
  65. package/app/apps/web/.next/server/app/insights/[key]/page/next-font-manifest.json +6 -0
  66. package/app/apps/web/.next/server/app/insights/[key]/page/react-loadable-manifest.json +1 -0
  67. package/app/apps/web/.next/server/app/insights/[key]/page/server-reference-manifest.json +4 -0
  68. package/app/apps/web/.next/server/app/insights/[key]/page.js +16 -0
  69. package/app/apps/web/.next/server/app/insights/[key]/page.js.map +5 -0
  70. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -0
  71. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +3 -0
  72. package/app/apps/web/.next/server/app/insights/page/app-paths-manifest.json +3 -0
  73. package/app/apps/web/.next/server/app/insights/page/build-manifest.json +17 -0
  74. package/app/apps/web/.next/server/app/insights/page/next-font-manifest.json +6 -0
  75. package/app/apps/web/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
  76. package/app/apps/web/.next/server/app/insights/page/server-reference-manifest.json +4 -0
  77. package/app/apps/web/.next/server/app/insights/page.js +16 -0
  78. package/app/apps/web/.next/server/app/insights/page.js.map +5 -0
  79. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -0
  80. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +3 -0
  81. package/app/apps/web/.next/server/app/insights/print/[key]/page/app-paths-manifest.json +3 -0
  82. package/app/apps/web/.next/server/app/insights/print/[key]/page/build-manifest.json +17 -0
  83. package/app/apps/web/.next/server/app/insights/print/[key]/page/next-font-manifest.json +6 -0
  84. package/app/apps/web/.next/server/app/insights/print/[key]/page/react-loadable-manifest.json +1 -0
  85. package/app/apps/web/.next/server/app/insights/print/[key]/page/server-reference-manifest.json +4 -0
  86. package/app/apps/web/.next/server/app/insights/print/[key]/page.js +16 -0
  87. package/app/apps/web/.next/server/app/insights/print/[key]/page.js.map +5 -0
  88. package/app/apps/web/.next/server/app/insights/print/[key]/page.js.nft.json +1 -0
  89. package/app/apps/web/.next/server/app/insights/print/[key]/page_client-reference-manifest.js +3 -0
  90. package/app/apps/web/.next/server/app/page/build-manifest.json +3 -3
  91. package/app/apps/web/.next/server/app/page.js +1 -1
  92. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  93. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  94. package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +3 -3
  95. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  96. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  97. package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +3 -3
  98. package/app/apps/web/.next/server/app/projects/[slug]/page.js +1 -2
  99. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  100. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  101. package/app/apps/web/.next/server/app/projects/page/build-manifest.json +3 -3
  102. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  103. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  104. package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +3 -3
  105. package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -2
  106. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  107. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  108. package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +3 -3
  109. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  110. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  111. package/app/apps/web/.next/server/app/usage/page/build-manifest.json +3 -3
  112. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  113. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  114. package/app/apps/web/.next/server/app-paths-manifest.json +9 -0
  115. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_months-index_route_actions_0~h~zci.js +3 -0
  116. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_saved_[key]_route_actions_0a5j9yw.js +3 -0
  117. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_weeks-index_route_actions_0hh-1c7.js +3 -0
  118. package/app/apps/web/.next/server/chunks/{[externals]__0rv9ct5._.js → [externals]__0_xztrf._.js} +2 -2
  119. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0.hmj0z._.js +3 -0
  120. package/app/apps/web/.next/server/chunks/[root-of-the-server]__01h_g-p._.js +3 -0
  121. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0nh-z10._.js +3 -0
  122. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0op9af~._.js +1 -1
  123. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0ri7q~r._.js +3 -0
  124. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0rr1~~x._.js +3 -0
  125. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0s0gv.0._.js +3 -0
  126. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_pdf_[key]_route_actions_0lu-lnx.js +3 -0
  127. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_route_actions_0h8l5lz.js +3 -0
  128. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_saved_route_actions_13rlblw.js +3 -0
  129. package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_00t2xj6.js +68 -0
  130. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0sgyieh._.js → node_modules_next_dist_esm_build_templates_app-route_0ck.78s.js} +5 -5
  131. package/app/apps/web/.next/server/chunks/packages_parser_dist_fs_0bc7ptm.js +3 -0
  132. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__00z7zlm._.js +1 -1
  133. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08.d5ia._.js +12 -0
  134. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b323~k._.js +8 -0
  135. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0euu8sa._.js +3 -0
  136. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gio_ck._.js +3 -0
  137. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gx-4qa._.js +3 -0
  138. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0w52fbc._.js +3 -3
  139. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0z45d0g._.js +49 -0
  140. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__13itnvx._.js +3 -0
  141. package/app/apps/web/.next/server/chunks/ssr/_0r~__0v._.js +12 -0
  142. package/app/apps/web/.next/server/chunks/ssr/_0xies90._.js +1 -1
  143. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_[key]_page_actions_0_z2~e_.js +3 -0
  144. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_page_actions_0zjdm~n.js +3 -0
  145. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_print_[key]_page_actions_0a853fw.js +3 -0
  146. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_06j3c.0._.js +1 -1
  147. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insight-report_tsx_13q2a-c._.js +12 -0
  148. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insights-view_tsx_11730ta._.js +6 -0
  149. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +3 -0
  150. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0nqz8dw._.js +3 -0
  151. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +3 -0
  152. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0vz7_px.js +4 -0
  153. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10cdrh2.js +4 -0
  154. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12n1lw_.js +4 -0
  155. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0-jvt~a._.js +1 -1
  156. package/app/apps/web/.next/server/functions-config-manifest.json +7 -1
  157. package/app/apps/web/.next/server/middleware-build-manifest.js +6 -6
  158. package/app/apps/web/.next/server/pages/500.html +1 -1
  159. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  160. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  161. package/app/apps/web/.next/static/chunks/03_7xtpi9sv4g.js +4 -0
  162. package/app/apps/web/.next/static/chunks/08c1pcrocqq5s.js +6 -0
  163. package/app/apps/web/.next/static/chunks/0e-wrhqh4h3r5.js +2 -0
  164. package/app/apps/web/.next/static/chunks/0i7er7_bfab36.js +10 -0
  165. package/app/apps/web/.next/static/chunks/0ipytyi01h0ta.js +1 -0
  166. package/app/apps/web/.next/static/chunks/0u-n8fz-ms.ee.js +6 -0
  167. package/app/apps/web/.next/static/chunks/137uw2-qbro07.js +31 -0
  168. package/app/apps/web/.next/static/chunks/{0p1lpoh02zh8h.js → 15w5_bct86ut6.js} +3 -3
  169. package/app/apps/web/.next/static/chunks/{turbopack-17n9y8y0h4q9f.js → turbopack-169w-1._23ra_.js} +1 -1
  170. package/app/apps/web/app/api/insights/months-index/route.ts +80 -0
  171. package/app/apps/web/app/api/insights/pdf/[key]/route.ts +45 -0
  172. package/app/apps/web/app/api/insights/route.ts +508 -0
  173. package/app/apps/web/app/api/insights/saved/[key]/route.ts +11 -0
  174. package/app/apps/web/app/api/insights/saved/route.ts +9 -0
  175. package/app/apps/web/app/api/insights/weeks-index/route.ts +95 -0
  176. package/app/apps/web/app/insights/[key]/page.tsx +59 -0
  177. package/app/apps/web/app/insights/page.tsx +7 -0
  178. package/app/apps/web/app/insights/print/[key]/page.tsx +73 -0
  179. package/app/apps/web/app/sessions/[id]/session-view.tsx +450 -21
  180. package/app/apps/web/components/insight-report.tsx +1031 -0
  181. package/app/apps/web/components/insights-view.tsx +647 -0
  182. package/app/apps/web/components/live-refresher.tsx +11 -2
  183. package/app/apps/web/components/sidebar.tsx +8 -0
  184. package/app/apps/web/eslint.config.mjs +2 -0
  185. package/app/apps/web/lib/ai/chrome-pdf.ts +64 -0
  186. package/app/apps/web/lib/ai/insights-prompt.ts +71 -0
  187. package/app/apps/web/lib/ai/saved-reports.ts +83 -0
  188. package/app/apps/web/lib/date-range.ts +11 -2
  189. package/app/apps/web/lib/use-live-events.ts +7 -2
  190. package/app/apps/web/package.json +3 -2
  191. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  192. package/dist/daemon-worker.js +2852 -11
  193. package/dist/index.js +3091 -268
  194. package/package.json +5 -3
  195. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__05lbtny._.js +0 -12
  196. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ms7h0~._.js +0 -8
  197. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0p~g4m6._.js +0 -3
  198. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0wvzyl7._.js +0 -3
  199. package/app/apps/web/.next/server/chunks/ssr/_0ph19-k._.js +0 -12
  200. package/app/apps/web/.next/server/chunks/ssr/_0u29mdd._.js +0 -3
  201. package/app/apps/web/.next/server/chunks/ssr/node_modules_0i2xw~e._.js +0 -3
  202. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0.knjn.._.js +0 -3
  203. package/app/apps/web/.next/static/chunks/0257pdz1-imal.js +0 -2
  204. package/app/apps/web/.next/static/chunks/08983lz4~hffx.js +0 -6
  205. package/app/apps/web/.next/static/chunks/0qmh881w772ef.js +0 -31
  206. package/app/apps/web/.next/static/chunks/0wdga7zbwvg70.js +0 -1
  207. package/app/apps/web/.next/static/chunks/14q3xsfpkvuef.js +0 -6
  208. /package/app/apps/web/.next/static/{0fPDRTdMQ-Y62h6fHNJXL → 95-RytHk7ZQtfpZj5BLST}/_buildManifest.js +0 -0
  209. /package/app/apps/web/.next/static/{0fPDRTdMQ-Y62h6fHNJXL → 95-RytHk7ZQtfpZj5BLST}/_clientMiddlewareManifest.js +0 -0
  210. /package/app/apps/web/.next/static/{0fPDRTdMQ-Y62h6fHNJXL → 95-RytHk7ZQtfpZj5BLST}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -454,9 +454,9 @@ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath
454
454
  console.warn(
455
455
  ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
456
456
  );
457
- } else if (verify.installedVersion !== "0.3.0") {
457
+ } else if (verify.installedVersion !== "0.4.1") {
458
458
  console.log(
459
- ` \u2192 This process is still running ${"0.3.0"}. Next invocation will use ${verify.installedVersion}.`
459
+ ` \u2192 This process is still running ${"0.4.1"}. Next invocation will use ${verify.installedVersion}.`
460
460
  );
461
461
  }
462
462
  }
@@ -492,7 +492,7 @@ async function checkForUpdate() {
492
492
  if (process.env.__FLEETLENS_UPDATED === "1" || process.env.__CCLENS_UPDATED === "1") return;
493
493
  const latest = await fetchLatestVersion();
494
494
  if (latest === null) return;
495
- const current = "0.3.0";
495
+ const current = "0.4.1";
496
496
  if (!shouldUpdate(current, latest)) return;
497
497
  console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
498
498
  await stopRunningServices();
@@ -574,7 +574,7 @@ async function restartServices(state) {
574
574
  }
575
575
  async function forceUpdate() {
576
576
  const latest = await fetchLatestVersion();
577
- const current = "0.3.0";
577
+ const current = "0.4.1";
578
578
  if (latest === null) {
579
579
  console.error("Could not reach npm registry. Check your network.");
580
580
  process.exit(1);
@@ -851,7 +851,7 @@ function toEvent(raw, index) {
851
851
  const stopReason = msg.stop_reason;
852
852
  const first = content[0];
853
853
  let role = "agent";
854
- let preview = "";
854
+ let preview;
855
855
  let toolName;
856
856
  let toolUseId;
857
857
  if (first?.type === "thinking") {
@@ -1018,6 +1018,91 @@ function parseTranscript(rawLines) {
1018
1018
  }
1019
1019
  activeSegments.push({ startMs: segStart, endMs: segEnd });
1020
1020
  }
1021
+ const COLD_GAP_MS = 5 * 60 * 1e3;
1022
+ const COLD_MIN_WRITE = 1e3;
1023
+ const COLD_MIN_RATIO = 0.3;
1024
+ const buckets = /* @__PURE__ */ new Map();
1025
+ const compactBoundaries = [];
1026
+ for (const e of events) {
1027
+ if (!e.timestamp)
1028
+ continue;
1029
+ const ms = Date.parse(e.timestamp);
1030
+ if (Number.isNaN(ms))
1031
+ continue;
1032
+ if (e.rawType === "assistant" && e.usage && e.messageId) {
1033
+ const b = buckets.get(e.messageId);
1034
+ if (b) {
1035
+ b.siblings.push(e);
1036
+ if (ms < b.ms) {
1037
+ b.rep = e;
1038
+ b.ms = ms;
1039
+ }
1040
+ } else {
1041
+ buckets.set(e.messageId, { rep: e, ms, siblings: [e] });
1042
+ }
1043
+ } else if (e.rawType === "system") {
1044
+ const r = e.raw;
1045
+ if (r?.subtype !== "compact_boundary")
1046
+ continue;
1047
+ const meta = r.compactMetadata ?? {};
1048
+ compactBoundaries.push({
1049
+ ms,
1050
+ trigger: meta.trigger === "manual" ? "manual" : "auto",
1051
+ preTokens: typeof meta.preTokens === "number" ? meta.preTokens : 0
1052
+ });
1053
+ }
1054
+ }
1055
+ const assistantChrono = [...buckets.values()].sort((a, b) => a.ms - b.ms);
1056
+ compactBoundaries.sort((a, b) => a.ms - b.ms);
1057
+ let coldResumeCount = 0;
1058
+ let cacheRebuildTokens = 0;
1059
+ const applyFlag = (bucket, flag2) => {
1060
+ for (const sib of bucket.siblings)
1061
+ sib.coldResume = flag2;
1062
+ coldResumeCount++;
1063
+ cacheRebuildTokens += flag2.writeTokens;
1064
+ };
1065
+ const flagged = /* @__PURE__ */ new Set();
1066
+ for (const b of compactBoundaries) {
1067
+ const after = assistantChrono.find((a) => a.ms > b.ms && (a.rep.usage?.cacheWrite ?? 0) >= COLD_MIN_WRITE);
1068
+ if (!after)
1069
+ continue;
1070
+ let prevMs;
1071
+ for (let i = assistantChrono.length - 1; i >= 0; i--) {
1072
+ if (assistantChrono[i].ms <= b.ms) {
1073
+ prevMs = assistantChrono[i].ms;
1074
+ break;
1075
+ }
1076
+ }
1077
+ const u = after.rep.usage;
1078
+ const denom = u.cacheWrite + u.cacheRead;
1079
+ applyFlag(after, {
1080
+ trigger: "compact",
1081
+ gapMs: prevMs !== void 0 ? after.ms - prevMs : 0,
1082
+ writeTokens: u.cacheWrite,
1083
+ writeRatio: denom > 0 ? u.cacheWrite / denom : 0,
1084
+ compact: { trigger: b.trigger, preTokens: b.preTokens }
1085
+ });
1086
+ flagged.add(after.rep.messageId);
1087
+ }
1088
+ for (let i = 1; i < assistantChrono.length; i++) {
1089
+ const prev = assistantChrono[i - 1];
1090
+ const cur = assistantChrono[i];
1091
+ if (flagged.has(cur.rep.messageId))
1092
+ continue;
1093
+ const u = cur.rep.usage;
1094
+ const denom = u.cacheWrite + u.cacheRead;
1095
+ const writeRatio = denom > 0 ? u.cacheWrite / denom : 0;
1096
+ const gapMs = cur.ms - prev.ms;
1097
+ if (gapMs > COLD_GAP_MS && u.cacheWrite >= COLD_MIN_WRITE && writeRatio >= COLD_MIN_RATIO) {
1098
+ applyFlag(cur, {
1099
+ trigger: "idle",
1100
+ gapMs,
1101
+ writeTokens: u.cacheWrite,
1102
+ writeRatio
1103
+ });
1104
+ }
1105
+ }
1021
1106
  const totalUsage = { ...BLANK_USAGE };
1022
1107
  const seenMessageIds = /* @__PURE__ */ new Set();
1023
1108
  let model;
@@ -1141,6 +1226,8 @@ function parseTranscript(rawLines) {
1141
1226
  linesAdded,
1142
1227
  linesRemoved,
1143
1228
  filesEdited: filesEdited.size,
1229
+ coldResumeCount,
1230
+ cacheRebuildTokens,
1144
1231
  teamName,
1145
1232
  agentName,
1146
1233
  isTeamLead: teamName !== void 0 && agentName === void 0 && (hasTeamCreate || hasOutboundDispatch)
@@ -1157,13 +1244,622 @@ var init_parser = __esm({
1157
1244
  });
1158
1245
 
1159
1246
  // ../parser/dist/analytics.js
1247
+ function toLocalDay(ms) {
1248
+ const d = new Date(ms);
1249
+ const y = d.getFullYear();
1250
+ const m = String(d.getMonth() + 1).padStart(2, "0");
1251
+ const day = String(d.getDate()).padStart(2, "0");
1252
+ return `${y}-${m}-${day}`;
1253
+ }
1254
+ function sessionDay(meta) {
1255
+ if (!meta.firstTimestamp)
1256
+ return void 0;
1257
+ const ms = Date.parse(meta.firstTimestamp);
1258
+ if (Number.isNaN(ms))
1259
+ return void 0;
1260
+ return toLocalDay(ms);
1261
+ }
1262
+ function findWorktreeMarker(projectName) {
1263
+ for (const marker of WORKTREE_MARKERS) {
1264
+ const idx = projectName.lastIndexOf(marker);
1265
+ if (idx >= 0)
1266
+ return { idx, marker };
1267
+ }
1268
+ return null;
1269
+ }
1270
+ function canonicalProjectName(projectName) {
1271
+ const hit = findWorktreeMarker(projectName);
1272
+ if (hit)
1273
+ return projectName.slice(0, hit.idx);
1274
+ return projectName;
1275
+ }
1276
+ function worktreeName(projectName) {
1277
+ const hit = findWorktreeMarker(projectName);
1278
+ if (!hit)
1279
+ return null;
1280
+ const tail = projectName.slice(hit.idx + hit.marker.length);
1281
+ const slash = tail.indexOf("/");
1282
+ return slash >= 0 ? tail.slice(0, slash) : tail;
1283
+ }
1284
+ function makeDay(date) {
1285
+ return {
1286
+ date,
1287
+ sessions: 0,
1288
+ toolCalls: 0,
1289
+ turns: 0,
1290
+ tokens: { ...BLANK_USAGE2 },
1291
+ durationMs: 0,
1292
+ airTimeMs: 0,
1293
+ peakParallelism: 0
1294
+ };
1295
+ }
1296
+ function daysBetween(startDay, endDay) {
1297
+ const [ys, ms, ds] = startDay.split("-").map(Number);
1298
+ const [ye, me, de] = endDay.split("-").map(Number);
1299
+ const start2 = new Date(ys, ms - 1, ds);
1300
+ const end = new Date(ye, me - 1, de);
1301
+ const out = [];
1302
+ for (let d = new Date(start2); d <= end; d.setDate(d.getDate() + 1)) {
1303
+ out.push(toLocalDay(d.getTime()));
1304
+ }
1305
+ return out;
1306
+ }
1307
+ function dailyActivity(sessions) {
1308
+ if (sessions.length === 0)
1309
+ return [];
1310
+ const byDay = /* @__PURE__ */ new Map();
1311
+ let minDay;
1312
+ let maxDay;
1313
+ const touchBucket = (day) => {
1314
+ if (!minDay || day < minDay)
1315
+ minDay = day;
1316
+ if (!maxDay || day > maxDay)
1317
+ maxDay = day;
1318
+ const b = byDay.get(day) ?? makeDay(day);
1319
+ byDay.set(day, b);
1320
+ return b;
1321
+ };
1322
+ for (const s of sessions) {
1323
+ const startDay = sessionDay(s);
1324
+ if (startDay) {
1325
+ const bucket = touchBucket(startDay);
1326
+ bucket.toolCalls += s.toolCallCount ?? 0;
1327
+ bucket.turns += s.turnCount ?? 0;
1328
+ bucket.tokens.input += s.totalUsage.input;
1329
+ bucket.tokens.output += s.totalUsage.output;
1330
+ bucket.tokens.cacheRead += s.totalUsage.cacheRead;
1331
+ bucket.tokens.cacheWrite += s.totalUsage.cacheWrite;
1332
+ bucket.durationMs += s.durationMs ?? 0;
1333
+ }
1334
+ const segments = s.activeSegments;
1335
+ if (segments && segments.length > 0) {
1336
+ const touchedDays = /* @__PURE__ */ new Set();
1337
+ for (const seg of segments) {
1338
+ let cur = seg.startMs;
1339
+ while (cur < seg.endMs) {
1340
+ const day = toLocalDay(cur);
1341
+ touchedDays.add(day);
1342
+ const d = new Date(cur);
1343
+ d.setHours(24, 0, 0, 0);
1344
+ const dayEndMs = Math.min(d.getTime(), seg.endMs);
1345
+ const bucket = touchBucket(day);
1346
+ bucket.airTimeMs += dayEndMs - cur;
1347
+ cur = dayEndMs;
1348
+ }
1349
+ }
1350
+ for (const day of touchedDays) {
1351
+ const bucket = touchBucket(day);
1352
+ bucket.sessions++;
1353
+ }
1354
+ } else if (startDay) {
1355
+ const bucket = touchBucket(startDay);
1356
+ bucket.sessions++;
1357
+ bucket.airTimeMs += s.airTimeMs ?? 0;
1358
+ }
1359
+ }
1360
+ const range = minDay && maxDay ? daysBetween(minDay, maxDay) : [];
1361
+ const out = [];
1362
+ for (const day of range) {
1363
+ out.push(byDay.get(day) ?? makeDay(day));
1364
+ }
1365
+ const parallelism = computeParallelism(sessions);
1366
+ const byDayPar = /* @__PURE__ */ new Map();
1367
+ for (const p of parallelism) {
1368
+ const day = toLocalDay(p.atMs);
1369
+ const cur = byDayPar.get(day) ?? 0;
1370
+ if (p.active > cur)
1371
+ byDayPar.set(day, p.active);
1372
+ }
1373
+ for (const b of out) {
1374
+ b.peakParallelism = byDayPar.get(b.date) ?? 0;
1375
+ }
1376
+ return out;
1377
+ }
1378
+ function computeParallelism(sessions) {
1379
+ const events = [];
1380
+ for (const s of sessions) {
1381
+ const segs = s.activeSegments && s.activeSegments.length > 0 ? s.activeSegments : s.firstTimestamp && s.lastTimestamp ? (() => {
1382
+ const start2 = Date.parse(s.firstTimestamp);
1383
+ const end = Date.parse(s.lastTimestamp);
1384
+ if (Number.isNaN(start2) || Number.isNaN(end) || end < start2)
1385
+ return [];
1386
+ return [{ startMs: start2, endMs: end }];
1387
+ })() : [];
1388
+ for (const seg of segs) {
1389
+ events.push({ ms: seg.startMs, delta: 1, sessionId: s.id });
1390
+ events.push({ ms: seg.endMs, delta: -1, sessionId: s.id });
1391
+ }
1392
+ }
1393
+ events.sort((a, b) => a.ms - b.ms || b.delta - a.delta);
1394
+ const segCount = /* @__PURE__ */ new Map();
1395
+ const points = [];
1396
+ for (const e of events) {
1397
+ if (e.delta === 1) {
1398
+ segCount.set(e.sessionId, (segCount.get(e.sessionId) ?? 0) + 1);
1399
+ } else {
1400
+ const n = (segCount.get(e.sessionId) ?? 0) - 1;
1401
+ if (n <= 0)
1402
+ segCount.delete(e.sessionId);
1403
+ else
1404
+ segCount.set(e.sessionId, n);
1405
+ }
1406
+ points.push({
1407
+ atMs: e.ms,
1408
+ active: segCount.size,
1409
+ sessions: Array.from(segCount.keys()).slice(0, 10)
1410
+ });
1411
+ }
1412
+ return points;
1413
+ }
1414
+ function detectParallelRuns(sessions, minActive = 2) {
1415
+ const points = computeParallelism(sessions);
1416
+ const runs = [];
1417
+ let cur = null;
1418
+ for (const p of points) {
1419
+ if (p.active >= minActive) {
1420
+ if (!cur) {
1421
+ cur = { startMs: p.atMs, endMs: p.atMs, peak: p.active, sessions: [...p.sessions] };
1422
+ } else {
1423
+ cur.endMs = p.atMs;
1424
+ if (p.active > cur.peak)
1425
+ cur.peak = p.active;
1426
+ for (const sid of p.sessions) {
1427
+ if (!cur.sessions.includes(sid))
1428
+ cur.sessions.push(sid);
1429
+ }
1430
+ }
1431
+ } else if (cur) {
1432
+ cur.endMs = p.atMs;
1433
+ runs.push(cur);
1434
+ cur = null;
1435
+ }
1436
+ }
1437
+ if (cur)
1438
+ runs.push(cur);
1439
+ return runs;
1440
+ }
1441
+ function detectPrMarkers(session) {
1442
+ const out = [];
1443
+ const total = session.durationMs ?? 0;
1444
+ for (const e of session.events) {
1445
+ if (e.role !== "tool-call" || e.toolName !== "Bash")
1446
+ continue;
1447
+ const toolUseBlock = e.blocks.find((b) => b && "type" in b && b.type === "tool_use");
1448
+ if (!toolUseBlock || toolUseBlock.type !== "tool_use")
1449
+ continue;
1450
+ const input = toolUseBlock.input;
1451
+ const command2 = typeof input?.command === "string" ? input.command : "";
1452
+ if (!PR_CREATE_RE.test(command2))
1453
+ continue;
1454
+ const titleMatch = command2.match(TITLE_FLAG_RE);
1455
+ const numberMatch = command2.match(PR_NUMBER_RE);
1456
+ out.push({
1457
+ sessionId: session.id,
1458
+ timestamp: e.timestamp ?? session.firstTimestamp ?? "",
1459
+ tOffsetMs: e.tOffsetMs,
1460
+ positionInSession: total > 0 && e.tOffsetMs !== void 0 ? Math.min(1, Math.max(0, e.tOffsetMs / total)) : void 0,
1461
+ command: command2.slice(0, 300),
1462
+ prNumber: numberMatch ? Number(numberMatch[1]) : void 0,
1463
+ title: titleMatch?.[1]
1464
+ });
1465
+ }
1466
+ return out;
1467
+ }
1468
+ function sessionAirTimeMs(events, idleThresholdMs = 3 * 60 * 1e3) {
1469
+ let air = 0;
1470
+ for (const e of events) {
1471
+ if (e.gapMs === void 0)
1472
+ continue;
1473
+ if (e.gapMs <= idleThresholdMs)
1474
+ air += e.gapMs;
1475
+ }
1476
+ return air;
1477
+ }
1478
+ function highLevelMetrics(sessions) {
1479
+ const totals = {
1480
+ sessionCount: sessions.length,
1481
+ totalDurationMs: 0,
1482
+ totalAirTimeMs: 0,
1483
+ totalTokens: { ...BLANK_USAGE2 },
1484
+ totalToolCalls: 0,
1485
+ totalTurns: 0,
1486
+ avgTurnsPerSession: 0,
1487
+ avgDurationMs: 0,
1488
+ totalLinesAdded: 0,
1489
+ totalLinesRemoved: 0,
1490
+ totalFilesEdited: 0
1491
+ };
1492
+ for (const s of sessions) {
1493
+ totals.totalDurationMs += s.durationMs ?? 0;
1494
+ totals.totalAirTimeMs += s.airTimeMs ?? s.durationMs ?? 0;
1495
+ totals.totalTokens.input += s.totalUsage.input;
1496
+ totals.totalTokens.output += s.totalUsage.output;
1497
+ totals.totalTokens.cacheRead += s.totalUsage.cacheRead;
1498
+ totals.totalTokens.cacheWrite += s.totalUsage.cacheWrite;
1499
+ totals.totalToolCalls += s.toolCallCount ?? 0;
1500
+ totals.totalTurns += s.turnCount ?? 0;
1501
+ totals.totalLinesAdded += s.linesAdded ?? 0;
1502
+ totals.totalLinesRemoved += s.linesRemoved ?? 0;
1503
+ totals.totalFilesEdited += s.filesEdited ?? 0;
1504
+ }
1505
+ if (sessions.length > 0) {
1506
+ totals.avgTurnsPerSession = totals.totalTurns / sessions.length;
1507
+ totals.avgDurationMs = totals.totalDurationMs / sessions.length;
1508
+ }
1509
+ return totals;
1510
+ }
1511
+ function computeActiveSegments(events, idleThresholdMs = 3 * 60 * 1e3) {
1512
+ const timed = events.filter((e) => e.timestamp).map((e) => Date.parse(e.timestamp)).filter((ms) => !Number.isNaN(ms)).sort((a, b) => a - b);
1513
+ if (timed.length === 0)
1514
+ return [];
1515
+ const segments = [];
1516
+ let segStart = timed[0];
1517
+ let segEnd = timed[0];
1518
+ for (let i = 1; i < timed.length; i++) {
1519
+ const t = timed[i];
1520
+ if (t - segEnd > idleThresholdMs) {
1521
+ segments.push({ startMs: segStart, endMs: segEnd });
1522
+ segStart = t;
1523
+ }
1524
+ segEnd = t;
1525
+ }
1526
+ segments.push({ startMs: segStart, endMs: segEnd });
1527
+ return segments;
1528
+ }
1529
+ function buildGanttDay(sessions, date) {
1530
+ const [y, m, d] = date.split("-").map(Number);
1531
+ const dayStartMs = new Date(y, m - 1, d).getTime();
1532
+ const dayEndMs = new Date(y, m - 1, d + 1).getTime();
1533
+ const ganttSessions = [];
1534
+ for (const s of sessions) {
1535
+ const allSegments = computeActiveSegments(s.events);
1536
+ const daySegments = allSegments.filter((seg) => seg.endMs > dayStartMs && seg.startMs < dayEndMs).map((seg) => ({
1537
+ startMs: Math.max(seg.startMs, dayStartMs),
1538
+ endMs: Math.min(seg.endMs, dayEndMs)
1539
+ }));
1540
+ if (daySegments.length === 0)
1541
+ continue;
1542
+ const startMs = Math.min(...daySegments.map((seg) => seg.startMs));
1543
+ const endMs = Math.max(...daySegments.map((seg) => seg.endMs));
1544
+ const activeMs = daySegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
1545
+ ganttSessions.push({
1546
+ id: s.id,
1547
+ projectName: s.projectName,
1548
+ projectDir: s.projectDir,
1549
+ firstUserPreview: s.firstUserPreview,
1550
+ lastAgentPreview: s.lastAgentPreview,
1551
+ model: s.model,
1552
+ segments: daySegments,
1553
+ startMs,
1554
+ endMs,
1555
+ activeMs,
1556
+ totalUsage: s.totalUsage
1557
+ });
1558
+ }
1559
+ ganttSessions.sort((a, b) => a.startMs - b.startMs);
1560
+ const evts = [];
1561
+ for (const s of ganttSessions) {
1562
+ for (const seg of s.segments) {
1563
+ evts.push({ ms: seg.startMs, delta: 1 });
1564
+ evts.push({ ms: seg.endMs, delta: -1 });
1565
+ }
1566
+ }
1567
+ evts.sort((a, b) => a.ms - b.ms || b.delta - a.delta);
1568
+ let active = 0;
1569
+ let peak = 0;
1570
+ for (const e of evts) {
1571
+ active += e.delta;
1572
+ if (active > peak)
1573
+ peak = active;
1574
+ }
1575
+ return { date, sessions: ganttSessions, dayStartMs, dayEndMs, peakActiveParallelism: peak };
1576
+ }
1577
+ function detectBurstsCore(sessions, opts) {
1578
+ const minDurationMs = opts.minDurationMs ?? 6e4;
1579
+ const mergeGapMs = opts.mergeGapMs ?? 10 * 6e4;
1580
+ const minActive = opts.minActive ?? 2;
1581
+ const events = [];
1582
+ for (const s of sessions) {
1583
+ for (const seg of s.segments) {
1584
+ if (seg.endMs <= seg.startMs)
1585
+ continue;
1586
+ events.push({ ms: seg.startMs, delta: 1, sessionId: s.id });
1587
+ events.push({ ms: seg.endMs, delta: -1, sessionId: s.id });
1588
+ }
1589
+ }
1590
+ events.sort((a, b) => a.ms - b.ms || b.delta - a.delta);
1591
+ const segCount = /* @__PURE__ */ new Map();
1592
+ const rawRuns = [];
1593
+ let cur = null;
1594
+ for (const e of events) {
1595
+ if (e.delta === 1) {
1596
+ segCount.set(e.sessionId, (segCount.get(e.sessionId) ?? 0) + 1);
1597
+ } else {
1598
+ const n = (segCount.get(e.sessionId) ?? 0) - 1;
1599
+ if (n <= 0)
1600
+ segCount.delete(e.sessionId);
1601
+ else
1602
+ segCount.set(e.sessionId, n);
1603
+ }
1604
+ const active = segCount.size;
1605
+ if (active >= minActive) {
1606
+ if (!cur) {
1607
+ cur = {
1608
+ startMs: e.ms,
1609
+ endMs: e.ms,
1610
+ peak: active,
1611
+ sessionIds: new Set(segCount.keys())
1612
+ };
1613
+ } else {
1614
+ cur.endMs = e.ms;
1615
+ if (active > cur.peak)
1616
+ cur.peak = active;
1617
+ for (const sid of segCount.keys())
1618
+ cur.sessionIds.add(sid);
1619
+ }
1620
+ } else if (cur) {
1621
+ cur.endMs = e.ms;
1622
+ rawRuns.push(cur);
1623
+ cur = null;
1624
+ }
1625
+ }
1626
+ if (cur)
1627
+ rawRuns.push(cur);
1628
+ const significant = rawRuns.filter((r) => r.endMs - r.startMs >= minDurationMs);
1629
+ const merged = [];
1630
+ for (const r of significant) {
1631
+ const last = merged[merged.length - 1];
1632
+ if (last && r.startMs - last.endMs <= mergeGapMs) {
1633
+ last.endMs = r.endMs;
1634
+ if (r.peak > last.peak)
1635
+ last.peak = r.peak;
1636
+ for (const sid of r.sessionIds)
1637
+ last.sessionIds.add(sid);
1638
+ } else {
1639
+ merged.push({
1640
+ startMs: r.startMs,
1641
+ endMs: r.endMs,
1642
+ peak: r.peak,
1643
+ sessionIds: new Set(r.sessionIds)
1644
+ });
1645
+ }
1646
+ }
1647
+ const projectBySession = /* @__PURE__ */ new Map();
1648
+ for (const s of sessions)
1649
+ projectBySession.set(s.id, s.projectDir);
1650
+ return merged.map((r) => {
1651
+ const projectDirs = Array.from(new Set(Array.from(r.sessionIds).map((sid) => projectBySession.get(sid)).filter((p) => !!p)));
1652
+ return {
1653
+ startMs: r.startMs,
1654
+ endMs: r.endMs,
1655
+ peak: r.peak,
1656
+ sessionIds: Array.from(r.sessionIds),
1657
+ projectDirs,
1658
+ crossProject: projectDirs.length > 1
1659
+ };
1660
+ });
1661
+ }
1662
+ function computeParallelismBursts(gantt, opts = {}) {
1663
+ return detectBurstsCore(gantt.sessions, opts);
1664
+ }
1665
+ function computeBurstsFromSessions(sessions, opts = {}) {
1666
+ const input = [];
1667
+ for (const s of sessions) {
1668
+ if (!s.activeSegments || s.activeSegments.length === 0)
1669
+ continue;
1670
+ input.push({
1671
+ id: s.id,
1672
+ projectDir: s.projectDir,
1673
+ segments: s.activeSegments
1674
+ });
1675
+ }
1676
+ return detectBurstsCore(input, opts);
1677
+ }
1678
+ function summarizeBursts(bursts) {
1679
+ let peakConcurrent = 0;
1680
+ let totalParallelMs = 0;
1681
+ let crossProjectBurstCount = 0;
1682
+ const days = /* @__PURE__ */ new Set();
1683
+ for (const b of bursts) {
1684
+ if (b.peak > peakConcurrent)
1685
+ peakConcurrent = b.peak;
1686
+ totalParallelMs += b.endMs - b.startMs;
1687
+ if (b.crossProject)
1688
+ crossProjectBurstCount++;
1689
+ days.add(toLocalDay(b.startMs));
1690
+ }
1691
+ return {
1692
+ peakConcurrent,
1693
+ totalParallelMs,
1694
+ burstCount: bursts.length,
1695
+ crossProjectBurstCount,
1696
+ activeDayCount: days.size
1697
+ };
1698
+ }
1699
+ function groupByProject(sessions) {
1700
+ const map = /* @__PURE__ */ new Map();
1701
+ for (const s of sessions) {
1702
+ const canonical = canonicalProjectName(s.projectName);
1703
+ const key = canonical;
1704
+ let cur = map.get(key);
1705
+ if (!cur) {
1706
+ cur = {
1707
+ projectDir: canonical,
1708
+ projectName: canonical,
1709
+ sessions: [],
1710
+ rawProjectDirs: [],
1711
+ worktreeCount: 0,
1712
+ metrics: highLevelMetrics([]),
1713
+ lastActiveMs: void 0
1714
+ };
1715
+ map.set(key, cur);
1716
+ }
1717
+ cur.sessions.push(s);
1718
+ if (!cur.rawProjectDirs.includes(s.projectDir)) {
1719
+ cur.rawProjectDirs.push(s.projectDir);
1720
+ }
1721
+ const last = s.lastTimestamp ? Date.parse(s.lastTimestamp) : void 0;
1722
+ if (last && (!cur.lastActiveMs || last > cur.lastActiveMs)) {
1723
+ cur.lastActiveMs = last;
1724
+ }
1725
+ }
1726
+ for (const p of map.values()) {
1727
+ p.metrics = highLevelMetrics(p.sessions);
1728
+ const wtNames = /* @__PURE__ */ new Set();
1729
+ for (const s of p.sessions) {
1730
+ const wt = worktreeName(s.projectName);
1731
+ if (wt)
1732
+ wtNames.add(wt);
1733
+ }
1734
+ p.worktreeCount = wtNames.size;
1735
+ }
1736
+ return Array.from(map.values()).sort((a, b) => (b.lastActiveMs ?? 0) - (a.lastActiveMs ?? 0));
1737
+ }
1738
+ var BLANK_USAGE2, WORKTREE_MARKERS, PR_CREATE_RE, PR_NUMBER_RE, TITLE_FLAG_RE;
1160
1739
  var init_analytics = __esm({
1161
1740
  "../parser/dist/analytics.js"() {
1162
1741
  "use strict";
1742
+ BLANK_USAGE2 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
1743
+ WORKTREE_MARKERS = ["/.claude/worktrees/", "/.worktrees/"];
1744
+ PR_CREATE_RE = /gh\s+pr\s+create\b/i;
1745
+ PR_NUMBER_RE = /#(\d+)/;
1746
+ TITLE_FLAG_RE = /--title\s+["']([^"']+)["']/;
1163
1747
  }
1164
1748
  });
1165
1749
 
1166
1750
  // ../parser/dist/team.js
1751
+ function groupByTeam(sessions, details) {
1752
+ const byTeam = /* @__PURE__ */ new Map();
1753
+ for (const s of sessions) {
1754
+ if (!s.teamName)
1755
+ continue;
1756
+ const arr = byTeam.get(s.teamName) ?? [];
1757
+ arr.push(s);
1758
+ byTeam.set(s.teamName, arr);
1759
+ }
1760
+ const views = [];
1761
+ for (const [teamName, group] of byTeam) {
1762
+ const view = buildTeamView(teamName, group, details);
1763
+ if (view)
1764
+ views.push(view);
1765
+ }
1766
+ return views;
1767
+ }
1768
+ function buildTeamView(teamName, group, details) {
1769
+ const candidates = group.filter((s) => s.isTeamLead);
1770
+ if (candidates.length === 0)
1771
+ return null;
1772
+ let lead;
1773
+ for (const c of candidates) {
1774
+ const d = details.get(c.sessionId);
1775
+ if (d && containsTeamCreate(d.events)) {
1776
+ lead = c;
1777
+ break;
1778
+ }
1779
+ }
1780
+ if (!lead) {
1781
+ const sorted = [...candidates].sort((a, b) => Date.parse(a.firstTimestamp ?? "") - Date.parse(b.firstTimestamp ?? ""));
1782
+ lead = sorted[0];
1783
+ }
1784
+ const agentNameBySessionId = /* @__PURE__ */ new Map();
1785
+ for (const s of group)
1786
+ agentNameBySessionId.set(s.sessionId, s.agentName);
1787
+ const members = group.filter((s) => s.sessionId !== lead.sessionId).sort((a, b) => Date.parse(a.firstTimestamp ?? "") - Date.parse(b.firstTimestamp ?? ""));
1788
+ const sessionIdByAgentName = /* @__PURE__ */ new Map();
1789
+ for (const m of members) {
1790
+ if (m.agentName)
1791
+ sessionIdByAgentName.set(m.agentName, m.sessionId);
1792
+ }
1793
+ const messages = [];
1794
+ for (const s of group) {
1795
+ const d = details.get(s.sessionId);
1796
+ if (!d)
1797
+ continue;
1798
+ const senderId = s.sessionId;
1799
+ const senderTeammateId = s.agentName ?? "team-lead";
1800
+ for (const ev of d.events) {
1801
+ const msg = extractSendMessage(ev);
1802
+ if (!msg)
1803
+ continue;
1804
+ const toTeammateId = msg.to;
1805
+ const toSessionId = toTeammateId === "team-lead" ? lead.sessionId : sessionIdByAgentName.get(toTeammateId) ?? "";
1806
+ messages.push({
1807
+ tsMs: ev.timestamp ? Date.parse(ev.timestamp) : 0,
1808
+ fromSessionId: senderId,
1809
+ fromTeammateId: senderTeammateId,
1810
+ toSessionId,
1811
+ toTeammateId,
1812
+ body: msg.body,
1813
+ kind: "message"
1814
+ });
1815
+ }
1816
+ }
1817
+ messages.sort((a, b) => a.tsMs - b.tsMs);
1818
+ const allTs = [];
1819
+ for (const s of group) {
1820
+ if (s.firstTimestamp)
1821
+ allTs.push(Date.parse(s.firstTimestamp));
1822
+ if (s.lastTimestamp)
1823
+ allTs.push(Date.parse(s.lastTimestamp));
1824
+ }
1825
+ const firstEventMs = allTs.length ? Math.min(...allTs) : 0;
1826
+ const lastEventMs = allTs.length ? Math.max(...allTs) : 0;
1827
+ return {
1828
+ teamName,
1829
+ leadSessionId: lead.sessionId,
1830
+ memberSessionIds: members.map((m) => m.sessionId),
1831
+ agentNameBySessionId,
1832
+ messages,
1833
+ firstEventMs,
1834
+ lastEventMs
1835
+ };
1836
+ }
1837
+ function containsTeamCreate(events) {
1838
+ for (const ev of events) {
1839
+ if (ev.role !== "tool-call")
1840
+ continue;
1841
+ if (ev.toolName === "TeamCreate")
1842
+ return true;
1843
+ }
1844
+ return false;
1845
+ }
1846
+ function extractSendMessage(ev) {
1847
+ if (ev.role !== "tool-call" || ev.toolName !== "SendMessage")
1848
+ return null;
1849
+ for (const b of ev.blocks) {
1850
+ if (!b || typeof b !== "object")
1851
+ continue;
1852
+ if (b.type !== "tool_use")
1853
+ continue;
1854
+ const input = b.input ?? {};
1855
+ const to = typeof input.to === "string" ? input.to : "";
1856
+ const body = typeof input.message === "string" ? input.message : "";
1857
+ if (!to)
1858
+ return null;
1859
+ return { to, body };
1860
+ }
1861
+ return null;
1862
+ }
1167
1863
  var init_team = __esm({
1168
1864
  "../parser/dist/team.js"() {
1169
1865
  "use strict";
@@ -1171,6 +1867,23 @@ var init_team = __esm({
1171
1867
  });
1172
1868
 
1173
1869
  // ../parser/dist/fs.js
1870
+ var fs_exports = {};
1871
+ __export(fs_exports, {
1872
+ DEFAULT_ROOT: () => DEFAULT_ROOT,
1873
+ cacheStats: () => cacheStats,
1874
+ clearCaches: () => clearCaches,
1875
+ decodeProjectName: () => decodeProjectName,
1876
+ findTeamLead: () => findTeamLead,
1877
+ getSession: () => getSession,
1878
+ invalidateFile: () => invalidateFile,
1879
+ listProjects: () => listProjects,
1880
+ listSessions: () => listSessions,
1881
+ loadTeamForSession: () => loadTeamForSession,
1882
+ loadUsageByDay: () => loadUsageByDay,
1883
+ readJsonlFile: () => readJsonlFile,
1884
+ sessionIdFromFileName: () => sessionIdFromFileName,
1885
+ walkJsonlFiles: () => walkJsonlFiles
1886
+ });
1174
1887
  import { promises as fs } from "node:fs";
1175
1888
  import os from "node:os";
1176
1889
  import path from "node:path";
@@ -1193,11 +1906,24 @@ async function readJsonlFile(filePath) {
1193
1906
  }
1194
1907
  return out;
1195
1908
  }
1909
+ function cacheStats() {
1910
+ return { metaEntries: metaCache.size, detailEntries: detailCache.size };
1911
+ }
1912
+ function clearCaches() {
1913
+ metaCache.clear();
1914
+ detailCache.clear();
1915
+ fileListCache = null;
1916
+ }
1917
+ function invalidateFile(fullPath) {
1918
+ metaCache.delete(fullPath);
1919
+ detailCache.delete(fullPath);
1920
+ fileListCache = null;
1921
+ }
1196
1922
  async function walkJsonlFiles(root = DEFAULT_ROOT) {
1197
1923
  if (fileListCache && fileListCache.root === root && Date.now() - fileListCache.capturedAtMs < FILE_LIST_TTL_MS) {
1198
1924
  return fileListCache.files;
1199
1925
  }
1200
- let topEntries = [];
1926
+ let topEntries;
1201
1927
  try {
1202
1928
  topEntries = await fs.readdir(root, { withFileTypes: true });
1203
1929
  } catch {
@@ -1265,7 +1991,7 @@ function aggregateUsageFromLines(lines, seenKeys, usage2) {
1265
1991
  }
1266
1992
  }
1267
1993
  async function computeSessionUsageWithSubagents(parentLines, projectDirPath, sessionId) {
1268
- const usage2 = { ...BLANK_USAGE2 };
1994
+ const usage2 = { ...BLANK_USAGE3 };
1269
1995
  const seenKeys = /* @__PURE__ */ new Set();
1270
1996
  aggregateUsageFromLines(parentLines, seenKeys, usage2);
1271
1997
  const subagentsDir = path.join(projectDirPath, sessionId, "subagents");
@@ -1311,6 +2037,31 @@ async function getCachedMeta(f) {
1311
2037
  return null;
1312
2038
  }
1313
2039
  }
2040
+ async function getCachedDetail(f) {
2041
+ const cached = detailCache.get(f.fullPath);
2042
+ if (cached && cached.mtimeMs === f.mtimeMs && cached.sizeBytes === f.sizeBytes) {
2043
+ return cached.detail;
2044
+ }
2045
+ try {
2046
+ const rawLines = await readJsonlFile(f.fullPath);
2047
+ const { meta, events } = parseTranscript(rawLines);
2048
+ const detail = {
2049
+ ...meta,
2050
+ id: sessionIdFromFileName(f.fileName),
2051
+ filePath: f.fullPath,
2052
+ projectDir: f.projectDir,
2053
+ projectName: meta.cwd ?? decodeProjectName(f.projectDir),
2054
+ events
2055
+ };
2056
+ detailCache.set(f.fullPath, { detail, mtimeMs: f.mtimeMs, sizeBytes: f.sizeBytes });
2057
+ const metaOnly = { ...detail };
2058
+ delete metaOnly.events;
2059
+ metaCache.set(f.fullPath, { meta: metaOnly, mtimeMs: f.mtimeMs, sizeBytes: f.sizeBytes });
2060
+ return detail;
2061
+ } catch {
2062
+ return null;
2063
+ }
2064
+ }
1314
2065
  async function listSessions(opts = {}) {
1315
2066
  const { root = DEFAULT_ROOT, limit = 500, projectDir } = opts;
1316
2067
  let files = await walkJsonlFiles(root);
@@ -1321,38 +2072,1806 @@ async function listSessions(opts = {}) {
1321
2072
  const metas = await Promise.all(sliced.map((f) => getCachedMeta(f)));
1322
2073
  return metas.filter((m) => m !== null);
1323
2074
  }
1324
- var DEFAULT_ROOT, metaCache, fileListCache, FILE_LIST_TTL_MS, BLANK_USAGE2;
1325
- var init_fs = __esm({
1326
- "../parser/dist/fs.js"() {
1327
- "use strict";
1328
- init_parser();
1329
- init_analytics();
1330
- init_team();
1331
- DEFAULT_ROOT = path.join(os.homedir(), ".claude", "projects");
1332
- metaCache = /* @__PURE__ */ new Map();
1333
- fileListCache = null;
1334
- FILE_LIST_TTL_MS = 1e3;
1335
- BLANK_USAGE2 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2075
+ async function getSession(id, opts = {}) {
2076
+ const { root = DEFAULT_ROOT } = opts;
2077
+ const files = await walkJsonlFiles(root);
2078
+ const hit = files.find((f) => sessionIdFromFileName(f.fileName) === id);
2079
+ if (!hit)
2080
+ return null;
2081
+ const detail = await getCachedDetail(hit);
2082
+ if (!detail)
2083
+ return detail;
2084
+ const sessionStartMs = detail.firstTimestamp ? Date.parse(detail.firstTimestamp) : void 0;
2085
+ const subagentsDir = path.join(path.dirname(hit.fullPath), sessionIdFromFileName(hit.fileName), "subagents");
2086
+ const subagents = await loadSubagents(subagentsDir, sessionStartMs, detail.events);
2087
+ return { ...detail, subagents };
2088
+ }
2089
+ function summarizeSubagentLines(lines) {
2090
+ let startMs;
2091
+ let endMs;
2092
+ const totalUsage = { ...BLANK_USAGE3 };
2093
+ const seenMessageIds = /* @__PURE__ */ new Set();
2094
+ let finalText;
2095
+ let model;
2096
+ let eventCount = 0;
2097
+ let assistantMessageCount = 0;
2098
+ let toolCallCount = 0;
2099
+ const toolCounts = /* @__PURE__ */ new Map();
2100
+ let initialPrompt;
2101
+ for (const raw of lines) {
2102
+ if (!raw || typeof raw !== "object")
2103
+ continue;
2104
+ const r = raw;
2105
+ eventCount++;
2106
+ const ts = typeof r.timestamp === "string" ? Date.parse(r.timestamp) : NaN;
2107
+ if (!Number.isNaN(ts)) {
2108
+ if (startMs === void 0 || ts < startMs)
2109
+ startMs = ts;
2110
+ if (endMs === void 0 || ts > endMs)
2111
+ endMs = ts;
2112
+ }
2113
+ if (initialPrompt === void 0 && r.type === "user" && (r.parentUuid === null || r.parentUuid === void 0)) {
2114
+ const msg = r.message;
2115
+ const content = msg?.content;
2116
+ if (typeof content === "string") {
2117
+ initialPrompt = content;
2118
+ } else if (Array.isArray(content)) {
2119
+ const txt = content.find((b) => b && typeof b === "object" && b.type === "text");
2120
+ if (txt?.text)
2121
+ initialPrompt = txt.text;
2122
+ }
2123
+ }
2124
+ if (r.type === "assistant") {
2125
+ const m = r.message;
2126
+ if (!m)
2127
+ continue;
2128
+ if (!model && typeof m.model === "string")
2129
+ model = m.model;
2130
+ const mid = typeof m.id === "string" ? m.id : void 0;
2131
+ const rid = typeof r.requestId === "string" ? r.requestId : void 0;
2132
+ const dedupKey = mid != null && rid != null ? `${mid}:${rid}` : void 0;
2133
+ const fresh = dedupKey ? !seenMessageIds.has(dedupKey) : true;
2134
+ if (dedupKey && fresh)
2135
+ seenMessageIds.add(dedupKey);
2136
+ if (fresh) {
2137
+ assistantMessageCount++;
2138
+ const u = m.usage;
2139
+ if (u) {
2140
+ const toNum = (v) => typeof v === "number" ? v : 0;
2141
+ totalUsage.input += toNum(u.input_tokens);
2142
+ totalUsage.output += toNum(u.output_tokens);
2143
+ totalUsage.cacheRead += toNum(u.cache_read_input_tokens);
2144
+ totalUsage.cacheWrite += toNum(u.cache_creation_input_tokens);
2145
+ }
2146
+ }
2147
+ const content = m.content;
2148
+ if (Array.isArray(content)) {
2149
+ for (const block of content) {
2150
+ if (!block || typeof block !== "object")
2151
+ continue;
2152
+ if (block.type === "text" && typeof block.text === "string") {
2153
+ finalText = block.text;
2154
+ } else if (block.type === "tool_use" && typeof block.name === "string") {
2155
+ toolCallCount++;
2156
+ toolCounts.set(block.name, (toolCounts.get(block.name) ?? 0) + 1);
2157
+ }
2158
+ }
2159
+ }
2160
+ }
1336
2161
  }
1337
- });
1338
-
1339
- // src/pricing.ts
1340
- function findPricing(model) {
1341
- for (const [prefix, pricing] of PRICING) {
1342
- if (model.startsWith(prefix)) return pricing;
2162
+ const toolCalls = Array.from(toolCounts.entries()).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
2163
+ const finalPreview = finalText !== void 0 ? finalText.replace(/\s+/g, " ").trim().slice(0, 240) : void 0;
2164
+ return {
2165
+ startMs,
2166
+ endMs,
2167
+ totalUsage,
2168
+ eventCount,
2169
+ finalPreview,
2170
+ finalText,
2171
+ model,
2172
+ toolCalls,
2173
+ toolCallCount,
2174
+ assistantMessageCount,
2175
+ initialPrompt
2176
+ };
2177
+ }
2178
+ async function loadSubagents(dir, sessionStartMs, parentEvents) {
2179
+ let entries;
2180
+ try {
2181
+ entries = await fs.readdir(dir);
2182
+ } catch {
2183
+ return [];
1343
2184
  }
1344
- return null;
2185
+ const byDesc = /* @__PURE__ */ new Map();
2186
+ const dispatchesByTs = [];
2187
+ for (const e of parentEvents) {
2188
+ if (e.role !== "tool-call" || e.toolName !== "Agent")
2189
+ continue;
2190
+ for (const b of e.blocks) {
2191
+ if (b?.type !== "tool_use" || b.name !== "Agent")
2192
+ continue;
2193
+ const input = b.input ?? {};
2194
+ const desc = typeof input.description === "string" ? input.description : void 0;
2195
+ const tsMs = e.timestamp ? Date.parse(e.timestamp) : void 0;
2196
+ const ref = {
2197
+ toolUseId: b.id,
2198
+ parentUuid: e.uuid ?? "",
2199
+ runInBackground: input.run_in_background === true,
2200
+ prompt: typeof input.prompt === "string" ? input.prompt : void 0,
2201
+ tsMs
2202
+ };
2203
+ if (desc)
2204
+ byDesc.set(desc, ref);
2205
+ if (tsMs !== void 0)
2206
+ dispatchesByTs.push(ref);
2207
+ }
2208
+ }
2209
+ dispatchesByTs.sort((a, b) => (a.tsMs ?? 0) - (b.tsMs ?? 0));
2210
+ function matchByTime(startMs) {
2211
+ if (startMs === void 0)
2212
+ return void 0;
2213
+ const TOLERANCE_MS = 2e3;
2214
+ let best;
2215
+ let bestDelta = Infinity;
2216
+ for (const ref of dispatchesByTs) {
2217
+ if (ref.tsMs === void 0)
2218
+ continue;
2219
+ const delta = Math.abs(startMs - ref.tsMs);
2220
+ if (delta < bestDelta && delta <= TOLERANCE_MS) {
2221
+ best = ref;
2222
+ bestDelta = delta;
2223
+ }
2224
+ }
2225
+ return best;
2226
+ }
2227
+ const jsonlFiles = entries.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
2228
+ const runs = await Promise.all(jsonlFiles.map(async (f) => {
2229
+ const agentId = f.replace(/^agent-/, "").replace(/\.jsonl$/, "");
2230
+ const jsonlPath = path.join(dir, f);
2231
+ const metaPath = path.join(dir, `agent-${agentId}.meta.json`);
2232
+ let meta = {};
2233
+ try {
2234
+ const raw = await fs.readFile(metaPath, "utf8");
2235
+ meta = JSON.parse(raw);
2236
+ } catch {
2237
+ }
2238
+ let lines;
2239
+ try {
2240
+ lines = await readJsonlFile(jsonlPath);
2241
+ } catch {
2242
+ return null;
2243
+ }
2244
+ const summary = summarizeSubagentLines(lines);
2245
+ const metaDesc = meta.description && meta.description.trim().length > 0 ? meta.description : void 0;
2246
+ const parentRef = metaDesc ? byDesc.get(metaDesc) : matchByTime(summary.startMs);
2247
+ const description = metaDesc ?? (summary.initialPrompt ? summary.initialPrompt.replace(/\s+/g, " ").trim().slice(0, 80) : "(no description)");
2248
+ const prompt = parentRef?.prompt ?? summary.initialPrompt;
2249
+ const startTOffsetMs = sessionStartMs !== void 0 && summary.startMs !== void 0 ? Math.max(0, summary.startMs - sessionStartMs) : void 0;
2250
+ const endTOffsetMs = sessionStartMs !== void 0 && summary.endMs !== void 0 ? Math.max(0, summary.endMs - sessionStartMs) : void 0;
2251
+ const durationMs = summary.startMs !== void 0 && summary.endMs !== void 0 ? summary.endMs - summary.startMs : void 0;
2252
+ return {
2253
+ agentId,
2254
+ agentType: meta.agentType ?? "unknown",
2255
+ description,
2256
+ startMs: summary.startMs,
2257
+ endMs: summary.endMs,
2258
+ durationMs,
2259
+ startTOffsetMs,
2260
+ endTOffsetMs,
2261
+ eventCount: summary.eventCount,
2262
+ totalUsage: summary.totalUsage,
2263
+ parentUuid: parentRef?.parentUuid,
2264
+ parentToolUseId: parentRef?.toolUseId,
2265
+ runInBackground: parentRef?.runInBackground,
2266
+ prompt,
2267
+ finalPreview: summary.finalPreview,
2268
+ finalText: summary.finalText,
2269
+ model: summary.model,
2270
+ toolCalls: summary.toolCalls,
2271
+ toolCallCount: summary.toolCallCount,
2272
+ assistantMessageCount: summary.assistantMessageCount
2273
+ };
2274
+ }));
2275
+ return runs.filter((r) => r !== null).sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
1345
2276
  }
1346
- function estimateCost(model, usage2) {
1347
- const p = findPricing(model);
1348
- if (!p) return null;
1349
- return (usage2.input * p.input + usage2.output * p.output + usage2.cacheRead * p.cacheRead + usage2.cacheWrite * p.cacheWrite) / 1e6;
2277
+ async function listProjects(root = DEFAULT_ROOT) {
2278
+ const files = await walkJsonlFiles(root);
2279
+ const byRawDir = /* @__PURE__ */ new Map();
2280
+ for (const f of files) {
2281
+ const cur = byRawDir.get(f.projectDir) ?? { count: 0, lastActiveMs: 0 };
2282
+ cur.count++;
2283
+ if (f.mtimeMs > cur.lastActiveMs)
2284
+ cur.lastActiveMs = f.mtimeMs;
2285
+ byRawDir.set(f.projectDir, cur);
2286
+ }
2287
+ const cwdForProject = (dir) => {
2288
+ for (const [, entry] of metaCache.entries()) {
2289
+ if (entry.meta.projectDir === dir && entry.meta.cwd) {
2290
+ return entry.meta.cwd;
2291
+ }
2292
+ }
2293
+ return void 0;
2294
+ };
2295
+ const canonicalMap = /* @__PURE__ */ new Map();
2296
+ for (const [rawDir, { count, lastActiveMs }] of byRawDir) {
2297
+ const cwdOrDecoded = cwdForProject(rawDir) ?? decodeProjectName(rawDir);
2298
+ const canonical = canonicalProjectName(cwdOrDecoded);
2299
+ const wt = worktreeName(cwdOrDecoded);
2300
+ const agg = canonicalMap.get(canonical) ?? {
2301
+ canonicalName: canonical,
2302
+ count: 0,
2303
+ lastActiveMs: 0,
2304
+ worktreeBranches: /* @__PURE__ */ new Set()
2305
+ };
2306
+ agg.count += count;
2307
+ if (lastActiveMs > agg.lastActiveMs)
2308
+ agg.lastActiveMs = lastActiveMs;
2309
+ if (wt)
2310
+ agg.worktreeBranches.add(wt);
2311
+ canonicalMap.set(canonical, agg);
2312
+ }
2313
+ return Array.from(canonicalMap.values()).map((a) => ({
2314
+ projectDir: a.canonicalName,
2315
+ projectName: a.canonicalName,
2316
+ sessionCount: a.count,
2317
+ lastActiveMs: a.lastActiveMs,
2318
+ worktreeCount: a.worktreeBranches.size
2319
+ })).sort((a, b) => b.lastActiveMs - a.lastActiveMs);
1350
2320
  }
1351
- var PRICING;
1352
- var init_pricing = __esm({
1353
- "src/pricing.ts"() {
1354
- "use strict";
1355
- PRICING = [
2321
+ async function loadTeamForSession(sessionId, opts = {}) {
2322
+ const { root = DEFAULT_ROOT } = opts;
2323
+ const all = await listSessions({ root });
2324
+ const self = all.find((s) => s.sessionId === sessionId);
2325
+ if (!self || !self.teamName)
2326
+ return null;
2327
+ const teamName = self.teamName;
2328
+ const candidates = all.filter((s) => s.teamName === teamName && !s.filePath.includes("/subagents/"));
2329
+ const loaded = await Promise.all(candidates.map((c) => getSession(c.sessionId, { root })));
2330
+ const details = /* @__PURE__ */ new Map();
2331
+ candidates.forEach((c, i) => {
2332
+ const d = loaded[i];
2333
+ if (d)
2334
+ details.set(c.sessionId, d);
2335
+ });
2336
+ const views = groupByTeam(candidates, details);
2337
+ const view = views.find((v) => v.teamName === teamName);
2338
+ if (!view)
2339
+ return null;
2340
+ return { view, details };
2341
+ }
2342
+ async function findTeamLead(sessionId, opts = {}) {
2343
+ const { root = DEFAULT_ROOT } = opts;
2344
+ const all = await listSessions({ root });
2345
+ const self = all.find((s) => s.sessionId === sessionId);
2346
+ if (!self || !self.teamName || !self.agentName)
2347
+ return null;
2348
+ if (self.isTeamLead)
2349
+ return null;
2350
+ const candidates = all.filter((s) => s.teamName === self.teamName && s.isTeamLead && !s.filePath.includes("/subagents/"));
2351
+ if (candidates.length === 0)
2352
+ return null;
2353
+ return {
2354
+ leadSessionId: candidates[0].sessionId,
2355
+ teamName: self.teamName,
2356
+ agentName: self.agentName
2357
+ };
2358
+ }
2359
+ async function loadUsageByDay(start2, end) {
2360
+ let raw;
2361
+ try {
2362
+ raw = await fs.readFile(USAGE_LOG2, "utf8");
2363
+ } catch {
2364
+ return { by_day: [] };
2365
+ }
2366
+ const startMs = new Date(start2.getFullYear(), start2.getMonth(), start2.getDate()).getTime();
2367
+ const endMs = new Date(end.getFullYear(), end.getMonth(), end.getDate(), 23, 59, 59, 999).getTime();
2368
+ const byDay = /* @__PURE__ */ new Map();
2369
+ for (const line of raw.split("\n")) {
2370
+ const trimmed = line.trim();
2371
+ if (!trimmed)
2372
+ continue;
2373
+ try {
2374
+ const snap = JSON.parse(trimmed);
2375
+ if (!snap.captured_at)
2376
+ continue;
2377
+ const ms = Date.parse(snap.captured_at);
2378
+ if (Number.isNaN(ms))
2379
+ continue;
2380
+ if (ms < startMs)
2381
+ continue;
2382
+ if (ms > endMs)
2383
+ break;
2384
+ const key = toLocalDay(ms);
2385
+ const util = snap.five_hour?.utilization ?? 0;
2386
+ const cur2 = byDay.get(key) ?? 0;
2387
+ if (util > cur2)
2388
+ byDay.set(key, util);
2389
+ } catch {
2390
+ }
2391
+ }
2392
+ const out = [];
2393
+ const cur = new Date(start2.getFullYear(), start2.getMonth(), start2.getDate());
2394
+ const stop2 = new Date(end.getFullYear(), end.getMonth(), end.getDate());
2395
+ while (cur.getTime() <= stop2.getTime()) {
2396
+ const k = toLocalDay(cur.getTime());
2397
+ out.push({ date: k, peak_util_pct: byDay.get(k) ?? 0 });
2398
+ cur.setDate(cur.getDate() + 1);
2399
+ }
2400
+ return { by_day: out };
2401
+ }
2402
+ var DEFAULT_ROOT, metaCache, detailCache, fileListCache, FILE_LIST_TTL_MS, BLANK_USAGE3, USAGE_LOG2;
2403
+ var init_fs = __esm({
2404
+ "../parser/dist/fs.js"() {
2405
+ "use strict";
2406
+ init_parser();
2407
+ init_analytics();
2408
+ init_team();
2409
+ DEFAULT_ROOT = path.join(os.homedir(), ".claude", "projects");
2410
+ metaCache = /* @__PURE__ */ new Map();
2411
+ detailCache = /* @__PURE__ */ new Map();
2412
+ fileListCache = null;
2413
+ FILE_LIST_TTL_MS = 1e3;
2414
+ BLANK_USAGE3 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2415
+ USAGE_LOG2 = path.join(os.homedir(), ".cclens", "usage.jsonl");
2416
+ }
2417
+ });
2418
+
2419
+ // ../parser/dist/types.js
2420
+ var init_types = __esm({
2421
+ "../parser/dist/types.js"() {
2422
+ "use strict";
2423
+ }
2424
+ });
2425
+
2426
+ // ../parser/dist/presentation.js
2427
+ function rowPrimaryIndex(r) {
2428
+ if (r.kind === "tool-group")
2429
+ return r.events[0].index;
2430
+ return r.event.index;
2431
+ }
2432
+ function buildMegaRows(rows) {
2433
+ const out = [];
2434
+ let buffer = [];
2435
+ const flush = () => {
2436
+ if (buffer.length === 0)
2437
+ return;
2438
+ let agentMessages = 0;
2439
+ let toolCalls = 0;
2440
+ let errors = 0;
2441
+ let firstAgentIndex;
2442
+ const toolMap = /* @__PURE__ */ new Map();
2443
+ const seenMsgIds = /* @__PURE__ */ new Set();
2444
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2445
+ for (let i = 0; i < buffer.length; i++) {
2446
+ const r = buffer[i];
2447
+ if (r.kind === "agent") {
2448
+ agentMessages++;
2449
+ if (firstAgentIndex === void 0)
2450
+ firstAgentIndex = i;
2451
+ const msgId = r.event.messageId;
2452
+ const usage2 = r.event.usage;
2453
+ if (msgId && !seenMsgIds.has(msgId) && usage2) {
2454
+ seenMsgIds.add(msgId);
2455
+ tokens.input += usage2.input;
2456
+ tokens.output += usage2.output;
2457
+ tokens.cacheRead += usage2.cacheRead;
2458
+ tokens.cacheWrite += usage2.cacheWrite;
2459
+ }
2460
+ } else if (r.kind === "tool-group") {
2461
+ toolCalls += r.count;
2462
+ for (const t of r.toolNames) {
2463
+ toolMap.set(t.name, (toolMap.get(t.name) ?? 0) + t.count);
2464
+ }
2465
+ } else if (r.kind === "error") {
2466
+ errors++;
2467
+ }
2468
+ }
2469
+ let finalAgentIndex;
2470
+ for (let i = buffer.length - 1; i >= 0; i--) {
2471
+ const r = buffer[i];
2472
+ if (r.kind !== "agent")
2473
+ continue;
2474
+ const prev = buffer[i - 1];
2475
+ if (prev?.kind === "task-notification")
2476
+ continue;
2477
+ finalAgentIndex = i;
2478
+ break;
2479
+ }
2480
+ if (finalAgentIndex === void 0) {
2481
+ for (let i = buffer.length - 1; i >= 0; i--) {
2482
+ if (buffer[i].kind === "agent") {
2483
+ finalAgentIndex = i;
2484
+ break;
2485
+ }
2486
+ }
2487
+ }
2488
+ const firstAgentPreview = firstAgentIndex !== void 0 && buffer[firstAgentIndex].kind === "agent" ? buffer[firstAgentIndex].event.preview : void 0;
2489
+ const finalAgentPreview = finalAgentIndex !== void 0 && buffer[finalAgentIndex].kind === "agent" ? buffer[finalAgentIndex].event.preview : void 0;
2490
+ const toolNames = Array.from(toolMap.entries()).sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
2491
+ const first = buffer[0];
2492
+ const last = buffer[buffer.length - 1];
2493
+ const start2 = first.tOffsetMs;
2494
+ const end = last.tOffsetMs;
2495
+ const durationMs = start2 !== void 0 && end !== void 0 ? end - start2 : void 0;
2496
+ out.push({
2497
+ kind: "turn",
2498
+ rows: buffer,
2499
+ firstPrimaryIndex: rowPrimaryIndex(first),
2500
+ tOffsetMs: start2,
2501
+ durationMs,
2502
+ summary: {
2503
+ agentMessages,
2504
+ toolCalls,
2505
+ errors,
2506
+ firstAgentPreview,
2507
+ finalAgentPreview,
2508
+ firstAgentIndex,
2509
+ finalAgentIndex,
2510
+ toolNames,
2511
+ totalTokens: tokens
2512
+ }
2513
+ });
2514
+ buffer = [];
2515
+ };
2516
+ for (const r of rows) {
2517
+ if (r.kind === "user" || r.kind === "interrupt") {
2518
+ flush();
2519
+ out.push(r);
2520
+ } else {
2521
+ buffer.push(r);
2522
+ }
2523
+ }
2524
+ flush();
2525
+ return out;
2526
+ }
2527
+ function parseTaskNotification(text) {
2528
+ if (!TASK_NOTIFICATION_RE.test(text))
2529
+ return null;
2530
+ const pick = (tag) => {
2531
+ const m = text.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
2532
+ return m ? m[1].trim() : void 0;
2533
+ };
2534
+ const statusRaw = (pick("status") ?? "").toLowerCase();
2535
+ let status2 = "unknown";
2536
+ if (statusRaw === "success" || statusRaw === "ok" || statusRaw === "done" || statusRaw === "completed")
2537
+ status2 = "success";
2538
+ else if (statusRaw === "failed" || statusRaw === "error" || statusRaw === "fail")
2539
+ status2 = "failed";
2540
+ else if (statusRaw === "running" || statusRaw === "in_progress")
2541
+ status2 = "running";
2542
+ return {
2543
+ status: status2,
2544
+ summary: pick("summary") ?? "Background task notification",
2545
+ taskId: pick("task-id"),
2546
+ toolUseId: pick("tool-use-id"),
2547
+ outputFile: pick("output-file")
2548
+ };
2549
+ }
2550
+ function isAttachmentOrMeta(e) {
2551
+ return e.role === "system" || e.role === "meta";
2552
+ }
2553
+ function firstTextOfBlocks(blocks) {
2554
+ for (const b of blocks) {
2555
+ if (b?.type === "text")
2556
+ return b.text;
2557
+ }
2558
+ return void 0;
2559
+ }
2560
+ function hasZeroUsage(e) {
2561
+ if (!e.usage)
2562
+ return false;
2563
+ const u = e.usage;
2564
+ return u.input === 0 && u.output === 0 && u.cacheRead === 0 && u.cacheWrite === 0;
2565
+ }
2566
+ function buildPresentation(events) {
2567
+ const byMessageId = /* @__PURE__ */ new Map();
2568
+ for (const e of events) {
2569
+ if (!e.messageId)
2570
+ continue;
2571
+ const list = byMessageId.get(e.messageId) ?? [];
2572
+ list.push(e);
2573
+ byMessageId.set(e.messageId, list);
2574
+ }
2575
+ const visible = events.filter((e) => {
2576
+ if (isAttachmentOrMeta(e))
2577
+ return false;
2578
+ if (e.role === "tool-result")
2579
+ return false;
2580
+ if (e.role === "agent-thinking")
2581
+ return false;
2582
+ if (e.role === "user") {
2583
+ const txt = firstTextOfBlocks(e.blocks) ?? "";
2584
+ if (SKILL_CONTENT_RE.test(txt))
2585
+ return false;
2586
+ }
2587
+ return true;
2588
+ });
2589
+ const rows = [];
2590
+ for (let i = 0; i < visible.length; i++) {
2591
+ const e = visible[i];
2592
+ if (e.role === "tool-call") {
2593
+ const start2 = i;
2594
+ while (i + 1 < visible.length && visible[i + 1].role === "tool-call") {
2595
+ i++;
2596
+ }
2597
+ const group = visible.slice(start2, i + 1);
2598
+ const order = [];
2599
+ const counts = /* @__PURE__ */ new Map();
2600
+ for (const ev of group) {
2601
+ const name = ev.toolName ?? "tool";
2602
+ if (!counts.has(name))
2603
+ order.push(name);
2604
+ counts.set(name, (counts.get(name) ?? 0) + 1);
2605
+ }
2606
+ const toolNames = order.map((name) => ({
2607
+ name,
2608
+ count: counts.get(name) ?? 1
2609
+ }));
2610
+ rows.push({
2611
+ kind: "tool-group",
2612
+ toolNames,
2613
+ count: group.length,
2614
+ events: group,
2615
+ tOffsetMs: group[0].tOffsetMs,
2616
+ gapMs: group[0].gapMs
2617
+ });
2618
+ continue;
2619
+ }
2620
+ if (e.role === "user") {
2621
+ const txt = firstTextOfBlocks(e.blocks) ?? "";
2622
+ const taskNotif = parseTaskNotification(txt);
2623
+ if (taskNotif) {
2624
+ rows.push({
2625
+ kind: "task-notification",
2626
+ event: e,
2627
+ status: taskNotif.status,
2628
+ summary: taskNotif.summary,
2629
+ taskId: taskNotif.taskId,
2630
+ toolUseId: taskNotif.toolUseId,
2631
+ outputFile: taskNotif.outputFile,
2632
+ tOffsetMs: e.tOffsetMs,
2633
+ gapMs: e.gapMs
2634
+ });
2635
+ continue;
2636
+ }
2637
+ if (INTERRUPT_RE.test(txt)) {
2638
+ rows.push({
2639
+ kind: "interrupt",
2640
+ event: e,
2641
+ tOffsetMs: e.tOffsetMs,
2642
+ gapMs: e.gapMs
2643
+ });
2644
+ continue;
2645
+ }
2646
+ const slashMatch = txt.match(SLASH_COMMAND_RE);
2647
+ if (slashMatch) {
2648
+ const cmd = slashMatch[1];
2649
+ const args2 = (slashMatch[2] ?? "").trim();
2650
+ const pretty = args2 ? `${cmd} ${args2}` : cmd;
2651
+ rows.push({
2652
+ kind: "user",
2653
+ event: e,
2654
+ displayPreview: pretty,
2655
+ displayBlocks: [{ type: "text", text: pretty }],
2656
+ tOffsetMs: e.tOffsetMs,
2657
+ gapMs: e.gapMs
2658
+ });
2659
+ continue;
2660
+ }
2661
+ rows.push({
2662
+ kind: "user",
2663
+ event: e,
2664
+ tOffsetMs: e.tOffsetMs,
2665
+ gapMs: e.gapMs
2666
+ });
2667
+ continue;
2668
+ }
2669
+ if (e.role === "agent") {
2670
+ const txt = firstTextOfBlocks(e.blocks) ?? "";
2671
+ if (hasZeroUsage(e)) {
2672
+ rows.push({
2673
+ kind: "model",
2674
+ event: e,
2675
+ tOffsetMs: e.tOffsetMs,
2676
+ gapMs: e.gapMs
2677
+ });
2678
+ if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
2679
+ rows.push({
2680
+ kind: "error",
2681
+ event: e,
2682
+ message: txt,
2683
+ tOffsetMs: e.tOffsetMs,
2684
+ gapMs: 0
2685
+ });
2686
+ }
2687
+ continue;
2688
+ }
2689
+ if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
2690
+ rows.push({
2691
+ kind: "error",
2692
+ event: e,
2693
+ message: txt,
2694
+ tOffsetMs: e.tOffsetMs,
2695
+ gapMs: e.gapMs
2696
+ });
2697
+ continue;
2698
+ }
2699
+ const grouped = e.messageId ? byMessageId.get(e.messageId) ?? [e] : [e];
2700
+ rows.push({
2701
+ kind: "agent",
2702
+ event: e,
2703
+ groupedEvents: grouped,
2704
+ tOffsetMs: e.tOffsetMs,
2705
+ gapMs: e.gapMs
2706
+ });
2707
+ continue;
2708
+ }
2709
+ }
2710
+ return rows;
2711
+ }
2712
+ var INTERRUPT_RE, RATE_LIMIT_RE, SKILL_CONTENT_RE, SLASH_COMMAND_RE, TASK_NOTIFICATION_RE;
2713
+ var init_presentation = __esm({
2714
+ "../parser/dist/presentation.js"() {
2715
+ "use strict";
2716
+ INTERRUPT_RE = /\[request interrupted|interrupted by user/i;
2717
+ RATE_LIMIT_RE = /rate.?limit|overloaded_error|api error|Error sending message/i;
2718
+ SKILL_CONTENT_RE = /^Base directory for this skill:/;
2719
+ SLASH_COMMAND_RE = /<command-name>(\/[^<\s]+)<\/command-name>(?:[\s\S]*?<command-args>([\s\S]*?)<\/command-args>)?/;
2720
+ TASK_NOTIFICATION_RE = /<task-notification>/;
2721
+ }
2722
+ });
2723
+
2724
+ // ../parser/dist/capsule.js
2725
+ function trunc(s, n) {
2726
+ if (!s)
2727
+ return "";
2728
+ const one = s.replace(/\s+/g, " ").trim();
2729
+ return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
2730
+ }
2731
+ function minimizeImages(s) {
2732
+ if (!s)
2733
+ return s;
2734
+ let counter = 0;
2735
+ return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
2736
+ }
2737
+ function firstNSentences(s, n = 3, hardCap = 500) {
2738
+ if (!s)
2739
+ return "";
2740
+ const flat = s.replace(/\s+/g, " ").trim();
2741
+ const out = flat.split(SENTENCE_SPLIT_RE).slice(0, n).join(" ").trim();
2742
+ return out.length > hardCap ? out.slice(0, hardCap - 1) + "\u2026" : out;
2743
+ }
2744
+ function median(xs) {
2745
+ if (xs.length === 0)
2746
+ return 0;
2747
+ const s = [...xs].sort((a, b) => a - b);
2748
+ const n = s.length;
2749
+ return n % 2 ? s[n >> 1] : (s[(n >> 1) - 1] + s[n >> 1]) / 2;
2750
+ }
2751
+ function p90(xs) {
2752
+ if (xs.length === 0)
2753
+ return 0;
2754
+ const s = [...xs].sort((a, b) => a - b);
2755
+ return s[Math.min(Math.floor(0.9 * s.length), s.length - 1)];
2756
+ }
2757
+ function bashVerb(cmd) {
2758
+ if (!cmd)
2759
+ return "?";
2760
+ let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
2761
+ if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
2762
+ const parts = cmd.split(/&&|;/, 3);
2763
+ if (parts.length >= 2)
2764
+ firstSeg = parts[1].trim();
2765
+ }
2766
+ const tokens = firstSeg.split(/\s+/).filter(Boolean);
2767
+ while (tokens.length && SHELL_NOISE.has(tokens[0]))
2768
+ tokens.shift();
2769
+ if (!tokens.length)
2770
+ return "?";
2771
+ let first = tokens[0];
2772
+ if (first.includes("=") && !first.startsWith("/")) {
2773
+ tokens.shift();
2774
+ if (!tokens.length)
2775
+ return "?";
2776
+ first = tokens[0];
2777
+ }
2778
+ if (first.includes("/") && !first.startsWith("./")) {
2779
+ first = first.replace(/\/+$/, "").split("/").pop();
2780
+ }
2781
+ return first.replace(/^["']|["']$/g, "") || "?";
2782
+ }
2783
+ function newTurn(startMs, userInput = "") {
2784
+ return {
2785
+ startMs,
2786
+ lastMs: startMs,
2787
+ eventTs: startMs !== void 0 ? [startMs] : [],
2788
+ userInput,
2789
+ firstToolsIfNoText: [],
2790
+ toolsTotal: 0,
2791
+ toolMix: /* @__PURE__ */ new Map(),
2792
+ bashVerbs: /* @__PURE__ */ new Map(),
2793
+ skills: /* @__PURE__ */ new Map(),
2794
+ subagents: [],
2795
+ taskOps: 0,
2796
+ toolErrors: 0,
2797
+ consecSameToolMax: 0,
2798
+ consec: 0,
2799
+ endedInInterrupt: false,
2800
+ models: /* @__PURE__ */ new Map(),
2801
+ tokensIn: 0,
2802
+ tokensOut: 0,
2803
+ tokensCacheR: 0,
2804
+ tokensCacheW: 0,
2805
+ seenMsgIds: /* @__PURE__ */ new Set()
2806
+ };
2807
+ }
2808
+ function closeTurn(t, closed) {
2809
+ const ts = [...t.eventTs].sort((a, b) => a - b);
2810
+ let active = 0;
2811
+ for (let i = 1; i < ts.length; i++) {
2812
+ const dt = ts[i] - ts[i - 1];
2813
+ if (dt < IDLE_GAP_MS)
2814
+ active += dt;
2815
+ }
2816
+ const wall = t.startMs !== void 0 && t.lastMs !== void 0 ? t.lastMs - t.startMs : 0;
2817
+ closed.push({
2818
+ startMs: t.startMs ?? 0,
2819
+ activeMs: active,
2820
+ wallMs: wall,
2821
+ userInput: t.userInput,
2822
+ firstAgentText: t.firstAgentText,
2823
+ lastAgentText: t.lastAgentText,
2824
+ firstToolsIfNoText: t.firstToolsIfNoText,
2825
+ toolsTotal: t.toolsTotal,
2826
+ toolMix: t.toolMix,
2827
+ bashVerbs: t.bashVerbs,
2828
+ skills: t.skills,
2829
+ subagents: t.subagents,
2830
+ taskOps: t.taskOps,
2831
+ toolErrors: t.toolErrors,
2832
+ consecSameToolMax: t.consecSameToolMax,
2833
+ endedInInterrupt: t.endedInInterrupt,
2834
+ models: t.models,
2835
+ totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW
2836
+ });
2837
+ }
2838
+ function firstTextBlock(blocks) {
2839
+ if (!Array.isArray(blocks))
2840
+ return "";
2841
+ for (const b of blocks) {
2842
+ if (b && typeof b === "object" && b.type === "text") {
2843
+ return String(b.text ?? "");
2844
+ }
2845
+ }
2846
+ return "";
2847
+ }
2848
+ function parseMs(iso) {
2849
+ if (!iso)
2850
+ return void 0;
2851
+ const ms = Date.parse(iso);
2852
+ return Number.isNaN(ms) ? void 0 : ms;
2853
+ }
2854
+ function buildCapsule(session, opts = {}) {
2855
+ const { compact = false } = opts;
2856
+ let firstUserSession;
2857
+ let finalAgentSession;
2858
+ const prTitles = [];
2859
+ let commits = 0;
2860
+ let pushes = 0;
2861
+ const modelTurnsGlobal = /* @__PURE__ */ new Map();
2862
+ let totalToolErrors = 0;
2863
+ let totalInterrupts = 0;
2864
+ let exitPlanCalls = 0;
2865
+ let taskOpsGlobal = 0;
2866
+ const closed = [];
2867
+ let cur;
2868
+ let sessionFirstTs;
2869
+ let sessionLastTs;
2870
+ const allEventTs = [];
2871
+ for (const ev of session.events) {
2872
+ const tsMs = parseMs(ev.timestamp);
2873
+ if (tsMs !== void 0) {
2874
+ if (sessionFirstTs === void 0 || tsMs < sessionFirstTs)
2875
+ sessionFirstTs = tsMs;
2876
+ if (sessionLastTs === void 0 || tsMs > sessionLastTs)
2877
+ sessionLastTs = tsMs;
2878
+ allEventTs.push(tsMs);
2879
+ }
2880
+ const rawType = ev.rawType;
2881
+ const raw = ev.raw;
2882
+ const msg = raw?.message ?? {};
2883
+ const content = msg.content;
2884
+ if (rawType === "user") {
2885
+ const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
2886
+ if (!isToolResultOnly) {
2887
+ const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
2888
+ if (cur) {
2889
+ if (text && INTERRUPT_RE2.test(text)) {
2890
+ cur.endedInInterrupt = true;
2891
+ totalInterrupts++;
2892
+ }
2893
+ closeTurn(cur, closed);
2894
+ }
2895
+ cur = newTurn(tsMs, text);
2896
+ if (!firstUserSession && text && !text.startsWith("<command-name>")) {
2897
+ firstUserSession = text;
2898
+ }
2899
+ } else {
2900
+ if (Array.isArray(content)) {
2901
+ for (const c of content) {
2902
+ if (c && typeof c === "object" && c.type === "tool_result" && c.is_error) {
2903
+ totalToolErrors++;
2904
+ if (cur)
2905
+ cur.toolErrors++;
2906
+ }
2907
+ }
2908
+ }
2909
+ if (cur && tsMs !== void 0) {
2910
+ cur.eventTs.push(tsMs);
2911
+ cur.lastMs = tsMs;
2912
+ }
2913
+ }
2914
+ } else if (rawType === "assistant") {
2915
+ if (cur && tsMs !== void 0) {
2916
+ cur.eventTs.push(tsMs);
2917
+ cur.lastMs = tsMs;
2918
+ }
2919
+ const model = msg.model;
2920
+ if (model) {
2921
+ modelTurnsGlobal.set(model, (modelTurnsGlobal.get(model) ?? 0) + 1);
2922
+ if (cur)
2923
+ cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
2924
+ }
2925
+ const msgId = msg.id;
2926
+ const usage2 = msg.usage;
2927
+ if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage2) {
2928
+ cur.seenMsgIds.add(msgId);
2929
+ cur.tokensIn += usage2.input_tokens ?? 0;
2930
+ cur.tokensOut += usage2.output_tokens ?? 0;
2931
+ cur.tokensCacheR += usage2.cache_read_input_tokens ?? 0;
2932
+ cur.tokensCacheW += usage2.cache_creation_input_tokens ?? 0;
2933
+ }
2934
+ if (Array.isArray(content)) {
2935
+ for (const c of content) {
2936
+ if (!c || typeof c !== "object")
2937
+ continue;
2938
+ const ct = c;
2939
+ if (ct.type === "text" && ct.text) {
2940
+ finalAgentSession = ct.text;
2941
+ if (cur) {
2942
+ if (cur.firstAgentText === void 0)
2943
+ cur.firstAgentText = ct.text;
2944
+ cur.lastAgentText = ct.text;
2945
+ }
2946
+ } else if (ct.type === "tool_use") {
2947
+ const name = ct.name ?? "unknown";
2948
+ if (cur) {
2949
+ cur.toolsTotal++;
2950
+ cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
2951
+ if (cur.firstAgentText === void 0 && cur.firstToolsIfNoText.length < 3) {
2952
+ cur.firstToolsIfNoText.push(name);
2953
+ }
2954
+ if (name === cur.lastTool)
2955
+ cur.consec++;
2956
+ else {
2957
+ cur.lastTool = name;
2958
+ cur.consec = 1;
2959
+ }
2960
+ if (cur.consec > cur.consecSameToolMax)
2961
+ cur.consecSameToolMax = cur.consec;
2962
+ if (name === "Skill") {
2963
+ const sk = String(ct.input?.skill ?? "unknown");
2964
+ cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
2965
+ } else if (name === "ToolSearch") {
2966
+ const q = String(ct.input?.query ?? "");
2967
+ const key = `(ToolSearch: ${trunc(q, 60)})`;
2968
+ cur.skills.set(key, (cur.skills.get(key) ?? 0) + 1);
2969
+ }
2970
+ if (name === "Agent") {
2971
+ const inp = ct.input ?? {};
2972
+ cur.subagents.push({
2973
+ type: String(inp.subagent_type ?? "general-purpose"),
2974
+ model: typeof inp.model === "string" ? inp.model : void 0,
2975
+ description: trunc(String(inp.description ?? ""), 80),
2976
+ prompt_preview: trunc(String(inp.prompt ?? ""), 240),
2977
+ background: Boolean(inp.run_in_background)
2978
+ });
2979
+ }
2980
+ if (name === "ExitPlanMode")
2981
+ exitPlanCalls++;
2982
+ if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
2983
+ cur.taskOps++;
2984
+ taskOpsGlobal++;
2985
+ }
2986
+ }
2987
+ if (name === "Bash") {
2988
+ const cmd = String(ct.input?.command ?? "");
2989
+ if (cur)
2990
+ cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
2991
+ if (/\bgh\s+pr\s+create\b/.test(cmd)) {
2992
+ const m = cmd.match(PR_TITLE_RE);
2993
+ prTitles.push(m?.[1] ?? cmd.slice(0, 120));
2994
+ }
2995
+ if (/\bgit\s+commit\b/.test(cmd))
2996
+ commits++;
2997
+ if (/\bgit\s+push\b/.test(cmd))
2998
+ pushes++;
2999
+ }
3000
+ }
3001
+ }
3002
+ }
3003
+ }
3004
+ }
3005
+ if (cur)
3006
+ closeTurn(cur, closed);
3007
+ const tsSorted = [...allEventTs].sort((a, b) => a - b);
3008
+ let activeMsTotal = 0;
3009
+ for (let i = 1; i < tsSorted.length; i++) {
3010
+ const dt = tsSorted[i] - tsSorted[i - 1];
3011
+ if (dt < IDLE_GAP_MS)
3012
+ activeMsTotal += dt;
3013
+ }
3014
+ const turnActives = closed.map((t) => t.activeMs);
3015
+ const turnTools = closed.map((t) => t.toolsTotal);
3016
+ const turnTokens = closed.map((t) => t.totalTokens);
3017
+ const subagentTurns = closed.filter((t) => t.subagents.length > 0).length;
3018
+ const primaryModel = [...modelTurnsGlobal.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
3019
+ const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
3020
+ const longestTurnActive = Math.max(0, ...turnActives);
3021
+ const sessionSkills = /* @__PURE__ */ new Map();
3022
+ const sessionSubagents = [];
3023
+ for (const t of closed) {
3024
+ for (const [k, v] of t.skills)
3025
+ sessionSkills.set(k, (sessionSkills.get(k) ?? 0) + v);
3026
+ sessionSubagents.push(...t.subagents);
3027
+ }
3028
+ const skillCalls = [...sessionSkills.values()].reduce((a, b) => a + b, 0);
3029
+ const prs = prTitles.length;
3030
+ let outcome;
3031
+ if (prs > 0)
3032
+ outcome = "shipped";
3033
+ else if (commits > 0 && pushes > 0)
3034
+ outcome = "shipped-no-pr";
3035
+ else if (commits > 0)
3036
+ outcome = "partial";
3037
+ else if (activeMsTotal < 2 * 6e4 && closed.length < 3)
3038
+ outcome = "trivial";
3039
+ else
3040
+ outcome = "exploratory";
3041
+ const flags = [];
3042
+ if (totalInterrupts >= 3)
3043
+ flags.push("interrupt_heavy");
3044
+ if (totalToolErrors >= 20)
3045
+ flags.push("high_errors");
3046
+ if (consecMax >= 8)
3047
+ flags.push("loop_suspected");
3048
+ if (activeMsTotal < 5 * 6e4 && prs >= 1)
3049
+ flags.push("fast_ship");
3050
+ if (exitPlanCalls > 0)
3051
+ flags.push("plan_used");
3052
+ if (subagentTurns >= 3)
3053
+ flags.push("orchestrated");
3054
+ if (longestTurnActive >= 20 * 6e4 && totalInterrupts === 0)
3055
+ flags.push("long_autonomous");
3056
+ const maxTokens = Math.max(1, ...turnTokens);
3057
+ const maxTools = Math.max(1, ...turnTools);
3058
+ function significance(t) {
3059
+ return 0.5 * (t.totalTokens / maxTokens) + 0.5 * (t.toolsTotal / maxTools);
3060
+ }
3061
+ let substantive = closed.filter((t) => t.activeMs >= TURN_FLOOR_MS || t.toolsTotal >= 1);
3062
+ const droppedTrivial = closed.length - substantive.length;
3063
+ if (substantive.length > MAX_TURNS_IN_CAPSULE) {
3064
+ substantive = [...substantive].sort((a, b) => significance(b) - significance(a)).slice(0, MAX_TURNS_IN_CAPSULE);
3065
+ }
3066
+ substantive.sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
3067
+ function renderTurn(t) {
3068
+ const tOffsetS = t.startMs && sessionFirstTs ? (t.startMs - sessionFirstTs) / 1e3 : 0;
3069
+ const topToolsArr = [];
3070
+ const topTools = [...t.toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
3071
+ for (const [name, count] of topTools) {
3072
+ if (name === "Bash" && t.bashVerbs.size > 0) {
3073
+ const sub = [...t.bashVerbs.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
3074
+ topToolsArr.push(`Bash\xD7${count} (${sub})`);
3075
+ } else {
3076
+ topToolsArr.push(count > 1 ? `${name}\xD7${count}` : name);
3077
+ }
3078
+ }
3079
+ const firstAgentRaw = t.firstAgentText ?? (t.firstToolsIfNoText.length > 0 ? `(tools only: ${t.firstToolsIfNoText.join(", ")})` : "");
3080
+ const firstTrunc = trunc(minimizeImages(firstAgentRaw), 260);
3081
+ const finalTrunc = firstNSentences(minimizeImages(t.lastAgentText ?? ""), 3, 500);
3082
+ const ended = t.endedInInterrupt ? "interrupt" : t.lastAgentText ? "text" : "tool-in-flight";
3083
+ const model = [...t.models.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
3084
+ const turn = {
3085
+ t_offset_min: Math.round(tOffsetS / 6) / 10,
3086
+ active_min: Math.round(t.activeMs / 6e3) / 10,
3087
+ wall_min: Math.round(t.wallMs / 6e3) / 10,
3088
+ user_input: trunc(minimizeImages(t.userInput), 300),
3089
+ tools_total: t.toolsTotal,
3090
+ top_tools: topToolsArr,
3091
+ tokens: t.totalTokens,
3092
+ errors: t.toolErrors,
3093
+ consec_same_tool_max: t.consecSameToolMax,
3094
+ ended,
3095
+ model
3096
+ };
3097
+ if (firstTrunc && finalTrunc && firstTrunc === finalTrunc) {
3098
+ turn.agent = firstTrunc;
3099
+ } else if (firstAgentRaw && t.lastAgentText && t.firstAgentText === t.lastAgentText) {
3100
+ turn.agent = finalTrunc;
3101
+ } else {
3102
+ turn.first_agent = firstTrunc;
3103
+ turn.final_agent = finalTrunc;
3104
+ }
3105
+ if (t.skills.size > 0)
3106
+ turn.skills = Object.fromEntries(t.skills);
3107
+ if (t.subagents.length > 0)
3108
+ turn.subagents = t.subagents;
3109
+ return turn;
3110
+ }
3111
+ const capsule = {
3112
+ session_id: session.id,
3113
+ project: session.projectName,
3114
+ start_iso: session.firstTimestamp,
3115
+ end_iso: session.lastTimestamp,
3116
+ outcome,
3117
+ flags,
3118
+ primary_model: primaryModel,
3119
+ model_mix: Object.fromEntries(modelTurnsGlobal),
3120
+ numbers: {
3121
+ active_min: Math.round(activeMsTotal / 6e3) / 10,
3122
+ turn_count: closed.length,
3123
+ longest_turn_min: Math.round(longestTurnActive / 6e3) / 10,
3124
+ median_turn_min: Math.round(median(turnActives) / 6e3) / 10,
3125
+ p90_turn_min: Math.round(p90(turnActives) / 6e3) / 10,
3126
+ tools_total: turnTools.reduce((a, b) => a + b, 0),
3127
+ tokens_total: turnTokens.reduce((a, b) => a + b, 0),
3128
+ subagent_turns: subagentTurns,
3129
+ subagent_calls: sessionSubagents.length,
3130
+ skill_calls: skillCalls,
3131
+ task_ops: taskOpsGlobal,
3132
+ interrupts: totalInterrupts,
3133
+ tool_errors: totalToolErrors,
3134
+ consec_same_tool_max: consecMax,
3135
+ exit_plan_calls: exitPlanCalls,
3136
+ prs,
3137
+ commits,
3138
+ pushes
3139
+ },
3140
+ pr_titles: prTitles,
3141
+ first_user: trunc(minimizeImages(firstUserSession ?? ""), 400),
3142
+ final_agent: trunc(minimizeImages(finalAgentSession ?? ""), 400)
3143
+ };
3144
+ if (sessionSkills.size > 0)
3145
+ capsule.skills = Object.fromEntries(sessionSkills);
3146
+ if (sessionSubagents.length > 0)
3147
+ capsule.subagents = sessionSubagents;
3148
+ if (!compact) {
3149
+ capsule.numbers.turns_in_capsule = substantive.length;
3150
+ capsule.numbers.trivial_turns_dropped = droppedTrivial;
3151
+ capsule.turns = substantive.map(renderTurn);
3152
+ }
3153
+ return capsule;
3154
+ }
3155
+ var IDLE_GAP_MS, MAX_TURNS_IN_CAPSULE, TURN_FLOOR_MS, INTERRUPT_RE2, SENTENCE_SPLIT_RE, IMAGE_BLOCK_RE, PR_TITLE_RE, BASH_CHAIN_SPLIT_RE, SHELL_NOISE;
3156
+ var init_capsule = __esm({
3157
+ "../parser/dist/capsule.js"() {
3158
+ "use strict";
3159
+ IDLE_GAP_MS = 3 * 60 * 1e3;
3160
+ MAX_TURNS_IN_CAPSULE = 25;
3161
+ TURN_FLOOR_MS = 15 * 1e3;
3162
+ INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
3163
+ SENTENCE_SPLIT_RE = /(?<=[.!?])\s+(?=[A-Z[`])/;
3164
+ IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
3165
+ PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
3166
+ BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
3167
+ SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
3168
+ }
3169
+ });
3170
+
3171
+ // ../parser/dist/aggregate.js
3172
+ function isoDay(d) {
3173
+ return toLocalDay(d.getTime());
3174
+ }
3175
+ function rangeDays(start2, end) {
3176
+ const out = [];
3177
+ const cur = new Date(start2.getFullYear(), start2.getMonth(), start2.getDate());
3178
+ const stop2 = new Date(end.getFullYear(), end.getMonth(), end.getDate());
3179
+ while (cur.getTime() <= stop2.getTime()) {
3180
+ out.push(new Date(cur));
3181
+ cur.setDate(cur.getDate() + 1);
3182
+ }
3183
+ return out;
3184
+ }
3185
+ function prettyProjectName(p) {
3186
+ const cleaned = p.replace(/^\/Users\/[^/]+\//, "");
3187
+ return cleaned || p;
3188
+ }
3189
+ function buildPeriodBundle(caps, opts) {
3190
+ const { period, trivial_dropped, sessions_total } = opts;
3191
+ const days = rangeDays(period.start, period.end);
3192
+ const dayKeys = new Map(days.map((d) => [isoDay(d), d]));
3193
+ const dayAgent = /* @__PURE__ */ new Map();
3194
+ const daySessions = /* @__PURE__ */ new Map();
3195
+ for (const k of dayKeys.keys()) {
3196
+ dayAgent.set(k, 0);
3197
+ daySessions.set(k, 0);
3198
+ }
3199
+ for (const c of caps) {
3200
+ if (!c.start_iso)
3201
+ continue;
3202
+ const d = isoDay(new Date(c.start_iso));
3203
+ if (!dayAgent.has(d))
3204
+ continue;
3205
+ dayAgent.set(d, (dayAgent.get(d) ?? 0) + c.numbers.active_min);
3206
+ daySessions.set(d, (daySessions.get(d) ?? 0) + 1);
3207
+ }
3208
+ const by_day = days.map((d) => {
3209
+ const key = isoDay(d);
3210
+ return {
3211
+ date: key,
3212
+ day_name: DAY_NAMES[d.getDay()],
3213
+ agent_min: Math.round((dayAgent.get(key) ?? 0) * 10) / 10,
3214
+ sessions: daySessions.get(key) ?? 0
3215
+ };
3216
+ });
3217
+ const projectAccum = /* @__PURE__ */ new Map();
3218
+ for (const c of caps) {
3219
+ const canon = canonicalProjectName(c.project ?? "unknown");
3220
+ const b = projectAccum.get(canon) ?? { agent_min: 0, prs: 0, commits: 0 };
3221
+ b.agent_min += c.numbers.active_min;
3222
+ b.prs += c.numbers.prs;
3223
+ b.commits += c.numbers.commits;
3224
+ projectAccum.set(canon, b);
3225
+ }
3226
+ const totalProjectMin = [...projectAccum.values()].reduce((a, b) => a + b.agent_min, 0);
3227
+ const project_shares = [...projectAccum.entries()].map(([name, b]) => ({
3228
+ name,
3229
+ display_name: prettyProjectName(name),
3230
+ agent_min: Math.round(b.agent_min * 10) / 10,
3231
+ share_pct: totalProjectMin === 0 ? 0 : Math.round(b.agent_min / totalProjectMin * 100),
3232
+ prs: b.prs,
3233
+ commits: b.commits
3234
+ })).sort((a, b) => b.agent_min - a.agent_min);
3235
+ const shipped_prs = [];
3236
+ for (const c of caps) {
3237
+ for (const title of c.pr_titles) {
3238
+ shipped_prs.push({
3239
+ title,
3240
+ project: prettyProjectName(canonicalProjectName(c.project ?? "")),
3241
+ session_id: c.session_id,
3242
+ start_iso: c.start_iso ?? "",
3243
+ active_min: c.numbers.active_min,
3244
+ commits: c.numbers.commits,
3245
+ subagents: c.numbers.subagent_calls,
3246
+ flags: c.flags
3247
+ });
3248
+ }
3249
+ }
3250
+ const flags_count = {};
3251
+ const skills_total = {};
3252
+ const totals = { agent_min: 0, commits: 0, pushes: 0, prs: 0, subagent_calls: 0, tool_errors: 0 };
3253
+ for (const c of caps) {
3254
+ for (const f of c.flags)
3255
+ flags_count[f] = (flags_count[f] ?? 0) + 1;
3256
+ if (c.skills) {
3257
+ for (const [k, v] of Object.entries(c.skills)) {
3258
+ skills_total[k] = (skills_total[k] ?? 0) + v;
3259
+ }
3260
+ }
3261
+ totals.agent_min += c.numbers.active_min;
3262
+ totals.commits += c.numbers.commits;
3263
+ totals.pushes += c.numbers.pushes;
3264
+ totals.prs += c.numbers.prs;
3265
+ totals.subagent_calls += c.numbers.subagent_calls;
3266
+ totals.tool_errors += c.numbers.tool_errors;
3267
+ }
3268
+ totals.agent_min = Math.round(totals.agent_min * 10) / 10;
3269
+ const longest = [...caps].sort((a, b) => b.numbers.active_min - a.numbers.active_min)[0];
3270
+ const shipped = caps.filter((c) => c.numbers.prs >= 1);
3271
+ const fastest = shipped.sort((a, b) => a.numbers.active_min - b.numbers.active_min)[0];
3272
+ const mostErr = [...caps].sort((a, b) => b.numbers.tool_errors - a.numbers.tool_errors)[0];
3273
+ const wandered = [...caps].filter((c) => c.outcome === "exploratory" && c.numbers.prs === 0 && c.numbers.commits === 0 && c.numbers.pushes === 0 && c.numbers.active_min >= 10).sort((a, b) => b.numbers.active_min - a.numbers.active_min)[0];
3274
+ const outliers = {};
3275
+ if (longest && longest.numbers.active_min > 0) {
3276
+ outliers.longest_run = {
3277
+ session_id: longest.session_id,
3278
+ project: prettyProjectName(canonicalProjectName(longest.project ?? "")),
3279
+ active_min: longest.numbers.active_min,
3280
+ flags: longest.flags
3281
+ };
3282
+ }
3283
+ if (fastest) {
3284
+ outliers.fastest_ship = {
3285
+ session_id: fastest.session_id,
3286
+ project: prettyProjectName(canonicalProjectName(fastest.project ?? "")),
3287
+ active_min: fastest.numbers.active_min,
3288
+ pr_title: fastest.pr_titles[0] ?? ""
3289
+ };
3290
+ }
3291
+ if (mostErr && mostErr.numbers.tool_errors > 0) {
3292
+ outliers.most_errors = {
3293
+ session_id: mostErr.session_id,
3294
+ project: prettyProjectName(canonicalProjectName(mostErr.project ?? "")),
3295
+ active_min: mostErr.numbers.active_min,
3296
+ tool_errors: mostErr.numbers.tool_errors
3297
+ };
3298
+ }
3299
+ if (wandered) {
3300
+ outliers.wandered = {
3301
+ session_id: wandered.session_id,
3302
+ project: prettyProjectName(canonicalProjectName(wandered.project ?? "")),
3303
+ active_min: wandered.numbers.active_min
3304
+ };
3305
+ }
3306
+ const label = period.range_type === "month" ? period.start.toLocaleDateString("en-US", { month: "long", year: "numeric" }) : `${period.start.toLocaleDateString("en-US", { month: "short", day: "numeric" })} \u2014 ${period.end.toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;
3307
+ return {
3308
+ period: {
3309
+ start: isoDay(period.start),
3310
+ end: isoDay(period.end),
3311
+ label,
3312
+ day_count: days.length,
3313
+ range_type: period.range_type ?? "custom"
3314
+ },
3315
+ counts: { sessions_total, substantive: caps.length, trivial_dropped },
3316
+ totals,
3317
+ by_day,
3318
+ project_shares,
3319
+ shipped_prs,
3320
+ flags_count,
3321
+ skills_total,
3322
+ outliers
3323
+ };
3324
+ }
3325
+ function calendarWeek(ref = /* @__PURE__ */ new Date()) {
3326
+ const d = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate());
3327
+ const dow = d.getDay();
3328
+ const daysSinceMon = (dow + 6) % 7;
3329
+ const monday = new Date(d);
3330
+ monday.setDate(d.getDate() - daysSinceMon);
3331
+ const sunday = new Date(monday);
3332
+ sunday.setDate(monday.getDate() + 6);
3333
+ return { start: monday, end: sunday };
3334
+ }
3335
+ function priorCalendarWeek(ref = /* @__PURE__ */ new Date()) {
3336
+ const cur = calendarWeek(ref);
3337
+ const prevEnd = new Date(cur.start);
3338
+ prevEnd.setDate(cur.start.getDate() - 1);
3339
+ const prevStart = new Date(prevEnd);
3340
+ prevStart.setDate(prevEnd.getDate() - 6);
3341
+ return { start: prevStart, end: prevEnd };
3342
+ }
3343
+ function last4CompletedWeeks(ref = /* @__PURE__ */ new Date()) {
3344
+ const { end } = priorCalendarWeek(ref);
3345
+ const start2 = new Date(end);
3346
+ start2.setDate(end.getDate() - 27);
3347
+ return { start: start2, end };
3348
+ }
3349
+ function calendarMonth(ref = /* @__PURE__ */ new Date()) {
3350
+ const start2 = new Date(ref.getFullYear(), ref.getMonth(), 1);
3351
+ const end = new Date(ref.getFullYear(), ref.getMonth() + 1, 0);
3352
+ return { start: start2, end };
3353
+ }
3354
+ function priorCalendarMonth(ref = /* @__PURE__ */ new Date()) {
3355
+ const start2 = new Date(ref.getFullYear(), ref.getMonth() - 1, 1);
3356
+ const end = new Date(ref.getFullYear(), ref.getMonth(), 0);
3357
+ return { start: start2, end };
3358
+ }
3359
+ function aggregateConcurrency(bursts, period) {
3360
+ const days = rangeDays(period.start, period.end);
3361
+ const byDayKey = /* @__PURE__ */ new Map();
3362
+ for (const d of days)
3363
+ byDayKey.set(isoDay(d), { peak: 0, has_cross_project: false });
3364
+ for (const b of bursts) {
3365
+ const dayKey = isoDay(new Date(b.startMs));
3366
+ const cur = byDayKey.get(dayKey);
3367
+ if (!cur)
3368
+ continue;
3369
+ if (b.peak > cur.peak)
3370
+ cur.peak = b.peak;
3371
+ if (b.crossProject)
3372
+ cur.has_cross_project = true;
3373
+ }
3374
+ const by_day = days.map((d) => {
3375
+ const k = isoDay(d);
3376
+ const v = byDayKey.get(k);
3377
+ return { date: k, peak: v.peak, has_cross_project: v.has_cross_project };
3378
+ });
3379
+ let peak = 0;
3380
+ let peak_day;
3381
+ let multi_agent_days = 0;
3382
+ let cross_project_days = 0;
3383
+ for (const d of by_day) {
3384
+ if (d.peak > peak) {
3385
+ peak = d.peak;
3386
+ peak_day = d.date;
3387
+ }
3388
+ if (d.peak >= 3)
3389
+ multi_agent_days++;
3390
+ if (d.has_cross_project)
3391
+ cross_project_days++;
3392
+ }
3393
+ return { by_day, peak, peak_day, multi_agent_days, cross_project_days };
3394
+ }
3395
+ var DAY_NAMES;
3396
+ var init_aggregate = __esm({
3397
+ "../parser/dist/aggregate.js"() {
3398
+ "use strict";
3399
+ init_analytics();
3400
+ DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
3401
+ }
3402
+ });
3403
+
3404
+ // ../parser/dist/index.js
3405
+ var dist_exports = {};
3406
+ __export(dist_exports, {
3407
+ aggregateConcurrency: () => aggregateConcurrency,
3408
+ buildCapsule: () => buildCapsule,
3409
+ buildGanttDay: () => buildGanttDay,
3410
+ buildMegaRows: () => buildMegaRows,
3411
+ buildPeriodBundle: () => buildPeriodBundle,
3412
+ buildPresentation: () => buildPresentation,
3413
+ calendarMonth: () => calendarMonth,
3414
+ calendarWeek: () => calendarWeek,
3415
+ canonicalProjectName: () => canonicalProjectName,
3416
+ computeActiveSegments: () => computeActiveSegments,
3417
+ computeBurstsFromSessions: () => computeBurstsFromSessions,
3418
+ computeParallelism: () => computeParallelism,
3419
+ computeParallelismBursts: () => computeParallelismBursts,
3420
+ dailyActivity: () => dailyActivity,
3421
+ daysBetween: () => daysBetween,
3422
+ detectParallelRuns: () => detectParallelRuns,
3423
+ detectPrMarkers: () => detectPrMarkers,
3424
+ groupByProject: () => groupByProject,
3425
+ groupByTeam: () => groupByTeam,
3426
+ highLevelMetrics: () => highLevelMetrics,
3427
+ last4CompletedWeeks: () => last4CompletedWeeks,
3428
+ parseTranscript: () => parseTranscript,
3429
+ priorCalendarMonth: () => priorCalendarMonth,
3430
+ priorCalendarWeek: () => priorCalendarWeek,
3431
+ rowPrimaryIndex: () => rowPrimaryIndex,
3432
+ sessionAirTimeMs: () => sessionAirTimeMs,
3433
+ sessionDay: () => sessionDay,
3434
+ summarizeBursts: () => summarizeBursts,
3435
+ toLocalDay: () => toLocalDay,
3436
+ worktreeName: () => worktreeName
3437
+ });
3438
+ var init_dist = __esm({
3439
+ "../parser/dist/index.js"() {
3440
+ "use strict";
3441
+ init_types();
3442
+ init_parser();
3443
+ init_presentation();
3444
+ init_analytics();
3445
+ init_capsule();
3446
+ init_aggregate();
3447
+ init_team();
3448
+ }
3449
+ });
3450
+
3451
+ // src/args.ts
3452
+ function parseDateArg(raw) {
3453
+ if (!raw) return null;
3454
+ const compact = raw.replace(/-/g, "");
3455
+ if (!/^\d{8}$/.test(compact)) return null;
3456
+ const d = /* @__PURE__ */ new Date(`${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`);
3457
+ return Number.isNaN(d.getTime()) ? null : d;
3458
+ }
3459
+ function flag(args2, name) {
3460
+ const idx = args2.indexOf(name);
3461
+ return idx === -1 ? void 0 : args2[idx + 1];
3462
+ }
3463
+ var init_args = __esm({
3464
+ "src/args.ts"() {
3465
+ "use strict";
3466
+ }
3467
+ });
3468
+
3469
+ // src/commands/stats.ts
3470
+ var stats_exports = {};
3471
+ __export(stats_exports, {
3472
+ stats: () => stats
3473
+ });
3474
+ async function stats(args2) {
3475
+ if (args2.includes("--help") || args2.includes("-h")) {
3476
+ printHelp();
3477
+ return;
3478
+ }
3479
+ const json = args2.includes("--json") || !args2.includes("--pretty");
3480
+ const pretty = args2.includes("--pretty");
3481
+ let range;
3482
+ if (args2.includes("--week")) {
3483
+ range = { ...calendarWeek(), range_type: "week" };
3484
+ } else if (args2.includes("--4weeks")) {
3485
+ range = { ...last4CompletedWeeks(), range_type: "4weeks" };
3486
+ } else {
3487
+ const sinceArg = parseDateArg(flag(args2, "--since"));
3488
+ const untilArg = parseDateArg(flag(args2, "--until"));
3489
+ const daysArg = flag(args2, "--days");
3490
+ let start2 = sinceArg;
3491
+ const end = untilArg ?? /* @__PURE__ */ new Date();
3492
+ if (!start2 && daysArg) {
3493
+ const d = parseInt(daysArg, 10);
3494
+ if (Number.isFinite(d) && d > 0) {
3495
+ start2 = /* @__PURE__ */ new Date();
3496
+ start2.setDate(start2.getDate() - d);
3497
+ start2.setHours(0, 0, 0, 0);
3498
+ }
3499
+ }
3500
+ if (!start2) {
3501
+ range = { ...calendarWeek(), range_type: "week" };
3502
+ } else {
3503
+ range = { start: start2, end, range_type: "custom" };
3504
+ }
3505
+ }
3506
+ const bundle = await buildBundle(range);
3507
+ if (json) {
3508
+ process.stdout.write(JSON.stringify(bundle, null, pretty ? 2 : 0));
3509
+ if (pretty) process.stdout.write("\n");
3510
+ } else {
3511
+ printSummary(bundle);
3512
+ }
3513
+ }
3514
+ async function buildBundle(range) {
3515
+ const metas = await listSessions({ limit: 1e4 });
3516
+ const inRange = metas.filter((m) => {
3517
+ if (!m.firstTimestamp) return false;
3518
+ const t = new Date(m.firstTimestamp).getTime();
3519
+ return t >= range.start.getTime() && t <= range.end.getTime();
3520
+ });
3521
+ const caps = [];
3522
+ let trivial = 0;
3523
+ const CONCURRENCY = 8;
3524
+ for (let i = 0; i < inRange.length; i += CONCURRENCY) {
3525
+ const slice = inRange.slice(i, i + CONCURRENCY);
3526
+ const results = await Promise.all(slice.map(async (m) => {
3527
+ try {
3528
+ const d = await getSession(m.id);
3529
+ if (!d) return null;
3530
+ return buildCapsule(d, { compact: true });
3531
+ } catch {
3532
+ return null;
3533
+ }
3534
+ }));
3535
+ for (const cap of results) {
3536
+ if (!cap) continue;
3537
+ if (cap.outcome === "trivial") trivial++;
3538
+ else caps.push(cap);
3539
+ }
3540
+ }
3541
+ const bundle = buildPeriodBundle(caps, {
3542
+ period: range,
3543
+ trivial_dropped: trivial,
3544
+ sessions_total: inRange.length
3545
+ });
3546
+ bundle.concurrency = aggregateConcurrency(computeBurstsFromSessions(inRange), range);
3547
+ try {
3548
+ bundle.usage = await loadUsageByDay(range.start, range.end);
3549
+ } catch {
3550
+ }
3551
+ return bundle;
3552
+ }
3553
+ function printSummary(b) {
3554
+ console.log(`Insight aggregates \xB7 ${b.period.label} (${b.period.range_type})`);
3555
+ console.log(` ${b.counts.substantive} substantive \xB7 ${b.counts.trivial_dropped} trivial dropped \xB7 ${b.counts.sessions_total} total`);
3556
+ console.log(` ${b.totals.agent_min.toFixed(0)}m agent time \xB7 ${b.totals.prs} PRs \xB7 ${b.totals.commits} commits \xB7 ${b.totals.subagent_calls} subagents`);
3557
+ console.log("");
3558
+ console.log("By day:");
3559
+ for (const d of b.by_day) {
3560
+ const bar = "\u2588".repeat(Math.min(40, Math.round(d.agent_min / 15)));
3561
+ console.log(` ${d.day_name} ${d.date} ${String(d.agent_min).padStart(5)}m ${d.sessions} sess ${bar}`);
3562
+ }
3563
+ console.log("");
3564
+ console.log("Top projects:");
3565
+ for (const p of b.project_shares.slice(0, 6)) {
3566
+ console.log(` ${String(p.share_pct).padStart(3)}% ${p.display_name.padEnd(42)} ${p.prs} PR ${p.commits} commits`);
3567
+ }
3568
+ if (b.shipped_prs.length > 0) {
3569
+ console.log("");
3570
+ console.log("Shipped:");
3571
+ for (const s of b.shipped_prs) {
3572
+ console.log(` ${s.title} \xB7 ${s.active_min.toFixed(0)}m \xB7 ${s.flags.join(",") || "\u2014"}`);
3573
+ }
3574
+ }
3575
+ if (b.concurrency) {
3576
+ console.log("");
3577
+ console.log(`Concurrency: peak \xD7${b.concurrency.peak} on ${b.concurrency.peak_day ?? "\u2014"} \xB7 ${b.concurrency.multi_agent_days} multi-agent days \xB7 ${b.concurrency.cross_project_days} cross-project`);
3578
+ }
3579
+ if (b.usage) {
3580
+ const hot = b.usage.by_day.filter((d) => d.peak_util_pct > 0);
3581
+ if (hot.length > 0) {
3582
+ console.log(`Usage: peak ${Math.max(...hot.map((d) => d.peak_util_pct))}% (${hot.length} active days)`);
3583
+ }
3584
+ }
3585
+ console.log("");
3586
+ console.log("Flags:", Object.entries(b.flags_count).map(([k, v]) => `${k}\xD7${v}`).join(", ") || "\u2014");
3587
+ }
3588
+ function printHelp() {
3589
+ console.log(`fleetlens stats \u2014 period aggregates for the insights pipeline
3590
+
3591
+ Usage:
3592
+ fleetlens stats [--week | --4weeks | --since DATE --until DATE | --days N]
3593
+ [--json | --pretty]
3594
+
3595
+ Range selection:
3596
+ --week Current Monday-Sunday calendar week (default).
3597
+ --4weeks Last 28 days ending with this week's Sunday.
3598
+ --since DATE inclusive start (YYYYMMDD or YYYY-MM-DD)
3599
+ --until DATE inclusive end (default: now)
3600
+ --days N alternative to --since (last N days)
3601
+
3602
+ Output:
3603
+ --json (default when piped) structured JSON \u2014 the second half
3604
+ of the insight LLM payload. Matches the PeriodBundle
3605
+ type in @claude-lens/parser.
3606
+ --pretty human-readable terminal summary.
3607
+
3608
+ Examples:
3609
+ fleetlens stats --week --pretty
3610
+ fleetlens stats --week --json | jq '.concurrency'
3611
+ fleetlens stats --since 2026-04-14 --until 2026-04-20 --json > /tmp/bundle.json`);
3612
+ }
3613
+ var init_stats = __esm({
3614
+ "src/commands/stats.ts"() {
3615
+ "use strict";
3616
+ init_fs();
3617
+ init_dist();
3618
+ init_args();
3619
+ }
3620
+ });
3621
+
3622
+ // src/usage/token.ts
3623
+ import { execFileSync } from "node:child_process";
3624
+ import { readFileSync as readFileSync5 } from "node:fs";
3625
+ import { homedir as homedir3, platform } from "node:os";
3626
+ import { join as join4 } from "node:path";
3627
+ function readOAuthCredentials() {
3628
+ if (platform() === "darwin") {
3629
+ return readFromMacKeychain() ?? readFromCredentialsFile();
3630
+ }
3631
+ return readFromCredentialsFile();
3632
+ }
3633
+ function readFromMacKeychain() {
3634
+ try {
3635
+ const blob = execFileSync(
3636
+ "security",
3637
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
3638
+ { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }
3639
+ );
3640
+ return extractCredentials(blob);
3641
+ } catch {
3642
+ return null;
3643
+ }
3644
+ }
3645
+ function readFromCredentialsFile() {
3646
+ const candidates = [
3647
+ join4(homedir3(), ".claude", ".credentials.json"),
3648
+ join4(homedir3(), ".config", "claude", "credentials.json")
3649
+ ];
3650
+ for (const path2 of candidates) {
3651
+ try {
3652
+ const blob = readFileSync5(path2, "utf8");
3653
+ const creds = extractCredentials(blob);
3654
+ if (creds) return creds;
3655
+ } catch {
3656
+ }
3657
+ }
3658
+ return null;
3659
+ }
3660
+ function extractCredentials(blob) {
3661
+ try {
3662
+ const parsed = JSON.parse(blob);
3663
+ const oauth = parsed.claudeAiOauth;
3664
+ if (!oauth) return null;
3665
+ const accessToken = typeof oauth.accessToken === "string" ? oauth.accessToken : null;
3666
+ const expiresAt = typeof oauth.expiresAt === "number" ? oauth.expiresAt : null;
3667
+ if (!accessToken || expiresAt === null) return null;
3668
+ return { accessToken, expiresAt };
3669
+ } catch {
3670
+ return null;
3671
+ }
3672
+ }
3673
+ var init_token = __esm({
3674
+ "src/usage/token.ts"() {
3675
+ "use strict";
3676
+ }
3677
+ });
3678
+
3679
+ // src/usage/api.ts
3680
+ async function fetchUsage() {
3681
+ const creds = readOAuthCredentials();
3682
+ if (!creds) {
3683
+ throw new UsageApiError(
3684
+ "No Claude Code OAuth token found. Run `claude` to log in first.",
3685
+ "no_token"
3686
+ );
3687
+ }
3688
+ if (creds.expiresAt - EXPIRY_SKEW_MS <= Date.now()) {
3689
+ throw new UsageApiError(
3690
+ "Claude Code OAuth token expired. Open Claude Code to refresh it.",
3691
+ "expired"
3692
+ );
3693
+ }
3694
+ let res;
3695
+ try {
3696
+ res = await fetch(USAGE_ENDPOINT, {
3697
+ headers: {
3698
+ Authorization: `Bearer ${creds.accessToken}`,
3699
+ "anthropic-beta": BETA_HEADER
3700
+ }
3701
+ });
3702
+ } catch (err) {
3703
+ throw new UsageApiError(
3704
+ `Network error reaching Anthropic: ${err.message}`,
3705
+ "network"
3706
+ );
3707
+ }
3708
+ if (!res.ok) {
3709
+ throw new UsageApiError(
3710
+ `Usage endpoint returned ${res.status} ${res.statusText}`,
3711
+ "http"
3712
+ );
3713
+ }
3714
+ let body;
3715
+ try {
3716
+ body = await res.json();
3717
+ } catch (err) {
3718
+ throw new UsageApiError(`Failed to parse usage response: ${err.message}`, "parse");
3719
+ }
3720
+ const b = body;
3721
+ return {
3722
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
3723
+ five_hour: normalizeWindow(b.five_hour) ?? { utilization: null, resets_at: null },
3724
+ seven_day: normalizeWindow(b.seven_day) ?? { utilization: null, resets_at: null },
3725
+ seven_day_opus: normalizeWindow(b.seven_day_opus),
3726
+ seven_day_sonnet: normalizeWindow(b.seven_day_sonnet),
3727
+ seven_day_oauth_apps: normalizeWindow(b.seven_day_oauth_apps),
3728
+ seven_day_cowork: normalizeWindow(b.seven_day_cowork),
3729
+ extra_usage: normalizeExtra(b.extra_usage)
3730
+ };
3731
+ }
3732
+ function normalizeWindow(raw) {
3733
+ if (!raw || typeof raw !== "object") return null;
3734
+ const r = raw;
3735
+ return {
3736
+ utilization: typeof r.utilization === "number" ? r.utilization : null,
3737
+ resets_at: typeof r.resets_at === "string" ? r.resets_at : null
3738
+ };
3739
+ }
3740
+ function normalizeExtra(raw) {
3741
+ if (!raw || typeof raw !== "object") return null;
3742
+ const r = raw;
3743
+ return {
3744
+ is_enabled: r.is_enabled === true,
3745
+ monthly_limit: typeof r.monthly_limit === "number" ? r.monthly_limit : null,
3746
+ used_credits: typeof r.used_credits === "number" ? r.used_credits : null,
3747
+ utilization: typeof r.utilization === "number" ? r.utilization : null
3748
+ };
3749
+ }
3750
+ var EXPIRY_SKEW_MS, USAGE_ENDPOINT, BETA_HEADER, UsageApiError;
3751
+ var init_api = __esm({
3752
+ "src/usage/api.ts"() {
3753
+ "use strict";
3754
+ init_token();
3755
+ EXPIRY_SKEW_MS = 60 * 1e3;
3756
+ USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
3757
+ BETA_HEADER = "oauth-2025-04-20";
3758
+ UsageApiError = class extends Error {
3759
+ constructor(message, code) {
3760
+ super(message);
3761
+ this.code = code;
3762
+ }
3763
+ };
3764
+ }
3765
+ });
3766
+
3767
+ // src/usage/format.ts
3768
+ function formatUsage(snapshot) {
3769
+ const rows = [
3770
+ ["5 hour", snapshot.five_hour],
3771
+ ["7 day", snapshot.seven_day],
3772
+ ["7 day (Opus)", snapshot.seven_day_opus],
3773
+ ["7 day (Sonnet)", snapshot.seven_day_sonnet],
3774
+ ["7 day (OAuth apps)", snapshot.seven_day_oauth_apps],
3775
+ ["7 day (Cowork)", snapshot.seven_day_cowork]
3776
+ ];
3777
+ const lines = [];
3778
+ lines.push("");
3779
+ lines.push(` ${BOLD}Claude Code Usage${RESET}`);
3780
+ lines.push("");
3781
+ for (const [label, window] of rows) {
3782
+ if (!window || window.utilization === null) continue;
3783
+ const pct = window.utilization;
3784
+ const bar = renderBar(pct);
3785
+ const pctStr = `${pct.toFixed(1)}%`.padStart(6);
3786
+ const labelStr = label.padEnd(16);
3787
+ lines.push(` ${labelStr}${bar} ${BOLD}${pctStr}${RESET}`);
3788
+ if (window.resets_at) {
3789
+ lines.push(` ${" ".repeat(16)}${DIM}resets ${formatRelative(window.resets_at)}${RESET}`);
3790
+ }
3791
+ lines.push("");
3792
+ }
3793
+ if (snapshot.extra_usage?.is_enabled) {
3794
+ const extra = snapshot.extra_usage;
3795
+ lines.push(` ${BOLD}Extra usage${RESET}`);
3796
+ if (extra.utilization !== null) {
3797
+ lines.push(` ${" ".repeat(16)}${extra.utilization.toFixed(1)}%`);
3798
+ }
3799
+ if (extra.used_credits !== null && extra.monthly_limit !== null) {
3800
+ lines.push(` ${" ".repeat(16)}${extra.used_credits} / ${extra.monthly_limit} credits`);
3801
+ }
3802
+ lines.push("");
3803
+ }
3804
+ lines.push(` ${DIM}captured ${formatRelative(snapshot.captured_at)}${RESET}`);
3805
+ lines.push("");
3806
+ return lines.join("\n");
3807
+ }
3808
+ function renderBar(utilization) {
3809
+ const clamped = Math.max(0, Math.min(100, utilization));
3810
+ const color = clamped >= 90 ? RED : clamped >= 70 ? YELLOW : GREEN;
3811
+ const totalEighths = Math.round(clamped / 100 * BAR_WIDTH * 8);
3812
+ const fullBlocks = Math.floor(totalEighths / 8);
3813
+ const remainder = totalEighths % 8;
3814
+ const partial = EIGHTHS[remainder];
3815
+ const filledCells = fullBlocks + (partial ? 1 : 0);
3816
+ const emptyCells = BAR_WIDTH - filledCells;
3817
+ const filled = "\u2588".repeat(fullBlocks) + partial;
3818
+ const empty = "\xB7".repeat(emptyCells);
3819
+ return `${color}${filled}${RESET}${DIM}${empty}${RESET}`;
3820
+ }
3821
+ function formatRelative(iso) {
3822
+ const then = new Date(iso).getTime();
3823
+ const now = Date.now();
3824
+ const diffSec = Math.round((then - now) / 1e3);
3825
+ const abs = Math.abs(diffSec);
3826
+ const past = diffSec < 0;
3827
+ let value;
3828
+ if (abs < 60) {
3829
+ value = `${abs}s`;
3830
+ } else if (abs < 3600) {
3831
+ value = `${Math.floor(abs / 60)}m`;
3832
+ } else if (abs < 86400) {
3833
+ const h = Math.floor(abs / 3600);
3834
+ const m = Math.floor(abs % 3600 / 60);
3835
+ value = m > 0 ? `${h}h${m}m` : `${h}h`;
3836
+ } else {
3837
+ const d = Math.floor(abs / 86400);
3838
+ const h = Math.floor(abs % 86400 / 3600);
3839
+ value = h > 0 ? `${d}d${h}h` : `${d}d`;
3840
+ }
3841
+ return past ? `${value} ago` : `in ${value}`;
3842
+ }
3843
+ var DIM, BOLD, RESET, GREEN, YELLOW, RED, BAR_WIDTH, EIGHTHS;
3844
+ var init_format = __esm({
3845
+ "src/usage/format.ts"() {
3846
+ "use strict";
3847
+ DIM = "\x1B[2m";
3848
+ BOLD = "\x1B[1m";
3849
+ RESET = "\x1B[0m";
3850
+ GREEN = "\x1B[32m";
3851
+ YELLOW = "\x1B[33m";
3852
+ RED = "\x1B[31m";
3853
+ BAR_WIDTH = 40;
3854
+ EIGHTHS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
3855
+ }
3856
+ });
3857
+
3858
+ // src/pricing.ts
3859
+ function findPricing(model) {
3860
+ for (const [prefix, pricing] of PRICING) {
3861
+ if (model.startsWith(prefix)) return pricing;
3862
+ }
3863
+ return null;
3864
+ }
3865
+ function estimateCost(model, usage2) {
3866
+ const p = findPricing(model);
3867
+ if (!p) return null;
3868
+ return (usage2.input * p.input + usage2.output * p.output + usage2.cacheRead * p.cacheRead + usage2.cacheWrite * p.cacheWrite) / 1e6;
3869
+ }
3870
+ var PRICING;
3871
+ var init_pricing = __esm({
3872
+ "src/pricing.ts"() {
3873
+ "use strict";
3874
+ PRICING = [
1356
3875
  ["claude-opus-4", { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 }],
1357
3876
  ["claude-sonnet-4", { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }],
1358
3877
  ["claude-haiku-4", { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }],
@@ -1378,7 +3897,7 @@ function pad(s, width, align = "right") {
1378
3897
  function renderTable(rows, title) {
1379
3898
  const lines = [];
1380
3899
  lines.push("");
1381
- lines.push(`${BOLD}${title}${RESET}`);
3900
+ lines.push(`${BOLD2}${title}${RESET2}`);
1382
3901
  lines.push("");
1383
3902
  const header = [
1384
3903
  pad("Date", 12, "left"),
@@ -1390,8 +3909,8 @@ function renderTable(rows, title) {
1390
3909
  pad("Total", 14),
1391
3910
  pad("Cost (USD)", 12)
1392
3911
  ].join(" ");
1393
- lines.push(`${DIM}${header}${RESET}`);
1394
- lines.push(`${DIM}${"\u2500".repeat(header.length)}${RESET}`);
3912
+ lines.push(`${DIM2}${header}${RESET2}`);
3913
+ lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
1395
3914
  let hasUnknownCost = false;
1396
3915
  for (const r of rows) {
1397
3916
  if (r.cost === null) hasUnknownCost = true;
@@ -1417,9 +3936,9 @@ function renderTable(rows, title) {
1417
3936
  }),
1418
3937
  { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, totalTokens: 0, cost: 0 }
1419
3938
  );
1420
- lines.push(`${DIM}${"\u2500".repeat(header.length)}${RESET}`);
3939
+ lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
1421
3940
  lines.push(
1422
- `${BOLD}${[
3941
+ `${BOLD2}${[
1423
3942
  pad("Total", 12, "left"),
1424
3943
  pad("", 20, "left"),
1425
3944
  pad(fmtNum(totals.input), 12),
@@ -1428,31 +3947,31 @@ function renderTable(rows, title) {
1428
3947
  pad(fmtNum(totals.cacheRead), 14),
1429
3948
  pad(fmtNum(totals.totalTokens), 14),
1430
3949
  pad(fmtCost(totals.cost), 12)
1431
- ].join(" ")}${RESET}`
3950
+ ].join(" ")}${RESET2}`
1432
3951
  );
1433
3952
  if (hasUnknownCost) {
1434
3953
  lines.push("");
1435
- lines.push(`${DIM} Note: some models had unknown pricing (shown as \u2014)${RESET}`);
3954
+ lines.push(`${DIM2} Note: some models had unknown pricing (shown as \u2014)${RESET2}`);
1436
3955
  }
1437
3956
  lines.push("");
1438
3957
  return lines.join("\n");
1439
3958
  }
1440
- var DIM, BOLD, RESET;
3959
+ var DIM2, BOLD2, RESET2;
1441
3960
  var init_table = __esm({
1442
3961
  "src/table.ts"() {
1443
3962
  "use strict";
1444
- DIM = "\x1B[2m";
1445
- BOLD = "\x1B[1m";
1446
- RESET = "\x1B[0m";
3963
+ DIM2 = "\x1B[2m";
3964
+ BOLD2 = "\x1B[1m";
3965
+ RESET2 = "\x1B[0m";
1447
3966
  }
1448
3967
  });
1449
3968
 
1450
- // src/commands/stats.ts
1451
- var stats_exports = {};
1452
- __export(stats_exports, {
1453
- stats: () => stats
3969
+ // src/commands/usage-history.ts
3970
+ var usage_history_exports = {};
3971
+ __export(usage_history_exports, {
3972
+ history: () => history
1454
3973
  });
1455
- async function stats(args2) {
3974
+ async function history(args2) {
1456
3975
  const live = args2.includes("--live");
1457
3976
  const sinceIdx = args2.indexOf("-s");
1458
3977
  const daysIdx = args2.indexOf("--days");
@@ -1556,8 +4075,8 @@ async function liveStats(sinceDate) {
1556
4075
  await refresh();
1557
4076
  setInterval(refresh, 2e3);
1558
4077
  }
1559
- var init_stats = __esm({
1560
- "src/commands/stats.ts"() {
4078
+ var init_usage_history = __esm({
4079
+ "src/commands/usage-history.ts"() {
1561
4080
  "use strict";
1562
4081
  init_fs();
1563
4082
  init_pricing();
@@ -1566,271 +4085,557 @@ var init_stats = __esm({
1566
4085
  }
1567
4086
  });
1568
4087
 
1569
- // src/usage/token.ts
1570
- import { execFileSync } from "node:child_process";
1571
- import { readFileSync as readFileSync5 } from "node:fs";
1572
- import { homedir as homedir3, platform } from "node:os";
1573
- import { join as join4 } from "node:path";
1574
- function readOAuthCredentials() {
1575
- if (platform() === "darwin") {
1576
- return readFromMacKeychain() ?? readFromCredentialsFile();
4088
+ // src/commands/usage.ts
4089
+ var usage_exports = {};
4090
+ __export(usage_exports, {
4091
+ usage: () => usage
4092
+ });
4093
+ import { join as join5 } from "node:path";
4094
+ import { homedir as homedir4 } from "node:os";
4095
+ async function usage(args2) {
4096
+ if (args2.includes("--help") || args2.includes("-h")) {
4097
+ console.log(`fleetlens usage \u2014 plan utilization snapshot
4098
+
4099
+ Usage:
4100
+ fleetlens usage [--save] Current 5h/7d plan utilization
4101
+ fleetlens usage --history [-s YYYYMMDD | --days N]
4102
+ Daily token / cost history table`);
4103
+ return;
4104
+ }
4105
+ if (args2.includes("--history")) {
4106
+ const { history: history2 } = await Promise.resolve().then(() => (init_usage_history(), usage_history_exports));
4107
+ await history2(args2.filter((a) => a !== "--history"));
4108
+ return;
4109
+ }
4110
+ const save = args2.includes("--save");
4111
+ try {
4112
+ const snapshot = await fetchUsage();
4113
+ if (save) appendSnapshot(USAGE_LOG3, snapshot);
4114
+ process.stdout.write(formatUsage(snapshot));
4115
+ process.stdout.write("\n (tip: run with --history for the daily token/cost table)\n");
4116
+ } catch (err) {
4117
+ if (err instanceof UsageApiError) {
4118
+ console.error(`Error: ${err.message}`);
4119
+ process.exit(1);
4120
+ }
4121
+ throw err;
1577
4122
  }
1578
- return readFromCredentialsFile();
1579
4123
  }
1580
- function readFromMacKeychain() {
4124
+ var USAGE_LOG3;
4125
+ var init_usage = __esm({
4126
+ "src/commands/usage.ts"() {
4127
+ "use strict";
4128
+ init_api();
4129
+ init_format();
4130
+ init_storage();
4131
+ USAGE_LOG3 = join5(homedir4(), ".cclens", "usage.jsonl");
4132
+ }
4133
+ });
4134
+
4135
+ // src/commands/capsules.ts
4136
+ var capsules_exports = {};
4137
+ __export(capsules_exports, {
4138
+ capsules: () => capsules
4139
+ });
4140
+ async function capsules(args2) {
4141
+ if (args2.includes("--help") || args2.includes("-h")) {
4142
+ printHelp2();
4143
+ return;
4144
+ }
4145
+ const compact = !args2.includes("--full");
4146
+ const pretty = args2.includes("--pretty");
4147
+ const json = args2.includes("--json") || !pretty;
4148
+ const daysArg = flag(args2, "--days");
4149
+ let sinceDate = parseDateArg(flag(args2, "--since"));
4150
+ let untilDate = parseDateArg(flag(args2, "--until"));
4151
+ if (!sinceDate && daysArg) {
4152
+ const d = parseInt(daysArg, 10);
4153
+ if (Number.isFinite(d) && d > 0) {
4154
+ sinceDate = /* @__PURE__ */ new Date();
4155
+ sinceDate.setDate(sinceDate.getDate() - d);
4156
+ sinceDate.setHours(0, 0, 0, 0);
4157
+ }
4158
+ }
4159
+ if (!sinceDate) {
4160
+ sinceDate = /* @__PURE__ */ new Date();
4161
+ sinceDate.setDate(sinceDate.getDate() - 7);
4162
+ sinceDate.setHours(0, 0, 0, 0);
4163
+ }
4164
+ if (!untilDate) untilDate = /* @__PURE__ */ new Date();
4165
+ const metas = await listSessions({ limit: 1e4 });
4166
+ const inRange = metas.filter((m) => {
4167
+ if (!m.firstTimestamp) return false;
4168
+ const t = new Date(m.firstTimestamp).getTime();
4169
+ return t >= sinceDate.getTime() && t <= untilDate.getTime();
4170
+ });
4171
+ const out = [];
4172
+ const CONCURRENCY = 8;
4173
+ for (let i = 0; i < inRange.length; i += CONCURRENCY) {
4174
+ const slice = inRange.slice(i, i + CONCURRENCY);
4175
+ const results = await Promise.all(slice.map(async (m) => {
4176
+ try {
4177
+ const d = await getSession(m.id);
4178
+ if (!d) return null;
4179
+ return buildCapsule(d, { compact });
4180
+ } catch {
4181
+ return null;
4182
+ }
4183
+ }));
4184
+ for (const cap of results) {
4185
+ if (!cap || cap.outcome === "trivial") continue;
4186
+ out.push(cap);
4187
+ }
4188
+ }
4189
+ out.sort((a, b) => (a.start_iso ?? "").localeCompare(b.start_iso ?? ""));
4190
+ if (json) {
4191
+ process.stdout.write(JSON.stringify(out, null, pretty ? 2 : 0));
4192
+ if (pretty) process.stdout.write("\n");
4193
+ } else {
4194
+ printSummary2(out, sinceDate, untilDate);
4195
+ }
4196
+ }
4197
+ function printSummary2(caps, since, until) {
4198
+ const since_s = since.toISOString().slice(0, 10);
4199
+ const until_s = until.toISOString().slice(0, 10);
4200
+ console.log(`Capsules from ${since_s} to ${until_s} \xB7 ${caps.length} substantive sessions`);
4201
+ console.log("");
4202
+ for (const c of caps) {
4203
+ const tag = c.outcome.padEnd(14);
4204
+ const flags = c.flags.length > 0 ? ` [${c.flags.join(",")}]` : "";
4205
+ const ship = c.pr_titles.length > 0 ? ` PR: ${c.pr_titles[0]}` : "";
4206
+ const proj = (c.project ?? "").replace("/Users/", "~/").slice(0, 40);
4207
+ const min = c.numbers.active_min.toFixed(1);
4208
+ console.log(` ${c.start_iso?.slice(0, 16)} ${tag} ${min.padStart(6)}m ${proj.padEnd(42)}${flags}${ship}`);
4209
+ }
4210
+ }
4211
+ function printHelp2() {
4212
+ console.log(`fleetlens capsules \u2014 per-session insight capsules
4213
+
4214
+ Usage:
4215
+ fleetlens capsules [--since DATE] [--until DATE] [--days N] [--compact] [--json|--pretty]
4216
+
4217
+ Options:
4218
+ --since DATE inclusive start (YYYYMMDD or YYYY-MM-DD). Default: 7 days ago.
4219
+ --until DATE inclusive end. Default: now.
4220
+ --days N alternative to --since (last N days).
4221
+ --full include per-turn detail (heavy: ~15 KB/session). Default: compact.
4222
+ --json structured JSON (default when piping).
4223
+ --pretty pretty-printed JSON (with --json) or human-readable table (without).
4224
+
4225
+ Examples:
4226
+ fleetlens capsules --days 7 --json | jq '.[] | .outcome' | sort | uniq -c
4227
+ fleetlens capsules --since 2026-04-14 --until 2026-04-20 --pretty
4228
+ fleetlens capsules --days 30 > /tmp/month.json`);
4229
+ }
4230
+ var init_capsules = __esm({
4231
+ "src/commands/capsules.ts"() {
4232
+ "use strict";
4233
+ init_fs();
4234
+ init_dist();
4235
+ init_args();
4236
+ }
4237
+ });
4238
+
4239
+ // src/team/config.ts
4240
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "node:fs";
4241
+ import { join as join6 } from "node:path";
4242
+ import { homedir as homedir5 } from "node:os";
4243
+ function readTeamConfig(dir = DEFAULT_DIR) {
1581
4244
  try {
1582
- const blob = execFileSync(
1583
- "security",
1584
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
1585
- { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }
1586
- );
1587
- return extractCredentials(blob);
4245
+ return JSON.parse(readFileSync6(join6(dir, CONFIG_FILE), "utf8"));
1588
4246
  } catch {
1589
4247
  return null;
1590
4248
  }
1591
4249
  }
1592
- function readFromCredentialsFile() {
1593
- const candidates = [
1594
- join4(homedir3(), ".claude", ".credentials.json"),
1595
- join4(homedir3(), ".config", "claude", "credentials.json")
1596
- ];
1597
- for (const path2 of candidates) {
1598
- try {
1599
- const blob = readFileSync5(path2, "utf8");
1600
- const creds = extractCredentials(blob);
1601
- if (creds) return creds;
1602
- } catch {
1603
- }
4250
+ function writeTeamConfig(config, dir = DEFAULT_DIR) {
4251
+ mkdirSync3(dir, { recursive: true });
4252
+ writeFileSync2(join6(dir, CONFIG_FILE), JSON.stringify(config, null, 2), { mode: 384 });
4253
+ }
4254
+ function clearTeamConfig(dir = DEFAULT_DIR) {
4255
+ try {
4256
+ unlinkSync2(join6(dir, CONFIG_FILE));
4257
+ } catch {
1604
4258
  }
1605
- return null;
1606
4259
  }
1607
- function extractCredentials(blob) {
4260
+ var DEFAULT_DIR, CONFIG_FILE;
4261
+ var init_config = __esm({
4262
+ "src/team/config.ts"() {
4263
+ "use strict";
4264
+ DEFAULT_DIR = join6(homedir5(), ".cclens");
4265
+ CONFIG_FILE = "team.json";
4266
+ }
4267
+ });
4268
+
4269
+ // src/team/join.ts
4270
+ var join_exports = {};
4271
+ __export(join_exports, {
4272
+ joinTeam: () => joinTeam
4273
+ });
4274
+ async function joinTeam(args2) {
4275
+ const [serverUrl, bearerToken] = args2;
4276
+ if (!serverUrl || !bearerToken) {
4277
+ console.error("Usage: fleetlens team join <server-url> <device-token>");
4278
+ console.error("");
4279
+ console.error("Get the device token from the dashboard after signup,");
4280
+ console.error("or from Settings \u2192 My device token.");
4281
+ process.exit(1);
4282
+ }
4283
+ const res = await fetch(`${serverUrl}/api/team/whoami`, {
4284
+ method: "GET",
4285
+ headers: { Authorization: `Bearer ${bearerToken}` }
4286
+ });
4287
+ if (!res.ok) {
4288
+ console.error(`Pairing failed: ${res.status} ${res.statusText}`);
4289
+ console.error("The device token may be revoked or the server URL wrong.");
4290
+ process.exit(1);
4291
+ }
4292
+ const data = await res.json();
4293
+ writeTeamConfig({
4294
+ serverUrl,
4295
+ memberId: data.membership.id,
4296
+ bearerToken,
4297
+ teamSlug: data.team.slug,
4298
+ pairedAt: (/* @__PURE__ */ new Date()).toISOString()
4299
+ });
4300
+ console.log(`Paired with "${data.team.name}" as ${data.user.displayName || data.user.email}`);
4301
+ console.log(` role: ${data.membership.role}`);
4302
+ console.log(" Your daemon will push metrics on the next 5-minute cycle.");
4303
+ }
4304
+ var init_join = __esm({
4305
+ "src/team/join.ts"() {
4306
+ "use strict";
4307
+ init_config();
4308
+ }
4309
+ });
4310
+
4311
+ // src/team/status.ts
4312
+ var status_exports2 = {};
4313
+ __export(status_exports2, {
4314
+ teamStatus: () => teamStatus
4315
+ });
4316
+ async function teamStatus() {
4317
+ const config = readTeamConfig();
4318
+ if (!config) {
4319
+ console.log("Not paired with any team.");
4320
+ console.log("Use 'fleetlens team join <url> <token>' to pair.");
4321
+ return;
4322
+ }
4323
+ console.log(`Team: ${config.teamSlug}`);
4324
+ console.log(`Server: ${config.serverUrl}`);
4325
+ console.log(`Member: ${config.memberId}`);
4326
+ console.log(`Paired: ${config.pairedAt}`);
1608
4327
  try {
1609
- const parsed = JSON.parse(blob);
1610
- const oauth = parsed.claudeAiOauth;
1611
- if (!oauth) return null;
1612
- const accessToken = typeof oauth.accessToken === "string" ? oauth.accessToken : null;
1613
- const expiresAt = typeof oauth.expiresAt === "number" ? oauth.expiresAt : null;
1614
- if (!accessToken || expiresAt === null) return null;
1615
- return { accessToken, expiresAt };
4328
+ const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
4329
+ method: "POST",
4330
+ headers: { Authorization: `Bearer ${config.bearerToken}`, "Content-Type": "application/json" },
4331
+ body: JSON.stringify({}),
4332
+ signal: AbortSignal.timeout(5e3)
4333
+ });
4334
+ if (res.status === 401) {
4335
+ console.log("Status: ! Token revoked \u2014 run 'fleetlens team leave' then re-join");
4336
+ } else {
4337
+ console.log("Status: Connected");
4338
+ }
1616
4339
  } catch {
1617
- return null;
4340
+ console.log("Status: ! Cannot reach server");
1618
4341
  }
1619
4342
  }
1620
- var init_token = __esm({
1621
- "src/usage/token.ts"() {
4343
+ var init_status2 = __esm({
4344
+ "src/team/status.ts"() {
1622
4345
  "use strict";
4346
+ init_config();
1623
4347
  }
1624
4348
  });
1625
4349
 
1626
- // src/usage/api.ts
1627
- async function fetchUsage() {
1628
- const creds = readOAuthCredentials();
1629
- if (!creds) {
1630
- throw new UsageApiError(
1631
- "No Claude Code OAuth token found. Run `claude` to log in first.",
1632
- "no_token"
1633
- );
1634
- }
1635
- if (creds.expiresAt - EXPIRY_SKEW_MS <= Date.now()) {
1636
- throw new UsageApiError(
1637
- "Claude Code OAuth token expired. Open Claude Code to refresh it.",
1638
- "expired"
1639
- );
4350
+ // src/team/leave.ts
4351
+ var leave_exports = {};
4352
+ __export(leave_exports, {
4353
+ teamLeave: () => teamLeave
4354
+ });
4355
+ async function teamLeave() {
4356
+ const config = readTeamConfig();
4357
+ if (!config) {
4358
+ console.log("Not paired with any team.");
4359
+ return;
1640
4360
  }
1641
- let res;
1642
4361
  try {
1643
- res = await fetch(USAGE_ENDPOINT, {
1644
- headers: {
1645
- Authorization: `Bearer ${creds.accessToken}`,
1646
- "anthropic-beta": BETA_HEADER
1647
- }
4362
+ await fetch(`${config.serverUrl}/api/team/leave`, {
4363
+ method: "POST",
4364
+ headers: { Authorization: `Bearer ${config.bearerToken}` },
4365
+ signal: AbortSignal.timeout(5e3)
1648
4366
  });
1649
- } catch (err) {
1650
- throw new UsageApiError(
1651
- `Network error reaching Anthropic: ${err.message}`,
1652
- "network"
1653
- );
4367
+ } catch {
1654
4368
  }
1655
- if (!res.ok) {
1656
- throw new UsageApiError(
1657
- `Usage endpoint returned ${res.status} ${res.statusText}`,
1658
- "http"
1659
- );
4369
+ clearTeamConfig();
4370
+ console.log("Left team. Local data is unaffected.");
4371
+ }
4372
+ var init_leave = __esm({
4373
+ "src/team/leave.ts"() {
4374
+ "use strict";
4375
+ init_config();
1660
4376
  }
1661
- let body;
4377
+ });
4378
+
4379
+ // src/team/logs.ts
4380
+ var logs_exports = {};
4381
+ __export(logs_exports, {
4382
+ teamLogs: () => teamLogs
4383
+ });
4384
+ import { readFileSync as readFileSync7 } from "node:fs";
4385
+ import { join as join7 } from "node:path";
4386
+ import { homedir as homedir6 } from "node:os";
4387
+ async function teamLogs() {
4388
+ const logPath = join7(homedir6(), ".cclens", "daemon.log");
1662
4389
  try {
1663
- body = await res.json();
1664
- } catch (err) {
1665
- throw new UsageApiError(`Failed to parse usage response: ${err.message}`, "parse");
4390
+ const lines = readFileSync7(logPath, "utf8").trim().split("\n");
4391
+ const teamLines = lines.filter((l) => l.includes("team "));
4392
+ if (teamLines.length === 0) {
4393
+ console.log("No team-related log entries found.");
4394
+ return;
4395
+ }
4396
+ for (const line of teamLines.slice(-20)) {
4397
+ console.log(line);
4398
+ }
4399
+ } catch {
4400
+ console.log("No daemon log found. Is the daemon running?");
1666
4401
  }
1667
- const b = body;
1668
- return {
1669
- captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1670
- five_hour: normalizeWindow(b.five_hour) ?? { utilization: null, resets_at: null },
1671
- seven_day: normalizeWindow(b.seven_day) ?? { utilization: null, resets_at: null },
1672
- seven_day_opus: normalizeWindow(b.seven_day_opus),
1673
- seven_day_sonnet: normalizeWindow(b.seven_day_sonnet),
1674
- seven_day_oauth_apps: normalizeWindow(b.seven_day_oauth_apps),
1675
- seven_day_cowork: normalizeWindow(b.seven_day_cowork),
1676
- extra_usage: normalizeExtra(b.extra_usage)
1677
- };
1678
4402
  }
1679
- function normalizeWindow(raw) {
1680
- if (!raw || typeof raw !== "object") return null;
1681
- const r = raw;
4403
+ var init_logs = __esm({
4404
+ "src/team/logs.ts"() {
4405
+ "use strict";
4406
+ }
4407
+ });
4408
+
4409
+ // src/team/push.ts
4410
+ import { randomUUID } from "node:crypto";
4411
+ function bucketToRollup(b) {
1682
4412
  return {
1683
- utilization: typeof r.utilization === "number" ? r.utilization : null,
1684
- resets_at: typeof r.resets_at === "string" ? r.resets_at : null
4413
+ day: b.date,
4414
+ agentTimeMs: b.airTimeMs,
4415
+ sessions: b.sessions,
4416
+ toolCalls: b.toolCalls,
4417
+ turns: b.turns,
4418
+ tokens: { ...b.tokens }
1685
4419
  };
1686
4420
  }
1687
- function normalizeExtra(raw) {
1688
- if (!raw || typeof raw !== "object") return null;
1689
- const r = raw;
4421
+ function buildRollupsForRange(sessions, sinceDay) {
4422
+ const buckets = dailyActivity(sessions);
4423
+ const startCounts = /* @__PURE__ */ new Map();
4424
+ for (const s of sessions) {
4425
+ const d = sessionDay(s);
4426
+ if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
4427
+ }
4428
+ return buckets.filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({
4429
+ ...bucketToRollup(b),
4430
+ sessions: startCounts.get(b.date) ?? 0
4431
+ }));
4432
+ }
4433
+ function buildIngestPayload(rollup) {
1690
4434
  return {
1691
- is_enabled: r.is_enabled === true,
1692
- monthly_limit: typeof r.monthly_limit === "number" ? r.monthly_limit : null,
1693
- used_credits: typeof r.used_credits === "number" ? r.used_credits : null,
1694
- utilization: typeof r.utilization === "number" ? r.utilization : null
4435
+ ingestId: randomUUID(),
4436
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
4437
+ dailyRollup: rollup
1695
4438
  };
1696
4439
  }
1697
- var EXPIRY_SKEW_MS, USAGE_ENDPOINT, BETA_HEADER, UsageApiError;
1698
- var init_api = __esm({
1699
- "src/usage/api.ts"() {
4440
+ async function pushToTeamServer(config, payload) {
4441
+ const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
4442
+ method: "POST",
4443
+ headers: {
4444
+ "Content-Type": "application/json",
4445
+ Authorization: `Bearer ${config.bearerToken}`
4446
+ },
4447
+ body: JSON.stringify(payload)
4448
+ });
4449
+ const body = await res.json().catch(() => null);
4450
+ return { ok: res.ok, status: res.status, body };
4451
+ }
4452
+ var init_push = __esm({
4453
+ "src/team/push.ts"() {
1700
4454
  "use strict";
1701
- init_token();
1702
- EXPIRY_SKEW_MS = 60 * 1e3;
1703
- USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
1704
- BETA_HEADER = "oauth-2025-04-20";
1705
- UsageApiError = class extends Error {
1706
- constructor(message, code) {
1707
- super(message);
1708
- this.code = code;
1709
- }
1710
- };
4455
+ init_dist();
1711
4456
  }
1712
4457
  });
1713
4458
 
1714
- // src/usage/format.ts
1715
- function formatUsage(snapshot) {
1716
- const rows = [
1717
- ["5 hour", snapshot.five_hour],
1718
- ["7 day", snapshot.seven_day],
1719
- ["7 day (Opus)", snapshot.seven_day_opus],
1720
- ["7 day (Sonnet)", snapshot.seven_day_sonnet],
1721
- ["7 day (OAuth apps)", snapshot.seven_day_oauth_apps],
1722
- ["7 day (Cowork)", snapshot.seven_day_cowork]
1723
- ];
1724
- const lines = [];
1725
- lines.push("");
1726
- lines.push(` ${BOLD2}Claude Code Usage${RESET2}`);
1727
- lines.push("");
1728
- for (const [label, window] of rows) {
1729
- if (!window || window.utilization === null) continue;
1730
- const pct = window.utilization;
1731
- const bar = renderBar(pct);
1732
- const pctStr = `${pct.toFixed(1)}%`.padStart(6);
1733
- const labelStr = label.padEnd(16);
1734
- lines.push(` ${labelStr}${bar} ${BOLD2}${pctStr}${RESET2}`);
1735
- if (window.resets_at) {
1736
- lines.push(` ${" ".repeat(16)}${DIM2}resets ${formatRelative(window.resets_at)}${RESET2}`);
4459
+ // src/team/queue.ts
4460
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2, statSync as statSync2, mkdirSync as mkdirSync4 } from "node:fs";
4461
+ import { join as join8, dirname as dirname6 } from "node:path";
4462
+ import { homedir as homedir7 } from "node:os";
4463
+ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
4464
+ mkdirSync4(dirname6(queuePath), { recursive: true });
4465
+ const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
4466
+ appendFileSync2(queuePath, JSON.stringify(entry) + "\n", "utf8");
4467
+ try {
4468
+ const { size } = statSync2(queuePath);
4469
+ if (size > MAX_SIZE_BYTES) {
4470
+ pruneOldest(queuePath);
1737
4471
  }
1738
- lines.push("");
4472
+ } catch {
1739
4473
  }
1740
- if (snapshot.extra_usage?.is_enabled) {
1741
- const extra = snapshot.extra_usage;
1742
- lines.push(` ${BOLD2}Extra usage${RESET2}`);
1743
- if (extra.utilization !== null) {
1744
- lines.push(` ${" ".repeat(16)}${extra.utilization.toFixed(1)}%`);
1745
- }
1746
- if (extra.used_credits !== null && extra.monthly_limit !== null) {
1747
- lines.push(` ${" ".repeat(16)}${extra.used_credits} / ${extra.monthly_limit} credits`);
4474
+ }
4475
+ function dequeuePayloads(queuePath = QUEUE_PATH) {
4476
+ let lines;
4477
+ try {
4478
+ lines = readFileSync8(queuePath, "utf8").trim().split("\n").filter(Boolean);
4479
+ } catch {
4480
+ return [];
4481
+ }
4482
+ const now = Date.now();
4483
+ const valid = [];
4484
+ for (const line of lines) {
4485
+ try {
4486
+ const entry = JSON.parse(line);
4487
+ if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
4488
+ valid.push(entry);
4489
+ }
4490
+ } catch {
1748
4491
  }
1749
- lines.push("");
1750
4492
  }
1751
- lines.push(` ${DIM2}captured ${formatRelative(snapshot.captured_at)}${RESET2}`);
1752
- lines.push("");
1753
- return lines.join("\n");
1754
- }
1755
- function renderBar(utilization) {
1756
- const clamped = Math.max(0, Math.min(100, utilization));
1757
- const color = clamped >= 90 ? RED : clamped >= 70 ? YELLOW : GREEN;
1758
- const totalEighths = Math.round(clamped / 100 * BAR_WIDTH * 8);
1759
- const fullBlocks = Math.floor(totalEighths / 8);
1760
- const remainder = totalEighths % 8;
1761
- const partial = EIGHTHS[remainder];
1762
- const filledCells = fullBlocks + (partial ? 1 : 0);
1763
- const emptyCells = BAR_WIDTH - filledCells;
1764
- const filled = "\u2588".repeat(fullBlocks) + partial;
1765
- const empty = "\xB7".repeat(emptyCells);
1766
- return `${color}${filled}${RESET2}${DIM2}${empty}${RESET2}`;
4493
+ writeFileSync3(queuePath, "", "utf8");
4494
+ return valid.map((e) => e.payload);
1767
4495
  }
1768
- function formatRelative(iso) {
1769
- const then = new Date(iso).getTime();
1770
- const now = Date.now();
1771
- const diffSec = Math.round((then - now) / 1e3);
1772
- const abs = Math.abs(diffSec);
1773
- const past = diffSec < 0;
1774
- let value;
1775
- if (abs < 60) {
1776
- value = `${abs}s`;
1777
- } else if (abs < 3600) {
1778
- value = `${Math.floor(abs / 60)}m`;
1779
- } else if (abs < 86400) {
1780
- const h = Math.floor(abs / 3600);
1781
- const m = Math.floor(abs % 3600 / 60);
1782
- value = m > 0 ? `${h}h${m}m` : `${h}h`;
1783
- } else {
1784
- const d = Math.floor(abs / 86400);
1785
- const h = Math.floor(abs % 86400 / 3600);
1786
- value = h > 0 ? `${d}d${h}h` : `${d}d`;
4496
+ function pruneOldest(queuePath) {
4497
+ let lines;
4498
+ try {
4499
+ lines = readFileSync8(queuePath, "utf8").trim().split("\n").filter(Boolean);
4500
+ } catch {
4501
+ return;
1787
4502
  }
1788
- return past ? `${value} ago` : `in ${value}`;
4503
+ const keep = lines.slice(Math.floor(lines.length / 2));
4504
+ writeFileSync3(queuePath, keep.join("\n") + "\n", "utf8");
1789
4505
  }
1790
- var DIM2, BOLD2, RESET2, GREEN, YELLOW, RED, BAR_WIDTH, EIGHTHS;
1791
- var init_format = __esm({
1792
- "src/usage/format.ts"() {
4506
+ var QUEUE_PATH, MAX_SIZE_BYTES, MAX_AGE_MS;
4507
+ var init_queue = __esm({
4508
+ "src/team/queue.ts"() {
1793
4509
  "use strict";
1794
- DIM2 = "\x1B[2m";
1795
- BOLD2 = "\x1B[1m";
1796
- RESET2 = "\x1B[0m";
1797
- GREEN = "\x1B[32m";
1798
- YELLOW = "\x1B[33m";
1799
- RED = "\x1B[31m";
1800
- BAR_WIDTH = 40;
1801
- EIGHTHS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
4510
+ QUEUE_PATH = join8(homedir7(), ".cclens", "ingest-queue.jsonl");
4511
+ MAX_SIZE_BYTES = 10 * 1024 * 1024;
4512
+ MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
1802
4513
  }
1803
4514
  });
1804
4515
 
1805
- // src/commands/usage.ts
1806
- var usage_exports = {};
1807
- __export(usage_exports, {
1808
- usage: () => usage
4516
+ // src/team/sync.ts
4517
+ var sync_exports = {};
4518
+ __export(sync_exports, {
4519
+ runTeamSync: () => runTeamSync
1809
4520
  });
1810
- import { join as join5 } from "node:path";
1811
- import { homedir as homedir4 } from "node:os";
1812
- async function usage(args2) {
1813
- const save = args2.includes("--save");
4521
+ async function runTeamSync(log = noopLog) {
4522
+ const config = readTeamConfig();
4523
+ if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
1814
4524
  try {
1815
- const snapshot = await fetchUsage();
1816
- if (save) appendSnapshot(USAGE_LOG2, snapshot);
1817
- process.stdout.write(formatUsage(snapshot));
4525
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
4526
+ const { toLocalDay: toLocalDay2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
4527
+ const today = toLocalDay2(Date.now());
4528
+ const sessions = await listSessions2({ limit: 1e4 });
4529
+ const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
4530
+ if (rollups.length === 0) {
4531
+ log("info", "team push: nothing to sync");
4532
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0 };
4533
+ }
4534
+ let pushed = 0;
4535
+ let queued = 0;
4536
+ let failedDay;
4537
+ for (const rollup of rollups) {
4538
+ const payload = buildIngestPayload(rollup);
4539
+ const result = await pushToTeamServer(config, payload);
4540
+ if (!result.ok) {
4541
+ log("warn", `team push failed on ${rollup.day} (${result.status}); queueing`);
4542
+ enqueuePayload(payload);
4543
+ queued++;
4544
+ failedDay = rollup.day;
4545
+ break;
4546
+ }
4547
+ pushed++;
4548
+ }
4549
+ const nextConfig = { ...config, lastSyncedDay: today };
4550
+ if (pushed > 0 || !failedDay) writeTeamConfig(nextConfig);
4551
+ let queuedDrained = 0;
4552
+ if (!failedDay) {
4553
+ const backlog = dequeuePayloads();
4554
+ for (let i = 0; i < backlog.length; i++) {
4555
+ const qResult = await pushToTeamServer(config, backlog[i]);
4556
+ if (!qResult.ok) {
4557
+ for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
4558
+ break;
4559
+ }
4560
+ queuedDrained++;
4561
+ }
4562
+ }
4563
+ if (pushed > 0) {
4564
+ log("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
4565
+ }
4566
+ return { paired: true, pushed, queued, queuedDrained, failedDay };
1818
4567
  } catch (err) {
1819
- if (err instanceof UsageApiError) {
1820
- console.error(`Error: ${err.message}`);
1821
- process.exit(1);
4568
+ const message = err.message;
4569
+ log("warn", `team push error: ${message}`);
4570
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, error: message };
4571
+ }
4572
+ }
4573
+ var noopLog;
4574
+ var init_sync = __esm({
4575
+ "src/team/sync.ts"() {
4576
+ "use strict";
4577
+ init_config();
4578
+ init_push();
4579
+ init_queue();
4580
+ noopLog = () => {
4581
+ };
4582
+ }
4583
+ });
4584
+
4585
+ // src/commands/team.ts
4586
+ var team_exports = {};
4587
+ __export(team_exports, {
4588
+ team: () => team
4589
+ });
4590
+ async function team(args2) {
4591
+ const sub = args2[0];
4592
+ switch (sub) {
4593
+ case "join": {
4594
+ const { joinTeam: joinTeam2 } = await Promise.resolve().then(() => (init_join(), join_exports));
4595
+ await joinTeam2(args2.slice(1));
4596
+ break;
1822
4597
  }
1823
- throw err;
4598
+ case "status": {
4599
+ const { teamStatus: teamStatus2 } = await Promise.resolve().then(() => (init_status2(), status_exports2));
4600
+ await teamStatus2();
4601
+ break;
4602
+ }
4603
+ case "leave": {
4604
+ const { teamLeave: teamLeave2 } = await Promise.resolve().then(() => (init_leave(), leave_exports));
4605
+ await teamLeave2();
4606
+ break;
4607
+ }
4608
+ case "logs": {
4609
+ const { teamLogs: teamLogs2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
4610
+ await teamLogs2();
4611
+ break;
4612
+ }
4613
+ case "sync": {
4614
+ const { runTeamSync: runTeamSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
4615
+ const outcome = await runTeamSync2((level, msg) => console.log(`[${level}] ${msg}`));
4616
+ if (!outcome.paired) {
4617
+ console.error("Not paired. Run 'fleetlens team join <url> <device-token>' first.");
4618
+ process.exit(1);
4619
+ }
4620
+ if (outcome.error) process.exit(1);
4621
+ console.log(
4622
+ `\u2713 ${outcome.pushed} day${outcome.pushed === 1 ? "" : "s"} pushed` + (outcome.queuedDrained ? `, ${outcome.queuedDrained} queued retried` : "") + (outcome.queued ? `, ${outcome.queued} queued for retry (will fire next cycle)` : "")
4623
+ );
4624
+ break;
4625
+ }
4626
+ default:
4627
+ console.log(`Usage: fleetlens team <join|status|leave|logs|sync>
4628
+
4629
+ join <url> <device-token> Pair daemon with a team server
4630
+ status Show team pairing state and sync info
4631
+ leave Unpair from the team server
4632
+ logs Show recent team-related daemon log entries
4633
+ sync Push any un-synced days now (skip 5-min wait)`);
1824
4634
  }
1825
4635
  }
1826
- var USAGE_LOG2;
1827
- var init_usage = __esm({
1828
- "src/commands/usage.ts"() {
4636
+ var init_team2 = __esm({
4637
+ "src/commands/team.ts"() {
1829
4638
  "use strict";
1830
- init_api();
1831
- init_format();
1832
- init_storage();
1833
- USAGE_LOG2 = join5(homedir4(), ".cclens", "usage.jsonl");
1834
4639
  }
1835
4640
  });
1836
4641
 
@@ -1874,15 +4679,25 @@ async function main() {
1874
4679
  await usage2(args.slice(1));
1875
4680
  break;
1876
4681
  }
4682
+ case "capsules": {
4683
+ const { capsules: capsules2 } = await Promise.resolve().then(() => (init_capsules(), capsules_exports));
4684
+ await capsules2(args.slice(1));
4685
+ break;
4686
+ }
1877
4687
  case "daemon": {
1878
4688
  const { daemon: daemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
1879
4689
  await daemon2(args.slice(1));
1880
4690
  break;
1881
4691
  }
4692
+ case "team": {
4693
+ const { team: team2 } = await Promise.resolve().then(() => (init_team2(), team_exports));
4694
+ await team2(args.slice(1));
4695
+ break;
4696
+ }
1882
4697
  case "version":
1883
4698
  case "--version":
1884
4699
  case "-v":
1885
- console.log(`fleetlens ${"0.3.0"}`);
4700
+ console.log(`fleetlens ${"0.4.1"}`);
1886
4701
  break;
1887
4702
  case "help":
1888
4703
  case "--help":
@@ -1896,14 +4711,22 @@ Common:
1896
4711
  update Update to the latest version
1897
4712
 
1898
4713
  Terminal:
1899
- stats [--live] [-s D] [--days N] Daily token usage table
1900
- usage [--save] Plan utilization (5h/7d) in one shot
4714
+ stats [--week|--4weeks|--days N] [--json] Period aggregates (for insights pipeline)
4715
+ usage [--save] Plan utilization snapshot (5h/7d)
4716
+ usage --history [-s D] [--days N] Daily token/cost table
4717
+ capsules [--days N] [--json] Per-session insight capsules
1901
4718
 
1902
4719
  Advanced:
1903
4720
  web [page] [--no-open] Open dashboard in browser without auto-starting daemon
1904
4721
  start --no-daemon Start only the web server (no daemon)
1905
4722
  daemon <start|stop|status|logs> Manage the usage daemon by itself
1906
4723
 
4724
+ Team:
4725
+ team join <url> <token> Pair with a team server
4726
+ team status Show team pairing state
4727
+ team leave Unpair from team server
4728
+ team logs Show team-related daemon logs
4729
+
1907
4730
  version Print version`);
1908
4731
  break;
1909
4732
  default: