claude-session-dashboard 0.3.0 → 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.
Files changed (28) hide show
  1. package/dist/client/assets/{_dashboard-I7m6D7BE.js → _dashboard-C-1YOzkf.js} +1 -1
  2. package/dist/client/assets/_sessionId-C4jQeEqE.js +12 -0
  3. package/dist/client/assets/app-DNBe9Acr.css +1 -0
  4. package/dist/client/assets/{createServerFn-Bn6_ISOt.js → createServerFn-B5mibSc4.js} +1 -1
  5. package/dist/client/assets/{index-BkqRvnEf.js → index-C83jHUdL.js} +1 -1
  6. package/dist/client/assets/{main-CfJIADCp.js → main-CkUc_xJ0.js} +2 -2
  7. package/dist/client/assets/{sessions.queries-CrJg4dYU.js → sessions.queries-C-HTNzuR.js} +1 -1
  8. package/dist/client/assets/{settings-C4_lsEzl.js → settings-D56cUmNH.js} +1 -1
  9. package/dist/client/assets/{settings.types-9Qf5WcRY.js → settings.types-l5MKKuAK.js} +1 -1
  10. package/dist/client/assets/{stats-_r1gmaTe.js → stats-BunIdzj_.js} +1 -1
  11. package/dist/client/assets/{useSessionCost-DPZ-ubM1.js → useSessionCost-BDldLkTA.js} +1 -1
  12. package/dist/server/assets/{_sessionId-C-XZIPqn.js → _sessionId-BvDwvNyA.js} +132 -114
  13. package/dist/server/assets/{_tanstack-start-manifest_v-B51mSkGz.js → _tanstack-start-manifest_v-CVdzOaof.js} +1 -1
  14. package/dist/server/assets/{claude-path-BdwflgZ1.js → claude-path-B2oho3NT.js} +2 -2
  15. package/dist/server/assets/{index-CKfH7HpA.js → index-Biupny11.js} +1 -1
  16. package/dist/server/assets/{project-analytics.server-BkWSd6a8.js → project-analytics.server-t1bM6wAa.js} +3 -3
  17. package/dist/server/assets/{router-Cb_hBXHI.js → router-kB-tCwY9.js} +5 -5
  18. package/dist/server/assets/{session-detail.server-DLXl-Pn-.js → session-detail.server-IUw67jz-.js} +2 -2
  19. package/dist/server/assets/{session-parser-CAEXxF1D.js → session-parser-CIucKYBT.js} +67 -0
  20. package/dist/server/assets/{session-scanner-CLfls9u-.js → session-scanner-1h9TTTAV.js} +2 -2
  21. package/dist/server/assets/{sessions.server-CUhasKW2.js → sessions.server-Cpffr3MU.js} +3 -3
  22. package/dist/server/assets/{settings-C0_KyVQQ.js → settings-jxAA3KAS.js} +1 -1
  23. package/dist/server/assets/{stats-BtgVene-.js → stats-CzGBAoxT.js} +1 -1
  24. package/dist/server/assets/{stats.server-qTOvID9-.js → stats.server-DXJiLqey.js} +1 -1
  25. package/dist/server/server.js +13 -13
  26. package/package.json +1 -1
  27. package/dist/client/assets/_sessionId-DEliIff6.js +0 -12
  28. package/dist/client/assets/app-D7yorIIh.css +0 -1
@@ -10,7 +10,7 @@ import { ResponsiveContainer, AreaChart, YAxis, Tooltip, ReferenceLine, Area } f
10
10
  import { s as settingsQuery } from "./settings.queries-DSQd324O.js";
11
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
12
  import { a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
13
- import { b as Route, u as usePrivacy } from "./router-Cb_hBXHI.js";
13
+ import { b as Route, u as usePrivacy } from "./router-kB-tCwY9.js";
14
14
  import "@tanstack/history";
15
15
  import "@tanstack/router-core/ssr/client";
16
16
  import "@tanstack/router-core";
@@ -121,7 +121,8 @@ function buildTimelineChartData(turns, agents, skills, errors) {
121
121
  durationMs: agent.durationMs ?? null,
122
122
  totalTokens: agent.totalTokens ?? null,
123
123
  totalToolUseCount: agent.totalToolUseCount ?? null,
124
- toolDots
124
+ toolDots,
125
+ skills: agent.skills?.map((s) => ({ skill: s.skill, args: s.args })) ?? []
125
126
  };
126
127
  });
