claude-session-dashboard 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/_dashboard-Bxw4OxIS.js +1 -0
- package/dist/client/assets/_sessionId-CNR4Ln7m.js +12 -0
- package/dist/client/assets/app-u2nTs9ny.css +1 -0
- package/dist/client/assets/{createServerFn-Bmn60lX_.js → createServerFn-B0pEGqTk.js} +1 -1
- package/dist/client/assets/format-Bsprb3az.js +1 -0
- package/dist/client/assets/index-BbdJ1jMA.js +1 -0
- package/dist/client/assets/{main-Bssrw_E_.js → main-CM5g2n-_.js} +7 -7
- package/dist/client/assets/sessions.queries-AUVV0tJj.js +1 -0
- package/dist/client/assets/{settings-Buc0ndXk.js → settings-CIwZDakc.js} +1 -1
- package/dist/client/assets/{settings.types-4U9-Yxq3.js → settings.types-BRNIMHGJ.js} +1 -1
- package/dist/client/assets/stats-CjWSMX3y.js +4 -0
- package/dist/client/assets/useSessionCost-DgFKglaG.js +65 -0
- package/dist/server/assets/{_dashboard-CG6ub7j3.js → _dashboard-CAO6-qAS.js} +22 -3
- package/dist/server/assets/{_sessionId-CcWGJarz.js → _sessionId-BZf2Aqy5.js} +30 -7
- package/dist/server/assets/_tanstack-start-manifest_v-C5hwNzs-.js +4 -0
- package/dist/server/assets/{claude-path-on7ZBAjl.js → claude-path-BdwflgZ1.js} +7 -1
- package/dist/server/assets/{format-CGmJnuhZ.js → format-DIZHV7IJ.js} +3 -3
- package/dist/server/assets/{index-DjrI63_C.js → index-Do0HxVmM.js} +7 -40
- package/dist/server/assets/project-analytics.server-BkWSd6a8.js +61 -0
- package/dist/server/assets/{router-ByIey__S.js → router-ChxlsPNU.js} +13 -7
- package/dist/server/assets/{session-detail.server-ClCRw8BG.js → session-detail.server-DLXl-Pn-.js} +1 -1
- package/dist/server/assets/session-scanner-CLfls9u-.js +93 -0
- package/dist/server/assets/sessions.queries-B5ZBiVJy.js +42 -0
- package/dist/server/assets/{sessions.server-Cl8Ao_-2.js → sessions.server-CUhasKW2.js} +5 -89
- package/dist/server/assets/stats-C9cZXTP5.js +649 -0
- package/dist/server/assets/{stats.server-BBNHZZ4h.js → stats.server-52mNk2Yw.js} +1 -1
- package/dist/server/assets/useSessionCost-CYs5UOX-.js +209 -0
- package/dist/server/server.js +16 -13
- package/package.json +5 -1
- package/dist/client/assets/_dashboard-Oaur6UHf.js +0 -1
- package/dist/client/assets/_sessionId-CWavmGnC.js +0 -12
- package/dist/client/assets/format-Bf-cSf6L.js +0 -1
- package/dist/client/assets/index-CN4cqOcf.js +0 -1
- package/dist/client/assets/stats-BEUCPbcP.js +0 -4
- package/dist/client/assets/useSessionCost-C7ox6YwA.js +0 -61
- package/dist/server/assets/_tanstack-start-manifest_v-DidrnaMJ.js +0 -4
- package/dist/server/assets/stats-DItsFPp5.js +0 -266
- 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-
|
|
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 {
|
|
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-
|
|
12
|
-
import { a as
|
|
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__ */
|
|
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
|
-
|
|
3
|
+
function resolveClaudeDir() {
|
|
4
|
+
if (process.env.CLAUDE_HOME) {
|
|
5
|
+
return path.resolve(process.env.CLAUDE_HOME);
|
|
6
|
+
}
|
|
7
|
+
return path.join(os.homedir(), ".claude");
|
|
8
|
+
}
|
|
9
|
+
const CLAUDE_DIR = resolveClaudeDir();
|
|
4
10
|
function getProjectsDir() {
|
|
5
11
|
return path.join(CLAUDE_DIR, "projects");
|
|
6
12
|
}
|
|
@@ -48,9 +48,9 @@ function formatUSD(amount) {
|
|
|
48
48
|
return `$${amount.toFixed(2)}`;
|
|
49
49
|
}
|
|
50
50
|
export {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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 {
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
4
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
5
|
+
import { p as paginatedSessionListQuery, a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
|
|
6
|
+
import { a as formatDuration, b as formatRelativeTime, d as formatBytes } from "./format-DIZHV7IJ.js";
|
|
7
|
+
import { u as usePrivacy, a as Route } from "./router-ChxlsPNU.js";
|
|
8
|
+
import "./createSsrRpc-CVg2UDl0.js";
|
|
9
|
+
import "../server.js";
|
|
10
10
|
import "@tanstack/history";
|
|
11
11
|
import "@tanstack/router-core/ssr/client";
|
|
12
12
|
import "@tanstack/router-core";
|
|
@@ -16,41 +16,8 @@ import "h3-v2";
|
|
|
16
16
|
import "tiny-invariant";
|
|
17
17
|
import "seroval";
|
|
18
18
|
import "@tanstack/react-router/ssr/server";
|
|
19
|
+
import "zod";
|
|
19
20
|
import "date-fns";
|
|
20
|
-
const getSessionList = createServerFn({
|
|
21
|
-
method: "GET"
|
|
22
|
-
}).handler(createSsrRpc("bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d"));
|
|
23
|
-
const getActiveSessionList = createServerFn({
|
|
24
|
-
method: "GET"
|
|
25
|
-
}).handler(createSsrRpc("839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f"));
|
|
26
|
-
const paginatedSessionsInputSchema = z.object({
|
|
27
|
-
page: z.number().int().min(1),
|
|
28
|
-
pageSize: z.number().int().min(5).max(100),
|
|
29
|
-
search: z.string(),
|
|
30
|
-
status: z.enum(["all", "active", "completed"]),
|
|
31
|
-
project: z.string()
|
|
32
|
-
});
|
|
33
|
-
const getPaginatedSessions = createServerFn({
|
|
34
|
-
method: "GET"
|
|
35
|
-
}).inputValidator((input) => paginatedSessionsInputSchema.parse(input)).handler(createSsrRpc("a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495"));
|
|
36
|
-
queryOptions({
|
|
37
|
-
queryKey: ["sessions", "list"],
|
|
38
|
-
queryFn: () => getSessionList(),
|
|
39
|
-
refetchInterval: 3e4
|
|
40
|
-
});
|
|
41
|
-
const activeSessionsQuery = queryOptions({
|
|
42
|
-
queryKey: ["sessions", "active"],
|
|
43
|
-
queryFn: () => getActiveSessionList(),
|
|
44
|
-
refetchInterval: 3e3
|
|
45
|
-
});
|
|
46
|
-
function paginatedSessionListQuery(params) {
|
|
47
|
-
return queryOptions({
|
|
48
|
-
queryKey: ["sessions", "paginated", params],
|
|
49
|
-
queryFn: () => getPaginatedSessions({ data: params }),
|
|
50
|
-
placeholderData: keepPreviousData,
|
|
51
|
-
refetchInterval: 3e4
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
21
|
function StatusBadge({ isActive }) {
|
|
55
22
|
if (isActive) {
|
|
56
23
|
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-emerald-500/15 px-2.5 py-0.5 text-xs font-medium text-emerald-400", children: [
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
|
|
2
|
+
import { s as scanAllSessions } from "./session-scanner-CLfls9u-.js";
|
|
3
|
+
import { c as createServerFn } from "../server.js";
|
|
4
|
+
import "node:fs";
|
|
5
|
+
import "node:path";
|
|
6
|
+
import "./claude-path-BdwflgZ1.js";
|
|
7
|
+
import "node:os";
|
|
8
|
+
import "./session-parser-CAEXxF1D.js";
|
|
9
|
+
import "node:readline";
|
|
10
|
+
import "@tanstack/history";
|
|
11
|
+
import "@tanstack/router-core/ssr/client";
|
|
12
|
+
import "@tanstack/router-core";
|
|
13
|
+
import "node:async_hooks";
|
|
14
|
+
import "@tanstack/router-core/ssr/server";
|
|
15
|
+
import "h3-v2";
|
|
16
|
+
import "tiny-invariant";
|
|
17
|
+
import "seroval";
|
|
18
|
+
import "react/jsx-runtime";
|
|
19
|
+
import "@tanstack/react-router/ssr/server";
|
|
20
|
+
import "@tanstack/react-router";
|
|
21
|
+
function aggregateProjectAnalytics(allSessions) {
|
|
22
|
+
const projectMap = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const session of allSessions) {
|
|
24
|
+
const existing = projectMap.get(session.projectPath) ?? [];
|
|
25
|
+
existing.push(session);
|
|
26
|
+
projectMap.set(session.projectPath, existing);
|
|
27
|
+
}
|
|
28
|
+
const projects = [];
|
|
29
|
+
for (const [projectPath, sessions] of projectMap) {
|
|
30
|
+
if (sessions.length === 0) continue;
|
|
31
|
+
const firstSession = sessions[0];
|
|
32
|
+
projects.push({
|
|
33
|
+
projectPath,
|
|
34
|
+
projectName: firstSession.projectName ?? projectPath.split("/").pop() ?? "Unknown",
|
|
35
|
+
totalSessions: sessions.length,
|
|
36
|
+
activeSessions: sessions.filter((s) => s.isActive).length,
|
|
37
|
+
totalMessages: sessions.reduce((sum, s) => sum + s.messageCount, 0),
|
|
38
|
+
totalDurationMs: sessions.reduce((sum, s) => sum + s.durationMs, 0),
|
|
39
|
+
firstSessionAt: sessions.reduce((min, s) => s.startedAt < min ? s.startedAt : min, firstSession.startedAt),
|
|
40
|
+
lastSessionAt: sessions.reduce((max, s) => s.lastActiveAt > max ? s.lastActiveAt : max, firstSession.lastActiveAt)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
projects.sort((a, b) => b.lastSessionAt.localeCompare(a.lastSessionAt));
|
|
44
|
+
return {
|
|
45
|
+
projects
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const getProjectAnalytics_createServerFn_handler = createServerRpc({
|
|
49
|
+
id: "64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb",
|
|
50
|
+
name: "getProjectAnalytics",
|
|
51
|
+
filename: "src/features/project-analytics/project-analytics.server.ts"
|
|
52
|
+
}, (opts) => getProjectAnalytics.__executeServer(opts));
|
|
53
|
+
const getProjectAnalytics = createServerFn({
|
|
54
|
+
method: "GET"
|
|
55
|
+
}).handler(getProjectAnalytics_createServerFn_handler, async () => {
|
|
56
|
+
const allSessions = await scanAllSessions();
|
|
57
|
+
return aggregateProjectAnalytics(allSessions);
|
|
58
|
+
});
|
|
59
|
+
export {
|
|
60
|
+
getProjectAnalytics_createServerFn_handler
|
|
61
|
+
};
|
|
@@ -73,6 +73,7 @@ function usePrivacy() {
|
|
|
73
73
|
}
|
|
74
74
|
return ctx;
|
|
75
75
|
}
|
|
76
|
+
const appCss = "/assets/app-u2nTs9ny.css";
|
|
76
77
|
const queryClient = new QueryClient({
|
|
77
78
|
defaultOptions: {
|
|
78
79
|
queries: {
|
|
@@ -91,7 +92,7 @@ const Route$6 = createRootRoute({
|
|
|
91
92
|
links: [
|
|
92
93
|
{
|
|
93
94
|
rel: "stylesheet",
|
|
94
|
-
href:
|
|
95
|
+
href: appCss
|
|
95
96
|
}
|
|
96
97
|
]
|
|
97
98
|
}),
|
|
@@ -109,7 +110,7 @@ function RootDocument({ children }) {
|
|
|
109
110
|
] })
|
|
110
111
|
] });
|
|
111
112
|
}
|
|
112
|
-
const $$splitComponentImporter$4 = () => import("./_dashboard-
|
|
113
|
+
const $$splitComponentImporter$4 = () => import("./_dashboard-CAO6-qAS.js");
|
|
113
114
|
const Route$5 = createFileRoute("/_dashboard")({
|
|
114
115
|
component: lazyRouteComponent($$splitComponentImporter$4, "component")
|
|
115
116
|
});
|
|
@@ -118,15 +119,19 @@ const Route$4 = createFileRoute("/")({
|
|
|
118
119
|
throw redirect({ to: "/sessions" });
|
|
119
120
|
}
|
|
120
121
|
});
|
|
121
|
-
const $$splitComponentImporter$3 = () => import("./stats-
|
|
122
|
+
const $$splitComponentImporter$3 = () => import("./stats-C9cZXTP5.js");
|
|
123
|
+
const statsSearchSchema = z.object({
|
|
124
|
+
tab: z.enum(["overview", "projects"]).default("overview").catch("overview")
|
|
125
|
+
});
|
|
122
126
|
const Route$3 = createFileRoute("/_dashboard/stats")({
|
|
127
|
+
validateSearch: statsSearchSchema,
|
|
123
128
|
component: lazyRouteComponent($$splitComponentImporter$3, "component")
|
|
124
129
|
});
|
|
125
130
|
const $$splitComponentImporter$2 = () => import("./settings-ko61yfVs.js");
|
|
126
131
|
const Route$2 = createFileRoute("/_dashboard/settings")({
|
|
127
132
|
component: lazyRouteComponent($$splitComponentImporter$2, "component")
|
|
128
133
|
});
|
|
129
|
-
const $$splitComponentImporter$1 = () => import("./index-
|
|
134
|
+
const $$splitComponentImporter$1 = () => import("./index-Do0HxVmM.js");
|
|
130
135
|
const sessionsSearchSchema = z.object({
|
|
131
136
|
page: z.number().int().min(1).default(1).catch(1),
|
|
132
137
|
pageSize: z.number().int().min(5).max(100).default(5).catch(5),
|
|
@@ -138,7 +143,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
|
|
|
138
143
|
validateSearch: sessionsSearchSchema,
|
|
139
144
|
component: lazyRouteComponent($$splitComponentImporter$1, "component")
|
|
140
145
|
});
|
|
141
|
-
const $$splitComponentImporter = () => import("./_sessionId-
|
|
146
|
+
const $$splitComponentImporter = () => import("./_sessionId-BZf2Aqy5.js");
|
|
142
147
|
const searchSchema = z.object({
|
|
143
148
|
project: z.string().optional()
|
|
144
149
|
});
|
|
@@ -201,8 +206,9 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
201
206
|
getRouter
|
|
202
207
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
203
208
|
export {
|
|
204
|
-
Route$
|
|
205
|
-
Route as a,
|
|
209
|
+
Route$3 as R,
|
|
210
|
+
Route$1 as a,
|
|
211
|
+
Route as b,
|
|
206
212
|
router as r,
|
|
207
213
|
usePrivacy as u
|
|
208
214
|
};
|
package/dist/server/assets/{session-detail.server-ClCRw8BG.js → session-detail.server-DLXl-Pn-.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
|
-
import { e as extractProjectName, a as getProjectsDir, d as decodeProjectDirName } from "./claude-path-
|
|
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
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { a as getProjectsDir, d as decodeProjectDirName, e as extractProjectName, b as extractSessionId } from "./claude-path-on7ZBAjl.js";
|
|
6
|
-
import { a as parseSummary } from "./session-parser-CAEXxF1D.js";
|
|
3
|
+
import { s as scanAllSessions, g as getActiveSessions } from "./session-scanner-CLfls9u-.js";
|
|
7
4
|
import { c as createServerFn } from "../server.js";
|
|
5
|
+
import "node:fs";
|
|
6
|
+
import "node:path";
|
|
7
|
+
import "./claude-path-BdwflgZ1.js";
|
|
8
8
|
import "node:os";
|
|
9
|
+
import "./session-parser-CAEXxF1D.js";
|
|
9
10
|
import "node:readline";
|
|
10
11
|
import "@tanstack/history";
|
|
11
12
|
import "@tanstack/router-core/ssr/client";
|
|
@@ -18,91 +19,6 @@ import "seroval";
|
|
|
18
19
|
import "react/jsx-runtime";
|
|
19
20
|
import "@tanstack/react-router/ssr/server";
|
|
20
21
|
import "@tanstack/react-router";
|
|
21
|
-
async function scanProjects() {
|
|
22
|
-
const projectsDir = getProjectsDir();
|
|
23
|
-
let entries;
|
|
24
|
-
try {
|
|
25
|
-
entries = await fs.promises.readdir(projectsDir);
|
|
26
|
-
} catch {
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
const projects = [];
|
|
30
|
-
for (const dirName of entries) {
|
|
31
|
-
const dirPath = path.join(projectsDir, dirName);
|
|
32
|
-
const stat = await fs.promises.stat(dirPath).catch(() => null);
|
|
33
|
-
if (!stat?.isDirectory()) continue;
|
|
34
|
-
const files = await fs.promises.readdir(dirPath).catch(() => []);
|
|
35
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
36
|
-
if (sessionFiles.length === 0) continue;
|
|
37
|
-
const decodedPath = decodeProjectDirName(dirName);
|
|
38
|
-
projects.push({
|
|
39
|
-
dirName,
|
|
40
|
-
decodedPath,
|
|
41
|
-
projectName: extractProjectName(decodedPath),
|
|
42
|
-
sessionFiles
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return projects;
|
|
46
|
-
}
|
|
47
|
-
const ACTIVE_THRESHOLD_MS = 12e4;
|
|
48
|
-
async function isSessionActive(projectDirName, sessionId) {
|
|
49
|
-
const projectsDir = getProjectsDir();
|
|
50
|
-
const jsonlPath = path.join(projectsDir, projectDirName, `${sessionId}.jsonl`);
|
|
51
|
-
const lockDirPath = path.join(projectsDir, projectDirName, sessionId);
|
|
52
|
-
const stat = await fs.promises.stat(jsonlPath).catch(() => null);
|
|
53
|
-
if (!stat) return false;
|
|
54
|
-
const age = Date.now() - stat.mtimeMs;
|
|
55
|
-
if (age > ACTIVE_THRESHOLD_MS) return false;
|
|
56
|
-
const lockStat = await fs.promises.stat(lockDirPath).catch(() => null);
|
|
57
|
-
return lockStat?.isDirectory() ?? false;
|
|
58
|
-
}
|
|
59
|
-
const summaryCache = /* @__PURE__ */ new Map();
|
|
60
|
-
async function scanAllSessions() {
|
|
61
|
-
const projects = await scanProjects();
|
|
62
|
-
const summaries = [];
|
|
63
|
-
for (const project of projects) {
|
|
64
|
-
for (const file of project.sessionFiles) {
|
|
65
|
-
const sessionId = extractSessionId(file);
|
|
66
|
-
const filePath = path.join(
|
|
67
|
-
getProjectsDir(),
|
|
68
|
-
project.dirName,
|
|
69
|
-
file
|
|
70
|
-
);
|
|
71
|
-
const stat = await fs.promises.stat(filePath).catch(() => null);
|
|
72
|
-
if (!stat) continue;
|
|
73
|
-
const cached = summaryCache.get(sessionId);
|
|
74
|
-
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
75
|
-
const active = await isSessionActive(project.dirName, sessionId);
|
|
76
|
-
summaries.push({ ...cached.summary, isActive: active });
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const summary = await parseSummary(
|
|
80
|
-
filePath,
|
|
81
|
-
sessionId,
|
|
82
|
-
project.decodedPath,
|
|
83
|
-
project.projectName,
|
|
84
|
-
stat.size
|
|
85
|
-
);
|
|
86
|
-
if (summary) {
|
|
87
|
-
const active = await isSessionActive(project.dirName, sessionId);
|
|
88
|
-
summary.isActive = active;
|
|
89
|
-
summaryCache.set(sessionId, {
|
|
90
|
-
mtimeMs: stat.mtimeMs,
|
|
91
|
-
summary
|
|
92
|
-
});
|
|
93
|
-
summaries.push(summary);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
summaries.sort(
|
|
98
|
-
(a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime()
|
|
99
|
-
);
|
|
100
|
-
return summaries;
|
|
101
|
-
}
|
|
102
|
-
async function getActiveSessions() {
|
|
103
|
-
const all = await scanAllSessions();
|
|
104
|
-
return all.filter((s) => s.isActive);
|
|
105
|
-
}
|
|
106
22
|
const getSessionList_createServerFn_handler = createServerRpc({
|
|
107
23
|
id: "bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d",
|
|
108
24
|
name: "getSessionList",
|