claude-session-dashboard 0.1.3 → 0.3.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.
Files changed (44) hide show
  1. package/README.md +156 -14
  2. package/dist/client/assets/_dashboard-I7m6D7BE.js +1 -0
  3. package/dist/client/assets/_sessionId-DEliIff6.js +12 -0
  4. package/dist/client/assets/app-D7yorIIh.css +1 -0
  5. package/dist/client/assets/{createServerFn-Le0d8Pjz.js → createServerFn-Bn6_ISOt.js} +1 -1
  6. package/dist/client/assets/format-Bsprb3az.js +1 -0
  7. package/dist/client/assets/index-BkqRvnEf.js +1 -0
  8. package/dist/client/assets/{main-CzD8HjLq.js → main-CfJIADCp.js} +7 -7
  9. package/dist/client/assets/sessions.queries-CrJg4dYU.js +1 -0
  10. package/dist/client/assets/settings-C4_lsEzl.js +1 -0
  11. package/dist/client/assets/{settings.types-B4841OLF.js → settings.types-9Qf5WcRY.js} +1 -1
  12. package/dist/client/assets/stats-_r1gmaTe.js +4 -0
  13. package/dist/client/assets/useSessionCost-DPZ-ubM1.js +65 -0
  14. package/dist/client/favicon.svg +3 -0
  15. package/dist/server/assets/_dashboard-TUzgwLqB.js +112 -0
  16. package/dist/server/assets/{_sessionId-BwZK4Ezz.js → _sessionId-C-XZIPqn.js} +57 -35
  17. package/dist/server/assets/_tanstack-start-manifest_v-B51mSkGz.js +4 -0
  18. package/dist/server/assets/{claude-path-CkuljM34.js → claude-path-BdwflgZ1.js} +9 -3
  19. package/dist/server/assets/{format-CGmJnuhZ.js → format-DIZHV7IJ.js} +3 -3
  20. package/dist/server/assets/{index-D4VWrt2z.js → index-CKfH7HpA.js} +28 -60
  21. package/dist/server/assets/project-analytics.server-BkWSd6a8.js +61 -0
  22. package/dist/server/assets/{router-xTSe9UH_.js → router-Cb_hBXHI.js} +62 -31
  23. package/dist/server/assets/{session-detail.server-azkRfON2.js → session-detail.server-DLXl-Pn-.js} +1 -1
  24. package/dist/server/assets/session-scanner-CLfls9u-.js +93 -0
  25. package/dist/server/assets/sessions.queries-B5ZBiVJy.js +42 -0
  26. package/dist/server/assets/{sessions.server-B8zbmvSM.js → sessions.server-CUhasKW2.js} +5 -89
  27. package/dist/server/assets/{settings-ko61yfVs.js → settings-C0_KyVQQ.js} +66 -20
  28. package/dist/server/assets/stats-BtgVene-.js +886 -0
  29. package/dist/server/assets/{stats.server-BZWxV-mC.js → stats.server-qTOvID9-.js} +62 -3
  30. package/dist/server/assets/useSessionCost-CYs5UOX-.js +209 -0
  31. package/dist/server/server.js +13 -10
  32. package/package.json +11 -1
  33. package/dist/client/assets/_dashboard-CYwTENkn.js +0 -1
  34. package/dist/client/assets/_sessionId-Bwfhm_El.js +0 -12
  35. package/dist/client/assets/app-DhZyFob1.css +0 -1
  36. package/dist/client/assets/format-Bf-cSf6L.js +0 -1
  37. package/dist/client/assets/index-DXhX1hdS.js +0 -1
  38. package/dist/client/assets/settings-BSPc79zZ.js +0 -1
  39. package/dist/client/assets/stats-CDIvpOt9.js +0 -4
  40. package/dist/client/assets/useSessionCost-9NP6uhla.js +0 -61
  41. package/dist/server/assets/_dashboard--ukhquwO.js +0 -97
  42. package/dist/server/assets/_tanstack-start-manifest_v-gtQY7f-T.js +0 -4
  43. package/dist/server/assets/stats-DItsFPp5.js +0 -266
  44. package/dist/server/assets/useSessionCost-EB0VxklP.js +0 -76
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" width="32" height="32" fill="#d97757">
2
+ <path d="m146-346-42-42 147-147-147-147 42-42 189 189-189 189Zm254 26v-60h414v60H400Z"/>
3
+ </svg>
@@ -0,0 +1,112 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMatches, Link, Outlet } from "@tanstack/react-router";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
5
+ import "./createSsrRpc-CVg2UDl0.js";
6
+ import "../server.js";
7
+ import "@tanstack/history";
8
+ import "@tanstack/router-core/ssr/client";
9
+ import "@tanstack/router-core";
10
+ import "node:async_hooks";
11
+ import "@tanstack/router-core/ssr/server";
12
+ import "h3-v2";
13
+ import "tiny-invariant";
14
+ import "seroval";
15
+ import "@tanstack/react-router/ssr/server";
16
+ import "zod";
17
+ function ActiveSessionsBadge() {
18
+ const { data: activeSessions } = useQuery(activeSessionsQuery);
19
+ const count = activeSessions?.length ?? 0;
20
+ if (count === 0) return null;
21
+ return /* @__PURE__ */ jsx("span", { className: "ml-auto rounded-full bg-emerald-500/20 px-1.5 py-0.5 text-[10px] font-medium text-emerald-400", children: count });
22
+ }
23
+ const NAV_ITEMS = [
24
+ {
25
+ to: "/sessions",
26
+ label: "Sessions",
27
+ icon: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
28
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "4", x2: "14", y2: "4" }),
29
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "8", x2: "14", y2: "8" }),
30
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "14", y2: "12" })
31
+ ] })
32
+ },
33
+ {
34
+ to: "/stats",
35
+ label: "Stats",
36
+ icon: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "currentColor", children: [
37
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "8", width: "3", height: "7", rx: "0.5" }),
38
+ /* @__PURE__ */ jsx("rect", { x: "6.5", y: "4", width: "3", height: "11", rx: "0.5" }),
39
+ /* @__PURE__ */ jsx("rect", { x: "12", y: "1", width: "3", height: "14", rx: "0.5" })
40
+ ] })
41
+ },
42
+ {
43
+ to: "/settings",
44
+ label: "Settings",
45
+ icon: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "currentColor", children: [
46
+ /* @__PURE__ */ jsx("path", { d: "M8 10a2 2 0 100-4 2 2 0 000 4z" }),
47
+ /* @__PURE__ */ jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M6.5.8a1 1 0 011-.8h1a1 1 0 011 .8l.15.9a5.5 5.5 0 011.1.64l.86-.36a1 1 0 011.17.36l.5.86a1 1 0 01-.18 1.17l-.7.55a5.5 5.5 0 010 1.27l.7.55a1 1 0 01.18 1.17l-.5.86a1 1 0 01-1.17.36l-.86-.36a5.5 5.5 0 01-1.1.64l-.14.9a1 1 0 01-1 .8h-1a1 1 0 01-1-.8l-.14-.9a5.5 5.5 0 01-1.1-.64l-.87.36a1 1 0 01-1.17-.36l-.5-.86a1 1 0 01.18-1.17l.7-.55a5.5 5.5 0 010-1.27l-.7-.55a1 1 0 01-.18-1.17l.5-.86a1 1 0 011.17-.36l.87.36a5.5 5.5 0 011.1-.64L6.5.8zM8 11a3 3 0 100-6 3 3 0 000 6z" })
48
+ ] })
49
+ }
50
+ ];
51
+ function AppShell({ children }) {
52
+ const matches = useMatches();
53
+ const currentPath = matches[matches.length - 1]?.pathname ?? "";
54
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen", children: [
55
+ /* @__PURE__ */ jsxs("aside", { className: "flex w-56 shrink-0 flex-col border-r border-gray-800 bg-gray-950", children: [
56
+ /* @__PURE__ */ jsx("div", { className: "flex h-14 items-center border-b border-gray-800 px-4", children: /* @__PURE__ */ jsxs(Link, { to: "/sessions", className: "text-sm font-bold text-white", children: [
57
+ /* @__PURE__ */ jsx("span", { className: "text-brand-500", children: "Claude" }),
58
+ " Dashboard"
59
+ ] }) }),
60
+ /* @__PURE__ */ jsx("nav", { className: "flex-1 p-3", children: NAV_ITEMS.map((item) => {
61
+ const isActive = currentPath.startsWith(item.to);
62
+ return /* @__PURE__ */ jsxs(
63
+ Link,
64
+ {
65
+ to: item.to,
66
+ 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"}`,
67
+ children: [
68
+ /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: item.icon }),
69
+ item.label,
70
+ item.to === "/sessions" && /* @__PURE__ */ jsx(ActiveSessionsBadge, {})
71
+ ]
72
+ },
73
+ item.to
74
+ );
75
+ }) }),
76
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-800 p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
77
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
78
+ /* @__PURE__ */ jsx(
79
+ "a",
80
+ {
81
+ href: "https://github.com/dlupiak/claude-session-dashboard",
82
+ target: "_blank",
83
+ rel: "noopener noreferrer",
84
+ className: "text-gray-500 hover:text-gray-300 transition-colors",
85
+ title: "GitHub Repository",
86
+ children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" }) })
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsx(
90
+ "a",
91
+ {
92
+ href: "https://www.npmjs.com/package/claude-session-dashboard",
93
+ target: "_blank",
94
+ rel: "noopener noreferrer",
95
+ className: "text-gray-500 hover:text-gray-300 transition-colors",
96
+ title: "npm Package",
97
+ children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M0 0v16h16V0H0zm13 13H8V5H5v8H3V3h10v10z" }) })
98
+ }
99
+ )
100
+ ] }),
101
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600", children: "Read-only" })
102
+ ] }) })
103
+ ] }),
104
+ /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-5xl px-6 py-6", children }) })
105
+ ] });
106
+ }
107
+ function DashboardLayout() {
108
+ return /* @__PURE__ */ jsx(AppShell, { children: /* @__PURE__ */ jsx(Outlet, {}) });
109
+ }
110
+ export {
111
+ DashboardLayout as component
112
+ };
@@ -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-Cb_hBXHI.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) {
@@ -159,10 +161,10 @@ function shortenToolName(name) {
159
161
  }
160
162
  const TOOL_COLORS = {
161
163
  // File reading
162
- Read: "#60a5fa",
163
- // blue-400
164
- Grep: "#60a5fa",
165
- Glob: "#60a5fa",
164
+ Read: "#e09070",
165
+ // brand-400
166
+ Grep: "#e09070",
167
+ Glob: "#e09070",
166
168
  // File writing
167
169
  Write: "#34d399",
168
170
  // emerald-400
@@ -380,7 +382,6 @@ function TimelineChart({ data, width, onHover }) {
380
382
  y: agentLaneYs[i],
381
383
  toX,
382
384
  laneHeight: LANE_HEIGHT,
383
- leftMargin: LEFT_MARGIN,
384
385
  onHover,
385
386
  getPosition
386
387
  },
@@ -524,7 +525,6 @@ function AgentLaneSVG({
524
525
  y,
525
526
  toX,
526
527
  laneHeight,
527
- leftMargin,
528
528
  onHover,
529
529
  getPosition
530
530
  }) {
@@ -825,7 +825,7 @@ function StatBadge({
825
825
  color
826
826
  }) {
827
827
  const colorMap = {
828
- blue: "bg-blue-500/15 text-blue-400",
828
+ blue: "bg-brand-500/15 text-brand-400",
829
829
  purple: "bg-purple-500/15 text-purple-400",
830
830
  gray: "bg-gray-800 text-gray-300",
831
831
  indigo: "bg-indigo-500/15 text-indigo-400",
@@ -898,7 +898,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
898
898
  /* @__PURE__ */ jsx(
899
899
  "div",
900
900
  {
901
- className: "bg-blue-500",
901
+ className: "bg-brand-500",
902
902
  style: { width: `${messagesPct}%` },
903
903
  title: `Messages: ~${formatTokenCount(messagesEstimate)}`
904
904
  }
@@ -932,7 +932,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
932
932
  "system"
933
933
  ] }),
934
934
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
935
- /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-2 rounded-sm bg-blue-500" }),
935
+ /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-2 rounded-sm bg-brand-500" }),
936
936
  "messages"
937
937
  ] }),