127
128
  const skillMarkers = skills.map((s) => {
@@ -603,7 +604,40 @@ function AgentLaneSVG({
603
604
  ] })
604
605
  },
605
606
  `${dot.toolName}-${i}`
606
- ))
607
+ )),
608
+ lane.skills && lane.skills.length > 0 && lane.skills.map((skill, i) => {
609
+ const skillRelX = lane.startX + (lane.endX - lane.startX) / (lane.skills.length + 1) * (i + 1);
610
+ const cx = toX(skillRelX);
611
+ const cy = y + 6;
612
+ const size = 3;
613
+ return /* @__PURE__ */ jsx(
614
+ "polygon",
615
+ {
616
+ points: `${cx},${cy - size} ${cx + size},${cy} ${cx},${cy + size} ${cx - size},${cy}`,
617
+ fill: "#fbbf24",
618
+ opacity: 0.85,
619
+ className: "cursor-pointer hover:opacity-100",
620
+ onMouseEnter: (e) => onHover(
621
+ {
622
+ kind: "skill",
623
+ skill: skill.skill,
624
+ args: skill.args,
625
+ timestamp: new Date(lane.startMs).toISOString()
626
+ },
627
+ getPosition(e)
628
+ ),
629
+ onMouseLeave: () => onHover(null, { x: 0, y: 0 }),
630
+ children: /* @__PURE__ */ jsxs("title", { children: [
631
+ "/",
632
+ skill.skill,
633
+ " (",
634
+ lane.subagentType,
635
+ ")"
636
+ ] })
637
+ },
638
+ `skill-${skill.skill}-${i}`
639
+ );
640
+ })
607
641
  ] });
608
642
  }
609
643
  function TimelineTooltip({ item, position }) {
@@ -1190,9 +1224,12 @@ function ErrorPanel({ errors }) {
1190
1224
  ] }, i)) })
1191
1225
  ] });
1192
1226
  }
