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