938
938
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
@@ -957,7 +957,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
957
957
  label: "Messages",
958
958
  value: messagesEstimate,
959
959
  total: contextLimit,
960
- color: "bg-blue-500",
960
+ color: "bg-brand-500",
961
961
  prefix: "~"
962
962
  }
963
963
  ),
@@ -984,8 +984,8 @@ function ContextWindowPanel({ contextWindow, tokens }) {
984
984
  /* @__PURE__ */ jsx("p", { className: "mb-1 text-[10px] text-gray-500", children: "Context growth" }),
985
985
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: 96, children: /* @__PURE__ */ jsxs(AreaChart, { data: chartData, children: [
986
986
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "contextGrad", x1: "0", y1: "0", x2: "0", y2: "1", children: [
987
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#3b82f6", stopOpacity: 0.3 }),
988
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#3b82f6", stopOpacity: 0.05 })
987
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#d97757", stopOpacity: 0.3 }),
988
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#d97757", stopOpacity: 0.05 })
989
989
  ] }) }),
990
990
  /* @__PURE__ */ jsx(
991
991
  YAxis,
@@ -998,8 +998,8 @@ function ContextWindowPanel({ contextWindow, tokens }) {
998
998
  Tooltip,
999
999
  {
1000
1000
  contentStyle: {
1001
- background: "#1f2937",
1002
- border: "1px solid #374151",
1001
+ background: "#1c1c1a",
1002
+ border: "1px solid #3d3b36",
1003
1003
  borderRadius: "8px",
1004
1004
  fontSize: "11px"
1005
1005
  },
@@ -1030,7 +1030,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
1030
1030
  {
1031
1031
  type: "stepAfter",
1032
1032
  dataKey: "context",
1033
- stroke: "#3b82f6",
1033
+ stroke: "#d97757",
1034
1034
  fill: "url(#contextGrad)",
1035
1035
  strokeWidth: 1.5,
1036
1036
  dot: false
@@ -1090,7 +1090,7 @@ function CategoryRow({
1090
1090
  function TokenBreakdown({ tokens }) {
1091
1091
  const allTotal = tokens.inputTokens + tokens.outputTokens + tokens.cacheReadInputTokens + tokens.cacheCreationInputTokens;
1092
1092
  const items = [
1093
- { label: "Input", value: tokens.inputTokens, color: "bg-blue-400" },
1093
+ { label: "Input", value: tokens.inputTokens, color: "bg-brand-400" },
1094
1094
  { label: "Output", value: tokens.outputTokens, color: "bg-emerald-400" },
1095
1095
  { label: "Cache Read", value: tokens.cacheReadInputTokens, color: "bg-amber-400" },
1096
1096
  { label: "Cache Create", value: tokens.cacheCreationInputTokens, color: "bg-purple-400" }
@@ -1114,7 +1114,7 @@ function TokenFallback({ tokens }) {
1114
1114
  const activeTotal = tokens.inputTokens + tokens.outputTokens;
1115
1115
  const allTotal = activeTotal + tokens.cacheReadInputTokens + tokens.cacheCreationInputTokens;
1116
1116
  const items = [
1117
- { label: "Input", value: tokens.inputTokens, color: "text-blue-400" },
1117
+ { label: "Input", value: tokens.inputTokens, color: "text-brand-400" },
1118
1118
  { label: "Output", value: tokens.outputTokens, color: "text-emerald-400" },
1119
1119
  { label: "Cache Read", value: tokens.cacheReadInputTokens, color: "text-amber-400" },
1120
1120
  { label: "Cache Create", value: tokens.cacheCreationInputTokens, color: "text-purple-400" }
@@ -1165,9 +1165,9 @@ function ToolUsagePanel({
1165
1165
  /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
1166
1166
  "div",
1167
1167
  {
1168
- className: "h-4 rounded bg-blue-500/20",
1168
+ className: "h-4 rounded bg-brand-500/20",
1169
1169
  style: { width: `${count / maxCount * 100}%` },
1170
- children: /* @__PURE__ */ jsx("span", { className: "px-1.5 text-xs text-blue-300", children: count })
1170
+ children: /* @__PURE__ */ jsx("span", { className: "px-1.5 text-xs text-brand-300", children: count })
1171
1171
  }
1172
1172
  ) })
1173
1173
  ] }, tool)) })
@@ -1181,7 +1181,7 @@ function ErrorPanel({ errors }) {
1181
1181
  errors.length,
1182
1182
  ")"
1183
1183
  ] }),
1184
- /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-2", children: errors.map((error, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-red-950/30 p-2.5", children: [
1184
+ /* @__PURE__ */ jsx("div", { className: "mt-3 max-h-64 space-y-2 overflow-y-auto", children: errors.map((error, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-red-950/30 p-2.5", children: [
1185
1185
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1186
1186
  /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-red-300", children: error.type }),
1187
1187
  error.timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: formatDateTime(error.timestamp) })
@@ -1342,8 +1342,8 @@ const statusConfig = {
1342
1342
  pending: { label: "Pending", bg: "bg-gray-500/20", text: "text-gray-400" },
1343
1343
  in_progress: {
1344
1344
  label: "In Progress",
1345
- bg: "bg-blue-500/20",
1346
- text: "text-blue-400"
1345
+ bg: "bg-brand-500/20",
1346
+ text: "text-brand-400"
1347
1347
  },
1348
1348
  completed: {
1349
1349
  label: "Done",
@@ -1437,7 +1437,7 @@ function CostEstimationPanel({ tokensByModel }) {
1437
1437
  }
1438
1438
  function CategoryBreakdown({ cost }) {
1439
1439
  const categories = [
1440
- { label: "Input tokens", value: cost.byCategory.input, color: "text-blue-400" },
1440
+ { label: "Input tokens", value: cost.byCategory.input, color: "text-brand-400" },
1441
1441
  { label: "Output tokens", value: cost.byCategory.output, color: "text-emerald-400" },
1442
1442
  { label: "Cache read", value: cost.byCategory.cacheRead, color: "text-amber-400" },
1443
1443
  { label: "Cache write", value: cost.byCategory.cacheWrite, color: "text-purple-400" }
@@ -1450,7 +1450,7 @@ function CategoryBreakdown({ cost }) {
1450
1450
  function CostBar({ cost }) {
1451
1451
  if (cost.totalUSD === 0) return null;
1452
1452
  const segments = [
1453
- { key: "input", value: cost.byCategory.input, color: "bg-blue-400" },
1453
+ { key: "input", value: cost.byCategory.input, color: "bg-brand-400" },
1454
1454
  { key: "output", value: cost.byCategory.output, color: "bg-emerald-400" },
1455
1455
  { key: "cacheRead", value: cost.byCategory.cacheRead, color: "bg-amber-400" },
1456
1456
  { key: "cacheWrite", value: cost.byCategory.cacheWrite, color: "bg-purple-400" }
@@ -1510,6 +1510,19 @@ function CostSummaryLine({ tokensByModel }) {
1510
1510
  }
1511
1511
  );
1512
1512
  }
1513
+ function ActiveSessionBanner() {
1514
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-2 rounded-lg border border-emerald-800 bg-emerald-900/30 px-4 py-2 text-sm text-emerald-300", children: [
1515
+ /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2", children: [
1516
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" }),
1517
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-emerald-500" })
1518
+ ] }),
1519
+ "This session is currently active. Data refreshes automatically."
1520
+ ] });
1521
+ }
1522
+ function useIsSessionActive(sessionId) {
1523
+ const { data: activeSessions } = useQuery(activeSessionsQuery);
1524
+ return activeSessions?.some((s) => s.sessionId === sessionId) ?? false;
1525
+ }
1513
1526
  function SessionDetailPage() {
1514
1527
  const {
1515
1528
  sessionId
@@ -1519,13 +1532,15 @@ function SessionDetailPage() {
1519
1532
  } = Route.useSearch();
1520
1533
  const {
1521
1534
  privacyMode,
1522
- anonymizeProjectName
1535
+ anonymizeProjectName,
1536
+ anonymizeBranch
1523
1537
  } = usePrivacy();
1538
+ const isActive = useIsSessionActive(sessionId);
1524
1539
  const {
1525
1540
  data: detail,
1526
1541
  isLoading,
1527
1542
  error
1528
- } = useQuery(sessionDetailQuery(sessionId, project));
1543
+ } = useQuery(sessionDetailQuery(sessionId, project, isActive));
1529
1544
  if (isLoading) {
1530
1545
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1531
1546
  /* @__PURE__ */ jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-gray-800" }),
@@ -1538,19 +1553,20 @@ function SessionDetailPage() {
1538
1553
  "Failed to load session: ",
1539
1554
  error?.message ?? "Not found"
1540
1555
  ] }),
1541
- /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "mt-2 inline-block text-sm text-blue-400 hover:underline", children: "Back to sessions" })
1556
+ /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "mt-2 inline-block text-sm text-brand-300 hover:underline", children: "Back to sessions" })
1542
1557
  ] });
