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