claude-session-dashboard 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/client/assets/_dashboard-Bxw4OxIS.js +1 -0
  2. package/dist/client/assets/_sessionId-CNR4Ln7m.js +12 -0
  3. package/dist/client/assets/app-u2nTs9ny.css +1 -0
  4. package/dist/client/assets/{createServerFn-Bmn60lX_.js → createServerFn-B0pEGqTk.js} +1 -1
  5. package/dist/client/assets/format-Bsprb3az.js +1 -0
  6. package/dist/client/assets/index-BbdJ1jMA.js +1 -0
  7. package/dist/client/assets/{main-Bssrw_E_.js → main-CM5g2n-_.js} +7 -7
  8. package/dist/client/assets/sessions.queries-AUVV0tJj.js +1 -0
  9. package/dist/client/assets/{settings-Buc0ndXk.js → settings-CIwZDakc.js} +1 -1
  10. package/dist/client/assets/{settings.types-4U9-Yxq3.js → settings.types-BRNIMHGJ.js} +1 -1
  11. package/dist/client/assets/stats-CjWSMX3y.js +4 -0
  12. package/dist/client/assets/useSessionCost-DgFKglaG.js +65 -0
  13. package/dist/server/assets/{_dashboard-CG6ub7j3.js → _dashboard-CAO6-qAS.js} +22 -3
  14. package/dist/server/assets/{_sessionId-CcWGJarz.js → _sessionId-BZf2Aqy5.js} +30 -7
  15. package/dist/server/assets/_tanstack-start-manifest_v-C5hwNzs-.js +4 -0
  16. package/dist/server/assets/{claude-path-on7ZBAjl.js → claude-path-BdwflgZ1.js} +7 -1
  17. package/dist/server/assets/{format-CGmJnuhZ.js → format-DIZHV7IJ.js} +3 -3
  18. package/dist/server/assets/{index-DjrI63_C.js → index-Do0HxVmM.js} +7 -40
  19. package/dist/server/assets/project-analytics.server-BkWSd6a8.js +61 -0
  20. package/dist/server/assets/{router-ByIey__S.js → router-ChxlsPNU.js} +13 -7
  21. package/dist/server/assets/{session-detail.server-ClCRw8BG.js → session-detail.server-DLXl-Pn-.js} +1 -1
  22. package/dist/server/assets/session-scanner-CLfls9u-.js +93 -0
  23. package/dist/server/assets/sessions.queries-B5ZBiVJy.js +42 -0
  24. package/dist/server/assets/{sessions.server-Cl8Ao_-2.js → sessions.server-CUhasKW2.js} +5 -89
  25. package/dist/server/assets/stats-C9cZXTP5.js +649 -0
  26. package/dist/server/assets/{stats.server-BBNHZZ4h.js → stats.server-52mNk2Yw.js} +1 -1
  27. package/dist/server/assets/useSessionCost-CYs5UOX-.js +209 -0
  28. package/dist/server/server.js +16 -13
  29. package/package.json +5 -1
  30. package/dist/client/assets/_dashboard-Oaur6UHf.js +0 -1
  31. package/dist/client/assets/_sessionId-CWavmGnC.js +0 -12
  32. package/dist/client/assets/format-Bf-cSf6L.js +0 -1
  33. package/dist/client/assets/index-CN4cqOcf.js +0 -1
  34. package/dist/client/assets/stats-BEUCPbcP.js +0 -4
  35. package/dist/client/assets/useSessionCost-C7ox6YwA.js +0 -61
  36. package/dist/server/assets/_tanstack-start-manifest_v-DidrnaMJ.js +0 -4
  37. package/dist/server/assets/stats-DItsFPp5.js +0 -266
  38. package/dist/server/assets/useSessionCost-EB0VxklP.js +0 -76
@@ -1,9 +1,21 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useMatches, Link, Outlet } from "@tanstack/react-router";
3
- import { u as usePrivacy } from "./router-ByIey__S.js";
4
- import "@tanstack/react-query";
3
+ import { u as usePrivacy } from "./router-ChxlsPNU.js";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
5
6
  import "react";
6
7
  import "zod";