1543
1558
  }
1544
1559
  const startedAt = detail.turns[0]?.timestamp;
1545
1560
  const endedAt = detail.turns[detail.turns.length - 1]?.timestamp;
1546
1561
  const durationMs = startedAt && endedAt ? new Date(endedAt).getTime() - new Date(startedAt).getTime() : 0;
1547
1562
  return /* @__PURE__ */ jsxs("div", { children: [
1563
+ isActive && /* @__PURE__ */ jsx(ActiveSessionBanner, {}),
1548
1564
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
1549
1565
  /* @__PURE__ */ jsxs("div", { children: [
1550
1566
  /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "text-xs text-gray-500 hover:text-gray-300", children: "← Sessions" }),
1551
1567
  /* @__PURE__ */ jsx("h1", { className: "mt-1 text-xl font-bold text-white", children: privacyMode ? anonymizeProjectName(detail.projectName) : detail.projectName }),
1552
1568
  /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-3 text-xs text-gray-400", children: [
1553
- detail.branch && /* @__PURE__ */ jsx("span", { className: "font-mono", children: detail.branch }),
1569
+ detail.branch && /* @__PURE__ */ jsx("span", { className: "font-mono", children: anonymizeBranch(detail.branch) }),
1554
1570
  startedAt && /* @__PURE__ */ jsx("span", { children: formatDateTime(startedAt) }),
1555
1571
  /* @__PURE__ */ jsx("span", { children: formatDuration(durationMs) }),
1556
1572
  /* @__PURE__ */ jsxs("span", { children: [
@@ -1561,7 +1577,13 @@ function SessionDetailPage() {
1561
1577
  ] }),
1562
1578
  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
1579
  ] }),
1564
- /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-gray-600", children: sessionId.slice(0, 8) })
1580
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1581
+ /* @__PURE__ */ jsx(ExportDropdown, { options: [{
1582
+ label: "Export Session (JSON)",
1583
+ onClick: () => downloadFile(sessionToJSON(detail), `session-${sessionId.slice(0, 8)}.json`, "application/json")
1584
+ }] }),
1585
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-gray-600", children: sessionId.slice(0, 8) })
1586
+ ] })
1565
1587
  ] }),
