claude-session-dashboard 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -14
- package/dist/client/assets/_dashboard-C-1YOzkf.js +1 -0
- package/dist/client/assets/_sessionId-C4jQeEqE.js +12 -0
- package/dist/client/assets/app-DNBe9Acr.css +1 -0
- package/dist/client/assets/{createServerFn-B0pEGqTk.js → createServerFn-B5mibSc4.js} +1 -1
- package/dist/client/assets/index-C83jHUdL.js +1 -0
- package/dist/client/assets/{main-CM5g2n-_.js → main-CkUc_xJ0.js} +7 -7
- package/dist/client/assets/{sessions.queries-AUVV0tJj.js → sessions.queries-C-HTNzuR.js} +1 -1
- package/dist/client/assets/settings-D56cUmNH.js +1 -0
- package/dist/client/assets/{settings.types-BRNIMHGJ.js → settings.types-l5MKKuAK.js} +1 -1
- package/dist/client/assets/stats-BunIdzj_.js +4 -0
- package/dist/client/assets/{useSessionCost-DgFKglaG.js → useSessionCost-BDldLkTA.js} +1 -1
- package/dist/client/favicon.svg +3 -0
- package/dist/server/assets/_dashboard-TUzgwLqB.js +112 -0
- package/dist/server/assets/{_sessionId-BZf2Aqy5.js → _sessionId-BvDwvNyA.js} +162 -145
- package/dist/server/assets/_tanstack-start-manifest_v-CVdzOaof.js +4 -0
- package/dist/server/assets/{claude-path-BdwflgZ1.js → claude-path-B2oho3NT.js} +2 -2
- package/dist/server/assets/{index-Do0HxVmM.js → index-Biupny11.js} +22 -21
- package/dist/server/assets/{project-analytics.server-BkWSd6a8.js → project-analytics.server-t1bM6wAa.js} +3 -3
- package/dist/server/assets/{router-ChxlsPNU.js → router-kB-tCwY9.js} +55 -29
- package/dist/server/assets/{session-detail.server-DLXl-Pn-.js → session-detail.server-IUw67jz-.js} +2 -2
- package/dist/server/assets/{session-parser-CAEXxF1D.js → session-parser-CIucKYBT.js} +67 -0
- package/dist/server/assets/{session-scanner-CLfls9u-.js → session-scanner-1h9TTTAV.js} +2 -2
- package/dist/server/assets/{sessions.server-CUhasKW2.js → sessions.server-Cpffr3MU.js} +3 -3
- package/dist/server/assets/{settings-ko61yfVs.js → settings-jxAA3KAS.js} +66 -20
- package/dist/server/assets/{stats-C9cZXTP5.js → stats-CzGBAoxT.js} +261 -24
- package/dist/server/assets/{stats.server-52mNk2Yw.js → stats.server-DXJiLqey.js} +62 -3
- package/dist/server/server.js +17 -17
- package/package.json +7 -1
- package/dist/client/assets/_dashboard-Bxw4OxIS.js +0 -1
- package/dist/client/assets/_sessionId-CNR4Ln7m.js +0 -12
- package/dist/client/assets/app-u2nTs9ny.css +0 -1
- package/dist/client/assets/index-BbdJ1jMA.js +0 -1
- package/dist/client/assets/settings-CIwZDakc.js +0 -1
- package/dist/client/assets/stats-CjWSMX3y.js +0 -4
- package/dist/server/assets/_dashboard-CAO6-qAS.js +0 -116
- package/dist/server/assets/_tanstack-start-manifest_v-C5hwNzs-.js +0 -4
|
@@ -4,11 +4,12 @@ import { queryOptions, useQuery } from "@tanstack/react-query";
|
|
|
4
4
|
import { c as createSsrRpc } from "./createSsrRpc-CVg2UDl0.js";
|
|
5
5
|
import { c as createServerFn } from "../server.js";
|
|
6
6
|
import { ResponsiveContainer, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, Bar, AreaChart, Area, PieChart, Pie, Cell, Legend } from "recharts";
|
|
7
|
-
import { format,
|
|
7
|
+
import { format, addDays, getDay, parseISO, startOfISOWeek } from "date-fns";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
8
9
|
import { f as formatTokenCount, a as formatDuration, b as formatRelativeTime, c as formatUSD } from "./format-DIZHV7IJ.js";
|
|
9
10
|
import { Link } from "@tanstack/react-router";
|
|
11
|
+
import { u as usePrivacy, R as Route } from "./router-kB-tCwY9.js";
|
|
10
12
|
import { u as useSessionCost, E as ExportDropdown, d as downloadFile, a as dailyActivityToCSV, b as dailyTokensToCSV, m as modelUsageToCSV, s as statsToJSON } from "./useSessionCost-CYs5UOX-.js";
|
|
11
|
-
import { R as Route } from "./router-ChxlsPNU.js";
|
|
12
13
|
import "@tanstack/history";
|
|
13
14
|
import "@tanstack/router-core/ssr/client";
|
|
14
15
|
import "@tanstack/router-core";
|
|
@@ -18,9 +19,9 @@ import "h3-v2";
|
|
|
18
19
|
import "tiny-invariant";
|
|
19
20
|
import "seroval";
|
|
20
21
|
import "@tanstack/react-router/ssr/server";
|
|
22
|
+
import "zod";
|
|
21
23
|
import "./settings.queries-DSQd324O.js";
|
|
22
24
|
import "./settings.types-DntadCHo.js";
|
|
23
|
-
import "zod";
|
|
24
25
|
const getStats = createServerFn({
|
|
25
26
|
method: "GET"
|
|
26
27
|
}).handler(createSsrRpc("4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799"));
|
|
@@ -38,19 +39,19 @@ function ActivityChart({ data }) {
|
|
|
38
39
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Daily Activity" }),
|
|
39
40
|
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Messages, sessions, and tool calls per day" }),
|
|
40
41
|
/* @__PURE__ */ jsx("div", { className: "mt-4 h-64", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(BarChart, { data: chartData, children: [
|
|
41
|
-
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#
|
|
42
|
+
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#2a2926" }),
|
|
42
43
|
/* @__PURE__ */ jsx(
|
|
43
44
|
XAxis,
|
|
44
45
|
{
|
|
45
46
|
dataKey: "dateLabel",
|
|
46
|
-
tick: { fill: "#
|
|
47
|
+
tick: { fill: "#7a7668", fontSize: 10 },
|
|
47
48
|
tickLine: false
|
|
48
49
|
}
|
|
49
50
|
),
|
|
50
51
|
/* @__PURE__ */ jsx(
|
|
51
52
|
YAxis,
|
|
52
53
|
{
|
|
53
|
-
tick: { fill: "#
|
|
54
|
+
tick: { fill: "#7a7668", fontSize: 10 },
|
|
54
55
|
tickLine: false,
|
|
55
56
|
axisLine: false
|
|
56
57
|
}
|
|
@@ -59,8 +60,8 @@ function ActivityChart({ data }) {
|
|
|
59
60
|
Tooltip,
|
|
60
61
|
{
|
|
61
62
|
contentStyle: {
|
|
62
|
-
backgroundColor: "#
|
|
63
|
-
border: "1px solid #
|
|
63
|
+
backgroundColor: "#1c1c1a",
|
|
64
|
+
border: "1px solid #3d3b36",
|
|
64
65
|
borderRadius: "8px",
|
|
65
66
|
fontSize: "12px"
|
|
66
67
|
}
|
|
@@ -71,7 +72,7 @@ function ActivityChart({ data }) {
|
|
|
71
72
|
{
|
|
72
73
|
dataKey: "messageCount",
|
|
73
74
|
name: "Messages",
|
|
74
|
-
fill: "#
|
|
75
|
+
fill: "#d97757",
|
|
75
76
|
radius: [2, 2, 0, 0]
|
|
76
77
|
}
|
|
77
78
|
),
|
|
@@ -96,7 +97,240 @@ function ActivityChart({ data }) {
|
|
|
96
97
|
] }) }) })
|
|
97
98
|
] });
|
|
98
99
|
}
|
|
99
|
-
const
|
|
100
|
+
const INTENSITY_COLORS = [
|
|
101
|
+
"#2a2926",
|
|
102
|
+
// Level 0: warm gray-800 (no activity)
|
|
103
|
+
"#3d2a1e",
|
|
104
|
+
// Level 1: dark terracotta
|
|
105
|
+
"#a8512eb3",
|
|
106
|
+
// Level 2: brand-700 at ~70% opacity
|
|
107
|
+
"#d97757cc",
|
|
108
|
+
// Level 3: brand-500 at ~80% opacity
|
|
109
|
+
"#e09070"
|
|
110
|
+
// Level 4: brand-400 (most intense)
|
|
111
|
+
];
|
|
112
|
+
const DAY_LABELS = ["Mon", "", "Wed", "", "Fri", "", ""];
|
|
113
|
+
const CELL_SIZE = 10;
|
|
114
|
+
const CELL_GAP = 2;
|
|
115
|
+
const DAY_LABEL_WIDTH = 28;
|
|
116
|
+
function computePercentiles(values) {
|
|
117
|
+
if (values.length === 0) return { p25: 0, p50: 0, p75: 0 };
|
|
118
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
119
|
+
const percentile = (p) => {
|
|
120
|
+
const idx = p / 100 * (sorted.length - 1);
|
|
121
|
+
const lower = Math.floor(idx);
|
|
122
|
+
const upper = Math.ceil(idx);
|
|
123
|
+
if (lower === upper) return sorted[lower];
|
|
124
|
+
return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
p25: percentile(25),
|
|
128
|
+
p50: percentile(50),
|
|
129
|
+
p75: percentile(75)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function getIntensityLevel(tokens, percentiles) {
|
|
133
|
+
if (tokens === 0) return 0;
|
|
134
|
+
if (tokens <= percentiles.p25) return 1;
|
|
135
|
+
if (tokens <= percentiles.p50) return 2;
|
|
136
|
+
if (tokens <= percentiles.p75) return 3;
|
|
137
|
+
return 4;
|
|
138
|
+
}
|
|
139
|
+
function toMondayFirst(jsDay) {
|
|
140
|
+
return jsDay === 0 ? 6 : jsDay - 1;
|
|
141
|
+
}
|
|
142
|
+
function ContributionHeatmap({
|
|
143
|
+
dailyActivity,
|
|
144
|
+
dailyModelTokens
|
|
145
|
+
}) {
|
|
146
|
+
const [tooltip, setTooltip] = useState(null);
|
|
147
|
+
const { days, monthLabels } = useMemo(() => {
|
|
148
|
+
const dataMap = /* @__PURE__ */ new Map();
|
|
149
|
+
for (const entry of dailyActivity) {
|
|
150
|
+
const existing = dataMap.get(entry.date) ?? { sessionCount: 0, totalTokens: 0 };
|
|
151
|
+
existing.sessionCount = entry.sessionCount;
|
|
152
|
+
dataMap.set(entry.date, existing);
|
|
153
|
+
}
|
|
154
|
+
for (const entry of dailyModelTokens) {
|
|
155
|
+
const existing = dataMap.get(entry.date) ?? { sessionCount: 0, totalTokens: 0 };
|
|
156
|
+
existing.totalTokens = Object.values(entry.tokensByModel).reduce((sum, t) => sum + t, 0);
|
|
157
|
+
dataMap.set(entry.date, existing);
|
|
158
|
+
}
|
|
159
|
+
const today = /* @__PURE__ */ new Date();
|
|
160
|
+
today.setHours(0, 0, 0, 0);
|
|
161
|
+
let startDate = addDays(today, -364);
|
|
162
|
+
const startDayOfWeek = toMondayFirst(getDay(startDate));
|
|
163
|
+
if (startDayOfWeek !== 0) {
|
|
164
|
+
startDate = addDays(startDate, -startDayOfWeek);
|
|
165
|
+
}
|
|
166
|
+
const allDays = [];
|
|
167
|
+
let currentDate = startDate;
|
|
168
|
+
while (currentDate <= today) {
|
|
169
|
+
const dateStr = format(currentDate, "yyyy-MM-dd");
|
|
170
|
+
const dayOfWeek = toMondayFirst(getDay(currentDate));
|
|
171
|
+
const daysSinceStart = Math.floor(
|
|
172
|
+
(currentDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24)
|
|
173
|
+
);
|
|
174
|
+
const weekIndex = Math.floor(daysSinceStart / 7);
|
|
175
|
+
const data = dataMap.get(dateStr);
|
|
176
|
+
allDays.push({
|
|
177
|
+
date: dateStr,
|
|
178
|
+
dateFormatted: format(currentDate, "MMM d, yyyy"),
|
|
179
|
+
sessionCount: data?.sessionCount ?? 0,
|
|
180
|
+
totalTokens: data?.totalTokens ?? 0,
|
|
181
|
+
intensity: 0,
|
|
182
|
+
// computed below
|
|
183
|
+
weekIndex,
|
|
184
|
+
dayOfWeek
|
|
185
|
+
});
|
|
186
|
+
currentDate = addDays(currentDate, 1);
|
|
187
|
+
}
|
|
188
|
+
const nonZeroTokens = allDays.filter((d) => d.totalTokens > 0).map((d) => d.totalTokens);
|
|
189
|
+
const percentiles = computePercentiles(nonZeroTokens);
|
|
190
|
+
for (const day of allDays) {
|
|
191
|
+
day.intensity = getIntensityLevel(day.totalTokens, percentiles);
|
|
192
|
+
}
|
|
193
|
+
const labels = [];
|
|
194
|
+
let lastMonth = -1;
|
|
195
|
+
for (const day of allDays) {
|
|
196
|
+
if (day.dayOfWeek === 0) {
|
|
197
|
+
const parsed = parseISO(day.date);
|
|
198
|
+
const month = parsed.getMonth();
|
|
199
|
+
if (month !== lastMonth) {
|
|
200
|
+
labels.push({
|
|
201
|
+
label: format(parsed, "MMM"),
|
|
202
|
+
weekIndex: day.weekIndex
|
|
203
|
+
});
|
|
204
|
+
lastMonth = month;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { days: allDays, monthLabels: labels };
|
|
209
|
+
}, [dailyActivity, dailyModelTokens]);
|
|
210
|
+
if (days.length === 0) {
|
|
211
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
|
|
212
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Activity" }),
|
|
213
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-32 items-center justify-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No activity data available" }) })
|
|
214
|
+
] });
|
|
215
|
+
}
|
|
216
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
|
|
217
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Activity" }),
|
|
218
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Token usage intensity over the past year" }),
|
|
219
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4 overflow-x-auto", children: /* @__PURE__ */ jsx("div", { className: "relative w-full", children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
|
|
220
|
+
/* @__PURE__ */ jsxs(
|
|
221
|
+
"div",
|
|
222
|
+
{
|
|
223
|
+
className: "flex flex-col text-[10px] text-gray-500",
|
|
224
|
+
style: { width: DAY_LABEL_WIDTH },
|
|
225
|
+
children: [
|
|
226
|
+
/* @__PURE__ */ jsx("div", { style: { height: 16, marginBottom: CELL_GAP } }),
|
|
227
|
+
DAY_LABELS.map((label, i) => /* @__PURE__ */ jsx(
|
|
228
|
+
"div",
|
|
229
|
+
{
|
|
230
|
+
className: "flex items-center",
|
|
231
|
+
style: { height: CELL_SIZE, marginTop: i > 0 ? CELL_GAP : 0 },
|
|
232
|
+
children: label
|
|
233
|
+
},
|
|
234
|
+
i
|
|
235
|
+
))
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
),
|
|
239
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col", children: [
|
|
240
|
+
/* @__PURE__ */ jsx(
|
|
241
|
+
"div",
|
|
242
|
+
{
|
|
243
|
+
className: "grid text-[10px] text-gray-500",
|
|
244
|
+
style: {
|
|
245
|
+
gridAutoFlow: "column",
|
|
246
|
+
gridAutoColumns: `minmax(${CELL_SIZE}px, 1fr)`,
|
|
247
|
+
gap: CELL_GAP,
|
|
248
|
+
height: 16,
|
|
249
|
+
marginBottom: CELL_GAP
|
|
250
|
+
},
|
|
251
|
+
children: monthLabels.map((ml, i) => /* @__PURE__ */ jsx(
|
|
252
|
+
"div",
|
|
253
|
+
{
|
|
254
|
+
className: "flex items-end",
|
|
255
|
+
style: { gridColumn: ml.weekIndex + 1 },
|
|
256
|
+
children: ml.label
|
|
257
|
+
},
|
|
258
|
+
`${ml.label}-${i}`
|
|
259
|
+
))
|
|
260
|
+
}
|
|
261
|
+
),
|
|
262
|
+
/* @__PURE__ */ jsx(
|
|
263
|
+
"div",
|
|
264
|
+
{
|
|
265
|
+
className: "relative grid",
|
|
266
|
+
style: {
|
|
267
|
+
gridTemplateRows: `repeat(7, ${CELL_SIZE}px)`,
|
|
268
|
+
gridAutoFlow: "column",
|
|
269
|
+
gridAutoColumns: `minmax(${CELL_SIZE}px, 1fr)`,
|
|
270
|
+
gap: CELL_GAP
|
|
271
|
+
},
|
|
272
|
+
children: days.map((day) => /* @__PURE__ */ jsx(
|
|
273
|
+
"div",
|
|
274
|
+
{
|
|
275
|
+
className: "rounded-sm transition-colors",
|
|
276
|
+
style: {
|
|
277
|
+
width: CELL_SIZE,
|
|
278
|
+
height: CELL_SIZE,
|
|
279
|
+
backgroundColor: INTENSITY_COLORS[day.intensity],
|
|
280
|
+
gridRow: day.dayOfWeek + 1,
|
|
281
|
+
gridColumn: day.weekIndex + 1
|
|
282
|
+
},
|
|
283
|
+
onMouseEnter: (e) => {
|
|
284
|
+
const rect = e.target.getBoundingClientRect();
|
|
285
|
+
setTooltip({
|
|
286
|
+
date: day.date,
|
|
287
|
+
dateFormatted: day.dateFormatted,
|
|
288
|
+
sessionCount: day.sessionCount,
|
|
289
|
+
totalTokens: day.totalTokens,
|
|
290
|
+
x: rect.left + rect.width / 2,
|
|
291
|
+
y: rect.top - 8
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
onMouseLeave: () => setTooltip(null)
|
|
295
|
+
},
|
|
296
|
+
day.date
|
|
297
|
+
))
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
] })
|
|
301
|
+
] }) }) }),
|
|
302
|
+
tooltip && createPortal(
|
|
303
|
+
/* @__PURE__ */ jsxs(
|
|
304
|
+
"div",
|
|
305
|
+
{
|
|
306
|
+
className: "pointer-events-none fixed z-50 rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-xs shadow-lg",
|
|
307
|
+
style: {
|
|
308
|
+
left: tooltip.x,
|
|
309
|
+
top: tooltip.y,
|
|
310
|
+
transform: "translate(-50%, -100%)",
|
|
311
|
+
whiteSpace: "nowrap"
|
|
312
|
+
},
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-gray-300", children: tooltip.dateFormatted }),
|
|
315
|
+
tooltip.totalTokens > 0 || tooltip.sessionCount > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5 text-gray-400", children: [
|
|
316
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
317
|
+
tooltip.sessionCount,
|
|
318
|
+
" ",
|
|
319
|
+
tooltip.sessionCount === 1 ? "session" : "sessions"
|
|
320
|
+
] }),
|
|
321
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
322
|
+
formatTokenCount(tooltip.totalTokens),
|
|
323
|
+
" tokens"
|
|
324
|
+
] })
|
|
325
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-gray-500", children: "No activity" })
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
),
|
|
329
|
+
document.body
|
|
330
|
+
)
|
|
331
|
+
] });
|
|
332
|
+
}
|
|
333
|
+
const COLORS$1 = ["#d97757", "#8b5cf6", "#10b981", "#f59e0b", "#ef4444", "#b07cc5"];
|
|
100
334
|
function normalizeModelName(model) {
|
|
101
335
|
return model.replace(/^claude-/, "").split("-202")[0];
|
|
102
336
|
}
|
|
@@ -244,19 +478,19 @@ function TokenTrendChart({ data }) {
|
|
|
244
478
|
] })
|
|
245
479
|
] }),
|
|
246
480
|
/* @__PURE__ */ jsx("div", { className: "mt-4 h-72", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(AreaChart, { data: chartData, children: [
|
|
247
|
-
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#
|
|
481
|
+
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#2a2926" }),
|
|
248
482
|
/* @__PURE__ */ jsx(
|
|
249
483
|
XAxis,
|
|
250
484
|
{
|
|
251
485
|
dataKey: "dateLabel",
|
|
252
|
-
tick: { fill: "#
|
|
486
|
+
tick: { fill: "#7a7668", fontSize: 10 },
|
|
253
487
|
tickLine: false
|
|
254
488
|
}
|
|
255
489
|
),
|
|
256
490
|
/* @__PURE__ */ jsx(
|
|
257
491
|
YAxis,
|
|
258
492
|
{
|
|
259
|
-
tick: { fill: "#
|
|
493
|
+
tick: { fill: "#7a7668", fontSize: 10 },
|
|
260
494
|
tickLine: false,
|
|
261
495
|
axisLine: false,
|
|
262
496
|
tickFormatter: (value) => formatTokenCount(value)
|
|
@@ -278,7 +512,7 @@ function TokenTrendChart({ data }) {
|
|
|
278
512
|
] }) }) })
|
|
279
513
|
] });
|
|
280
514
|
}
|
|
281
|
-
const COLORS = ["#
|
|
515
|
+
const COLORS = ["#d97757", "#8b5cf6", "#10b981", "#f59e0b", "#ef4444", "#b07cc5"];
|
|
282
516
|
function ModelUsageChart({ data }) {
|
|
283
517
|
const chartData = Object.entries(data).map(([model, usage]) => ({
|
|
284
518
|
name: model.replace(/^claude-/, "").split("-202")[0],
|
|
@@ -310,8 +544,8 @@ function ModelUsageChart({ data }) {
|
|
|
310
544
|
{
|
|
311
545
|
formatter: (value) => formatTokenCount(value),
|
|
312
546
|
contentStyle: {
|
|
313
|
-
backgroundColor: "#
|
|
314
|
-
border: "1px solid #
|
|
547
|
+
backgroundColor: "#1c1c1a",
|
|
548
|
+
border: "1px solid #3d3b36",
|
|
315
549
|
borderRadius: "8px",
|
|
316
550
|
fontSize: "12px"
|
|
317
551
|
}
|
|
@@ -353,7 +587,7 @@ function HourlyDistribution({
|
|
|
353
587
|
className: "absolute bottom-0 w-full rounded-t transition-colors",
|
|
354
588
|
style: {
|
|
355
589
|
height: `${height}%`,
|
|
356
|
-
backgroundColor: `rgba(
|
|
590
|
+
backgroundColor: `rgba(217, 119, 87, ${0.2 + intensity * 0.6})`
|
|
357
591
|
}
|
|
358
592
|
}
|
|
359
593
|
),
|
|
@@ -392,6 +626,7 @@ const COLUMNS = [
|
|
|
392
626
|
{ key: "lastSessionAt", label: "Last Active", align: "right" }
|
|
393
627
|
];
|
|
394
628
|
function ProjectTable({ projects }) {
|
|
629
|
+
const { anonymizeProjectName } = usePrivacy();
|
|
395
630
|
const [sortField, setSortField] = useState("lastSessionAt");
|
|
396
631
|
const [sortDir, setSortDir] = useState("desc");
|
|
397
632
|
const sorted = useMemo(() => {
|
|
@@ -455,11 +690,11 @@ function ProjectTable({ projects }) {
|
|
|
455
690
|
{
|
|
456
691
|
to: "/sessions",
|
|
457
692
|
search: { project: project.projectName },
|
|
458
|
-
className: "text-sm text-
|
|
459
|
-
children: project.projectName
|
|
693
|
+
className: "text-sm text-brand-300 hover:underline",
|
|
694
|
+
children: anonymizeProjectName(project.projectName)
|
|
460
695
|
}
|
|
461
696
|
),
|
|
462
|
-
project.activeSessions > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 rounded-full bg-
|
|
697
|
+
project.activeSessions > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 rounded-full bg-emerald-500/20 px-1.5 py-0.5 text-[10px] font-medium text-emerald-400", children: [
|
|
463
698
|
project.activeSessions,
|
|
464
699
|
" active"
|
|
465
700
|
] })
|
|
@@ -475,6 +710,7 @@ function ProjectTable({ projects }) {
|
|
|
475
710
|
] }) });
|
|
476
711
|
}
|
|
477
712
|
function ProjectAnalytics() {
|
|
713
|
+
const { anonymizeProjectName } = usePrivacy();
|
|
478
714
|
const { data, isLoading } = useQuery(projectAnalyticsQuery);
|
|
479
715
|
if (isLoading) {
|
|
480
716
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
@@ -518,7 +754,7 @@ function ProjectAnalytics() {
|
|
|
518
754
|
SummaryCard,
|
|
519
755
|
{
|
|
520
756
|
label: "Most Active",
|
|
521
|
-
value: mostActive.projectName,
|
|
757
|
+
value: anonymizeProjectName(mostActive.projectName),
|
|
522
758
|
sub: `${mostActive.totalSessions} sessions`
|
|
523
759
|
}
|
|
524
760
|
)
|
|
@@ -588,12 +824,12 @@ function StatsPage() {
|
|
|
588
824
|
search: {
|
|
589
825
|
tab: "overview"
|
|
590
826
|
}
|
|
591
|
-
}), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "overview" ? "border-
|
|
827
|
+
}), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "overview" ? "border-brand-500 text-white" : "border-transparent text-gray-400 hover:text-gray-200"}`, children: "Overview" }),
|
|
592
828
|
/* @__PURE__ */ jsx("button", { onClick: () => navigate({
|
|
593
829
|
search: {
|
|
594
830
|
tab: "projects"
|
|
595
831
|
}
|
|
596
|
-
}), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "projects" ? "border-
|
|
832
|
+
}), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "projects" ? "border-brand-500 text-white" : "border-transparent text-gray-400 hover:text-gray-200"}`, children: "Projects" })
|
|
597
833
|
] }),
|
|
598
834
|
tab === "overview" ? /* @__PURE__ */ jsx(StatsOverview, { stats, isLoading, cost }) : /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ProjectAnalytics, {}) })
|
|
599
835
|
] });
|
|
@@ -625,7 +861,8 @@ function StatsOverview({
|
|
|
625
861
|
/* @__PURE__ */ jsx(StatCard, { label: "Total Estimated Cost", value: cost ? `~${formatUSD(cost.totalUSD)}` : "N/A" }),
|
|
626
862
|
/* @__PURE__ */ jsx(StatCard, { label: "Longest Session", value: formatDuration(stats.longestSession.duration), sub: `${stats.longestSession.messageCount} messages` })
|
|
627
863
|
] }),
|
|
628
|
-
/* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
864
|
+
/* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ContributionHeatmap, { dailyActivity: stats.dailyActivity, dailyModelTokens: stats.dailyModelTokens }) }),
|
|
865
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(ActivityChart, { data: stats.dailyActivity }) }),
|
|
629
866
|
/* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(TokenTrendChart, { data: stats.dailyModelTokens }) }),
|
|
630
867
|
/* @__PURE__ */ jsxs("div", { className: "mt-4 grid grid-cols-1 gap-4 md:grid-cols-2", children: [
|
|
631
868
|
/* @__PURE__ */ jsx(ModelUsageChart, { data: stats.modelUsage }),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { a as getStatsPath } from "./claude-path-B2oho3NT.js";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as os from "node:os";
|
|
4
6
|
import { z } from "zod";
|
|
5
7
|
import { c as createServerFn } from "../server.js";
|
|
6
|
-
import "node:path";
|
|
7
|
-
import "node:os";
|
|
8
8
|
import "@tanstack/history";
|
|
9
9
|
import "@tanstack/router-core/ssr/client";
|
|
10
10
|
import "@tanstack/router-core";
|
|
@@ -16,6 +16,59 @@ import "seroval";
|
|
|
16
16
|
import "react/jsx-runtime";
|
|
17
17
|
import "@tanstack/react-router/ssr/server";
|
|
18
18
|
import "@tanstack/react-router";
|
|
19
|
+
const CACHE_VERSION = 1;
|
|
20
|
+
function getCacheDir() {
|
|
21
|
+
return path.join(os.homedir(), ".claude-dashboard", "cache");
|
|
22
|
+
}
|
|
23
|
+
function getCachePath(cacheKey) {
|
|
24
|
+
return path.join(getCacheDir(), `${cacheKey}.cache.json`);
|
|
25
|
+
}
|
|
26
|
+
function readDiskCache(cacheKey, sourceMtimeMs, schema) {
|
|
27
|
+
try {
|
|
28
|
+
const cachePath = getCachePath(cacheKey);
|
|
29
|
+
if (!fs.existsSync(cachePath)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const raw = fs.readFileSync(cachePath, "utf-8");
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (parsed.version !== CACHE_VERSION) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (parsed.sourceMtimeMs !== sourceMtimeMs) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const result = schema.safeParse(parsed.data);
|
|
41
|
+
if (!result.success) {
|
|
42
|
+
console.warn(`[disk-cache] Zod validation failed for "${cacheKey}":`, result.error.message);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return result.data;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
console.warn(`[disk-cache] Read failed for "${cacheKey}":`, message);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function writeDiskCache(cacheKey, sourceFile, sourceMtimeMs, data) {
|
|
53
|
+
try {
|
|
54
|
+
const cacheDir = getCacheDir();
|
|
55
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
56
|
+
const cachePath = getCachePath(cacheKey);
|
|
57
|
+
const tmpPath = `${cachePath}.tmp`;
|
|
58
|
+
const entry = {
|
|
59
|
+
version: CACHE_VERSION,
|
|
60
|
+
sourceFile,
|
|
61
|
+
sourceMtimeMs,
|
|
62
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
data
|
|
64
|
+
};
|
|
65
|
+
fs.writeFileSync(tmpPath, JSON.stringify(entry), "utf-8");
|
|
66
|
+
fs.renameSync(tmpPath, cachePath);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
console.warn(`[disk-cache] Write failed for "${cacheKey}":`, message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
19
72
|
const DailyActivitySchema = z.object({
|
|
20
73
|
date: z.string(),
|
|
21
74
|
messageCount: z.number(),
|
|
@@ -64,9 +117,15 @@ async function parseStats() {
|
|
|
64
117
|
if (cachedStats && cachedStats.mtimeMs === stat.mtimeMs) {
|
|
65
118
|
return cachedStats.data;
|
|
66
119
|
}
|
|
120
|
+
const diskResult = readDiskCache("stats", stat.mtimeMs, StatsCacheSchema);
|
|
121
|
+
if (diskResult) {
|
|
122
|
+
cachedStats = { mtimeMs: stat.mtimeMs, data: diskResult };
|
|
123
|
+
return diskResult;
|
|
124
|
+
}
|
|
67
125
|
const raw = await fs.promises.readFile(statsPath, "utf-8");
|
|
68
126
|
const parsed = JSON.parse(raw);
|
|
69
127
|
const result = StatsCacheSchema.parse(parsed);
|
|
128
|
+
writeDiskCache("stats", statsPath, stat.mtimeMs, result);
|
|
70
129
|
cachedStats = { mtimeMs: stat.mtimeMs, data: result };
|
|
71
130
|
return result;
|
|
72
131
|
}
|
package/dist/server/server.js
CHANGED
|
@@ -423,7 +423,7 @@ function getResponse() {
|
|
|
423
423
|
return event.res;
|
|
424
424
|
}
|
|
425
425
|
async function getStartManifest(matchedRoutes) {
|
|
426
|
-
const { tsrStartManifest } = await import("./assets/_tanstack-start-manifest_v-
|
|
426
|
+
const { tsrStartManifest } = await import("./assets/_tanstack-start-manifest_v-CVdzOaof.js");
|
|
427
427
|
const startManifest = tsrStartManifest();
|
|
428
428
|
const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
|
|
429
429
|
rootRoute.assets = rootRoute.assets || [];
|
|
@@ -577,30 +577,30 @@ function createMultiplexedStream(jsonStream, rawStreams) {
|
|
|
577
577
|
}
|
|
578
578
|
});
|
|
579
579
|
}
|
|
580
|
-
const manifest = { "
|
|
581
|
-
functionName: "getStats_createServerFn_handler",
|
|
582
|
-
importer: () => import("./assets/stats.server-52mNk2Yw.js")
|
|
583
|
-
}, "ff8a3161afdfa175e9c519e4146a56ab5bce6e80745e99cfc2191ebbb7a859bb": {
|
|
580
|
+
const manifest = { "ff8a3161afdfa175e9c519e4146a56ab5bce6e80745e99cfc2191ebbb7a859bb": {
|
|
584
581
|
functionName: "getSessionDetail_createServerFn_handler",
|
|
585
|
-
importer: () => import("./assets/session-detail.server-
|
|
586
|
-
}, "
|
|
587
|
-
functionName: "
|
|
588
|
-
importer: () => import("./assets/
|
|
589
|
-
}, "839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f": {
|
|
590
|
-
functionName: "getActiveSessionList_createServerFn_handler",
|
|
591
|
-
importer: () => import("./assets/sessions.server-CUhasKW2.js")
|
|
592
|
-
}, "a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495": {
|
|
593
|
-
functionName: "getPaginatedSessions_createServerFn_handler",
|
|
594
|
-
importer: () => import("./assets/sessions.server-CUhasKW2.js")
|
|
582
|
+
importer: () => import("./assets/session-detail.server-IUw67jz-.js")
|
|
583
|
+
}, "4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799": {
|
|
584
|
+
functionName: "getStats_createServerFn_handler",
|
|
585
|
+
importer: () => import("./assets/stats.server-DXJiLqey.js")
|
|
595
586
|
}, "810657681a273df5b4e58f0d8fcc6a5451598b489431b9bcaa98eea0ad815da8": {
|
|
596
587
|
functionName: "getSettings_createServerFn_handler",
|
|
597
588
|
importer: () => import("./assets/settings.server-6B2PvLgf.js")
|
|
598
589
|
}, "3050115d92ca91ab1fd8fd698e33076328aae80dc64ca27c088eee16cebccc1a": {
|
|
599
590
|
functionName: "saveSettings_createServerFn_handler",
|
|
600
591
|
importer: () => import("./assets/settings.server-6B2PvLgf.js")
|
|
592
|
+
}, "bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d": {
|
|
593
|
+
functionName: "getSessionList_createServerFn_handler",
|
|
594
|
+
importer: () => import("./assets/sessions.server-Cpffr3MU.js")
|
|
595
|
+
}, "839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f": {
|
|
596
|
+
functionName: "getActiveSessionList_createServerFn_handler",
|
|
597
|
+
importer: () => import("./assets/sessions.server-Cpffr3MU.js")
|
|
598
|
+
}, "a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495": {
|
|
599
|
+
functionName: "getPaginatedSessions_createServerFn_handler",
|
|
600
|
+
importer: () => import("./assets/sessions.server-Cpffr3MU.js")
|
|
601
601
|
}, "64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb": {
|
|
602
602
|
functionName: "getProjectAnalytics_createServerFn_handler",
|
|
603
|
-
importer: () => import("./assets/project-analytics.server-
|
|
603
|
+
importer: () => import("./assets/project-analytics.server-t1bM6wAa.js")
|
|
604
604
|
} };
|
|
605
605
|
async function getServerFnById(id) {
|
|
606
606
|
const serverFnInfo = manifest[id];
|
|
@@ -1016,7 +1016,7 @@ let entriesPromise;
|
|
|
1016
1016
|
let baseManifestPromise;
|
|
1017
1017
|
let cachedFinalManifestPromise;
|
|
1018
1018
|
async function loadEntries() {
|
|
1019
|
-
const routerEntry = await import("./assets/router-
|
|
1019
|
+
const routerEntry = await import("./assets/router-kB-tCwY9.js").then((n) => n.r);
|
|
1020
1020
|
const startEntry = await import("./assets/start-HYkvq4Ni.js");
|
|
1021
1021
|
return { startEntry, routerEntry };
|
|
1022
1022
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-session-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Local observability dashboard for Claude Code sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"e2e": "playwright test",
|
|
43
43
|
"e2e:ui": "playwright test --ui",
|
|
44
44
|
"e2e:headed": "playwright test --headed",
|
|
45
|
+
"lint": "eslint src/",
|
|
45
46
|
"prepublishOnly": "npm run build"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"zod": "^3.24.0"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
59
|
+
"@eslint/js": "^9.39.2",
|
|
58
60
|
"@playwright/test": "^1.58.2",
|
|
59
61
|
"@tailwindcss/vite": "^4.1.0",
|
|
60
62
|
"@testing-library/react": "^16.3.2",
|
|
@@ -63,10 +65,14 @@
|
|
|
63
65
|
"@types/react-dom": "^19.1.0",
|
|
64
66
|
"@vitejs/plugin-react": "^5.1.0",
|
|
65
67
|
"@vitest/ui": "^4.0.18",
|
|
68
|
+
"eslint": "^9.39.2",
|
|
69
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
70
|
+
"eslint-plugin-react-refresh": "^0.5.0",
|
|
66
71
|
"happy-dom": "^20.6.1",
|
|
67
72
|
"jsdom": "^28.0.0",
|
|
68
73
|
"tailwindcss": "^4.1.0",
|
|
69
74
|
"typescript": "^5.8.0",
|
|
75
|
+
"typescript-eslint": "^8.55.0",
|
|
70
76
|
"vite": "^7.3.0",
|
|
71
77
|
"vite-tsconfig-paths": "^4.3.0",
|
|
72
78
|
"vitest": "^4.0.18"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as l,j as e,a as c,L as r,O as i}from"./main-CM5g2n-_.js";import{u as x}from"./createServerFn-B0pEGqTk.js";import{a as d}from"./sessions.queries-AUVV0tJj.js";function u(){const{privacyMode:s,togglePrivacyMode:t}=l();return e.jsxs("button",{type:"button",onClick:t,title:s?"Privacy mode on":"Privacy mode off",className:`flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs transition-colors ${s?"bg-blue-600 text-white":"bg-gray-800 text-gray-400 hover:text-gray-200"}`,children:[e.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",className:"h-3.5 w-3.5",children:[s?e.jsx("path",{fillRule:"evenodd",d:"M3.28 2.22a.75.75 0 00-1.06 1.06l14.5 14.5a.75.75 0 101.06-1.06l-1.745-1.745a10.029 10.029 0 003.3-4.38 1.651 1.651 0 000-1.185A10.004 10.004 0 009.999 3a9.956 9.956 0 00-4.744 1.194L3.28 2.22zM7.752 6.69l1.092 1.092a2.5 2.5 0 013.374 3.373l1.092 1.092a4 4 0 00-5.558-5.558z",clipRule:"evenodd"}):e.jsx("path",{d:"M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"}),s?e.jsx("path",{d:"M10.748 13.93l2.523 2.523a9.987 9.987 0 01-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186A10.007 10.007 0 012.839 6.02L6.07 9.252a4 4 0 004.678 4.678z"}):e.jsx("path",{fillRule:"evenodd",d:"M.458 10a9.996 9.996 0 019.542-6c4.258 0 7.894 2.66 9.337 6.41a1.651 1.651 0 010 1.186A10.004 10.004 0 0110 17.5c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186L.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z",clipRule:"evenodd"})]}),"Privacy"]})}function h(){const{data:s}=x(d),t=s?.length??0;return t===0?null:e.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:t})}const m=[{to:"/sessions",label:"Sessions",icon:">"},{to:"/stats",label:"Stats",icon:"#"},{to:"/settings",label:"Settings",icon:"*"}];function g({children:s}){const t=c(),o=t[t.length-1]?.pathname??"";return e.jsxs("div",{className:"flex min-h-screen",children:[e.jsxs("aside",{className:"flex w-56 shrink-0 flex-col border-r border-gray-800 bg-gray-950",children:[e.jsx("div",{className:"flex h-14 items-center border-b border-gray-800 px-4",children:e.jsxs(r,{to:"/sessions",className:"text-sm font-bold text-white",children:[e.jsx("span",{className:"text-blue-400",children:"Claude"})," Dashboard"]})}),e.jsx("nav",{className:"flex-1 p-3",children:m.map(a=>{const n=o.startsWith(a.to);return e.jsxs(r,{to:a.to,className:`flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${n?"bg-gray-800 text-white":"text-gray-400 hover:bg-gray-800/50 hover:text-gray-200"}`,children:[e.jsx("span",{className:"font-mono text-xs text-gray-500",children:a.icon}),a.label,a.to==="/sessions"&&e.jsx(h,{})]},a.to)})}),e.jsxs("div",{className:"border-t border-gray-800 p-3",children:[e.jsx(u,{}),e.jsx("p",{className:"mt-2 text-xs text-gray-600",children:"Read-only observer"})]})]}),e.jsx("main",{className:"flex-1 overflow-auto",children:e.jsx("div",{className:"mx-auto max-w-5xl px-6 py-6",children:s})})]})}function f(){return e.jsx(g,{children:e.jsx(i,{})})}export{f as component};
|