8
+ import "./createSsrRpc-CVg2UDl0.js";
9
+ import "../server.js";
10
+ import "@tanstack/history";
11
+ import "@tanstack/router-core/ssr/client";
12
+ import "@tanstack/router-core";
13
+ import "node:async_hooks";
14
+ import "@tanstack/router-core/ssr/server";
15
+ import "h3-v2";
16
+ import "tiny-invariant";
17
+ import "seroval";
18
+ import "@tanstack/react-router/ssr/server";
7
19
  function PrivacyToggle() {
8
20
  const { privacyMode, togglePrivacyMode } = usePrivacy();
9
21
  return /* @__PURE__ */ jsxs(
@@ -52,6 +64,12 @@ function PrivacyToggle() {
52
64
  }
53
65
  );
54
66
  }
67
+ function ActiveSessionsBadge() {
68
+ const { data: activeSessions } = useQuery(activeSessionsQuery);
69
+ const count = activeSessions?.length ?? 0;
70
+ if (count === 0) return null;
71
+ return /* @__PURE__ */ jsx("span", { className: "ml-auto rounded-full bg-green-500/20 px-1.5 py-0.5 text-[10px] font-medium text-green-400", children: count });
72
+ }
55
73
  const NAV_ITEMS = [
56
74
  { to: "/sessions", label: "Sessions", icon: ">" },
57
75
  { to: "/stats", label: "Stats", icon: "#" },
@@ -75,7 +93,8 @@ function AppShell({ children }) {
75
93
  className: `flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${isActive ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800/50 hover:text-gray-200"}`,
76
94
  children: [
77
95
  /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-gray-500", children: item.icon }),
78
- item.label
96
+ item.label,
97
+ item.to === "/sessions" && /* @__PURE__ */ jsx(ActiveSessionsBadge, {})
79
98
  ]
80
99
  },
81
100
  item.to
@@ -5,11 +5,12 @@ import { c as createSsrRpc } from "./createSsrRpc-CVg2UDl0.js";
5
5
  import { c as createServerFn } from "../server.js";
6
6
  import { useRef, useCallback, useState, useEffect, useMemo } from "react";
7
7
  import { format } from "date-fns";
8
- import { b as formatDuration, f as formatTokenCount, e as formatDateTime, a as formatUSD } from "./format-CGmJnuhZ.js";
8
+ import { a as formatDuration, f as formatTokenCount, e as formatDateTime, c as formatUSD } from "./format-DIZHV7IJ.js";
9
9
  import { ResponsiveContainer, AreaChart, YAxis, Tooltip, ReferenceLine, Area } from "recharts";
10
10
  import { s as settingsQuery } from "./settings.queries-DSQd324O.js";
11
- import { g as getMergedPricing, c as calculateSessionCost, u as useSessionCost } from "./useSessionCost-EB0VxklP.js";
12
- import { a as Route, u as usePrivacy } from "./router-ByIey__S.js";
11
+ import { g as getMergedPricing, c as calculateSessionCost, u as useSessionCost, E as ExportDropdown, d as downloadFile, e as sessionToJSON } from "./useSessionCost-CYs5UOX-.js";
12
+ import { a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
13
+ import { b as Route, u as usePrivacy } from "./router-ChxlsPNU.js";
13
14
  import "@tanstack/history";
14
15
  import "@tanstack/router-core/ssr/client";
15
16
  import "@tanstack/router-core";
@@ -24,11 +25,12 @@ import "zod";
24
25
  const getSessionDetail = createServerFn({
25
26
  method: "GET"
26
27
  }).inputValidator((input) => input).handler(createSsrRpc("ff8a3161afdfa175e9c519e4146a56ab5bce6e80745e99cfc2191ebbb7a859bb"));
27
- function sessionDetailQuery(sessionId, projectPath) {
28
+ function sessionDetailQuery(sessionId, projectPath, isActive) {
28
29
  return queryOptions({
29
30
  queryKey: ["session", "detail", sessionId],
30
31
  queryFn: () => getSessionDetail({ data: { sessionId, projectPath } }),
31
- staleTime: 3e4
32
+ staleTime: isActive ? 2e3 : 3e4,
33
+ refetchInterval: isActive ? 5e3 : void 0
32
34
  });
33
35
  }
34
36
  function buildTimelineChartData(turns, agents, skills, errors) {
@@ -1510,6 +1512,19 @@ function CostSummaryLine({ tokensByModel }) {
1510
1512
  }
1511
1513
  );
1512
1514
  }
1515
+ function ActiveSessionBanner() {
1516
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-2 rounded-lg border border-green-800 bg-green-900/30 px-4 py-2 text-sm text-green-300", children: [
1517
+ /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2", children: [
1518
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75" }),
1519
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-green-500" })
1520
+ ] }),
1521
+ "This session is currently active. Data refreshes automatically."
1522
+ ] });
1523
+ }
1524
+ function useIsSessionActive(sessionId) {
1525
+ const { data: activeSessions } = useQuery(activeSessionsQuery);
1526
+ return activeSessions?.some((s) => s.sessionId === sessionId) ?? false;
1527
+ }
1513
1528
  function SessionDetailPage() {
1514
1529
  const {
1515
1530
  sessionId
@@ -1521,11 +1536,12 @@ function SessionDetailPage() {
1521
1536
  privacyMode,
1522
1537
  anonymizeProjectName
1523
1538
  } = usePrivacy();
1539
+ const isActive = useIsSessionActive(sessionId);
1524
1540
  const {
1525
1541
  data: detail,
1526
1542
  isLoading,
1527
1543
  error
1528
- } = useQuery(sessionDetailQuery(sessionId, project));
1544
+ } = useQuery(sessionDetailQuery(sessionId, project, isActive));
1529
1545
  if (isLoading) {
1530
1546
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1531
1547
  /* @__PURE__ */ jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-gray-800" }),
@@ -1545,6 +1561,7 @@ function SessionDetailPage() {
1545
1561
  const endedAt = detail.turns[detail.turns.length - 1]?.timestamp;
1546
1562
  const durationMs = startedAt && endedAt ? new Date(endedAt).getTime() - new Date(startedAt).getTime() : 0;
1547
1563
  return /* @__PURE__ */ jsxs("div", { children: [
1564
+ isActive && /* @__PURE__ */ jsx(ActiveSessionBanner, {}),
1548
1565
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
1549
1566
  /* @__PURE__ */ jsxs("div", { children: [
1550
1567
  /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "text-xs text-gray-500 hover:text-gray-300", children: "← Sessions" }),
@@ -1561,7 +1578,13 @@ function SessionDetailPage() {
1561
1578
  ] }),
1562
1579
  detail.models.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-1 flex gap-1", children: detail.models.map((m) => /* @__PURE__ */ jsx("span", { className: "rounded bg-gray-800 px-1.5 py-0.5 text-[10px] font-mono text-gray-400", children: m.replace(/^claude-/, "").split("-202")[0] }, m)) })
1563
1580
  ] }),
1564
- /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-gray-600", children: sessionId.slice(0, 8) })
1581
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1582
+ /* @__PURE__ */ jsx(ExportDropdown, { options: [{
1583
+ label: "Export Session (JSON)",
1584
+ onClick: () => downloadFile(sessionToJSON(detail), `session-${sessionId.slice(0, 8)}.json`, "application/json")
1585
+ }] }),
1586
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-gray-600", children: sessionId.slice(0, 8) })
1587
+ ] })
1565
1588
  ] }),