1566
1588
  /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-1 gap-4 md:grid-cols-2", children: [
1567
1589
  /* @__PURE__ */ jsx(ContextWindowPanel, { contextWindow: detail.contextWindow, tokens: detail.totalTokens }),
@@ -1570,7 +1592,7 @@ function SessionDetailPage() {
1570
1592
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(CostEstimationPanel, { tokensByModel: detail.tokensByModel }) }),
1571
1593
  (detail.agents.length > 0 || detail.skills.length > 0) && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(AgentsSkillsPanel, { agents: detail.agents, skills: detail.skills }) }),
1572
1594
  detail.tasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(TasksPanel, { tasks: detail.tasks }) }),
1573
- /* @__PURE__ */ jsx(ErrorPanel, { errors: detail.errors }),
1595
+ /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(ErrorPanel, { errors: detail.errors }) }),
1574
1596
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
1575
1597
  /* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-gray-300", children: "Timeline" }),
1576
1598
  /* @__PURE__ */ jsx(TimelineEventsChart, { turns: detail.turns, agents: detail.agents, skills: detail.skills, errors: detail.errors })
@@ -0,0 +1,4 @@
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/__root.tsx", "children": ["/", "/_dashboard"], "preloads": ["/assets/main-CfJIADCp.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/index.tsx" }, "/_dashboard": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard.tsx", "children": ["/_dashboard/settings", "/_dashboard/stats", "/_dashboard/sessions/$sessionId", "/_dashboard/sessions/"], "assets": [], "preloads": ["/assets/_dashboard-I7m6D7BE.js", "/assets/createServerFn-Bn6_ISOt.js", "/assets/sessions.queries-CrJg4dYU.js"] }, "/_dashboard/settings": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/settings.tsx", "assets": [], "preloads": ["/assets/settings-C4_lsEzl.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/stats": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/stats.tsx", "assets": [], "preloads": ["/assets/stats-_r1gmaTe.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DPZ-ubM1.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/sessions/$sessionId": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-DEliIff6.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DPZ-ubM1.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/sessions/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/index.tsx", "assets": [], "preloads": ["/assets/index-BkqRvnEf.js", "/assets/format-Bsprb3az.js"] } }, "clientEntry": "/assets/main-CfJIADCp.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-Cb_hBXHI.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: [
@@ -79,9 +46,10 @@ function RunningTimer({ startedAt }) {
79
46
  return /* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: formatDuration(elapsed) });
80
47
  }
81
48
  function SessionCard({ session }) {
82
- const { privacyMode, anonymizePath, anonymizeProjectName } = usePrivacy();
49
+ const { privacyMode, anonymizePath, anonymizeProjectName, anonymizeBranch } = usePrivacy();
83
50
  const displayName = privacyMode ? anonymizeProjectName(session.projectName) : session.projectName;
84
- const displayCwd = session.cwd ? privacyMode ? anonymizePath(session.cwd) : session.cwd : null;
51
+ const displayCwd = session.cwd ? anonymizePath(session.cwd, session.projectName) : null;
52
+ const displayBranch = session.branch ? anonymizeBranch(session.branch) : null;
85
53
  return /* @__PURE__ */ jsxs(
86
54
  Link,
87
55
  {
@@ -96,7 +64,7 @@ function SessionCard({ session }) {
96
64
  /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-white", children: displayName }),
97
65
  /* @__PURE__ */ jsx(StatusBadge, { isActive: session.isActive })
98
66
  ] }),
99
- session.branch && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-gray-500", children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: session.branch }) })
67
+ displayBranch && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-gray-500", children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: displayBranch }) })
100
68
  ] }),
