fleetlens 0.2.9 → 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.
- package/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/app-path-routes-manifest.json +9 -0
- package/app/apps/web/.next/build-manifest.json +6 -6
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/routes-manifest.json +62 -0
- package/app/apps/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js +3 -2
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/events/route.js +1 -0
- package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/insights/months-index/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route.js +7 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js +6 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/route.js +8 -0
- package/app/apps/web/.next/server/app/api/insights/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js +6 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route.js +6 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js +7 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/insights/[key]/page.js +16 -0
- package/app/apps/web/.next/server/app/insights/[key]/page.js.map +5 -0
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/insights/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/insights/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/insights/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/insights/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/insights/page.js +16 -0
- package/app/apps/web/.next/server/app/insights/page.js.map +5 -0
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page.js +16 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page.js.map +5 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/page.js +3 -3
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -3
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/projects/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +2 -2
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/sessions/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/usage/page.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app-paths-manifest.json +9 -0
- 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
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_saved_[key]_route_actions_0a5j9yw.js +3 -0
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_weeks-index_route_actions_0hh-1c7.js +3 -0
- package/app/apps/web/.next/server/chunks/{[externals]__0rv9ct5._.js → [externals]__0_xztrf._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0.hmj0z._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__01h_g-p._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0nh-z10._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0op9af~._.js +1 -1
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0ri7q~r._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0rr1~~x._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0s0gv.0._.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_pdf_[key]_route_actions_0lu-lnx.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_route_actions_0h8l5lz.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_saved_route_actions_13rlblw.js +3 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_00t2xj6.js +68 -0
- 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
- package/app/apps/web/.next/server/chunks/packages_parser_dist_fs_0bc7ptm.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__00z7zlm._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08.d5ia._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b323~k._.js +8 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0euu8sa._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gio_ck._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gx-4qa._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0w52fbc._.js +3 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0z45d0g._.js +49 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__13itnvx._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/_09n75_5._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/_0r~__0v._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/_0xies90._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_[key]_page_actions_0_z2~e_.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_page_actions_0zjdm~n.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_print_[key]_page_actions_0a853fw.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_06j3c.0._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insight-report_tsx_13q2a-c._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insights-view_tsx_11730ta._.js +6 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0nqz8dw._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0vz7_px.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10cdrh2.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12n1lw_.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0-jvt~a._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_analytics_0k4dgst.js +1 -1
- package/app/apps/web/.next/server/functions-config-manifest.json +7 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +6 -6
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/03.mv4y4.w-vb.js +6 -0
- package/app/apps/web/.next/static/chunks/03_7xtpi9sv4g.js +4 -0
- package/app/apps/web/.next/static/chunks/03g5dw5eskotd.js +6 -0
- package/app/apps/web/.next/static/chunks/063oimt30jeo9.js +10 -0
- package/app/apps/web/.next/static/chunks/08c1pcrocqq5s.js +6 -0
- package/app/apps/web/.next/static/chunks/0e-wrhqh4h3r5.js +2 -0
- package/app/apps/web/.next/static/chunks/0fc6dk2qtokwh.js +1 -0
- package/app/apps/web/.next/static/chunks/0i7er7_bfab36.js +10 -0
- package/app/apps/web/.next/static/chunks/0ipytyi01h0ta.js +1 -0
- package/app/apps/web/.next/static/chunks/137uw2-qbro07.js +31 -0
- package/app/apps/web/.next/static/chunks/{turbopack-17n9y8y0h4q9f.js → turbopack-169w-1._23ra_.js} +1 -1
- package/app/apps/web/app/api/insights/months-index/route.ts +80 -0
- package/app/apps/web/app/api/insights/pdf/[key]/route.ts +45 -0
- package/app/apps/web/app/api/insights/route.ts +508 -0
- package/app/apps/web/app/api/insights/saved/[key]/route.ts +11 -0
- package/app/apps/web/app/api/insights/saved/route.ts +9 -0
- package/app/apps/web/app/api/insights/weeks-index/route.ts +95 -0
- package/app/apps/web/app/insights/[key]/page.tsx +59 -0
- package/app/apps/web/app/insights/page.tsx +7 -0
- package/app/apps/web/app/insights/print/[key]/page.tsx +73 -0
- package/app/apps/web/app/layout.tsx +2 -0
- package/app/apps/web/app/projects/[slug]/page.tsx +2 -0
- package/app/apps/web/app/sessions/[id]/page.tsx +27 -1
- package/app/apps/web/app/sessions/[id]/session-view.tsx +372 -793
- package/app/apps/web/app/sessions/[id]/team-tab/adapter.ts +633 -0
- package/app/apps/web/app/sessions/[id]/team-tab/format.ts +20 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-minimap.tsx +682 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-tab-client.tsx +78 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-table.tsx +796 -0
- package/app/apps/web/app/sessions/[id]/team-tab/turn-drawer.tsx +805 -0
- package/app/apps/web/app/sessions/[id]/tool-cards.tsx +461 -0
- package/app/apps/web/app/sessions/[id]/turn-steps.tsx +306 -0
- package/app/apps/web/app/sessions/sessions-grid.tsx +36 -5
- package/app/apps/web/components/insight-report.tsx +1031 -0
- package/app/apps/web/components/insights-view.tsx +647 -0
- package/app/apps/web/components/live-refresher.tsx +11 -2
- package/app/apps/web/components/live-sessions-widget.tsx +20 -6
- package/app/apps/web/components/sidebar.tsx +8 -0
- package/app/apps/web/components/team-badge.tsx +90 -0
- package/app/apps/web/eslint.config.mjs +2 -0
- package/app/apps/web/lib/ai/chrome-pdf.ts +64 -0
- package/app/apps/web/lib/ai/insights-prompt.ts +71 -0
- package/app/apps/web/lib/ai/saved-reports.ts +83 -0
- package/app/apps/web/lib/date-range.ts +11 -2
- package/app/apps/web/lib/use-live-events.ts +7 -2
- package/app/apps/web/package.json +3 -2
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +2765 -11
- package/dist/index.js +3047 -253
- package/package.json +5 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08cr2u9._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b..h_8._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0cdd~8n._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ms7h0~._.js +0 -8
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~a~v00._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/_0zv~jv-._.js +0 -12
- package/app/apps/web/.next/server/chunks/ssr/_12dtryh._.js +0 -12
- package/app/apps/web/.next/server/chunks/ssr/node_modules_0i2xw~e._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0.knjn.._.js +0 -3
- package/app/apps/web/.next/static/chunks/0257pdz1-imal.js +0 -2
- package/app/apps/web/.next/static/chunks/03fw7f2obcvq~.js +0 -6
- package/app/apps/web/.next/static/chunks/07oyxajlp3dvb.js +0 -6
- package/app/apps/web/.next/static/chunks/0a24elrqoqqep.js +0 -10
- package/app/apps/web/.next/static/chunks/0i4zodv86~~~i.js +0 -1
- package/app/apps/web/.next/static/chunks/0qmh881w772ef.js +0 -31
- package/app/apps/web/.next/static/chunks/0rbm60-qyshdm.js +0 -6
- package/app/apps/web/.next/static/chunks/0wdga7zbwvg70.js +0 -1
- /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → 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.
|
|
457
|
+
} else if (verify.installedVersion !== "0.4.0") {
|
|
458
458
|
console.log(
|
|
459
|
-
` \u2192 This process is still running ${"0.
|
|
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.
|
|
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.
|
|
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);
|
|
@@ -796,6 +796,31 @@ function truncate(s, n = 200) {
|
|
|
796
796
|
const one = cleanText(s);
|
|
797
797
|
return one.length > n ? one.slice(0, n - 1) + "\u2026" : one;
|
|
798
798
|
}
|
|
799
|
+
function classifyTeammateMessage(text) {
|
|
800
|
+
const m = text.match(TEAMMATE_MSG_RE);
|
|
801
|
+
if (!m)
|
|
802
|
+
return void 0;
|
|
803
|
+
const teammateId = m[1];
|
|
804
|
+
const body = m[2].trim();
|
|
805
|
+
let kind = "message";
|
|
806
|
+
if (body.startsWith("{")) {
|
|
807
|
+
try {
|
|
808
|
+
const parsed = JSON.parse(body);
|
|
809
|
+
if (parsed.type === "idle_notification")
|
|
810
|
+
kind = "idle-notification";
|
|
811
|
+
else if (parsed.type === "shutdown_request")
|
|
812
|
+
kind = "shutdown-request";
|
|
813
|
+
else if (parsed.type === "shutdown_approved")
|
|
814
|
+
kind = "shutdown-approved";
|
|
815
|
+
else if (parsed.type === "task_assignment")
|
|
816
|
+
kind = "task-assignment";
|
|
817
|
+
else if (parsed.type === "teammate_terminated")
|
|
818
|
+
kind = "teammate-terminated";
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return { teammateId, body, kind };
|
|
823
|
+
}
|
|
799
824
|
function extractUsage(u) {
|
|
800
825
|
if (!u || typeof u !== "object")
|
|
801
826
|
return void 0;
|
|
@@ -826,7 +851,7 @@ function toEvent(raw, index) {
|
|
|
826
851
|
const stopReason = msg.stop_reason;
|
|
827
852
|
const first = content[0];
|
|
828
853
|
let role = "agent";
|
|
829
|
-
let preview
|
|
854
|
+
let preview;
|
|
830
855
|
let toolName;
|
|
831
856
|
let toolUseId;
|
|
832
857
|
if (first?.type === "thinking") {
|
|
@@ -865,6 +890,7 @@ function toEvent(raw, index) {
|
|
|
865
890
|
const msg = r.message ?? {};
|
|
866
891
|
const c = msg.content;
|
|
867
892
|
if (typeof c === "string") {
|
|
893
|
+
const tm = classifyTeammateMessage(c);
|
|
868
894
|
return {
|
|
869
895
|
index,
|
|
870
896
|
uuid,
|
|
@@ -874,7 +900,8 @@ function toEvent(raw, index) {
|
|
|
874
900
|
rawType,
|
|
875
901
|
preview: truncate(c, 200),
|
|
876
902
|
blocks: [{ type: "text", text: c }],
|
|
877
|
-
raw
|
|
903
|
+
raw,
|
|
904
|
+
teammateMessage: tm
|
|
878
905
|
};
|
|
879
906
|
}
|
|
880
907
|
if (Array.isArray(c)) {
|
|
@@ -896,6 +923,7 @@ function toEvent(raw, index) {
|
|
|
896
923
|
};
|
|
897
924
|
}
|
|
898
925
|
const textBlock = c.find((b) => b?.type === "text");
|
|
926
|
+
const tm = textBlock ? classifyTeammateMessage(textBlock.text) : void 0;
|
|
899
927
|
return {
|
|
900
928
|
index,
|
|
901
929
|
uuid,
|
|
@@ -905,7 +933,8 @@ function toEvent(raw, index) {
|
|
|
905
933
|
rawType,
|
|
906
934
|
preview: textBlock ? truncate(textBlock.text, 200) : truncate(JSON.stringify(c), 200),
|
|
907
935
|
blocks: c,
|
|
908
|
-
raw
|
|
936
|
+
raw,
|
|
937
|
+
teammateMessage: tm
|
|
909
938
|
};
|
|
910
939
|
}
|
|
911
940
|
}
|
|
@@ -995,6 +1024,10 @@ function parseTranscript(rawLines) {
|
|
|
995
1024
|
let sessionId = "";
|
|
996
1025
|
let cwd;
|
|
997
1026
|
let gitBranch;
|
|
1027
|
+
let teamName;
|
|
1028
|
+
let agentName;
|
|
1029
|
+
let hasTeamCreate = false;
|
|
1030
|
+
let hasOutboundDispatch = false;
|
|
998
1031
|
let toolCallCount = 0;
|
|
999
1032
|
let turnCount = 0;
|
|
1000
1033
|
let firstUserPreview;
|
|
@@ -1009,6 +1042,13 @@ function parseTranscript(rawLines) {
|
|
|
1009
1042
|
const toolBlock = e.blocks.find((b) => b && b.type === "tool_use");
|
|
1010
1043
|
if (toolBlock) {
|
|
1011
1044
|
const input = toolBlock.input ?? {};
|
|
1045
|
+
if (toolBlock.name === "TeamCreate")
|
|
1046
|
+
hasTeamCreate = true;
|
|
1047
|
+
if (toolBlock.name === "SendMessage") {
|
|
1048
|
+
const to = typeof input.to === "string" ? input.to : "";
|
|
1049
|
+
if (to && to !== "team-lead")
|
|
1050
|
+
hasOutboundDispatch = true;
|
|
1051
|
+
}
|
|
1012
1052
|
const fp = typeof input.file_path === "string" ? input.file_path : void 0;
|
|
1013
1053
|
if (toolBlock.name === "Edit" && fp) {
|
|
1014
1054
|
filesEdited.add(fp);
|
|
@@ -1028,10 +1068,12 @@ function parseTranscript(rawLines) {
|
|
|
1028
1068
|
if (e.role === "user") {
|
|
1029
1069
|
const rawMsg = e.raw?.message;
|
|
1030
1070
|
const rawContent = typeof rawMsg?.content === "string" ? rawMsg.content : "";
|
|
1031
|
-
const isHidden = rawContent.startsWith("<command-name>") || rawContent.startsWith("Base directory for this skill:") || rawContent.startsWith("<task-notification>");
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1071
|
+
const isHidden = rawContent.startsWith("<command-name>") || rawContent.startsWith("<local-command-caveat>") || rawContent.startsWith("Base directory for this skill:") || rawContent.startsWith("<task-notification>");
|
|
1072
|
+
const isTeamNoise = e.teammateMessage !== void 0 && !agentName;
|
|
1073
|
+
if (!isHidden && !isTeamNoise) {
|
|
1074
|
+
if (!firstUserPreview) {
|
|
1075
|
+
firstUserPreview = e.teammateMessage ? e.teammateMessage.body : e.preview;
|
|
1076
|
+
}
|
|
1035
1077
|
turnCount++;
|
|
1036
1078
|
lastUserPreview = e.preview;
|
|
1037
1079
|
}
|
|
@@ -1050,6 +1092,10 @@ function parseTranscript(rawLines) {
|
|
|
1050
1092
|
cwd = o.cwd;
|
|
1051
1093
|
if (typeof o.gitBranch === "string" && !gitBranch)
|
|
1052
1094
|
gitBranch = o.gitBranch;
|
|
1095
|
+
if (typeof o.teamName === "string" && !teamName)
|
|
1096
|
+
teamName = o.teamName;
|
|
1097
|
+
if (typeof o.agentName === "string" && !agentName)
|
|
1098
|
+
agentName = o.agentName;
|
|
1053
1099
|
if (o.type === "assistant") {
|
|
1054
1100
|
const m = o.message;
|
|
1055
1101
|
if (m) {
|
|
@@ -1094,26 +1140,663 @@ function parseTranscript(rawLines) {
|
|
|
1094
1140
|
activeSegments,
|
|
1095
1141
|
linesAdded,
|
|
1096
1142
|
linesRemoved,
|
|
1097
|
-
filesEdited: filesEdited.size
|
|
1143
|
+
filesEdited: filesEdited.size,
|
|
1144
|
+
teamName,
|
|
1145
|
+
agentName,
|
|
1146
|
+
isTeamLead: teamName !== void 0 && agentName === void 0 && (hasTeamCreate || hasOutboundDispatch)
|
|
1098
1147
|
}
|
|
1099
1148
|
};
|
|
1100
1149
|
}
|
|
1101
|
-
var BLANK_USAGE;
|
|
1150
|
+
var BLANK_USAGE, TEAMMATE_MSG_RE;
|
|
1102
1151
|
var init_parser = __esm({
|
|
1103
1152
|
"../parser/dist/parser.js"() {
|
|
1104
1153
|
"use strict";
|
|
1105
1154
|
BLANK_USAGE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
1155
|
+
TEAMMATE_MSG_RE = /^\s*<teammate-message\s+teammate_id="([^"]+)"[^>]*>([\s\S]*?)<\/teammate-message>/;
|
|
1106
1156
|
}
|
|
1107
1157
|
});
|
|
1108
1158
|
|
|
1109
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;
|
|
1110
1652
|
var init_analytics = __esm({
|
|
1111
1653
|
"../parser/dist/analytics.js"() {
|
|
1112
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+["']([^"']+)["']/;
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
|
|
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
|
+
}
|
|
1776
|
+
var init_team = __esm({
|
|
1777
|
+
"../parser/dist/team.js"() {
|
|
1778
|
+
"use strict";
|
|
1113
1779
|
}
|
|
1114
1780
|
});
|
|
1115
1781
|
|
|
1116
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
|
+
});
|
|
1117
1800
|
import { promises as fs } from "node:fs";
|
|
1118
1801
|
import os from "node:os";
|
|
1119
1802
|
import path from "node:path";
|
|
@@ -1136,11 +1819,24 @@ async function readJsonlFile(filePath) {
|
|
|
1136
1819
|
}
|
|
1137
1820
|
return out;
|
|
1138
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
|
+
}
|
|
1139
1835
|
async function walkJsonlFiles(root = DEFAULT_ROOT) {
|
|
1140
1836
|
if (fileListCache && fileListCache.root === root && Date.now() - fileListCache.capturedAtMs < FILE_LIST_TTL_MS) {
|
|
1141
1837
|
return fileListCache.files;
|
|
1142
1838
|
}
|
|
1143
|
-
let topEntries
|
|
1839
|
+
let topEntries;
|
|
1144
1840
|
try {
|
|
1145
1841
|
topEntries = await fs.readdir(root, { withFileTypes: true });
|
|
1146
1842
|
} catch {
|
|
@@ -1208,7 +1904,7 @@ function aggregateUsageFromLines(lines, seenKeys, usage2) {
|
|
|
1208
1904
|
}
|
|
1209
1905
|
}
|
|
1210
1906
|
async function computeSessionUsageWithSubagents(parentLines, projectDirPath, sessionId) {
|
|
1211
|
-
const usage2 = { ...
|
|
1907
|
+
const usage2 = { ...BLANK_USAGE3 };
|
|
1212
1908
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
1213
1909
|
aggregateUsageFromLines(parentLines, seenKeys, usage2);
|
|
1214
1910
|
const subagentsDir = path.join(projectDirPath, sessionId, "subagents");
|
|
@@ -1254,6 +1950,31 @@ async function getCachedMeta(f) {
|
|
|
1254
1950
|
return null;
|
|
1255
1951
|
}
|
|
1256
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
|
+
}
|
|
1257
1978
|
async function listSessions(opts = {}) {
|
|
1258
1979
|
const { root = DEFAULT_ROOT, limit = 500, projectDir } = opts;
|
|
1259
1980
|
let files = await walkJsonlFiles(root);
|
|
@@ -1264,23 +1985,1792 @@ async function listSessions(opts = {}) {
|
|
|
1264
1985
|
const metas = await Promise.all(sliced.map((f) => getCachedMeta(f)));
|
|
1265
1986
|
return metas.filter((m) => m !== null);
|
|
1266
1987
|
}
|
|
1267
|
-
|
|
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;
|
|
1268
2316
|
var init_fs = __esm({
|
|
1269
2317
|
"../parser/dist/fs.js"() {
|
|
1270
2318
|
"use strict";
|
|
1271
2319
|
init_parser();
|
|
1272
2320
|
init_analytics();
|
|
2321
|
+
init_team();
|
|
1273
2322
|
DEFAULT_ROOT = path.join(os.homedir(), ".claude", "projects");
|
|
1274
2323
|
metaCache = /* @__PURE__ */ new Map();
|
|
2324
|
+
detailCache = /* @__PURE__ */ new Map();
|
|
1275
2325
|
fileListCache = null;
|
|
1276
2326
|
FILE_LIST_TTL_MS = 1e3;
|
|
1277
|
-
|
|
2327
|
+
BLANK_USAGE3 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2328
|
+
USAGE_LOG2 = path.join(os.homedir(), ".cclens", "usage.jsonl");
|
|
1278
2329
|
}
|
|
1279
2330
|
});
|
|
1280
2331
|
|
|
1281
|
-
//
|
|
1282
|
-
|
|
1283
|
-
|
|
2332
|
+
// ../parser/dist/types.js
|
|
2333
|
+
var init_types = __esm({
|
|
2334
|
+
"../parser/dist/types.js"() {
|
|
2335
|
+
"use strict";
|
|
2336
|
+
}
|
|
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;
|
|
2344
|
+
}
|
|
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) {
|
|
1284
3774
|
if (model.startsWith(prefix)) return pricing;
|
|
1285
3775
|
}
|
|
1286
3776
|
return null;
|
|
@@ -1320,7 +3810,7 @@ function pad(s, width, align = "right") {
|
|
|
1320
3810
|
function renderTable(rows, title) {
|
|
1321
3811
|
const lines = [];
|
|
1322
3812
|
lines.push("");
|
|
1323
|
-
lines.push(`${
|
|
3813
|
+
lines.push(`${BOLD2}${title}${RESET2}`);
|
|
1324
3814
|
lines.push("");
|
|
1325
3815
|
const header = [
|
|
1326
3816
|
pad("Date", 12, "left"),
|
|
@@ -1332,8 +3822,8 @@ function renderTable(rows, title) {
|
|
|
1332
3822
|
pad("Total", 14),
|
|
1333
3823
|
pad("Cost (USD)", 12)
|
|
1334
3824
|
].join(" ");
|
|
1335
|
-
lines.push(`${
|
|
1336
|
-
lines.push(`${
|
|
3825
|
+
lines.push(`${DIM2}${header}${RESET2}`);
|
|
3826
|
+
lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
|
|
1337
3827
|
let hasUnknownCost = false;
|
|
1338
3828
|
for (const r of rows) {
|
|
1339
3829
|
if (r.cost === null) hasUnknownCost = true;
|
|
@@ -1359,9 +3849,9 @@ function renderTable(rows, title) {
|
|
|
1359
3849
|
}),
|
|
1360
3850
|
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0, totalTokens: 0, cost: 0 }
|
|
1361
3851
|
);
|
|
1362
|
-
lines.push(`${
|
|
3852
|
+
lines.push(`${DIM2}${"\u2500".repeat(header.length)}${RESET2}`);
|
|
1363
3853
|
lines.push(
|
|
1364
|
-
`${
|
|
3854
|
+
`${BOLD2}${[
|
|
1365
3855
|
pad("Total", 12, "left"),
|
|
1366
3856
|
pad("", 20, "left"),
|
|
1367
3857
|
pad(fmtNum(totals.input), 12),
|
|
@@ -1370,31 +3860,31 @@ function renderTable(rows, title) {
|
|
|
1370
3860
|
pad(fmtNum(totals.cacheRead), 14),
|
|
1371
3861
|
pad(fmtNum(totals.totalTokens), 14),
|
|
1372
3862
|
pad(fmtCost(totals.cost), 12)
|
|
1373
|
-
].join(" ")}${
|
|
3863
|
+
].join(" ")}${RESET2}`
|
|
1374
3864
|
);
|
|
1375
3865
|
if (hasUnknownCost) {
|
|
1376
3866
|
lines.push("");
|
|
1377
|
-
lines.push(`${
|
|
3867
|
+
lines.push(`${DIM2} Note: some models had unknown pricing (shown as \u2014)${RESET2}`);
|
|
1378
3868
|
}
|
|
1379
3869
|
lines.push("");
|
|
1380
3870
|
return lines.join("\n");
|
|
1381
3871
|
}
|
|
1382
|
-
var
|
|
3872
|
+
var DIM2, BOLD2, RESET2;
|
|
1383
3873
|
var init_table = __esm({
|
|
1384
3874
|
"src/table.ts"() {
|
|
1385
3875
|
"use strict";
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
3876
|
+
DIM2 = "\x1B[2m";
|
|
3877
|
+
BOLD2 = "\x1B[1m";
|
|
3878
|
+
RESET2 = "\x1B[0m";
|
|
1389
3879
|
}
|
|
1390
3880
|
});
|
|
1391
3881
|
|
|
1392
|
-
// src/commands/
|
|
1393
|
-
var
|
|
1394
|
-
__export(
|
|
1395
|
-
|
|
3882
|
+
// src/commands/usage-history.ts
|
|
3883
|
+
var usage_history_exports = {};
|
|
3884
|
+
__export(usage_history_exports, {
|
|
3885
|
+
history: () => history
|
|
1396
3886
|
});
|
|
1397
|
-
async function
|
|
3887
|
+
async function history(args2) {
|
|
1398
3888
|
const live = args2.includes("--live");
|
|
1399
3889
|
const sinceIdx = args2.indexOf("-s");
|
|
1400
3890
|
const daysIdx = args2.indexOf("--days");
|
|
@@ -1498,8 +3988,8 @@ async function liveStats(sinceDate) {
|
|
|
1498
3988
|
await refresh();
|
|
1499
3989
|
setInterval(refresh, 2e3);
|
|
1500
3990
|
}
|
|
1501
|
-
var
|
|
1502
|
-
"src/commands/
|
|
3991
|
+
var init_usage_history = __esm({
|
|
3992
|
+
"src/commands/usage-history.ts"() {
|
|
1503
3993
|
"use strict";
|
|
1504
3994
|
init_fs();
|
|
1505
3995
|
init_pricing();
|
|
@@ -1508,271 +3998,557 @@ var init_stats = __esm({
|
|
|
1508
3998
|
}
|
|
1509
3999
|
});
|
|
1510
4000
|
|
|
1511
|
-
// src/usage
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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;
|
|
1519
4035
|
}
|
|
1520
|
-
return readFromCredentialsFile();
|
|
1521
4036
|
}
|
|
1522
|
-
|
|
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) {
|
|
1523
4157
|
try {
|
|
1524
|
-
|
|
1525
|
-
"security",
|
|
1526
|
-
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
1527
|
-
{ stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }
|
|
1528
|
-
);
|
|
1529
|
-
return extractCredentials(blob);
|
|
4158
|
+
return JSON.parse(readFileSync6(join6(dir, CONFIG_FILE), "utf8"));
|
|
1530
4159
|
} catch {
|
|
1531
4160
|
return null;
|
|
1532
4161
|
}
|
|
1533
4162
|
}
|
|
1534
|
-
function
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
const creds = extractCredentials(blob);
|
|
1543
|
-
if (creds) return creds;
|
|
1544
|
-
} catch {
|
|
1545
|
-
}
|
|
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 {
|
|
1546
4171
|
}
|
|
1547
|
-
return null;
|
|
1548
4172
|
}
|
|
1549
|
-
|
|
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}`);
|
|
1550
4240
|
try {
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
+
}
|
|
1558
4252
|
} catch {
|
|
1559
|
-
|
|
4253
|
+
console.log("Status: ! Cannot reach server");
|
|
1560
4254
|
}
|
|
1561
4255
|
}
|
|
1562
|
-
var
|
|
1563
|
-
"src/
|
|
4256
|
+
var init_status2 = __esm({
|
|
4257
|
+
"src/team/status.ts"() {
|
|
1564
4258
|
"use strict";
|
|
4259
|
+
init_config();
|
|
1565
4260
|
}
|
|
1566
4261
|
});
|
|
1567
4262
|
|
|
1568
|
-
// src/
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
throw new UsageApiError(
|
|
1579
|
-
"Claude Code OAuth token expired. Open Claude Code to refresh it.",
|
|
1580
|
-
"expired"
|
|
1581
|
-
);
|
|
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;
|
|
1582
4273
|
}
|
|
1583
|
-
let res;
|
|
1584
4274
|
try {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
}
|
|
4275
|
+
await fetch(`${config.serverUrl}/api/team/leave`, {
|
|
4276
|
+
method: "POST",
|
|
4277
|
+
headers: { Authorization: `Bearer ${config.bearerToken}` },
|
|
4278
|
+
signal: AbortSignal.timeout(5e3)
|
|
1590
4279
|
});
|
|
1591
|
-
} catch
|
|
1592
|
-
throw new UsageApiError(
|
|
1593
|
-
`Network error reaching Anthropic: ${err.message}`,
|
|
1594
|
-
"network"
|
|
1595
|
-
);
|
|
4280
|
+
} catch {
|
|
1596
4281
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
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();
|
|
1602
4289
|
}
|
|
1603
|
-
|
|
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");
|
|
1604
4302
|
try {
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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?");
|
|
1608
4314
|
}
|
|
1609
|
-
const b = body;
|
|
1610
|
-
return {
|
|
1611
|
-
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1612
|
-
five_hour: normalizeWindow(b.five_hour) ?? { utilization: null, resets_at: null },
|
|
1613
|
-
seven_day: normalizeWindow(b.seven_day) ?? { utilization: null, resets_at: null },
|
|
1614
|
-
seven_day_opus: normalizeWindow(b.seven_day_opus),
|
|
1615
|
-
seven_day_sonnet: normalizeWindow(b.seven_day_sonnet),
|
|
1616
|
-
seven_day_oauth_apps: normalizeWindow(b.seven_day_oauth_apps),
|
|
1617
|
-
seven_day_cowork: normalizeWindow(b.seven_day_cowork),
|
|
1618
|
-
extra_usage: normalizeExtra(b.extra_usage)
|
|
1619
|
-
};
|
|
1620
4315
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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) {
|
|
1624
4325
|
return {
|
|
1625
|
-
|
|
1626
|
-
|
|
4326
|
+
day: b.date,
|
|
4327
|
+
agentTimeMs: b.airTimeMs,
|
|
4328
|
+
sessions: b.sessions,
|
|
4329
|
+
toolCalls: b.toolCalls,
|
|
4330
|
+
turns: b.turns,
|
|
4331
|
+
tokens: { ...b.tokens }
|
|
1627
4332
|
};
|
|
1628
4333
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
1631
|
-
const
|
|
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) {
|
|
1632
4347
|
return {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
utilization: typeof r.utilization === "number" ? r.utilization : null
|
|
4348
|
+
ingestId: randomUUID(),
|
|
4349
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4350
|
+
dailyRollup: rollup
|
|
1637
4351
|
};
|
|
1638
4352
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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"() {
|
|
1642
4367
|
"use strict";
|
|
1643
|
-
|
|
1644
|
-
EXPIRY_SKEW_MS = 60 * 1e3;
|
|
1645
|
-
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
|
|
1646
|
-
BETA_HEADER = "oauth-2025-04-20";
|
|
1647
|
-
UsageApiError = class extends Error {
|
|
1648
|
-
constructor(message, code) {
|
|
1649
|
-
super(message);
|
|
1650
|
-
this.code = code;
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
4368
|
+
init_dist();
|
|
1653
4369
|
}
|
|
1654
4370
|
});
|
|
1655
4371
|
|
|
1656
|
-
// src/
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
lines.push(` ${BOLD2}Claude Code Usage${RESET2}`);
|
|
1669
|
-
lines.push("");
|
|
1670
|
-
for (const [label, window] of rows) {
|
|
1671
|
-
if (!window || window.utilization === null) continue;
|
|
1672
|
-
const pct = window.utilization;
|
|
1673
|
-
const bar = renderBar(pct);
|
|
1674
|
-
const pctStr = `${pct.toFixed(1)}%`.padStart(6);
|
|
1675
|
-
const labelStr = label.padEnd(16);
|
|
1676
|
-
lines.push(` ${labelStr}${bar} ${BOLD2}${pctStr}${RESET2}`);
|
|
1677
|
-
if (window.resets_at) {
|
|
1678
|
-
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);
|
|
1679
4384
|
}
|
|
1680
|
-
|
|
4385
|
+
} catch {
|
|
1681
4386
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
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 {
|
|
1690
4404
|
}
|
|
1691
|
-
lines.push("");
|
|
1692
4405
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
return lines.join("\n");
|
|
1696
|
-
}
|
|
1697
|
-
function renderBar(utilization) {
|
|
1698
|
-
const clamped = Math.max(0, Math.min(100, utilization));
|
|
1699
|
-
const color = clamped >= 90 ? RED : clamped >= 70 ? YELLOW : GREEN;
|
|
1700
|
-
const totalEighths = Math.round(clamped / 100 * BAR_WIDTH * 8);
|
|
1701
|
-
const fullBlocks = Math.floor(totalEighths / 8);
|
|
1702
|
-
const remainder = totalEighths % 8;
|
|
1703
|
-
const partial = EIGHTHS[remainder];
|
|
1704
|
-
const filledCells = fullBlocks + (partial ? 1 : 0);
|
|
1705
|
-
const emptyCells = BAR_WIDTH - filledCells;
|
|
1706
|
-
const filled = "\u2588".repeat(fullBlocks) + partial;
|
|
1707
|
-
const empty = "\xB7".repeat(emptyCells);
|
|
1708
|
-
return `${color}${filled}${RESET2}${DIM2}${empty}${RESET2}`;
|
|
4406
|
+
writeFileSync3(queuePath, "", "utf8");
|
|
4407
|
+
return valid.map((e) => e.payload);
|
|
1709
4408
|
}
|
|
1710
|
-
function
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
let value;
|
|
1717
|
-
if (abs < 60) {
|
|
1718
|
-
value = `${abs}s`;
|
|
1719
|
-
} else if (abs < 3600) {
|
|
1720
|
-
value = `${Math.floor(abs / 60)}m`;
|
|
1721
|
-
} else if (abs < 86400) {
|
|
1722
|
-
const h = Math.floor(abs / 3600);
|
|
1723
|
-
const m = Math.floor(abs % 3600 / 60);
|
|
1724
|
-
value = m > 0 ? `${h}h${m}m` : `${h}h`;
|
|
1725
|
-
} else {
|
|
1726
|
-
const d = Math.floor(abs / 86400);
|
|
1727
|
-
const h = Math.floor(abs % 86400 / 3600);
|
|
1728
|
-
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;
|
|
1729
4415
|
}
|
|
1730
|
-
|
|
4416
|
+
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
4417
|
+
writeFileSync3(queuePath, keep.join("\n") + "\n", "utf8");
|
|
1731
4418
|
}
|
|
1732
|
-
var
|
|
1733
|
-
var
|
|
1734
|
-
"src/
|
|
4419
|
+
var QUEUE_PATH, MAX_SIZE_BYTES, MAX_AGE_MS;
|
|
4420
|
+
var init_queue = __esm({
|
|
4421
|
+
"src/team/queue.ts"() {
|
|
1735
4422
|
"use strict";
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
GREEN = "\x1B[32m";
|
|
1740
|
-
YELLOW = "\x1B[33m";
|
|
1741
|
-
RED = "\x1B[31m";
|
|
1742
|
-
BAR_WIDTH = 40;
|
|
1743
|
-
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;
|
|
1744
4426
|
}
|
|
1745
4427
|
});
|
|
1746
4428
|
|
|
1747
|
-
// src/
|
|
1748
|
-
var
|
|
1749
|
-
__export(
|
|
1750
|
-
|
|
4429
|
+
// src/team/sync.ts
|
|
4430
|
+
var sync_exports = {};
|
|
4431
|
+
__export(sync_exports, {
|
|
4432
|
+
runTeamSync: () => runTeamSync
|
|
1751
4433
|
});
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
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 };
|
|
1756
4437
|
try {
|
|
1757
|
-
const
|
|
1758
|
-
|
|
1759
|
-
|
|
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 };
|
|
1760
4480
|
} catch (err) {
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
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;
|
|
1764
4510
|
}
|
|
1765
|
-
|
|
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)`);
|
|
1766
4547
|
}
|
|
1767
4548
|
}
|
|
1768
|
-
var
|
|
1769
|
-
|
|
1770
|
-
"src/commands/usage.ts"() {
|
|
4549
|
+
var init_team2 = __esm({
|
|
4550
|
+
"src/commands/team.ts"() {
|
|
1771
4551
|
"use strict";
|
|
1772
|
-
init_api();
|
|
1773
|
-
init_format();
|
|
1774
|
-
init_storage();
|
|
1775
|
-
USAGE_LOG2 = join5(homedir4(), ".cclens", "usage.jsonl");
|
|
1776
4552
|
}
|
|
1777
4553
|
});
|
|
1778
4554
|
|
|
@@ -1816,15 +4592,25 @@ async function main() {
|
|
|
1816
4592
|
await usage2(args.slice(1));
|
|
1817
4593
|
break;
|
|
1818
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
|
+
}
|
|
1819
4600
|
case "daemon": {
|
|
1820
4601
|
const { daemon: daemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
1821
4602
|
await daemon2(args.slice(1));
|
|
1822
4603
|
break;
|
|
1823
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
|
+
}
|
|
1824
4610
|
case "version":
|
|
1825
4611
|
case "--version":
|
|
1826
4612
|
case "-v":
|
|
1827
|
-
console.log(`fleetlens ${"0.
|
|
4613
|
+
console.log(`fleetlens ${"0.4.0"}`);
|
|
1828
4614
|
break;
|
|
1829
4615
|
case "help":
|
|
1830
4616
|
case "--help":
|
|
@@ -1838,14 +4624,22 @@ Common:
|
|
|
1838
4624
|
update Update to the latest version
|
|
1839
4625
|
|
|
1840
4626
|
Terminal:
|
|
1841
|
-
stats [--
|
|
1842
|
-
usage [--save]
|
|
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
|
|
1843
4631
|
|
|
1844
4632
|
Advanced:
|
|
1845
4633
|
web [page] [--no-open] Open dashboard in browser without auto-starting daemon
|
|
1846
4634
|
start --no-daemon Start only the web server (no daemon)
|
|
1847
4635
|
daemon <start|stop|status|logs> Manage the usage daemon by itself
|
|
1848
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
|
+
|
|
1849
4643
|
version Print version`);
|
|
1850
4644
|
break;
|
|
1851
4645
|
default:
|