1566
1589
  /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-1 gap-4 md:grid-cols-2", children: [
1567
1590
  /* @__PURE__ */ jsx(ContextWindowPanel, { contextWindow: detail.contextWindow, tokens: detail.totalTokens }),
@@ -0,0 +1,4 @@
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/__root.tsx", "children": ["/", "/_dashboard"], "preloads": ["/assets/main-CM5g2n-_.js"], "assets": [] }, "/": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/index.tsx" }, "/_dashboard": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard.tsx", "children": ["/_dashboard/settings", "/_dashboard/stats", "/_dashboard/sessions/$sessionId", "/_dashboard/sessions/"], "assets": [], "preloads": ["/assets/_dashboard-Bxw4OxIS.js", "/assets/createServerFn-B0pEGqTk.js", "/assets/sessions.queries-AUVV0tJj.js"] }, "/_dashboard/settings": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/settings.tsx", "assets": [], "preloads": ["/assets/settings-CIwZDakc.js", "/assets/settings.types-BRNIMHGJ.js"] }, "/_dashboard/stats": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/stats.tsx", "assets": [], "preloads": ["/assets/stats-CjWSMX3y.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DgFKglaG.js", "/assets/settings.types-BRNIMHGJ.js"] }, "/_dashboard/sessions/$sessionId": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-CNR4Ln7m.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DgFKglaG.js", "/assets/settings.types-BRNIMHGJ.js"] }, "/_dashboard/sessions/": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/index.tsx", "assets": [], "preloads": ["/assets/index-BbdJ1jMA.js", "/assets/format-Bsprb3az.js"] } }, "clientEntry": "/assets/main-CM5g2n-_.js" });
2
+ export {
3
+ tsrStartManifest
4
+ };
@@ -1,6 +1,12 @@
1
1
  import * as path from "node:path";
