claude-session-dashboard 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +156 -14
  2. package/dist/client/assets/_dashboard-C-1YOzkf.js +1 -0
  3. package/dist/client/assets/_sessionId-C4jQeEqE.js +12 -0
  4. package/dist/client/assets/app-DNBe9Acr.css +1 -0
  5. package/dist/client/assets/{createServerFn-B0pEGqTk.js → createServerFn-B5mibSc4.js} +1 -1
  6. package/dist/client/assets/index-C83jHUdL.js +1 -0
  7. package/dist/client/assets/{main-CM5g2n-_.js → main-CkUc_xJ0.js} +7 -7
  8. package/dist/client/assets/{sessions.queries-AUVV0tJj.js → sessions.queries-C-HTNzuR.js} +1 -1
  9. package/dist/client/assets/settings-D56cUmNH.js +1 -0
  10. package/dist/client/assets/{settings.types-BRNIMHGJ.js → settings.types-l5MKKuAK.js} +1 -1
  11. package/dist/client/assets/stats-BunIdzj_.js +4 -0
  12. package/dist/client/assets/{useSessionCost-DgFKglaG.js → useSessionCost-BDldLkTA.js} +1 -1
  13. package/dist/client/favicon.svg +3 -0
  14. package/dist/server/assets/_dashboard-TUzgwLqB.js +112 -0
  15. package/dist/server/assets/{_sessionId-BZf2Aqy5.js → _sessionId-BvDwvNyA.js} +162 -145
  16. package/dist/server/assets/_tanstack-start-manifest_v-CVdzOaof.js +4 -0
  17. package/dist/server/assets/{claude-path-BdwflgZ1.js → claude-path-B2oho3NT.js} +2 -2
  18. package/dist/server/assets/{index-Do0HxVmM.js → index-Biupny11.js} +22 -21
  19. package/dist/server/assets/{project-analytics.server-BkWSd6a8.js → project-analytics.server-t1bM6wAa.js} +3 -3
  20. package/dist/server/assets/{router-ChxlsPNU.js → router-kB-tCwY9.js} +55 -29
  21. package/dist/server/assets/{session-detail.server-DLXl-Pn-.js → session-detail.server-IUw67jz-.js} +2 -2
  22. package/dist/server/assets/{session-parser-CAEXxF1D.js → session-parser-CIucKYBT.js} +67 -0
  23. package/dist/server/assets/{session-scanner-CLfls9u-.js → session-scanner-1h9TTTAV.js} +2 -2
  24. package/dist/server/assets/{sessions.server-CUhasKW2.js → sessions.server-Cpffr3MU.js} +3 -3
  25. package/dist/server/assets/{settings-ko61yfVs.js → settings-jxAA3KAS.js} +66 -20
  26. package/dist/server/assets/{stats-C9cZXTP5.js → stats-CzGBAoxT.js} +261 -24
  27. package/dist/server/assets/{stats.server-52mNk2Yw.js → stats.server-DXJiLqey.js} +62 -3
  28. package/dist/server/server.js +17 -17
  29. package/package.json +7 -1
  30. package/dist/client/assets/_dashboard-Bxw4OxIS.js +0 -1
  31. package/dist/client/assets/_sessionId-CNR4Ln7m.js +0 -12
  32. package/dist/client/assets/app-u2nTs9ny.css +0 -1
  33. package/dist/client/assets/index-BbdJ1jMA.js +0 -1
  34. package/dist/client/assets/settings-CIwZDakc.js +0 -1
  35. package/dist/client/assets/stats-CjWSMX3y.js +0 -4
  36. package/dist/server/assets/_dashboard-CAO6-qAS.js +0 -116
  37. package/dist/server/assets/_tanstack-start-manifest_v-C5hwNzs-.js +0 -4