1193
- function AgentsSkillsPanel({
1194
- agents,
1195
- skills
1227
+ function computeAgentTokens(agent) {
1228
+ if (!agent.tokens) return void 0;
1229
+ return agent.tokens.inputTokens + agent.tokens.outputTokens;
1230
+ }
1231
+ function AgentDispatchesPanel({
1232
+ agents
1196
1233
  }) {
1197
1234
  const { data: settings } = useQuery(settingsQuery);
1198
1235
  const { agentCosts, totalAgentCost } = useMemo(() => {
@@ -1213,27 +1250,13 @@ function AgentsSkillsPanel({
1213
1250
  }
1214
1251
  return { agentCosts: costs, totalAgentCost: total };
1215
1252
  }, [settings, agents]);
1216
- if (agents.length === 0 && skills.length === 0) return null;
1217
- const agentCounts = /* @__PURE__ */ new Map();
1218
- for (const a of agents) {
1219
- agentCounts.set(a.subagentType, (agentCounts.get(a.subagentType) ?? 0) + 1);
1220
- }
1221
- const sortedAgentCounts = [...agentCounts.entries()].sort(
1222
- ([, a], [, b]) => b - a
1223
- );
1253
+ if (agents.length === 0) return null;
1224
1254
  const totalAgentTokens = agents.reduce(
1225
1255
  (sum, a) => sum + (a.totalTokens ?? computeAgentTokens(a) ?? 0),
1226
1256
  0
1227
1257
  );
1228
- const skillCounts = /* @__PURE__ */ new Map();
1229
- for (const s of skills) {
1230
- skillCounts.set(s.skill, (skillCounts.get(s.skill) ?? 0) + 1);
1231
- }
1232
- const sortedSkillCounts = [...skillCounts.entries()].sort(
1233
- ([, a], [, b]) => b - a
1234
- );
1235
1258
  return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
1236
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Agents & Skills" }),
1259
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Agent Dispatches" }),
1237
1260
  /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-gray-500", children: [
1238
1261
  agents.length,
1239
1262
  " agent dispatch",
@@ -1244,99 +1267,93 @@ function AgentsSkillsPanel({
1244
1267
  " tokens",
1245
1268
  totalAgentCost > 0 && ` · ~${formatUSD(totalAgentCost)}`,
1246
1269
  ")"
1247
- ] }),
1248
- skills.length > 0 && `, ${skills.length} skill invocation${skills.length !== 1 ? "s" : ""}`
1249
- ] }),
1250
- sortedAgentCounts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
1251
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-500", children: "Agent Types" }),
1252
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: sortedAgentCounts.map(([type, count]) => /* @__PURE__ */ jsxs(
1253
- "span",
1254
- {
1255
- className: "inline-flex items-center gap-1 rounded-md bg-indigo-500/15 px-2 py-0.5 text-xs text-indigo-300",
1256
- children: [
1257
- type,
1258
- count > 1 && /* @__PURE__ */ jsxs("span", { className: "text-indigo-400/60", children: [
1259
- "×",
1260
- count
1261
- ] })
1262
- ]
1263
- },
1264
- type
1265
- )) })
1266
- ] }),
1267
- sortedSkillCounts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
1268
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-500", children: "Skills" }),
1269
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: sortedSkillCounts.map(([skill, count]) => /* @__PURE__ */ jsxs(
1270
- "span",
1271
- {
1272
- className: "inline-flex items-center gap-1 rounded-md bg-amber-500/15 px-2 py-0.5 text-xs text-amber-300",
1273
- children: [
1274
- "/",
1275
- skill,
1276
- count > 1 && /* @__PURE__ */ jsxs("span", { className: "text-amber-400/60", children: [
1277
- "×",
1278
- count
1279
- ] })
1280
- ]
1281
- },
1282
- skill
1283
- )) })
1284
- ] }),
1285
- agents.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-1", children: [
1286
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-500", children: "Agent Dispatches" }),
1287
- agents.map((a, i) => {
1288
- const tokenCount = a.totalTokens ?? computeAgentTokens(a);
1289
- const agentCost = agentCosts.get(i);
1290
- return /* @__PURE__ */ jsxs(
1291
- "div",
1292
- {
1293
- className: "flex items-start gap-2 rounded bg-gray-950/40 px-2 py-1.5",
1294
- children: [
1295
- /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded bg-indigo-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-indigo-300", children: a.subagentType }),
1296
- a.model && /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded bg-gray-800 px-1.5 py-0.5 text-[10px] font-mono text-gray-400", children: a.model.replace(/^claude-/, "").replace(/-\d{8}$/, "") }),
1297
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-gray-400", children: a.description }),
1298
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1299
- tokenCount != null && tokenCount > 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-indigo-400/80", children: formatTokenCount(tokenCount) }),
1300
- agentCost != null && agentCost > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-emerald-400/80", children: [
1301
- "~",
1302
- formatUSD(agentCost)
1303
- ] }),
1304
- a.totalToolUseCount != null && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500", children: [
1305
- a.totalToolUseCount,
1306
- " tools"
1307
- ] }),
1308
- a.durationMs != null && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-600", children: formatDuration(a.durationMs) }),
1309
- a.timestamp && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-600", children: format(new Date(a.timestamp), "HH:mm:ss") })
1310
- ] })
1311
- ]
1312
- },
1313
- `a-${i}`
1314
- );
1315
- })
1270
+ ] })
1316
1271
  ] }),
1317
- skills.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-1", children: [
1318
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-500", children: "Skill Invocations" }),
1319
- skills.map((s, i) => /* @__PURE__ */ jsxs(
1320
- "div",
1321
- {
1322
- className: "flex items-start gap-2 rounded bg-gray-950/40 px-2 py-1.5",
1323
- children: [
1324
- /* @__PURE__ */ jsxs("span", { className: "shrink-0 rounded bg-amber-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-amber-300", children: [
1325
- "/",
1326
- s.skill
1327
- ] }),
1328
- s.args && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-mono text-gray-500", children: s.args }),
1329
- s.timestamp && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-[10px] text-gray-600", children: format(new Date(s.timestamp), "HH:mm:ss") })
1330
- ]
1331
- },
1332
- `s-${i}`
1333
- ))
1334
- ] })
1272
+ /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-1", children: agents.map((a, i) => {
1273
+ const tokenCount = a.totalTokens ?? computeAgentTokens(a);
1274
+ const agentCost = agentCosts.get(i);
1275
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded bg-gray-950/40 px-2 py-1.5", children: [
1276
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded bg-indigo-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-indigo-300", children: a.subagentType }),
1277
+ a.model && /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded bg-gray-800 px-1.5 py-0.5 text-[10px] font-mono text-gray-400", children: a.model.replace(/^claude-/, "").replace(/-\d{8}$/, "") }),
1278
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-gray-400", children: a.description }),
1279
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1280
+ tokenCount != null && tokenCount > 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-indigo-400/80", children: formatTokenCount(tokenCount) }),
1281
+ agentCost != null && agentCost > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-emerald-400/80", children: [
1282
+ "~",
1283
+ formatUSD(agentCost)
1284
+ ] }),
1285
+ a.totalToolUseCount != null && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500", children: [
1286
+ a.totalToolUseCount,
1287
+ " tools"
1288
+ ] }),
1289
+ a.durationMs != null && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-600", children: formatDuration(a.durationMs) }),
1290
+ a.timestamp && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-600", children: format(new Date(a.timestamp), "HH:mm:ss") })
1291
+ ] })
1292
+ ] }, `a-${i}`);
1293
+ }) })
1335
1294
  ] });
