claude-session-dashboard 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-Le0d8Pjz.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-CzD8HjLq.js → main-CM5g2n-_.js} +7 -7
- package/dist/client/assets/sessions.queries-AUVV0tJj.js +1 -0
- package/dist/client/assets/{settings-BSPc79zZ.js → settings-CIwZDakc.js} +1 -1
- package/dist/client/assets/{settings.types-B4841OLF.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--ukhquwO.js → _dashboard-CAO6-qAS.js} +22 -3
- package/dist/server/assets/{_sessionId-BwZK4Ezz.js → _sessionId-BZf2Aqy5.js} +30 -7
- package/dist/server/assets/_tanstack-start-manifest_v-C5hwNzs-.js +4 -0
- package/dist/server/assets/{claude-path-CkuljM34.js → claude-path-BdwflgZ1.js} +9 -3
- package/dist/server/assets/{format-CGmJnuhZ.js → format-DIZHV7IJ.js} +3 -3
- package/dist/server/assets/{index-D4VWrt2z.js → index-Do0HxVmM.js} +7 -40
- package/dist/server/assets/project-analytics.server-BkWSd6a8.js +61 -0
- package/dist/server/assets/{router-xTSe9UH_.js → router-ChxlsPNU.js} +12 -7
- package/dist/server/assets/{session-detail.server-azkRfON2.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-B8zbmvSM.js → sessions.server-CUhasKW2.js} +5 -89
- package/dist/server/assets/stats-C9cZXTP5.js +649 -0
- package/dist/server/assets/{stats.server-BZWxV-mC.js → stats.server-52mNk2Yw.js} +1 -1
- package/dist/server/assets/useSessionCost-CYs5UOX-.js +209 -0
- package/dist/server/server.js +19 -16
- package/package.json +5 -1
- package/dist/client/assets/_dashboard-CYwTENkn.js +0 -1
- package/dist/client/assets/_sessionId-Bwfhm_El.js +0 -12
- package/dist/client/assets/app-DhZyFob1.css +0 -1
- package/dist/client/assets/format-Bf-cSf6L.js +0 -1
- package/dist/client/assets/index-DXhX1hdS.js +0 -1
- package/dist/client/assets/stats-CDIvpOt9.js +0 -4
- package/dist/client/assets/useSessionCost-9NP6uhla.js +0 -61
- package/dist/server/assets/_tanstack-start-manifest_v-gtQY7f-T.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
|
}
|
|
@@ -17,9 +23,9 @@ function extractSessionId(filename) {
|
|
|
17
23
|
return filename.replace(/\.jsonl$/, "");
|
|
18
24
|
}
|
|
19
25
|
export {
|
|
20
|
-
|
|
26
|
+
getProjectsDir as a,
|
|
21
27
|
extractSessionId as b,
|
|
22
28
|
decodeProjectDirName as d,
|
|
23
29
|
extractProjectName as e,
|
|
24
|
-
|
|
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
|
-
|
|
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,7 +73,7 @@ function usePrivacy() {
|
|
|
73
73
|
}
|
|
74
74
|
return ctx;
|
|
75
75
|
}
|
|
76
|
-
const appCss = "/assets/app-
|
|
76
|
+
const appCss = "/assets/app-u2nTs9ny.css";
|
|
77
77
|
const queryClient = new QueryClient({
|
|
78
78
|
defaultOptions: {
|
|
79
79
|
queries: {
|
|
@@ -110,7 +110,7 @@ function RootDocument({ children }) {
|
|
|
110
110
|
] })
|
|
111
111
|
] });
|
|
112
112
|
}
|
|
113
|
-
const $$splitComponentImporter$4 = () => import("./_dashboard
|
|
113
|
+
const $$splitComponentImporter$4 = () => import("./_dashboard-CAO6-qAS.js");
|
|
114
114
|
const Route$5 = createFileRoute("/_dashboard")({
|
|
115
115
|
component: lazyRouteComponent($$splitComponentImporter$4, "component")
|
|
116
116
|
});
|
|
@@ -119,15 +119,19 @@ const Route$4 = createFileRoute("/")({
|
|
|
119
119
|
throw redirect({ to: "/sessions" });
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
|
-
const $$splitComponentImporter$3 = () => import("./stats-
|
|
122
|
+
const $$splitComponentImporter$3 = () => import("./stats-C9cZXTP5.js");
|
|
123
|
+
const statsSearchSchema = z.object({
|
|
124
|
+
tab: z.enum(["overview", "projects"]).default("overview").catch("overview")
|
|
125
|
+
});
|
|
123
126
|
const Route$3 = createFileRoute("/_dashboard/stats")({
|
|
127
|
+
validateSearch: statsSearchSchema,
|
|
124
128
|
component: lazyRouteComponent($$splitComponentImporter$3, "component")
|
|
125
129
|
});
|
|
126
130
|
const $$splitComponentImporter$2 = () => import("./settings-ko61yfVs.js");
|
|
127
131
|
const Route$2 = createFileRoute("/_dashboard/settings")({
|
|
128
132
|
component: lazyRouteComponent($$splitComponentImporter$2, "component")
|
|
129
133
|
});
|
|
130
|
-
const $$splitComponentImporter$1 = () => import("./index-
|
|
134
|
+
const $$splitComponentImporter$1 = () => import("./index-Do0HxVmM.js");
|
|
131
135
|
const sessionsSearchSchema = z.object({
|
|
132
136
|
page: z.number().int().min(1).default(1).catch(1),
|
|
133
137
|
pageSize: z.number().int().min(5).max(100).default(5).catch(5),
|
|
@@ -139,7 +143,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
|
|
|
139
143
|
validateSearch: sessionsSearchSchema,
|
|
140
144
|
component: lazyRouteComponent($$splitComponentImporter$1, "component")
|
|
141
145
|
});
|
|
142
|
-
const $$splitComponentImporter = () => import("./_sessionId-
|
|
146
|
+
const $$splitComponentImporter = () => import("./_sessionId-BZf2Aqy5.js");
|
|
143
147
|
const searchSchema = z.object({
|
|
144
148
|
project: z.string().optional()
|
|
145
149
|
});
|
|
@@ -202,8 +206,9 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
202
206
|
getRouter
|
|
203
207
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
204
208
|
export {
|
|
205
|
-
Route$
|
|
206
|
-
Route as a,
|
|
209
|
+
Route$3 as R,
|
|
210
|
+
Route$1 as a,
|
|
211
|
+
Route as b,
|
|
207
212
|
router as r,
|
|
208
213
|
usePrivacy as u
|
|
209
214
|
};
|
package/dist/server/assets/{session-detail.server-azkRfON2.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,
|
|
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 { g as getProjectsDir, d as decodeProjectDirName, e as extractProjectName, b as extractSessionId } from "./claude-path-CkuljM34.js";
|
|
6
|
-
import { a as parseSummary } from "./session-parser-CAEXxF1D.js";
|
|
3
|
+
import { s as scanAllSessions, g as getActiveSessions } from "./session-scanner-CLfls9u-.js";
|
|
7
4
|
import { c as createServerFn } from "../server.js";
|
|
5
|
+
import "node:fs";
|
|
6
|
+
import "node:path";
|
|
7
|
+
import "./claude-path-BdwflgZ1.js";
|
|
8
8
|
import "node:os";
|
|
9
|
+
import "./session-parser-CAEXxF1D.js";
|
|
9
10
|
import "node:readline";
|
|
10
11
|
import "@tanstack/history";
|
|
11
12
|
import "@tanstack/router-core/ssr/client";
|
|
@@ -18,91 +19,6 @@ import "seroval";
|
|
|
18
19
|
import "react/jsx-runtime";
|
|
19
20
|
import "@tanstack/react-router/ssr/server";
|
|
20
21
|
import "@tanstack/react-router";
|
|
21
|
-
async function scanProjects() {
|
|
22
|
-
const projectsDir = getProjectsDir();
|
|
23
|
-
let entries;
|
|
24
|
-
try {
|
|
25
|
-
entries = await fs.promises.readdir(projectsDir);
|
|
26
|
-
} catch {
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
const projects = [];
|
|
30
|
-
for (const dirName of entries) {
|
|
31
|
-
const dirPath = path.join(projectsDir, dirName);
|
|
32
|
-
const stat = await fs.promises.stat(dirPath).catch(() => null);
|
|
33
|
-
if (!stat?.isDirectory()) continue;
|
|
34
|
-
const files = await fs.promises.readdir(dirPath).catch(() => []);
|
|
35
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
36
|
-
if (sessionFiles.length === 0) continue;
|
|
37
|
-
const decodedPath = decodeProjectDirName(dirName);
|
|
38
|
-
projects.push({
|
|
39
|
-
dirName,
|
|
40
|
-
decodedPath,
|
|
41
|
-
projectName: extractProjectName(decodedPath),
|
|
42
|
-
sessionFiles
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return projects;
|
|
46
|
-
}
|
|
47
|
-
const ACTIVE_THRESHOLD_MS = 12e4;
|
|
48
|
-
async function isSessionActive(projectDirName, sessionId) {
|
|
49
|
-
const projectsDir = getProjectsDir();
|
|
50
|
-
const jsonlPath = path.join(projectsDir, projectDirName, `${sessionId}.jsonl`);
|
|
51
|
-
const lockDirPath = path.join(projectsDir, projectDirName, sessionId);
|
|
52
|
-
const stat = await fs.promises.stat(jsonlPath).catch(() => null);
|
|
53
|
-
if (!stat) return false;
|
|
54
|
-
const age = Date.now() - stat.mtimeMs;
|
|
55
|
-
if (age > ACTIVE_THRESHOLD_MS) return false;
|
|
56
|
-
const lockStat = await fs.promises.stat(lockDirPath).catch(() => null);
|
|
57
|
-
return lockStat?.isDirectory() ?? false;
|
|
58
|
-
}
|
|
59
|
-
const summaryCache = /* @__PURE__ */ new Map();
|
|
60
|
-
async function scanAllSessions() {
|
|
61
|
-
const projects = await scanProjects();
|
|
62
|
-
const summaries = [];
|
|
63
|
-
for (const project of projects) {
|
|
64
|
-
for (const file of project.sessionFiles) {
|
|
65
|
-
const sessionId = extractSessionId(file);
|
|
66
|
-
const filePath = path.join(
|
|
67
|
-
getProjectsDir(),
|
|
68
|
-
project.dirName,
|
|
69
|
-
file
|
|
70
|
-
);
|
|
71
|
-
const stat = await fs.promises.stat(filePath).catch(() => null);
|
|
72
|
-
if (!stat) continue;
|
|
73
|
-
const cached = summaryCache.get(sessionId);
|
|
74
|
-
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
75
|
-
const active = await isSessionActive(project.dirName, sessionId);
|
|
76
|
-
summaries.push({ ...cached.summary, isActive: active });
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const summary = await parseSummary(
|
|
80
|
-
filePath,
|
|
81
|
-
sessionId,
|
|
82
|
-
project.decodedPath,
|
|
83
|
-
project.projectName,
|
|
84
|
-
stat.size
|
|
85
|
-
);
|
|
86
|
-
if (summary) {
|
|
87
|
-
const active = await isSessionActive(project.dirName, sessionId);
|
|
88
|
-
summary.isActive = active;
|
|
89
|
-
summaryCache.set(sessionId, {
|
|
90
|
-
mtimeMs: stat.mtimeMs,
|
|
91
|
-
summary
|
|
92
|
-
});
|
|
93
|
-
summaries.push(summary);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
summaries.sort(
|
|
98
|
-
(a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime()
|
|
99
|
-
);
|
|
100
|
-
return summaries;
|
|
101
|
-
}
|
|
102
|
-
async function getActiveSessions() {
|
|
103
|
-
const all = await scanAllSessions();
|
|
104
|
-
return all.filter((s) => s.isActive);
|
|
105
|
-
}
|
|
106
22
|
const getSessionList_createServerFn_handler = createServerRpc({
|
|
107
23
|
id: "bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d",
|
|
108
24
|
name: "getSessionList",
|