2
2
  import * as os from "node:os";
3
- const CLAUDE_DIR = path.join(os.homedir(), ".claude");
3
+ function resolveClaudeDir() {
4
+ if (process.env.CLAUDE_HOME) {
5
+ return path.resolve(process.env.CLAUDE_HOME);
6
+ }
7
+ return path.join(os.homedir(), ".claude");
8
+ }
9
+ const CLAUDE_DIR = resolveClaudeDir();
4
10
  function getProjectsDir() {
5
11
  return path.join(CLAUDE_DIR, "projects");
6
12
  }
@@ -48,9 +48,9 @@ function formatUSD(amount) {
48
48
  return `$${amount.toFixed(2)}`;
49
49
  }
50
50
  export {
51
- formatUSD as a,
52
- formatDuration as b,
53
- formatRelativeTime as c,
51
+ formatDuration as a,
52
+ formatRelativeTime as b,
53
+ formatUSD as c,
54
54
  formatBytes as d,
55
55
  formatDateTime as e,
56
56
  formatTokenCount as f
@@ -1,12 +1,12 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useRef, useCallback, useMemo } from "react";
3
- import { queryOptions, keepPreviousData, useQuery } from "@tanstack/react-query";
3
+ import { useQuery } from "@tanstack/react-query";
4
4
  import { Link, useNavigate } from "@tanstack/react-router";
