claude-session-dashboard 0.1.3 → 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 (39) 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-Le0d8Pjz.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-CzD8HjLq.js → main-CM5g2n-_.js} +7 -7
  8. package/dist/client/assets/sessions.queries-AUVV0tJj.js +1 -0
  9. package/dist/client/assets/{settings-BSPc79zZ.js → settings-CIwZDakc.js} +1 -1
  10. package/dist/client/assets/{settings.types-B4841OLF.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--ukhquwO.js → _dashboard-CAO6-qAS.js} +22 -3
  14. package/dist/server/assets/{_sessionId-BwZK4Ezz.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-CkuljM34.js → claude-path-BdwflgZ1.js} +9 -3
  17. package/dist/server/assets/{format-CGmJnuhZ.js → format-DIZHV7IJ.js} +3 -3
  18. package/dist/server/assets/{index-D4VWrt2z.js → index-Do0HxVmM.js} +7 -40
  19. package/dist/server/assets/project-analytics.server-BkWSd6a8.js +61 -0
  20. package/dist/server/assets/{router-xTSe9UH_.js → router-ChxlsPNU.js} +12 -7
  21. package/dist/server/assets/{session-detail.server-azkRfON2.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-B8zbmvSM.js → sessions.server-CUhasKW2.js} +5 -89
  25. package/dist/server/assets/stats-C9cZXTP5.js +649 -0
  26. package/dist/server/assets/{stats.server-BZWxV-mC.js → stats.server-52mNk2Yw.js} +1 -1
  27. package/dist/server/assets/useSessionCost-CYs5UOX-.js +209 -0
  28. package/dist/server/server.js +19 -16
  29. package/package.json +5 -1
  30. package/dist/client/assets/_dashboard-CYwTENkn.js +0 -1
  31. package/dist/client/assets/_sessionId-Bwfhm_El.js +0 -12
  32. package/dist/client/assets/app-DhZyFob1.css +0 -1
  33. package/dist/client/assets/format-Bf-cSf6L.js +0 -1
  34. package/dist/client/assets/index-DXhX1hdS.js +0 -1
  35. package/dist/client/assets/stats-CDIvpOt9.js +0 -4
  36. package/dist/client/assets/useSessionCost-9NP6uhla.js +0 -61
  37. package/dist/server/assets/_tanstack-start-manifest_v-gtQY7f-T.js +0 -4
  38. package/dist/server/assets/stats-DItsFPp5.js +0 -266
  39. 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-xTSe9UH_.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-xTSe9UH_.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
  }
@@ -17,9 +23,9 @@ function extractSessionId(filename) {
17
23
  return filename.replace(/\.jsonl$/, "");
18
24
  }
19
25
  export {
20
- getStatsPath as a,
26
+ getProjectsDir as a,
21
27
  extractSessionId as b,
22
28
  decodeProjectDirName as d,
23
29
  extractProjectName as e,
24
- getProjectsDir as g
30
+ getStatsPath as g
25
31
  };
@@ -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-xTSe9UH_.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,7 +73,7 @@ function usePrivacy() {
73
73
  }
74
74
  return ctx;
75
75
  }
76
- const appCss = "/assets/app-DhZyFob1.css";
76
+ const appCss = "/assets/app-u2nTs9ny.css";
77
77
  const queryClient = new QueryClient({
78
78
  defaultOptions: {
79
79
  queries: {
@@ -110,7 +110,7 @@ function RootDocument({ children }) {
110
110
  ] })
111
111
  ] });
112
112
  }
113
- const $$splitComponentImporter$4 = () => import("./_dashboard--ukhquwO.js");
113
+ const $$splitComponentImporter$4 = () => import("./_dashboard-CAO6-qAS.js");
114
114
  const Route$5 = createFileRoute("/_dashboard")({
115
115
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
116
116
  });
@@ -119,15 +119,19 @@ const Route$4 = createFileRoute("/")({
119
119
  throw redirect({ to: "/sessions" });
120
120
  }
121
121
  });
122
- 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
+ });
123
126
  const Route$3 = createFileRoute("/_dashboard/stats")({
127
+ validateSearch: statsSearchSchema,
124
128
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
125
129
  });
126
130
  const $$splitComponentImporter$2 = () => import("./settings-ko61yfVs.js");
127
131
  const Route$2 = createFileRoute("/_dashboard/settings")({
128
132
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
129
133
  });
130
- const $$splitComponentImporter$1 = () => import("./index-D4VWrt2z.js");
134
+ const $$splitComponentImporter$1 = () => import("./index-Do0HxVmM.js");
131
135
  const sessionsSearchSchema = z.object({
132
136
  page: z.number().int().min(1).default(1).catch(1),
133
137
  pageSize: z.number().int().min(5).max(100).default(5).catch(5),
@@ -139,7 +143,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
139
143
  validateSearch: sessionsSearchSchema,
140
144
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
141
145
  });
142
- const $$splitComponentImporter = () => import("./_sessionId-BwZK4Ezz.js");
146
+ const $$splitComponentImporter = () => import("./_sessionId-BZf2Aqy5.js");
143
147
  const searchSchema = z.object({
144
148
  project: z.string().optional()
145
149
  });
@@ -202,8 +206,9 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
202
206
  getRouter
203
207
  }, Symbol.toStringTag, { value: "Module" }));
204
208
  export {
205
- Route$1 as R,
206
- Route as a,
209
+ Route$3 as R,
210
+ Route$1 as a,
211
+ Route as b,
207
212
  router as r,
208
213
  usePrivacy as u
209
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, g as getProjectsDir, d as decodeProjectDirName } from "./claude-path-CkuljM34.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 { g as getProjectsDir, d as decodeProjectDirName, e as extractProjectName, b as extractSessionId } from "./claude-path-CkuljM34.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",