@@ -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-ChxlsPNU.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";
@@ -46,9 +46,10 @@ function RunningTimer({ startedAt }) {
46
46
  return /* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: formatDuration(elapsed) });
47
47
  }
48
48
  function SessionCard({ session }) {
49
- const { privacyMode, anonymizePath, anonymizeProjectName } = usePrivacy();
49
+ const { privacyMode, anonymizePath, anonymizeProjectName, anonymizeBranch } = usePrivacy();
50
50
  const displayName = privacyMode ? anonymizeProjectName(session.projectName) : session.projectName;
51
- const displayCwd = session.cwd ? privacyMode ? anonymizePath(session.cwd) : session.cwd : null;
51
+ const displayCwd = session.cwd ? anonymizePath(session.cwd, session.projectName) : null;
52
+ const displayBranch = session.branch ? anonymizeBranch(session.branch) : null;
52
53
  return /* @__PURE__ */ jsxs(
53
54
  Link,
54
55
  {
@@ -63,7 +64,7 @@ function SessionCard({ session }) {
63
64
  /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-white", children: displayName }),
64
65
  /* @__PURE__ */ jsx(StatusBadge, { isActive: session.isActive })
65
66
  ] }),
66
- session.branch && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-gray-500", children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: session.branch }) })
67
+ displayBranch && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-gray-500", children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: displayBranch }) })
67
68
  ] }),
68
69
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-gray-500", children: formatRelativeTime(session.lastActiveAt) })
69
70
  ] }),
@@ -129,7 +130,7 @@ function SessionFilters({ projects, activeCount }) {
129
130
  placeholder: "Search sessions...",
130
131
  value: localSearch,
131
132
  onChange: (e) => handleSearchChange(e.target.value),
132
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
133
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500"
133
134
  }
134
135
  ),