1336
1295
  }
1337
- function computeAgentTokens(agent) {
1338
- if (!agent.tokens) return void 0;
1339
- return agent.tokens.inputTokens + agent.tokens.outputTokens;
1296
+ function SkillInvocationsPanel({
1297
+ agents,
1298
+ skills
1299
+ }) {
1300
+ const { groups, totalCount } = useMemo(() => {
1301
+ const allSkills = [
1302
+ ...skills.map((s) => ({ ...s, agentSource: null })),
1303
+ ...agents.flatMap(
1304
+ (a) => (a.skills ?? []).map((s) => ({ ...s, agentSource: a.subagentType }))
1305
+ )
1306
+ ].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1307
+ const groupMap = /* @__PURE__ */ new Map();
1308
+ for (const s of allSkills) {
1309
+ let group = groupMap.get(s.skill);
1310
+ if (!group) {
1311
+ group = { skill: s.skill, count: 0, hasInjected: false, invocations: [] };
1312
+ groupMap.set(s.skill, group);
1313
+ }
1314
+ group.count++;
1315
+ if (s.source === "injected") group.hasInjected = true;
1316
+ group.invocations.push({
1317
+ agentSource: s.agentSource,
1318
+ timestamp: s.timestamp,
1319
+ source: s.source
1320
+ });
1321
+ }
1322
+ return { groups: Array.from(groupMap.values()), totalCount: allSkills.length };
1323
+ }, [skills, agents]);
1324
+ if (totalCount === 0) return null;
1325
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
1326
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Skill Invocations" }),
1327
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-gray-500", children: [
1328
+ groups.length,
1329
+ " unique skill",
1330
+ groups.length !== 1 ? "s" : "",
1331
+ ",",
1332
+ " ",
1333
+ totalCount,
1334
+ " invocation",
1335
+ totalCount !== 1 ? "s" : ""
1336
+ ] }),
1337
+ /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-1", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "rounded bg-gray-950/40 px-2 py-1.5", children: [
1338
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1339
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 rounded bg-amber-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-amber-300", children: [
1340
+ "/",
1341
+ group.skill
1342
+ ] }),
1343
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500", children: [
1344
+ "×",
1345
+ group.count
1346
+ ] }),
1347
+ group.hasInjected && /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded bg-gray-800/40 px-1 py-0.5 text-[9px] text-gray-500", children: "context" })
1348
+ ] }),
1349
+ /* @__PURE__ */ jsx("div", { className: "mt-1 ml-2 flex flex-wrap gap-x-1 text-[10px] text-gray-500", children: group.invocations.map((inv, j) => /* @__PURE__ */ jsxs("span", { children: [
1350
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: inv.agentSource ?? "session" }),
1351
+ ", ",
1352
+ /* @__PURE__ */ jsx("span", { className: "text-gray-600", children: format(new Date(inv.timestamp), "HH:mm:ss") }),
1353
+ j < group.invocations.length - 1 && /* @__PURE__ */ jsx("span", { className: "text-gray-700", children: "; " })
1354
+ ] }, j)) })
1355
+ ] }, group.skill)) })
1356
+ ] });
1340
1357
  }
1341
1358
  const statusConfig = {
1342
1359
  pending: { label: "Pending", bg: "bg-gray-500/20", text: "text-gray-400" },
@@ -1590,13 +1607,14 @@ function SessionDetailPage() {
1590
1607
  /* @__PURE__ */ jsx(ToolUsagePanel, { toolFrequency: detail.toolFrequency })
1591
1608
  ] }),
1592
1609
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(CostEstimationPanel, { tokensByModel: detail.tokensByModel }) }),
1593
- (detail.agents.length > 0 || detail.skills.length > 0) && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(AgentsSkillsPanel, { agents: detail.agents, skills: detail.skills }) }),
1610
+ detail.agents.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(AgentDispatchesPanel, { agents: detail.agents }) }),
1594
1611
  detail.tasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(TasksPanel, { tasks: detail.tasks }) }),