5
- import { c as createSsrRpc } from "./createSsrRpc-CVg2UDl0.js";
6
- import { z } from "zod";
7
- import { c as createServerFn } from "../server.js";
8
- import { b as formatDuration, c as formatRelativeTime, d as formatBytes } from "./format-CGmJnuhZ.js";
9
- import { u as usePrivacy, R as Route } from "./router-ByIey__S.js";
5
+ import { p as paginatedSessionListQuery, a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
6
+ import { a as formatDuration, b as formatRelativeTime, d as formatBytes } from "./format-DIZHV7IJ.js";
7
+ import { u as usePrivacy, a as Route } from "./router-ChxlsPNU.js";
8
+ import "./createSsrRpc-CVg2UDl0.js";
9
+ import "../server.js";
10
10
  import "@tanstack/history";
11
11
  import "@tanstack/router-core/ssr/client";
12
12
  import "@tanstack/router-core";
@@ -16,41 +16,8 @@ import "h3-v2";
16
16
  import "tiny-invariant";
17
17
  import "seroval";
18
18
  import "@tanstack/react-router/ssr/server";
19
+ import "zod";
19
20
  import "date-fns";
20
- const getSessionList = createServerFn({
21
- method: "GET"
22
- }).handler(createSsrRpc("bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d"));
23
- const getActiveSessionList = createServerFn({
24
- method: "GET"
25
- }).handler(createSsrRpc("839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f"));
26
- const paginatedSessionsInputSchema = z.object({
27
- page: z.number().int().min(1),
28
- pageSize: z.number().int().min(5).max(100),
29
- search: z.string(),
30
- status: z.enum(["all", "active", "completed"]),
31
- project: z.string()
32
- });
33
- const getPaginatedSessions = createServerFn({
34
- method: "GET"
35
- }).inputValidator((input) => paginatedSessionsInputSchema.parse(input)).handler(createSsrRpc("a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495"));
36
- queryOptions({
37
- queryKey: ["sessions", "list"],
38
- queryFn: () => getSessionList(),
39
- refetchInterval: 3e4
40
- });
41
- const activeSessionsQuery = queryOptions({
42
- queryKey: ["sessions", "active"],
43
- queryFn: () => getActiveSessionList(),
44
- refetchInterval: 3e3
45
- });
46
- function paginatedSessionListQuery(params) {
47
- return queryOptions({
48
- queryKey: ["sessions", "paginated", params],
49
- queryFn: () => getPaginatedSessions({ data: params }),
50
- placeholderData: keepPreviousData,
51
- refetchInterval: 3e4
52
- });
53
- }
54
21
  function StatusBadge({ isActive }) {
55
22
  if (isActive) {
56
23
  return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-emerald-500/15 px-2.5 py-0.5 text-xs font-medium text-emerald-400", children: [
@@ -0,0 +1,61 @@
1
+ import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
2
+ import { s as scanAllSessions } from "./session-scanner-CLfls9u-.js";
3
+ import { c as createServerFn } from "../server.js";
4
+ import "node:fs";
5
+ import "node:path";
6
+ import "./claude-path-BdwflgZ1.js";
7
+ import "node:os";
8
+ import "./session-parser-CAEXxF1D.js";
9
+ import "node:readline";
10
+ import "@tanstack/history";
11
+ import "@tanstack/router-core/ssr/client";
12
+ import "@tanstack/router-core";
13
+ import "node:async_hooks";
14
+ import "@tanstack/router-core/ssr/server";
15
+ import "h3-v2";
16
+ import "tiny-invariant";
17
+ import "seroval";
18
+ import "react/jsx-runtime";
19
+ import "@tanstack/react-router/ssr/server";
20
+ import "@tanstack/react-router";
21
+ function aggregateProjectAnalytics(allSessions) {
22
+ const projectMap = /* @__PURE__ */ new Map();
23
+ for (const session of allSessions) {
24
+ const existing = projectMap.get(session.projectPath) ?? [];
25
+ existing.push(session);
26
+ projectMap.set(session.projectPath, existing);
27
+ }
28
+ const projects = [];
29
+ for (const [projectPath, sessions] of projectMap) {
30
+ if (sessions.length === 0) continue;
31
+ const firstSession = sessions[0];
32
+ projects.push({
33
+ projectPath,
34
+ projectName: firstSession.projectName ?? projectPath.split("/").pop() ?? "Unknown",
35
+ totalSessions: sessions.length,
36
+ activeSessions: sessions.filter((s) => s.isActive).length,
37
+ totalMessages: sessions.reduce((sum, s) => sum + s.messageCount, 0),
38
+ totalDurationMs: sessions.reduce((sum, s) => sum + s.durationMs, 0),
39
+ firstSessionAt: sessions.reduce((min, s) => s.startedAt < min ? s.startedAt : min, firstSession.startedAt),
40
+ lastSessionAt: sessions.reduce((max, s) => s.lastActiveAt > max ? s.lastActiveAt : max, firstSession.lastActiveAt)
41
+ });
42
+ }
43
+ projects.sort((a, b) => b.lastSessionAt.localeCompare(a.lastSessionAt));
44
+ return {
45
+ projects
46
+ };
47
+ }
48
+ const getProjectAnalytics_createServerFn_handler = createServerRpc({
49
+ id: "64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb",
50
+ name: "getProjectAnalytics",
51
+ filename: "src/features/project-analytics/project-analytics.server.ts"
52
+ }, (opts) => getProjectAnalytics.__executeServer(opts));
53
+ const getProjectAnalytics = createServerFn({
54
+ method: "GET"
55
+ }).handler(getProjectAnalytics_createServerFn_handler, async () => {
56
+ const allSessions = await scanAllSessions();
57
+ return aggregateProjectAnalytics(allSessions);
58
+ });
59
+ export {
60
+ getProjectAnalytics_createServerFn_handler
61
+ };
@@ -73,6 +73,7 @@ function usePrivacy() {
73
73
  }
74
74
  return ctx;
75
75
  }
76
+ const appCss = "/assets/app-u2nTs9ny.css";
76
77
  const queryClient = new QueryClient({
77
78
  defaultOptions: {
78
79
  queries: {
@@ -91,7 +92,7 @@ const Route$6 = createRootRoute({
91
92
  links: [
92
93
  {
93
94
  rel: "stylesheet",
94
- href: "/src/styles/app.css"
95
+ href: appCss
95
96
  }
96
97
  ]
97
98
  }),
@@ -109,7 +110,7 @@ function RootDocument({ children }) {
109
110
  ] })
110
111
  ] });
111
112
  }
112
- const $$splitComponentImporter$4 = () => import("./_dashboard-CG6ub7j3.js");
113
+ const $$splitComponentImporter$4 = () => import("./_dashboard-CAO6-qAS.js");
113
114
  const Route$5 = createFileRoute("/_dashboard")({
114
115
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
115
116
  });