101
69
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-gray-500", children: formatRelativeTime(session.lastActiveAt) })
102
70
  ] }),
@@ -162,7 +130,7 @@ function SessionFilters({ projects, activeCount }) {
162
130
  placeholder: "Search sessions...",
163
131
  value: localSearch,
164
132
  onChange: (e) => handleSearchChange(e.target.value),
165
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
133
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500"
166
134
  }
167
135
  ),
168
136
  /* @__PURE__ */ jsx("div", { className: "flex rounded-lg border border-gray-700 text-xs", children: ["all", "active", "completed"].map((s) => /* @__PURE__ */ jsxs(
@@ -186,7 +154,7 @@ function SessionFilters({ projects, activeCount }) {
186
154
  {
187
155
  value: project,
188
156
  onChange: (e) => handleProjectChange(e.target.value),
189
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-blue-500",
157
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-brand-500",
190
158
  children: [
191
159
  /* @__PURE__ */ jsx("option", { value: "", children: "All projects" }),
192
160
  projects.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: privacyMode ? anonymizeProjectName(p) : p }, p))
@@ -200,20 +168,20 @@ const VALID_SIZES = [5, 10, 25, 50];
200
168
  function isValidSize(value) {
201
169
  return VALID_SIZES.includes(value);
202
170
  }
203
- function usePageSizePreference() {
204
- const [storedPageSize, setStoredPageSize] = useState(null);
205
- useEffect(() => {
206
- try {
207
- const raw = localStorage.getItem(STORAGE_KEY);
208
- if (raw !== null) {
209
- const parsed = Number(raw);
210
- if (isValidSize(parsed)) {
211
- setStoredPageSize(parsed);
212
- }
213
- }
214
- } catch {
171
+ function readStoredPageSize() {
172
+ if (typeof window === "undefined") return null;
173
+ try {
174
+ const raw = localStorage.getItem(STORAGE_KEY);
175
+ if (raw !== null) {
176
+ const parsed = Number(raw);
177
+ if (isValidSize(parsed)) return parsed;
215
178
  }
216
- }, []);
179
+ } catch {
180
+ }
181
+ return null;
182
+ }
183
+ function usePageSizePreference() {
184
+ const [storedPageSize, setStoredPageSize] = useState(readStoredPageSize);
217
185
  const setPageSize = useCallback((size) => {
218
186
  if (!isValidSize(size)) return;
219
187
  try {
@@ -244,7 +212,7 @@ function PaginationControls({
244
212
  {
245
213
  value: pageSize,
246
214
  onChange: (e) => onPageSizeChange(Number(e.target.value)),
247
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-2 py-1 text-xs text-gray-200 outline-none focus:border-blue-500",
215
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-2 py-1 text-xs text-gray-200 outline-none focus:border-brand-500",
248
216
  children: VALID_SIZES.map((size) => /* @__PURE__ */ jsxs("option", { value: size, children: [
249
217
  size,
250
218
  " / page"
@@ -288,7 +256,7 @@ function PaginationControls({
288
256
  "button",
289
257
  {
290
258
  onClick: () => onPageChange(item),
291
- className: `min-w-[2rem] rounded-lg px-2 py-1.5 text-xs font-mono transition-colors ${item === page ? "bg-blue-600 text-white" : "border border-gray-700 bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200"}`,
259
+ className: `min-w-[2rem] rounded-lg px-2 py-1.5 text-xs font-mono transition-colors ${item === page ? "bg-brand-600 text-white" : "border border-gray-700 bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200"}`,
292
260
  children: item
293
261
  },
294
262
  item