1595
1612
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(ErrorPanel, { errors: detail.errors }) }),
1596
1613
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
1597
1614
  /* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-gray-300", children: "Timeline" }),
1598
1615
  /* @__PURE__ */ jsx(TimelineEventsChart, { turns: detail.turns, agents: detail.agents, skills: detail.skills, errors: detail.errors })
1599
- ] })
1616
+ ] }),
1617
+ (detail.skills.length > 0 || detail.agents.some((a) => (a.skills?.length ?? 0) > 0)) && /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(SkillInvocationsPanel, { agents: detail.agents, skills: detail.skills }) })
1600
1618
  ] });
1601
1619
  }
1602
1620
  export {
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/__root.tsx", "children": ["/", "/_dashboard"], "preloads": ["/assets/main-CfJIADCp.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/index.tsx" }, "/_dashboard": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard.tsx", "children": ["/_dashboard/settings", "/_dashboard/stats", "/_dashboard/sessions/$sessionId", "/_dashboard/sessions/"], "assets": [], "preloads": ["/assets/_dashboard-I7m6D7BE.js", "/assets/createServerFn-Bn6_ISOt.js", "/assets/sessions.queries-CrJg4dYU.js"] }, "/_dashboard/settings": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/settings.tsx", "assets": [], "preloads": ["/assets/settings-C4_lsEzl.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/stats": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/stats.tsx", "assets": [], "preloads": ["/assets/stats-_r1gmaTe.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DPZ-ubM1.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/sessions/$sessionId": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-DEliIff6.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DPZ-ubM1.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/sessions/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/index.tsx", "assets": [], "preloads": ["/assets/index-BkqRvnEf.js", "/assets/format-Bsprb3az.js"] } }, "clientEntry": "/assets/main-CfJIADCp.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/__root.tsx", "children": ["/", "/_dashboard"], "preloads": ["/assets/main-CkUc_xJ0.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/index.tsx" }, "/_dashboard": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard.tsx", "children": ["/_dashboard/settings", "/_dashboard/stats", "/_dashboard/sessions/$sessionId", "/_dashboard/sessions/"], "assets": [], "preloads": ["/assets/_dashboard-C-1YOzkf.js", "/assets/createServerFn-B5mibSc4.js", "/assets/sessions.queries-C-HTNzuR.js"] }, "/_dashboard/settings": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/settings.tsx", "assets": [], "preloads": ["/assets/settings-D56cUmNH.js", "/assets/settings.types-l5MKKuAK.js"] }, "/_dashboard/stats": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/stats.tsx", "assets": [], "preloads": ["/assets/stats-BunIdzj_.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-BDldLkTA.js", "/assets/settings.types-l5MKKuAK.js"] }, "/_dashboard/sessions/$sessionId": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-C4jQeEqE.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-BDldLkTA.js", "/assets/settings.types-l5MKKuAK.js"] }, "/_dashboard/sessions/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/index.tsx", "assets": [], "preloads": ["/assets/index-C83jHUdL.js", "/assets/format-Bsprb3az.js"] } }, "clientEntry": "/assets/main-CkUc_xJ0.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -23,9 +23,9 @@ function extractSessionId(filename) {
23
23
  return filename.replace(/\.jsonl$/, "");
24
24
  }
25
25
  export {
26
- getProjectsDir as a,
26
+ getStatsPath as a,
27
27
  extractSessionId as b,
28
28
  decodeProjectDirName as d,
29
29
  extractProjectName as e,
30
- getStatsPath as g
30
+ getProjectsDir as g
31
31
  };
@@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query";
4
4
  import { Link, useNavigate } from "@tanstack/react-router";