@@ -118,15 +119,19 @@ const Route$4 = createFileRoute("/")({
118
119
  throw redirect({ to: "/sessions" });
119
120
  }
120
121
  });
121
- const $$splitComponentImporter$3 = () => import("./stats-DItsFPp5.js");
122
+ const $$splitComponentImporter$3 = () => import("./stats-C9cZXTP5.js");
123
+ const statsSearchSchema = z.object({
124
+ tab: z.enum(["overview", "projects"]).default("overview").catch("overview")
125
+ });
122
126
  const Route$3 = createFileRoute("/_dashboard/stats")({
127
+ validateSearch: statsSearchSchema,
123
128
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
124
129
  });
125
130
  const $$splitComponentImporter$2 = () => import("./settings-ko61yfVs.js");
126
131
  const Route$2 = createFileRoute("/_dashboard/settings")({
127
132
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
128
133
  });
129
- const $$splitComponentImporter$1 = () => import("./index-DjrI63_C.js");
134
+ const $$splitComponentImporter$1 = () => import("./index-Do0HxVmM.js");
130
135
  const sessionsSearchSchema = z.object({
131
136
  page: z.number().int().min(1).default(1).catch(1),
132
137
  pageSize: z.number().int().min(5).max(100).default(5).catch(5),
@@ -138,7 +143,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
138
143
  validateSearch: sessionsSearchSchema,
139
144
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
140
145
  });
141
- const $$splitComponentImporter = () => import("./_sessionId-CcWGJarz.js");
146
+ const $$splitComponentImporter = () => import("./_sessionId-BZf2Aqy5.js");
142
147
  const searchSchema = z.object({
143
148
  project: z.string().optional()
144
149
  });
@@ -201,8 +206,9 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
201
206
  getRouter
202
207
  }, Symbol.toStringTag, { value: "Module" }));
203
208
  export {
204
- Route$1 as R,
205
- Route as a,
209
+ Route$3 as R,
210
+ Route$1 as a,
211
+ Route as b,
206
212
  router as r,
207
213
  usePrivacy as u
208
214
  };
@@ -1,7 +1,7 @@
1
1
  import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
2
2
  import * as path from "node:path";
3
3
  import * as fs from "node:fs";
4
- import { e as extractProjectName, a as getProjectsDir, d as decodeProjectDirName } from "./claude-path-on7ZBAjl.js";
4
+ import { e as extractProjectName, a as getProjectsDir, d as decodeProjectDirName } from "./claude-path-BdwflgZ1.js";
5
5
  import { p as parseDetail } from "./session-parser-CAEXxF1D.js";
6
6
  import { c as createServerFn } from "../server.js";
7
7
  import "node:os";
