fleetlens 0.3.0 → 0.4.0

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