5
5
  import { p as paginatedSessionListQuery, a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
6
6
  import { a as formatDuration, b as formatRelativeTime, d as formatBytes } from "./format-DIZHV7IJ.js";
7
- import { u as usePrivacy, a as Route } from "./router-Cb_hBXHI.js";
7
+ import { u as usePrivacy, a as Route } from "./router-kB-tCwY9.js";
8
8
  import "./createSsrRpc-CVg2UDl0.js";
9
9
  import "../server.js";
10
10
  import "@tanstack/history";
@@ -1,11 +1,11 @@
1
1
  import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
2
- import { s as scanAllSessions } from "./session-scanner-CLfls9u-.js";
2
+ import { s as scanAllSessions } from "./session-scanner-1h9TTTAV.js";
3
3
  import { c as createServerFn } from "../server.js";
4
4
  import "node:fs";
5
5
  import "node:path";
6
- import "./claude-path-BdwflgZ1.js";
6
+ import "./claude-path-B2oho3NT.js";
7
7
  import "node:os";
8
- import "./session-parser-CAEXxF1D.js";
8
+ import "./session-parser-CIucKYBT.js";
9
9
  import "node:readline";
10
10
  import "@tanstack/history";
11
11
  import "@tanstack/router-core/ssr/client";
@@ -96,7 +96,7 @@ function usePrivacy() {
96
96
  }
97
97
  return ctx;
98
98
  }