@@ -0,0 +1,93 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { a as getProjectsDir, d as decodeProjectDirName, e as extractProjectName, b as extractSessionId } from "./claude-path-BdwflgZ1.js";
4
+ import { a as parseSummary } from "./session-parser-CAEXxF1D.js";
5
+ async function scanProjects() {
6
+ const projectsDir = getProjectsDir();
7
+ let entries;
8
+ try {
9
+ entries = await fs.promises.readdir(projectsDir);
10
+ } catch {
11
+ return [];
12
+ }
13
+ const projects = [];
14
+ for (const dirName of entries) {
15
+ const dirPath = path.join(projectsDir, dirName);
16
+ const stat = await fs.promises.stat(dirPath).catch(() => null);
17
+ if (!stat?.isDirectory()) continue;
18
+ const files = await fs.promises.readdir(dirPath).catch(() => []);
19
+ const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
20
+ if (sessionFiles.length === 0) continue;
21
+ const decodedPath = decodeProjectDirName(dirName);
22
+ projects.push({
23
+ dirName,
24
+ decodedPath,
25
+ projectName: extractProjectName(decodedPath),
26
+ sessionFiles
27
+ });
28
+ }
29
+ return projects;
30
+ }
31
+ const ACTIVE_THRESHOLD_MS = 12e4;
32
+ async function isSessionActive(projectDirName, sessionId) {
33
+ const projectsDir = getProjectsDir();
34
+ const jsonlPath = path.join(projectsDir, projectDirName, `${sessionId}.jsonl`);
35
+ const lockDirPath = path.join(projectsDir, projectDirName, sessionId);
36
+ const stat = await fs.promises.stat(jsonlPath).catch(() => null);
37
+ if (!stat) return false;
38
+ const age = Date.now() - stat.mtimeMs;
39
+ if (age > ACTIVE_THRESHOLD_MS) return false;
40
+ const lockStat = await fs.promises.stat(lockDirPath).catch(() => null);
41
+ return lockStat?.isDirectory() ?? false;
42
+ }
43
+ const summaryCache = /* @__PURE__ */ new Map();
44
+ async function scanAllSessions() {
45
+ const projects = await scanProjects();
46
+ const summaries = [];
47
+ for (const project of projects) {
48
+ for (const file of project.sessionFiles) {
49
+ const sessionId = extractSessionId(file);
50
+ const filePath = path.join(
51
+ getProjectsDir(),
52
+ project.dirName,
53
+ file
54
+ );
55
+ const stat = await fs.promises.stat(filePath).catch(() => null);
56
+ if (!stat) continue;
57
+ const cached = summaryCache.get(sessionId);
58
+ if (cached && cached.mtimeMs === stat.mtimeMs) {
59
+ const active = await isSessionActive(project.dirName, sessionId);
60
+ summaries.push({ ...cached.summary, isActive: active });
61
+ continue;
62
+ }
63
+ const summary = await parseSummary(
64
+ filePath,
65
+ sessionId,
66
+ project.decodedPath,
67
+ project.projectName,
68
+ stat.size
69
+ );
70
+ if (summary) {
71
+ const active = await isSessionActive(project.dirName, sessionId);
72
+ summary.isActive = active;
73
+ summaryCache.set(sessionId, {
74
+ mtimeMs: stat.mtimeMs,
75
+ summary
76
+ });
77
+ summaries.push(summary);
78
+ }
79
+ }
80
+ }
81
+ summaries.sort(
82
+ (a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime()
83
+ );
84
+ return summaries;
85
+ }
86
+ async function getActiveSessions() {
87
+ const all = await scanAllSessions();
88
+ return all.filter((s) => s.isActive);
89
+ }
90
+ export {
91
+ getActiveSessions as g,
92
+ scanAllSessions as s
93
+ };
@@ -0,0 +1,42 @@
1
+ import { queryOptions, keepPreviousData } from "@tanstack/react-query";
2
+ import { c as createSsrRpc } from "./createSsrRpc-CVg2UDl0.js";
3
+ import { z } from "zod";
4
+ import { c as createServerFn } from "../server.js";
5
+ const getSessionList = createServerFn({
6
+ method: "GET"
7
+ }).handler(createSsrRpc("bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d"));
8
+ const getActiveSessionList = createServerFn({
9
+ method: "GET"
10
+ }).handler(createSsrRpc("839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f"));
11
+ const paginatedSessionsInputSchema = z.object({
12
+ page: z.number().int().min(1),
13
+ pageSize: z.number().int().min(5).max(100),
14
+ search: z.string(),
15
+ status: z.enum(["all", "active", "completed"]),
16
+ project: z.string()
17
+ });
18
+ const getPaginatedSessions = createServerFn({
19
+ method: "GET"
20
+ }).inputValidator((input) => paginatedSessionsInputSchema.parse(input)).handler(createSsrRpc("a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495"));
21
+ queryOptions({
22
+ queryKey: ["sessions", "list"],
23
+ queryFn: () => getSessionList(),
24
+ refetchInterval: 3e4
25
+ });
26
+ const activeSessionsQuery = queryOptions({
27
+ queryKey: ["sessions", "active"],
28
+ queryFn: () => getActiveSessionList(),
29
+ refetchInterval: 3e3
30
+ });
31
+ function paginatedSessionListQuery(params) {
32
+ return queryOptions({
33
+ queryKey: ["sessions", "paginated", params],
34
+ queryFn: () => getPaginatedSessions({ data: params }),
35
+ placeholderData: keepPreviousData,
36
+ refetchInterval: 3e4
37
+ });
38
+ }
39
+ export {
40
+ activeSessionsQuery as a,
41
+ paginatedSessionListQuery as p
42
+ };
@@ -1,11 +1,12 @@
1
1
  import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
2
2
  import { z } from "zod";
3
- import * as fs from "node:fs";
4
- import * as path from "node:path";
5
- import { a as getProjectsDir, d as decodeProjectDirName, e as extractProjectName, b as extractSessionId } from "./claude-path-on7ZBAjl.js";
6
- import { a as parseSummary } from "./session-parser-CAEXxF1D.js";
3
+ import { s as scanAllSessions, g as getActiveSessions } from "./session-scanner-CLfls9u-.js";
7
4
  import { c as createServerFn } from "../server.js";
5
+ import "node:fs";
6
+ import "node:path";
7
+ import "./claude-path-BdwflgZ1.js";
8
8
  import "node:os";
9
+ import "./session-parser-CAEXxF1D.js";
9
10
  import "node:readline";
10
11
  import "@tanstack/history";
11
12
  import "@tanstack/router-core/ssr/client";
@@ -18,91 +19,6 @@ import "seroval";
18
19
  import "react/jsx-runtime";
19
20
  import "@tanstack/react-router/ssr/server";
20
21
  import "@tanstack/react-router";
21
- async function scanProjects() {
22
- const projectsDir = getProjectsDir();
23
- let entries;
24
- try {
25
- entries = await fs.promises.readdir(projectsDir);
26
- } catch {
27
- return [];
28
- }
29
- const projects = [];
30
- for (const dirName of entries) {
31
- const dirPath = path.join(projectsDir, dirName);
32
- const stat = await fs.promises.stat(dirPath).catch(() => null);
33
- if (!stat?.isDirectory()) continue;
34
- const files = await fs.promises.readdir(dirPath).catch(() => []);
35
- const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
36
- if (sessionFiles.length === 0) continue;
37
- const decodedPath = decodeProjectDirName(dirName);
38
- projects.push({
39
- dirName,
40
- decodedPath,
41
- projectName: extractProjectName(decodedPath),
42
- sessionFiles
43
- });
44
- }
45
- return projects;
46
- }
47
- const ACTIVE_THRESHOLD_MS = 12e4;
48
- async function isSessionActive(projectDirName, sessionId) {
49
- const projectsDir = getProjectsDir();
50
- const jsonlPath = path.join(projectsDir, projectDirName, `${sessionId}.jsonl`);
51
- const lockDirPath = path.join(projectsDir, projectDirName, sessionId);
52
- const stat = await fs.promises.stat(jsonlPath).catch(() => null);
53
- if (!stat) return false;
54
- const age = Date.now() - stat.mtimeMs;
55
- if (age > ACTIVE_THRESHOLD_MS) return false;
56
- const lockStat = await fs.promises.stat(lockDirPath).catch(() => null);
57
- return lockStat?.isDirectory() ?? false;
58
- }
59
- const summaryCache = /* @__PURE__ */ new Map();
60
- async function scanAllSessions() {
61
- const projects = await scanProjects();
62
- const summaries = [];
63
- for (const project of projects) {
64
- for (const file of project.sessionFiles) {
65
- const sessionId = extractSessionId(file);
66
- const filePath = path.join(
67
- getProjectsDir(),
68
- project.dirName,
69
- file
70
- );
71
- const stat = await fs.promises.stat(filePath).catch(() => null);
72
- if (!stat) continue;
73
- const cached = summaryCache.get(sessionId);
74
- if (cached && cached.mtimeMs === stat.mtimeMs) {
75
- const active = await isSessionActive(project.dirName, sessionId);
76
- summaries.push({ ...cached.summary, isActive: active });
77
- continue;
78
- }
79
- const summary = await parseSummary(
80
- filePath,
81
- sessionId,
82
- project.decodedPath,
83
- project.projectName,
84
- stat.size
85
- );
86
- if (summary) {
87
- const active = await isSessionActive(project.dirName, sessionId);
88
- summary.isActive = active;
89
- summaryCache.set(sessionId, {
90
- mtimeMs: stat.mtimeMs,
91
- summary
92
- });
93
- summaries.push(summary);
94
- }
95
- }
96
- }
97
- summaries.sort(
98
- (a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime()
99
- );
100
- return summaries;
101
- }
102
- async function getActiveSessions() {
103
- const all = await scanAllSessions();
104
- return all.filter((s) => s.isActive);
105
- }
106
22
  const getSessionList_createServerFn_handler = createServerRpc({
107
23
  id: "bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d",
108
24
  name: "getSessionList",