135
136
  /* @__PURE__ */ jsx("div", { className: "flex rounded-lg border border-gray-700 text-xs", children: ["all", "active", "completed"].map((s) => /* @__PURE__ */ jsxs(
@@ -153,7 +154,7 @@ function SessionFilters({ projects, activeCount }) {
153
154
  {
154
155
  value: project,
155
156
  onChange: (e) => handleProjectChange(e.target.value),
156
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-blue-500",
157
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-brand-500",
157
158
  children: [
158
159
  /* @__PURE__ */ jsx("option", { value: "", children: "All projects" }),
159
160
  projects.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: privacyMode ? anonymizeProjectName(p) : p }, p))
@@ -167,20 +168,20 @@ const VALID_SIZES = [5, 10, 25, 50];
167
168
  function isValidSize(value) {
168
169
  return VALID_SIZES.includes(value);
169
170
  }
170
- function usePageSizePreference() {
171
- const [storedPageSize, setStoredPageSize] = useState(null);
172
- useEffect(() => {
173
- try {
174
- const raw = localStorage.getItem(STORAGE_KEY);
175
- if (raw !== null) {
176
- const parsed = Number(raw);
177
- if (isValidSize(parsed)) {
178
- setStoredPageSize(parsed);
179
- }
180
- }
181
- } catch {
171
+ function readStoredPageSize() {
172
+ if (typeof window === "undefined") return null;
173
+ try {
174
+ const raw = localStorage.getItem(STORAGE_KEY);
175
+ if (raw !== null) {
176
+ const parsed = Number(raw);
177
+ if (isValidSize(parsed)) return parsed;
182
178
  }
183
- }, []);
179
+ } catch {
180
+ }
181
+ return null;
182
+ }
183
+ function usePageSizePreference() {
184
+ const [storedPageSize, setStoredPageSize] = useState(readStoredPageSize);
184
185
  const setPageSize = useCallback((size) => {
185
186
  if (!isValidSize(size)) return;
186
187
  try {
@@ -211,7 +212,7 @@ function PaginationControls({
211
212
  {
212
213
  value: pageSize,
213
214
  onChange: (e) => onPageSizeChange(Number(e.target.value)),
214
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-2 py-1 text-xs text-gray-200 outline-none focus:border-blue-500",
215
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-2 py-1 text-xs text-gray-200 outline-none focus:border-brand-500",
215
216
  children: VALID_SIZES.map((size) => /* @__PURE__ */ jsxs("option", { value: size, children: [
216
217
  size,
217
218
  " / page"
@@ -255,7 +256,7 @@ function PaginationControls({
255
256
  "button",
256
257
  {
257
258
  onClick: () => onPageChange(item),
258
- className: `min-w-[2rem] rounded-lg px-2 py-1.5 text-xs font-mono transition-colors ${item === page ? "bg-blue-600 text-white" : "border border-gray-700 bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200"}`,
259
+ className: `min-w-[2rem] rounded-lg px-2 py-1.5 text-xs font-mono transition-colors ${item === page ? "bg-brand-600 text-white" : "border border-gray-700 bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200"}`,
259
260
  children: item
260
261
  },
261
262
  item
@@ -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";
@@ -1,27 +1,31 @@
1
1
  import { createRootRoute, Outlet, HeadContent, Scripts, createFileRoute, lazyRouteComponent, redirect, createRouter } from "@tanstack/react-router";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
4
- import { useState, useRef, useEffect, useCallback, createContext, useContext } from "react";
4
+ import { useState, useRef, useCallback, createContext, useContext } from "react";
5
5
  import { z } from "zod";
6
6
  const OS_USERNAME_PATTERN = /^(\/(?:Users|home))\/[^/]+/;
7
- function anonymizePath(path) {
7
+ function anonymizePath(path, anonymizedProjectName) {
8
+ if (anonymizedProjectName) {
9
+ return `.../${anonymizedProjectName}`;
10
+ }
8
11
  return path.replace(OS_USERNAME_PATTERN, "$1/user");
9
12
  }
10
13
  const STORAGE_KEY = "claude-dashboard:privacy-mode";
14
+ function readStoredPrivacyMode() {
15
+ if (typeof window === "undefined") return false;
16
+ try {
17
+ return localStorage.getItem(STORAGE_KEY) === "true";
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
11
22
  const PrivacyContext = createContext(null);
12
23
  function PrivacyProvider({ children }) {
13
- const [privacyMode, setPrivacyMode] = useState(false);
24
+ const [privacyMode, setPrivacyMode] = useState(readStoredPrivacyMode);
14
25
  const projectNameMapRef = useRef(/* @__PURE__ */ new Map());
15
26
  const nextIndexRef = useRef(1);
16
- useEffect(() => {
17
- try {
18
- const stored = localStorage.getItem(STORAGE_KEY);
19
- if (stored === "true") {
20
- setPrivacyMode(true);
21
- }
22
- } catch {
23
- }
24
- }, []);
27
+ const branchNameMapRef = useRef(/* @__PURE__ */ new Map());
28
+ const nextBranchIndexRef = useRef(1);
25
29
  const togglePrivacyMode = useCallback(() => {
26
30
  setPrivacyMode((prev) => {
27
31
  const next = !prev;
@@ -31,16 +35,11 @@ function PrivacyProvider({ children }) {
31
35
  }
32
36
  projectNameMapRef.current = /* @__PURE__ */ new Map();
33
37
  nextIndexRef.current = 1;
38
+ branchNameMapRef.current = /* @__PURE__ */ new Map();
39
+ nextBranchIndexRef.current = 1;
34
40
  return next;
35
41
  });
36
42
  }, []);
37
- const anonymizePath$1 = useCallback(
38
- (path) => {
39
- if (!privacyMode) return path;
40
- return anonymizePath(path);
41
- },
42
- [privacyMode]
43
- );
44
43
  const anonymizeProjectName = useCallback(
45
44
  (name) => {
46
45
  if (!privacyMode) return name;
@@ -53,14 +52,38 @@ function PrivacyProvider({ children }) {
53
52
  },
54
53
  [privacyMode]
55
54
  );
55
+ const anonymizePathFn = useCallback(
56
+ (path, projectName) => {
57
+ if (!privacyMode) return path;
58
+ if (projectName) {
59
+ const anonName = anonymizeProjectName(projectName);
60
+ return anonymizePath(path, anonName);
61
+ }
62
+ return anonymizePath(path);
63
+ },
64
+ [privacyMode, anonymizeProjectName]
65
+ );
66
+ const anonymizeBranch = useCallback(
67
+ (branch) => {
68
+ if (!privacyMode) return branch;
69
+ const existing = branchNameMapRef.current.get(branch);
70
+ if (existing) return existing;
71
+ const anonymized = `branch-${nextBranchIndexRef.current}`;
72
+ nextBranchIndexRef.current += 1;
73
+ branchNameMapRef.current.set(branch, anonymized);
74
+ return anonymized;
75
+ },
76
+ [privacyMode]
77
+ );
56
78
  return /* @__PURE__ */ jsx(
57
79
  PrivacyContext.Provider,
58
80
  {
59
81
  value: {
60
82
  privacyMode,
61
83
  togglePrivacyMode,
62
- anonymizePath: anonymizePath$1,
63
- anonymizeProjectName
84
+ anonymizePath: anonymizePathFn,
85
+ anonymizeProjectName,
86
+ anonymizeBranch
64
87
  },
65
88
  children
66
89
  }
@@ -73,7 +96,7 @@ function usePrivacy() {
73
96
  }
74
97
  return ctx;
75
98
  }
76
- const appCss = "/assets/app-u2nTs9ny.css";
99
+ const appCss = "/assets/app-DNBe9Acr.css";
77
100
  const queryClient = new QueryClient({
78
101
  defaultOptions: {
79
102
  queries: {
@@ -87,13 +110,16 @@ const Route$6 = createRootRoute({
87
110
  meta: [
88
111
  { charSet: "utf-8" },
89
112
  { name: "viewport", content: "width=device-width, initial-scale=1" },
90
- { title: "Claude Session Dashboard" }
113
+ { title: "Claude Session Dashboard" },
114
+ { name: "theme-color", content: "#141413" },
115
+ { name: "description", content: "Local observability dashboard for Claude Code sessions" }
91
116
  ],
92
117
  links: [
93
118
  {
94
119
  rel: "stylesheet",
95
120
  href: appCss
96
- }
121
+ },
122
+ { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" }
97
123
  ]
98
124
  }),
99
125
  component: RootComponent
@@ -110,7 +136,7 @@ function RootDocument({ children }) {
110
136
  ] })
111
137
  ] });
112
138
  }
113
- const $$splitComponentImporter$4 = () => import("./_dashboard-CAO6-qAS.js");
139
+ const $$splitComponentImporter$4 = () => import("./_dashboard-TUzgwLqB.js");
114
140
  const Route$5 = createFileRoute("/_dashboard")({
115
141
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
116
142
  });
@@ -119,7 +145,7 @@ const Route$4 = createFileRoute("/")({
119
145
  throw redirect({ to: "/sessions" });
120
146
  }
121
147
  });
122
- const $$splitComponentImporter$3 = () => import("./stats-C9cZXTP5.js");
148
+ const $$splitComponentImporter$3 = () => import("./stats-CzGBAoxT.js");
123
149
  const statsSearchSchema = z.object({
124
150
  tab: z.enum(["overview", "projects"]).default("overview").catch("overview")
125
151
  });
@@ -127,11 +153,11 @@ const Route$3 = createFileRoute("/_dashboard/stats")({
127
153
  validateSearch: statsSearchSchema,
128
154
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
129
155
  });
130
- const $$splitComponentImporter$2 = () => import("./settings-ko61yfVs.js");
156
+ const $$splitComponentImporter$2 = () => import("./settings-jxAA3KAS.js");
131
157
  const Route$2 = createFileRoute("/_dashboard/settings")({
132
158
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
133
159
  });
134
- const $$splitComponentImporter$1 = () => import("./index-Do0HxVmM.js");
160
+ const $$splitComponentImporter$1 = () => import("./index-Biupny11.js");
135
161
  const sessionsSearchSchema = z.object({
136
162
  page: z.number().int().min(1).default(1).catch(1),
137
163
  pageSize: z.number().int().min(5).max(100).default(5).catch(5),
@@ -143,7 +169,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
143
169
  validateSearch: sessionsSearchSchema,
144
170
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
145
171
  });
146
- const $$splitComponentImporter = () => import("./_sessionId-BZf2Aqy5.js");
172
+ const $$splitComponentImporter = () => import("./_sessionId-BvDwvNyA.js");
147
173
  const searchSchema = z.object({
148
174
  project: z.string().optional()
149
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";
@@ -1,8 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
2
+ 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-kB-tCwY9.js";
6
7
  import "./createSsrRpc-CVg2UDl0.js";
7
8
  import "../server.js";
8
9
  import "@tanstack/history";
@@ -24,7 +25,7 @@ function TierSelector({ value, onChange }) {
24
25
  {
25
26
  type: "button",
26
27
  onClick: () => onChange(tier.id),
27
- className: `rounded-lg border px-3 py-2 text-left transition-colors ${isSelected ? "border-blue-500 bg-blue-500/10 text-white" : "border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-700 hover:text-gray-300"}`,
28
+ className: `rounded-lg border px-3 py-2 text-left transition-colors ${isSelected ? "border-brand-500 bg-brand-500/10 text-white" : "border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-700 hover:text-gray-300"}`,
28
29
  children: [
29
30
  /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: tier.displayName }),
30
31
  /* @__PURE__ */ jsx("div", { className: "mt-0.5 font-mono text-[10px] text-gray-500", children: tier.monthlyUSD !== null ? `$${tier.monthlyUSD}/mo` : "Custom" })
@@ -92,28 +93,31 @@ function PricingTableEditor({ overrides, onChange }) {
92
93
  min: "0",
93
94
  value,
94
95
  onChange: (e) => handleCellChange(model.modelId, f.key, e.target.value),
95
- className: `w-20 rounded border px-2 py-1 text-right font-mono text-xs ${changed ? "border-blue-500/50 bg-blue-500/10 text-blue-400" : "border-gray-700 bg-gray-800 text-gray-300"} focus:border-blue-500 focus:outline-none`
96
+ className: `w-20 rounded border px-2 py-1 text-right font-mono text-xs ${changed ? "border-brand-500/50 bg-brand-500/10 text-brand-400" : "border-gray-700 bg-gray-800 text-gray-300"} focus:border-brand-500 focus:outline-none`
96
97
  }
97
98
  ) }, f.key);
98
99
  })
99
100
  ] }, model.modelId)) })
100
101
  ] }),
101
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-[10px] text-gray-600", children: "Prices in USD per million tokens. Overridden values shown in blue." })
102
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-[10px] text-gray-600", children: "Prices in USD per million tokens. Overridden values are highlighted." })
102
103
  ] });
103
104
  }
104
105
  function SettingsPage() {
105
106
  const { data: settings, isLoading } = useQuery(settingsQuery);
107
+ if (isLoading || !settings) {
108
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
109
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-gray-800" }),
110
+ /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded-xl bg-gray-900/50" })
111
+ ] });
112
+ }
113
+ return /* @__PURE__ */ jsx(SettingsForm, { settings });
114
+ }
115
+ function SettingsForm({ settings }) {
106
116
  const mutation = useSettingsMutation();
107
- const [tier, setTier] = useState("pro");
108
- const [overrides, setOverrides] = useState({});
117
+ const { privacyMode, togglePrivacyMode } = usePrivacy();
118
+ const [tier, setTier] = useState(settings.subscriptionTier);
119
+ const [overrides, setOverrides] = useState(settings.pricingOverrides);
109
120
  const [isDirty, setIsDirty] = useState(false);
110
- useEffect(() => {
111
- if (settings) {
112
- setTier(settings.subscriptionTier);
113
- setOverrides(settings.pricingOverrides);
114
- setIsDirty(false);
115
- }
116
- }, [settings]);
117
121
  function handleTierChange(newTier) {
118
122
  setTier(newTier);
119
123
  setIsDirty(true);
@@ -139,15 +143,57 @@ function SettingsPage() {
139
143
  }
140
144
  });
141
145
  }
142
- if (isLoading) {
143
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
144
- /* @__PURE__ */ jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-gray-800" }),
145
- /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded-xl bg-gray-900/50" })
146
- ] });
147
- }
148
146
  return /* @__PURE__ */ jsxs("div", { children: [
149
147
  /* @__PURE__ */ jsx("h1", { className: "text-xl font-bold text-white", children: "Settings" }),
150
148
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Configure your subscription tier and API pricing for cost estimation." }),
149
+ /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
150
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-300", children: "Privacy Mode" }),
151
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-gray-500", children: "Hide project names, file paths, and branch names across the dashboard. Useful when screen-sharing or recording demos." }),
152
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
153
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
154
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-300", children: "Enable privacy mode" }),
155
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
156
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: privacyMode ? "On" : "Off" }),
157
+ /* @__PURE__ */ jsx(
158
+ "button",
159
+ {
160
+ type: "button",
161
+ role: "switch",
162
+ "aria-checked": privacyMode,
163
+ onClick: togglePrivacyMode,
164
+ className: `relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors ${privacyMode ? "bg-brand-600" : "bg-gray-800"}`,
165
+ children: /* @__PURE__ */ jsx(
166
+ "span",
167
+ {
168
+ className: `inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${privacyMode ? "translate-x-[18px]" : "translate-x-[3px]"}`
169
+ }
170
+ )
171
+ }
172
+ )
173
+ ] })
174
+ ] }),
175
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 border-t border-gray-800 pt-3", children: [
176
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-gray-400", children: "What gets hidden:" }),
177
+ /* @__PURE__ */ jsxs("ul", { className: "mt-1.5 space-y-1 text-[10px] text-gray-500", children: [
178
+ /* @__PURE__ */ jsxs("li", { children: [
179
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "Project names" }),
180
+ " ",
181
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-gray-600", children: "→ project-1, project-2, ..." })
182
+ ] }),
183
+ /* @__PURE__ */ jsxs("li", { children: [
184
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "File paths" }),
185
+ " ",
186
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-gray-600", children: "→ .../project-1" })
187
+ ] }),
188
+ /* @__PURE__ */ jsxs("li", { children: [
189
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "Branch names" }),
190
+ " ",
191
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-gray-600", children: "→ branch-1, branch-2, ..." })
192
+ ] })
193
+ ] })
194
+ ] })
195
+ ] })
196
+ ] }),
151
197
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
152
198
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-300", children: "Subscription Tier" }),
153
199
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-gray-500", children: "Select your Claude subscription plan. This is informational only and does not affect cost calculations." }),
@@ -186,7 +232,7 @@ function SettingsPage() {
186
232
  type: "button",
187
233
  onClick: handleSave,
188
234
  disabled: !isDirty || mutation.isPending,
189
- className: `rounded-lg px-4 py-1.5 text-xs font-medium transition-colors ${isDirty && !mutation.isPending ? "bg-blue-600 text-white hover:bg-blue-500" : "cursor-not-allowed bg-gray-800 text-gray-500"}`,
235
+ className: `rounded-lg px-4 py-1.5 text-xs font-medium transition-colors ${isDirty && !mutation.isPending ? "bg-brand-600 text-white hover:bg-brand-500" : "cursor-not-allowed bg-gray-800 text-gray-500"}`,
190
236
  children: mutation.isPending ? "Saving..." : "Save"
191
237
  }
192
238
  )