99
- const appCss = "/assets/app-D7yorIIh.css";
99
+ const appCss = "/assets/app-DNBe9Acr.css";
100
100
  const queryClient = new QueryClient({
101
101
  defaultOptions: {
102
102
  queries: {
@@ -145,7 +145,7 @@ const Route$4 = createFileRoute("/")({
145
145
  throw redirect({ to: "/sessions" });
146
146
  }
147
147
  });
148
- const $$splitComponentImporter$3 = () => import("./stats-BtgVene-.js");
148
+ const $$splitComponentImporter$3 = () => import("./stats-CzGBAoxT.js");
149
149
  const statsSearchSchema = z.object({
150
150
  tab: z.enum(["overview", "projects"]).default("overview").catch("overview")
151
151
  });
@@ -153,11 +153,11 @@ const Route$3 = createFileRoute("/_dashboard/stats")({
153
153
  validateSearch: statsSearchSchema,
154
154
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
155
155
  });
156
- const $$splitComponentImporter$2 = () => import("./settings-C0_KyVQQ.js");
156
+ const $$splitComponentImporter$2 = () => import("./settings-jxAA3KAS.js");
157
157
  const Route$2 = createFileRoute("/_dashboard/settings")({
158
158
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
159
159
  });
160
- const $$splitComponentImporter$1 = () => import("./index-CKfH7HpA.js");
160
+ const $$splitComponentImporter$1 = () => import("./index-Biupny11.js");
161
161
  const sessionsSearchSchema = z.object({
162
162
  page: z.number().int().min(1).default(1).catch(1),
163
163
  pageSize: z.number().int().min(5).max(100).default(5).catch(5),
@@ -169,7 +169,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
169
169
  validateSearch: sessionsSearchSchema,
170
170
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
171
171
  });
172
- const $$splitComponentImporter = () => import("./_sessionId-C-XZIPqn.js");
172
+ const $$splitComponentImporter = () => import("./_sessionId-BvDwvNyA.js");
173
173
  const searchSchema = z.object({
174
174
  project: z.string().optional()
175
175
  });
@@ -1,8 +1,8 @@
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-BdwflgZ1.js";
5
- import { p as parseDetail } from "./session-parser-CAEXxF1D.js";
4
+ import { e as extractProjectName, g as getProjectsDir, d as decodeProjectDirName } from "./claude-path-B2oho3NT.js";
5
+ import { p as parseDetail } from "./session-parser-CIucKYBT.js";
6
6
  import { c as createServerFn } from "../server.js";
7
7
  import "node:os";
8
8
  import "node:readline";
@@ -77,6 +77,7 @@ async function parseDetail(filePath, sessionId, projectPath, projectName) {
77
77
  const agentProgressTokens = /* @__PURE__ */ new Map();
78
78
  const agentProgressToolCalls = /* @__PURE__ */ new Map();
79
79
  const agentProgressModel = /* @__PURE__ */ new Map();
80
+ const agentIdByToolUseId = /* @__PURE__ */ new Map();
80
81
  const pendingTaskByToolUseId = /* @__PURE__ */ new Map();
81
82
  const taskById = /* @__PURE__ */ new Map();
82
83
  const contextSnapshots = [];
@@ -89,6 +90,10 @@ async function parseDetail(filePath, sessionId, projectPath, projectName) {
89
90
  if (msg.gitBranch && !branch) branch = msg.gitBranch;
90
91
  if (msg.type === "progress" && msg.parentToolUseID) {
91
92
  const parentId = msg.parentToolUseID;
93
+ const progressAgentId = msg.data?.agentId;
94
+ if (progressAgentId && parentId) {
95
+ agentIdByToolUseId.set(parentId, progressAgentId);
96
+ }
92
97
  const progressModel = msg.data?.message?.message?.model;
93
98
  if (progressModel && parentId) {
94
99
  agentProgressModel.set(parentId, progressModel);
@@ -313,6 +318,19 @@ async function parseDetail(filePath, sessionId, projectPath, projectName) {
313
318
  agent.model = actualModel;
314
319
  }
315
320
  }
321
+ const subagentDir = filePath.replace(/\.jsonl$/, "");
322
+ await Promise.all(
323
+ agents.map(async (agent) => {
324
+ const agentId = agentIdByToolUseId.get(agent.toolUseId);
325
+ if (!agentId) return;
326
+ agent.agentId = agentId;
327
+ const subagentFilePath = `${subagentDir}/subagents/agent-${agentId}.jsonl`;
328
+ try {
329
+ agent.skills = await parseSubagentSkills(subagentFilePath);
330
+ } catch {
331
+ }
332
+ })
333
+ );
316
334
  const modelName = modelsSet.size > 0 ? Array.from(modelsSet)[0] : "unknown";
317
335
  const contextWindow = buildContextWindowData(
318
336
  contextSnapshots,
@@ -335,6 +353,55 @@ async function parseDetail(filePath, sessionId, projectPath, projectName) {
335
353
  contextWindow
336
354
  };
337
355
  }
356
+ const COMMAND_NAME_RE = /<command-name>([^<]+)<\/command-name>/;
357
+ async function parseSubagentSkills(subagentFilePath) {
358
+ const skills = [];
359
+ const stream = fs.createReadStream(subagentFilePath, { encoding: "utf-8" });
360
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
361
+ let lineCount = 0;
362
+ const MAX_LINES_FOR_INJECTED = 20;
363
+ for await (const line of rl) {
364
+ const msg = safeParse(line);
365
+ if (!msg) continue;
366
+ lineCount++;
367
+ if (lineCount <= MAX_LINES_FOR_INJECTED && msg.type === "user" && msg.message?.content) {
368
+ const content = msg.message.content;
369
+ if (Array.isArray(content) && content.length >= 1) {
370
+ const firstBlock = content[0];
371
+ if (firstBlock.type === "text" && firstBlock.text) {
372
+ const match = COMMAND_NAME_RE.exec(firstBlock.text);
373
+ if (match) {
374
+ const skillName = match[1].trim();
375
+ if (skillName) {
376
+ skills.push({
377
+ skill: skillName,
378
+ args: null,
379
+ timestamp: msg.timestamp ?? "",
380
+ toolUseId: `injected-${skillName}-${msg.timestamp ?? lineCount}`,
381
+ source: "injected"
382
+ });
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+ if (msg.type === "assistant" && msg.message?.content) {
389
+ for (const block of msg.message.content) {
390
+ if (block.type !== "tool_use" || block.name !== "Skill") continue;
391
+ const inp = block.input;
392
+ if (!inp?.skill) continue;
393
+ skills.push({
394
+ skill: String(inp.skill),
395
+ args: inp.args ? String(inp.args) : null,
396
+ timestamp: msg.timestamp ?? "",
397
+ toolUseId: block.id ?? "",
398
+ source: "invoked"
399
+ });
400
+ }
401
+ }
402
+ }
403
+ return skills;
404
+ }
338
405
  function getContextLimit(_modelName) {
339
406
  return 2e5;
340
407
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
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";
3
+ import { g as getProjectsDir, d as decodeProjectDirName, e as extractProjectName, b as extractSessionId } from "./claude-path-B2oho3NT.js";
4
+ import { a as parseSummary } from "./session-parser-CIucKYBT.js";
5
5
  async function scanProjects() {
6
6
  const projectsDir = getProjectsDir();
7
7
  let entries;
@@ -1,12 +1,12 @@
1
1
  import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
2
2
  import { z } from "zod";
3
- import { s as scanAllSessions, g as getActiveSessions } from "./session-scanner-CLfls9u-.js";
3
+ import { s as scanAllSessions, g as getActiveSessions } from "./session-scanner-1h9TTTAV.js";
4
4
  import { c as createServerFn } from "../server.js";
5
5
  import "node:fs";
6
6
  import "node:path";
7
- import "./claude-path-BdwflgZ1.js";
7
+ import "./claude-path-B2oho3NT.js";
8
8
  import "node:os";
9
- import "./session-parser-CAEXxF1D.js";
9
+ import "./session-parser-CIucKYBT.js";
10
10
  import "node:readline";
11
11
  import "@tanstack/history";
12
12
  import "@tanstack/router-core/ssr/client";
@@ -3,7 +3,7 @@ import { useState } from "react";
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import { s as settingsQuery, u as useSettingsMutation } from "./settings.queries-DSQd324O.js";
5
5
  import { a as SUBSCRIPTION_TIERS, b as DEFAULT_PRICING, D as DEFAULT_SETTINGS } from "./settings.types-DntadCHo.js";
6
- import { u as usePrivacy } from "./router-Cb_hBXHI.js";
6
+ import { u as usePrivacy } from "./router-kB-tCwY9.js";
7
7
  import "./createSsrRpc-CVg2UDl0.js";
8
8
  import "../server.js";
9
9
  import "@tanstack/history";
@@ -8,7 +8,7 @@ import { format, addDays, getDay, parseISO, startOfISOWeek } from "date-fns";
8
8
  import { createPortal } from "react-dom";
9
9
  import { f as formatTokenCount, a as formatDuration, b as formatRelativeTime, c as formatUSD } from "./format-DIZHV7IJ.js";
10
10
  import { Link } from "@tanstack/react-router";
11
- import { u as usePrivacy, R as Route } from "./router-Cb_hBXHI.js";
11
+ import { u as usePrivacy, R as Route } from "./router-kB-tCwY9.js";
12
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";
13
13
  import "@tanstack/history";
14
14
  import "@tanstack/router-core/ssr/client";
@@ -1,6 +1,6 @@
1
1
  import { c as createServerRpc } from "./createServerRpc-Bd3B-Ah9.js";
2
2
  import * as fs from "node:fs";
3
- import { g as getStatsPath } from "./claude-path-BdwflgZ1.js";
3
+ import { a as getStatsPath } from "./claude-path-B2oho3NT.js";
4
4
  import * as path from "node:path";
5
5
  import * as os from "node:os";
6
6
  import { z } from "zod";
@@ -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-B51mSkGz.js");
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 = { "4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799": {
581
- functionName: "getStats_createServerFn_handler",
582
- importer: () => import("./assets/stats.server-qTOvID9-.js")
583
- }, "ff8a3161afdfa175e9c519e4146a56ab5bce6e80745e99cfc2191ebbb7a859bb": {
580
+ const manifest = { "ff8a3161afdfa175e9c519e4146a56ab5bce6e80745e99cfc2191ebbb7a859bb": {
584
581
  functionName: "getSessionDetail_createServerFn_handler",
585
- importer: () => import("./assets/session-detail.server-DLXl-Pn-.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")
586
586
  }, "810657681a273df5b4e58f0d8fcc6a5451598b489431b9bcaa98eea0ad815da8": {
587
587
  functionName: "getSettings_createServerFn_handler",
588
588
  importer: () => import("./assets/settings.server-6B2PvLgf.js")
589
589
  }, "3050115d92ca91ab1fd8fd698e33076328aae80dc64ca27c088eee16cebccc1a": {
590
590
  functionName: "saveSettings_createServerFn_handler",
591
591
  importer: () => import("./assets/settings.server-6B2PvLgf.js")
592
- }, "64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb": {
593
- functionName: "getProjectAnalytics_createServerFn_handler",
594
- importer: () => import("./assets/project-analytics.server-BkWSd6a8.js")
595
592
  }, "bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d": {
596
593
  functionName: "getSessionList_createServerFn_handler",
597
- importer: () => import("./assets/sessions.server-CUhasKW2.js")
594
+ importer: () => import("./assets/sessions.server-Cpffr3MU.js")
598
595
  }, "839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f": {
599
596
  functionName: "getActiveSessionList_createServerFn_handler",
600
- importer: () => import("./assets/sessions.server-CUhasKW2.js")
597
+ importer: () => import("./assets/sessions.server-Cpffr3MU.js")
601
598
  }, "a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495": {
602
599
  functionName: "getPaginatedSessions_createServerFn_handler",
603
- importer: () => import("./assets/sessions.server-CUhasKW2.js")
600
+ importer: () => import("./assets/sessions.server-Cpffr3MU.js")
601
+ }, "64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb": {
602
+ functionName: "getProjectAnalytics_createServerFn_handler",
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-Cb_hBXHI.js").then((n) => n.r);
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.0",
3
+ "version": "0.3.1",
4
4
  "description": "Local observability dashboard for Claude Code sessions",
5
5
  "type": "module",
6
6
  "license": "MIT",