agenttop 0.3.0 → 0.6.0

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/index.js CHANGED
@@ -2,28 +2,350 @@
2
2
  import {
3
3
  SecurityEngine,
4
4
  Watcher,
5
+ archiveSession,
5
6
  clearNickname,
7
+ deleteSessionFiles,
6
8
  discoverSessions,
9
+ getArchived,
7
10
  getNicknames,
8
11
  isFirstRun,
9
12
  loadConfig,
13
+ purgeExpiredArchives,
10
14
  resolveAlertLogPath,
11
15
  rotateLogFile,
12
16
  saveConfig,
13
17
  setNickname,
14
- startMcpServer
15
- } from "./chunk-4I4UZNKS.js";
18
+ startMcpServer,
19
+ unarchiveSession
20
+ } from "./chunk-6N34AD6O.js";
16
21
 
17
22
  // src/index.tsx
18
23
  import { readFileSync as readFileSync4 } from "fs";
19
24
  import { join as join4, dirname as dirname4 } from "path";
20
25
  import { fileURLToPath as fileURLToPath3 } from "url";
21
- import React9 from "react";
26
+ import React14 from "react";
22
27
  import { render } from "ink";
23
28
 
24
29
  // src/ui/App.tsx
25
- import { useState as useState7, useEffect as useEffect5, useCallback as useCallback3 } from "react";
26
- import { Box as Box8, Text as Text8, useApp, useInput as useInput2, useStdout } from "ink";
30
+ import { useState as useState11, useEffect as useEffect8, useCallback as useCallback4 } from "react";
31
+ import { Box as Box13, Text as Text12, useApp, useStdout as useStdout3 } from "ink";
32
+
33
+ // src/config/themes.ts
34
+ var COLOR_KEYS = [
35
+ "primary",
36
+ "secondary",
37
+ "accent",
38
+ "warning",
39
+ "error",
40
+ "critical",
41
+ "muted",
42
+ "text",
43
+ "bright",
44
+ "border",
45
+ "selected",
46
+ "header"
47
+ ];
48
+ var TOOL_COLOR_KEYS = [
49
+ "Bash",
50
+ "Read",
51
+ "Write",
52
+ "Edit",
53
+ "Grep",
54
+ "Glob",
55
+ "Task",
56
+ "WebFetch",
57
+ "WebSearch"
58
+ ];
59
+ var deriveToolColors = (c) => ({
60
+ Bash: c.error,
61
+ Read: c.secondary,
62
+ Write: c.accent,
63
+ Edit: c.accent,
64
+ Grep: c.primary,
65
+ Glob: c.primary,
66
+ Task: c.warning,
67
+ WebFetch: c.warning,
68
+ WebSearch: c.warning
69
+ });
70
+ var fromTuple = ([
71
+ name,
72
+ primary,
73
+ secondary,
74
+ accent,
75
+ warning,
76
+ error,
77
+ critical,
78
+ muted,
79
+ text,
80
+ bright,
81
+ border,
82
+ selected,
83
+ header
84
+ ]) => ({
85
+ name,
86
+ builtin: true,
87
+ colors: { primary, secondary, accent, warning, error, critical, muted, text, bright, border, selected, header },
88
+ toolColors: deriveToolColors({
89
+ primary,
90
+ secondary,
91
+ accent,
92
+ warning,
93
+ error,
94
+ critical,
95
+ muted,
96
+ text,
97
+ bright,
98
+ border,
99
+ selected,
100
+ header
101
+ })
102
+ });
103
+ var PRESETS = [
104
+ [
105
+ "one-dark",
106
+ "#61AFEF",
107
+ "#98C379",
108
+ "#C678DD",
109
+ "#E5C07B",
110
+ "#E06C75",
111
+ "#FF0000",
112
+ "#5C6370",
113
+ "#ABB2BF",
114
+ "#FFFFFF",
115
+ "#3E4451",
116
+ "#2C313A",
117
+ "#61AFEF"
118
+ ],
119
+ [
120
+ "dracula",
121
+ "#BD93F9",
122
+ "#50FA7B",
123
+ "#FF79C6",
124
+ "#F1FA8C",
125
+ "#FF5555",
126
+ "#FF0000",
127
+ "#6272A4",
128
+ "#F8F8F2",
129
+ "#FFFFFF",
130
+ "#44475A",
131
+ "#383A59",
132
+ "#BD93F9"
133
+ ],
134
+ [
135
+ "monokai-pro",
136
+ "#78DCE8",
137
+ "#A9DC76",
138
+ "#AB9DF2",
139
+ "#FFD866",
140
+ "#FF6188",
141
+ "#FF0000",
142
+ "#727072",
143
+ "#FCFCFA",
144
+ "#FFFFFF",
145
+ "#403E41",
146
+ "#2D2A2E",
147
+ "#78DCE8"
148
+ ],
149
+ [
150
+ "solarized-dark",
151
+ "#268BD2",
152
+ "#859900",
153
+ "#D33682",
154
+ "#B58900",
155
+ "#DC322F",
156
+ "#FF0000",
157
+ "#586E75",
158
+ "#839496",
159
+ "#FDF6E3",
160
+ "#073642",
161
+ "#002B36",
162
+ "#268BD2"
163
+ ],
164
+ [
165
+ "solarized-light",
166
+ "#268BD2",
167
+ "#859900",
168
+ "#D33682",
169
+ "#B58900",
170
+ "#DC322F",
171
+ "#FF0000",
172
+ "#93A1A1",
173
+ "#657B83",
174
+ "#002B36",
175
+ "#EEE8D5",
176
+ "#FDF6E3",
177
+ "#268BD2"
178
+ ],
179
+ [
180
+ "nord",
181
+ "#88C0D0",
182
+ "#A3BE8C",
183
+ "#B48EAD",
184
+ "#EBCB8B",
185
+ "#BF616A",
186
+ "#FF0000",
187
+ "#4C566A",
188
+ "#D8DEE9",
189
+ "#ECEFF4",
190
+ "#3B4252",
191
+ "#2E3440",
192
+ "#88C0D0"
193
+ ],
194
+ [
195
+ "gruvbox-dark",
196
+ "#83A598",
197
+ "#B8BB26",
198
+ "#D3869B",
199
+ "#FABD2F",
200
+ "#FB4934",
201
+ "#FF0000",
202
+ "#928374",
203
+ "#EBDBB2",
204
+ "#FBF1C7",
205
+ "#3C3836",
206
+ "#282828",
207
+ "#83A598"
208
+ ],
209
+ [
210
+ "tokyo-night",
211
+ "#7AA2F7",
212
+ "#9ECE6A",
213
+ "#BB9AF7",
214
+ "#E0AF68",
215
+ "#F7768E",
216
+ "#FF0000",
217
+ "#565F89",
218
+ "#A9B1D6",
219
+ "#C0CAF5",
220
+ "#292E42",
221
+ "#1A1B26",
222
+ "#7AA2F7"
223
+ ],
224
+ [
225
+ "catppuccin-mocha",
226
+ "#89B4FA",
227
+ "#A6E3A1",
228
+ "#CBA6F7",
229
+ "#F9E2AF",
230
+ "#F38BA8",
231
+ "#FF0000",
232
+ "#6C7086",
233
+ "#CDD6F4",
234
+ "#FFFFFF",
235
+ "#313244",
236
+ "#1E1E2E",
237
+ "#89B4FA"
238
+ ],
239
+ [
240
+ "catppuccin-latte",
241
+ "#1E66F5",
242
+ "#40A02B",
243
+ "#8839EF",
244
+ "#DF8E1D",
245
+ "#D20F39",
246
+ "#FF0000",
247
+ "#9CA0B0",
248
+ "#4C4F69",
249
+ "#11111B",
250
+ "#E6E9EF",
251
+ "#EFF1F5",
252
+ "#1E66F5"
253
+ ],
254
+ [
255
+ "rose-pine",
256
+ "#9CCFD8",
257
+ "#31748F",
258
+ "#C4A7E7",
259
+ "#F6C177",
260
+ "#EB6F92",
261
+ "#FF0000",
262
+ "#6E6A86",
263
+ "#E0DEF4",
264
+ "#E0DEF4",
265
+ "#26233A",
266
+ "#191724",
267
+ "#9CCFD8"
268
+ ],
269
+ [
270
+ "rose-pine-moon",
271
+ "#9CCFD8",
272
+ "#3E8FB0",
273
+ "#C4A7E7",
274
+ "#F6C177",
275
+ "#EB6F92",
276
+ "#FF0000",
277
+ "#6E6A86",
278
+ "#E0DEF4",
279
+ "#E0DEF4",
280
+ "#2A273F",
281
+ "#232136",
282
+ "#9CCFD8"
283
+ ],
284
+ [
285
+ "pastel-dark",
286
+ "#89CFF0",
287
+ "#77DD77",
288
+ "#FDCFE8",
289
+ "#FFD580",
290
+ "#FF6961",
291
+ "#FF0000",
292
+ "#7B8794",
293
+ "#D4D4D4",
294
+ "#FFFFFF",
295
+ "#3A3A4A",
296
+ "#2B2B3A",
297
+ "#89CFF0"
298
+ ],
299
+ [
300
+ "kanagawa",
301
+ "#7E9CD8",
302
+ "#76946A",
303
+ "#957FB8",
304
+ "#E6C384",
305
+ "#C34043",
306
+ "#FF0000",
307
+ "#727169",
308
+ "#DCD7BA",
309
+ "#FFFFFF",
310
+ "#2A2A37",
311
+ "#1F1F28",
312
+ "#7E9CD8"
313
+ ],
314
+ [
315
+ "everforest",
316
+ "#7FBBB3",
317
+ "#A7C080",
318
+ "#D699B6",
319
+ "#DBBC7F",
320
+ "#E67E80",
321
+ "#FF0000",
322
+ "#859289",
323
+ "#D3C6AA",
324
+ "#FFFFFF",
325
+ "#374145",
326
+ "#2D353B",
327
+ "#7FBBB3"
328
+ ]
329
+ ];
330
+ var BUILTIN_THEMES = PRESETS.map(fromTuple);
331
+ var getDefaultTheme = () => BUILTIN_THEMES[0];
332
+ var getAllThemes = (customThemes) => [
333
+ ...BUILTIN_THEMES,
334
+ ...Object.values(customThemes).map((t) => ({ ...t, builtin: false }))
335
+ ];
336
+ var resolveTheme = (name, customThemes) => {
337
+ const builtin = BUILTIN_THEMES.find((t) => t.name === name);
338
+ if (builtin) return builtin;
339
+ const custom = customThemes[name];
340
+ if (custom) return { ...custom, builtin: false };
341
+ return getDefaultTheme();
342
+ };
343
+ var deriveSeverityColors = (c) => ({
344
+ info: c.muted,
345
+ warn: c.warning,
346
+ high: c.error,
347
+ critical: c.critical
348
+ });
27
349
 
28
350
  // src/hooks/installer.ts
29
351
  import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, chmodSync } from "fs";
@@ -229,6 +551,11 @@ var toolColors = {
229
551
  WebSearch: colors.warning
230
552
  };
231
553
  var getToolColor = (toolName) => toolColors[toolName] || colors.text;
554
+ var applyTheme = (theme) => {
555
+ Object.assign(colors, theme.colors);
556
+ Object.assign(toolColors, theme.toolColors);
557
+ Object.assign(severityColors, deriveSeverityColors(theme.colors));
558
+ };
232
559
 
233
560
  // src/ui/components/StatusBar.tsx
234
561
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -273,77 +600,95 @@ var formatModel = (model) => {
273
600
  if (model.includes("haiku")) return "haiku";
274
601
  return model.slice(0, 8);
275
602
  };
276
- var formatProject = (project) => {
603
+ var formatProject = (project, max) => {
277
604
  const parts = project.split("/");
278
605
  const last = parts[parts.length - 1] || project;
279
- return last.length > 18 ? last.slice(0, 17) + "\u2026" : last;
606
+ return last.length > max ? last.slice(0, max - 1) + "\u2026" : last;
280
607
  };
281
608
  var formatTokens = (n) => {
282
609
  if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
283
610
  if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
284
611
  return String(n);
285
612
  };
286
- var SessionList = React2.memo(({ sessions, selectedIndex, focused, filter }) => {
287
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: 28, borderStyle: "single", borderColor: focused ? colors.primary : colors.border, children: [
288
- /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
289
- /* @__PURE__ */ jsx2(Text2, { color: colors.header, bold: true, children: "SESSIONS" }),
290
- filter && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
291
- " [",
292
- filter,
293
- "]"
294
- ] })
295
- ] }),
296
- sessions.length === 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: colors.muted, italic: true, children: filter ? "No matching sessions" : "No active sessions" }) }),
297
- sessions.map((session, i) => {
298
- const isSelected = i === selectedIndex;
299
- const indicator = isSelected ? ">" : " ";
300
- const displayName = session.nickname || session.slug;
301
- const totalIn = session.usage.inputTokens + session.usage.cacheReadTokens;
302
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
303
- /* @__PURE__ */ jsxs2(
304
- Text2,
305
- {
306
- color: isSelected ? colors.bright : colors.text,
307
- bold: isSelected,
308
- backgroundColor: isSelected ? colors.selected : void 0,
309
- children: [
310
- indicator,
311
- " ",
312
- displayName
313
- ]
314
- }
315
- ),
316
- session.nickname && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
317
- " ",
318
- session.slug
319
- ] }),
320
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
321
- " ",
322
- formatProject(session.project),
323
- " | ",
324
- formatModel(session.model)
325
- ] }),
326
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
327
- " ",
328
- "CPU ",
329
- session.cpu,
330
- "% | ",
331
- session.memMB,
332
- "MB | ",
333
- session.agentCount,
334
- " ag"
335
- ] }),
336
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
337
- " ",
338
- formatTokens(totalIn),
339
- " in | ",
340
- formatTokens(session.usage.outputTokens),
341
- " out"
342
- ] })
343
- ] }, session.sessionId);
344
- })
345
- ] });
346
- });
613
+ var truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
614
+ var SIDEBAR_WIDTH = 28;
615
+ var INNER_WIDTH = SIDEBAR_WIDTH - 4;
616
+ var SessionList = React2.memo(
617
+ ({ sessions, selectedIndex, focused, filter, viewingArchive }) => {
618
+ const divider = "-".repeat(INNER_WIDTH);
619
+ return /* @__PURE__ */ jsxs2(
620
+ Box2,
621
+ {
622
+ flexDirection: "column",
623
+ width: SIDEBAR_WIDTH,
624
+ borderStyle: "single",
625
+ borderColor: focused ? colors.primary : colors.border,
626
+ overflow: "hidden",
627
+ children: [
628
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
629
+ /* @__PURE__ */ jsx2(Text2, { color: viewingArchive ? colors.warning : colors.header, bold: true, children: viewingArchive ? "ARCHIVE" : "SESSIONS" }),
630
+ filter && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
631
+ " [",
632
+ truncate(filter, 10),
633
+ "]"
634
+ ] })
635
+ ] }),
636
+ sessions.length === 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: colors.muted, italic: true, children: filter ? "No matches" : viewingArchive ? "No archived sessions" : "No sessions" }) }),
637
+ sessions.map((session, i) => {
638
+ const isSelected = i === selectedIndex;
639
+ const indicator = isSelected ? ">" : " ";
640
+ const nameMaxLen = INNER_WIDTH - 2;
641
+ const displayName = truncate(session.nickname || session.slug, nameMaxLen);
642
+ const totalIn = session.usage.inputTokens + session.usage.cacheReadTokens;
643
+ const proj = formatProject(session.project, 12);
644
+ const model = formatModel(session.model);
645
+ const isActive = session.pid !== null;
646
+ const nameColor = isSelected ? colors.bright : isActive ? colors.secondary : colors.error;
647
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
648
+ i > 0 && /* @__PURE__ */ jsx2(Text2, { color: colors.border, wrap: "truncate", children: divider }),
649
+ /* @__PURE__ */ jsxs2(
650
+ Text2,
651
+ {
652
+ color: nameColor,
653
+ bold: isSelected,
654
+ backgroundColor: isSelected ? colors.selected : void 0,
655
+ wrap: "truncate",
656
+ children: [
657
+ indicator,
658
+ " ",
659
+ displayName
660
+ ]
661
+ }
662
+ ),
663
+ session.nickname && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
664
+ " ",
665
+ truncate(session.slug, nameMaxLen)
666
+ ] }),
667
+ /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
668
+ " ",
669
+ proj,
670
+ " ",
671
+ model,
672
+ " ",
673
+ session.agentCount,
674
+ "ag"
675
+ ] }),
676
+ /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
677
+ " ",
678
+ formatTokens(totalIn),
679
+ "in ",
680
+ formatTokens(session.usage.outputTokens),
681
+ "out ",
682
+ session.cpu,
683
+ "%"
684
+ ] })
685
+ ] }, session.sessionId);
686
+ })
687
+ ]
688
+ }
689
+ );
690
+ }
691
+ );
347
692
 
348
693
  // src/ui/components/ActivityFeed.tsx
349
694
  import React3 from "react";
@@ -376,7 +721,7 @@ var summarizeInput = (call) => {
376
721
  }
377
722
  };
378
723
  var ActivityFeed = React3.memo(
379
- ({ events, sessionSlug, focused, height, scrollOffset }) => {
724
+ ({ events, sessionSlug, focused, height, scrollOffset, filter }) => {
380
725
  const viewportRows = height - 2;
381
726
  const totalEvents = events.length;
382
727
  const start = Math.max(0, totalEvents - viewportRows - scrollOffset);
@@ -400,6 +745,11 @@ var ActivityFeed = React3.memo(
400
745
  " (",
401
746
  sessionSlug,
402
747
  ")"
748
+ ] }),
749
+ filter && /* @__PURE__ */ jsxs3(Text3, { color: colors.muted, children: [
750
+ " [",
751
+ filter.length > 10 ? filter.slice(0, 9) + "\u2026" : filter,
752
+ "]"
403
753
  ] })
404
754
  ] }),
405
755
  focused && canScroll && !isAtBottom && /* @__PURE__ */ jsxs3(Text3, { color: colors.muted, children: [
@@ -650,52 +1000,807 @@ var SetupModal = React6.memo(({ steps, onComplete }) => {
650
1000
  // src/ui/components/FooterBar.tsx
651
1001
  import React7 from "react";
652
1002
  import { Box as Box7, Text as Text7 } from "ink";
653
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1003
+ import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
654
1004
  var label = (key) => {
655
1005
  if (key === "tab") return "tab";
656
1006
  if (key === "shift+tab") return "S-tab";
657
1007
  if (key === "enter") return "enter";
658
1008
  return key;
659
1009
  };
660
- var FooterBar = React7.memo(({ keybindings, updateStatus }) => /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
661
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
662
- label(keybindings.quit),
663
- ":quit"
664
- ] }) }),
665
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
666
- label(keybindings.navDown),
667
- "/",
668
- label(keybindings.navUp),
669
- ":nav"
670
- ] }) }),
671
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
672
- label(keybindings.panelNext),
673
- ":panel"
674
- ] }) }),
675
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
676
- label(keybindings.filter),
677
- ":filter"
678
- ] }) }),
679
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
680
- label(keybindings.nickname),
681
- ":name"
682
- ] }) }),
683
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
684
- label(keybindings.detail),
685
- ":detail"
686
- ] }) }),
687
- updateStatus && /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsx7(Text7, { color: colors.secondary, children: updateStatus }) })
688
- ] }));
1010
+ var FooterBar = React7.memo(
1011
+ ({ keybindings, updateStatus, viewingArchive, splitMode }) => /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1012
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1013
+ label(keybindings.quit),
1014
+ ":quit"
1015
+ ] }) }),
1016
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1017
+ label(keybindings.navDown),
1018
+ "/",
1019
+ label(keybindings.navUp),
1020
+ ":nav"
1021
+ ] }) }),
1022
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1023
+ label(keybindings.panelNext),
1024
+ ":panel"
1025
+ ] }) }),
1026
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1027
+ label(keybindings.filter),
1028
+ ":filter"
1029
+ ] }) }),
1030
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1031
+ label(keybindings.nickname),
1032
+ ":name"
1033
+ ] }) }),
1034
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1035
+ label(keybindings.detail),
1036
+ ":detail"
1037
+ ] }) }),
1038
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1039
+ label(keybindings.split),
1040
+ ":",
1041
+ splitMode ? "unsplit" : "split"
1042
+ ] }) }),
1043
+ splitMode && /* @__PURE__ */ jsxs7(Fragment, { children: [
1044
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1045
+ label(keybindings.pinLeft),
1046
+ "/",
1047
+ label(keybindings.pinRight),
1048
+ ":pin"
1049
+ ] }) }),
1050
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1051
+ label(keybindings.swapPanels),
1052
+ ":swap"
1053
+ ] }) })
1054
+ ] }),
1055
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1056
+ label(keybindings.archive),
1057
+ ":",
1058
+ viewingArchive ? "restore" : "archive"
1059
+ ] }) }),
1060
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1061
+ label(keybindings.delete),
1062
+ ":delete"
1063
+ ] }) }),
1064
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1065
+ label(keybindings.viewArchive),
1066
+ ":archived"
1067
+ ] }) }),
1068
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, children: [
1069
+ label(keybindings.settings),
1070
+ ":settings"
1071
+ ] }) }),
1072
+ updateStatus && /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsx7(Text7, { color: colors.secondary, children: updateStatus }) })
1073
+ ] })
1074
+ );
1075
+
1076
+ // src/ui/components/SettingsMenu.tsx
1077
+ import React8, { useState as useState3, useMemo, useEffect as useEffect2, useRef } from "react";
1078
+ import { Box as Box8, Text as Text8, useInput as useInput2, useStdout } from "ink";
1079
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1080
+ var KEYBIND_LABELS = {
1081
+ quit: "Quit",
1082
+ navUp: "Navigate up",
1083
+ navDown: "Navigate down",
1084
+ panelNext: "Next panel",
1085
+ panelPrev: "Previous panel",
1086
+ scrollTop: "Scroll to top",
1087
+ scrollBottom: "Scroll to bottom",
1088
+ filter: "Filter",
1089
+ nickname: "Set nickname",
1090
+ clearNickname: "Clear nickname",
1091
+ detail: "Detail view",
1092
+ update: "Install update",
1093
+ settings: "Settings",
1094
+ archive: "Archive session",
1095
+ delete: "Delete session",
1096
+ viewArchive: "View archive",
1097
+ split: "Toggle split view",
1098
+ pinLeft: "Pin to left panel",
1099
+ pinRight: "Pin to right panel",
1100
+ swapPanels: "Swap panels",
1101
+ closePanel: "Close panel"
1102
+ };
1103
+ var RULE_LABELS = {
1104
+ network: "Network detection",
1105
+ exfiltration: "Exfiltration detection",
1106
+ sensitiveFiles: "Sensitive files",
1107
+ shellEscape: "Shell escape",
1108
+ injection: "Prompt injection"
1109
+ };
1110
+ var DEFAULT_KEYBINDINGS = {
1111
+ quit: "q",
1112
+ navUp: "k",
1113
+ navDown: "j",
1114
+ panelNext: "tab",
1115
+ panelPrev: "shift+tab",
1116
+ scrollTop: "g",
1117
+ scrollBottom: "G",
1118
+ filter: "/",
1119
+ nickname: "n",
1120
+ clearNickname: "N",
1121
+ detail: "enter",
1122
+ update: "u",
1123
+ settings: "s",
1124
+ archive: "a",
1125
+ delete: "d",
1126
+ viewArchive: "A",
1127
+ split: "x",
1128
+ pinLeft: "1",
1129
+ pinRight: "2",
1130
+ swapPanels: "S",
1131
+ closePanel: "X"
1132
+ };
1133
+ var SEVERITY_OPTIONS = ["info", "warn", "high", "critical"];
1134
+ var ARCHIVE_EXPIRY_OPTIONS = [0, 7, 14, 30, 60, 90];
1135
+ var formatExpiry = (days) => days === 0 ? "never" : `${days}d`;
1136
+ var displayKey = (key) => {
1137
+ if (key === "tab") return "tab";
1138
+ if (key === "shift+tab") return "S-tab";
1139
+ if (key === "enter") return "enter";
1140
+ return key;
1141
+ };
1142
+ var buildMenuItems = () => {
1143
+ const items = [];
1144
+ items.push({ type: "header", label: "THEMES", section: "themes", getValue: () => "", key: void 0 });
1145
+ items.push({
1146
+ type: "action",
1147
+ label: "Manage themes...",
1148
+ section: "themes",
1149
+ key: "manageThemes",
1150
+ getValue: (cfg) => cfg.theme,
1151
+ apply: void 0
1152
+ });
1153
+ items.push({ type: "header", label: "GENERAL", section: "general", getValue: () => "", key: void 0 });
1154
+ items.push({
1155
+ type: "cycle",
1156
+ label: "Archive expiry",
1157
+ section: "general",
1158
+ key: "archiveExpiryDays",
1159
+ getValue: (cfg) => formatExpiry(cfg.archiveExpiryDays),
1160
+ apply: (cfg) => {
1161
+ const idx = ARCHIVE_EXPIRY_OPTIONS.indexOf(cfg.archiveExpiryDays);
1162
+ return { ...cfg, archiveExpiryDays: ARCHIVE_EXPIRY_OPTIONS[(idx + 1) % ARCHIVE_EXPIRY_OPTIONS.length] };
1163
+ }
1164
+ });
1165
+ items.push({ type: "header", label: "KEYBINDINGS", section: "keybindings", getValue: () => "", key: void 0 });
1166
+ for (const [k, label2] of Object.entries(KEYBIND_LABELS)) {
1167
+ const kbKey = k;
1168
+ items.push({
1169
+ type: "keybind",
1170
+ label: label2,
1171
+ section: "keybindings",
1172
+ key: kbKey,
1173
+ getValue: (cfg) => displayKey(cfg.keybindings[kbKey]),
1174
+ apply: (cfg, newValue) => ({ ...cfg, keybindings: { ...cfg.keybindings, [kbKey]: newValue } })
1175
+ });
1176
+ }
1177
+ items.push({
1178
+ type: "action",
1179
+ label: "Reset all keybindings",
1180
+ section: "keybindings",
1181
+ key: "resetAllKeybinds",
1182
+ getValue: () => "",
1183
+ apply: (cfg) => ({ ...cfg, keybindings: { ...DEFAULT_KEYBINDINGS } })
1184
+ });
1185
+ items.push({ type: "header", label: "SECURITY RULES", section: "security", getValue: () => "", key: void 0 });
1186
+ for (const [k, label2] of Object.entries(RULE_LABELS)) {
1187
+ const ruleKey = k;
1188
+ items.push({
1189
+ type: "toggle",
1190
+ label: label2,
1191
+ section: "security",
1192
+ key: ruleKey,
1193
+ getValue: (cfg) => cfg.security.rules[ruleKey] ? "ON" : "OFF",
1194
+ apply: (cfg) => ({
1195
+ ...cfg,
1196
+ security: { ...cfg.security, rules: { ...cfg.security.rules, [ruleKey]: !cfg.security.rules[ruleKey] } }
1197
+ })
1198
+ });
1199
+ }
1200
+ items.push({ type: "header", label: "NOTIFICATIONS", section: "notifications", getValue: () => "", key: void 0 });
1201
+ items.push({
1202
+ type: "toggle",
1203
+ label: "Terminal bell",
1204
+ section: "notifications",
1205
+ key: "bell",
1206
+ getValue: (cfg) => cfg.notifications.bell ? "ON" : "OFF",
1207
+ apply: (cfg) => ({ ...cfg, notifications: { ...cfg.notifications, bell: !cfg.notifications.bell } })
1208
+ });
1209
+ items.push({
1210
+ type: "toggle",
1211
+ label: "Desktop notifications",
1212
+ section: "notifications",
1213
+ key: "desktop",
1214
+ getValue: (cfg) => cfg.notifications.desktop ? "ON" : "OFF",
1215
+ apply: (cfg) => ({ ...cfg, notifications: { ...cfg.notifications, desktop: !cfg.notifications.desktop } })
1216
+ });
1217
+ items.push({
1218
+ type: "cycle",
1219
+ label: "Minimum severity",
1220
+ section: "notifications",
1221
+ key: "minSeverity",
1222
+ getValue: (cfg) => cfg.notifications.minSeverity,
1223
+ apply: (cfg) => {
1224
+ const idx = SEVERITY_OPTIONS.indexOf(cfg.notifications.minSeverity);
1225
+ return {
1226
+ ...cfg,
1227
+ notifications: { ...cfg.notifications, minSeverity: SEVERITY_OPTIONS[(idx + 1) % SEVERITY_OPTIONS.length] }
1228
+ };
1229
+ }
1230
+ });
1231
+ return items;
1232
+ };
1233
+ var MENU_ITEMS = buildMenuItems();
1234
+ var SELECTABLE_INDICES = MENU_ITEMS.map((item, i) => item.type !== "header" ? i : -1).filter((i) => i >= 0);
1235
+ var SettingsMenu = React8.memo(({ config, onClose, onOpenThemeMenu }) => {
1236
+ const { stdout } = useStdout();
1237
+ const termHeight = stdout?.rows ?? 40;
1238
+ const [localConfig, setLocalConfig] = useState3(() => JSON.parse(JSON.stringify(config)));
1239
+ const [selectablePos, setSelectablePos] = useState3(0);
1240
+ const [rebinding, setRebinding] = useState3(false);
1241
+ const [toast, setToast] = useState3("");
1242
+ const toastTimer = useRef(null);
1243
+ const showToast = (msg) => {
1244
+ if (toastTimer.current) clearTimeout(toastTimer.current);
1245
+ setToast(msg);
1246
+ toastTimer.current = setTimeout(() => setToast(""), 2500);
1247
+ };
1248
+ useEffect2(
1249
+ () => () => {
1250
+ if (toastTimer.current) clearTimeout(toastTimer.current);
1251
+ },
1252
+ []
1253
+ );
1254
+ const selectedIndex = SELECTABLE_INDICES[selectablePos];
1255
+ const maxLabelLen = useMemo(
1256
+ () => Math.max(...MENU_ITEMS.filter((i) => i.type !== "header").map((i) => i.label.length)),
1257
+ []
1258
+ );
1259
+ useInput2((input, key) => {
1260
+ if (rebinding) {
1261
+ const item = MENU_ITEMS[selectedIndex];
1262
+ let newKey;
1263
+ if (key.tab && key.shift) newKey = "shift+tab";
1264
+ else if (key.tab) newKey = "tab";
1265
+ else if (key.return) newKey = "enter";
1266
+ else if (key.escape) {
1267
+ setRebinding(false);
1268
+ return;
1269
+ } else if (key.backspace || key.delete) {
1270
+ const kbKey2 = item.key;
1271
+ const defaultVal = DEFAULT_KEYBINDINGS[kbKey2];
1272
+ if (item.apply) setLocalConfig((c) => item.apply(c, defaultVal));
1273
+ showToast(`Reset to default: ${displayKey(defaultVal)}`);
1274
+ setRebinding(false);
1275
+ return;
1276
+ } else if (input && input.length === 1) newKey = input;
1277
+ else return;
1278
+ const kbKey = item.key;
1279
+ const conflict = Object.entries(localConfig.keybindings).find(([k, v]) => k !== kbKey && v === newKey);
1280
+ if (conflict) {
1281
+ const conflictLabel = KEYBIND_LABELS[conflict[0]] || conflict[0];
1282
+ showToast(`'${displayKey(newKey)}' is already assigned to '${conflictLabel}'`);
1283
+ setRebinding(false);
1284
+ return;
1285
+ }
1286
+ if (item.apply) setLocalConfig((c) => item.apply(c, newKey));
1287
+ setRebinding(false);
1288
+ return;
1289
+ }
1290
+ if (key.escape) {
1291
+ onClose(localConfig);
1292
+ return;
1293
+ }
1294
+ if (key.upArrow) {
1295
+ setSelectablePos((p) => Math.max(0, p - 1));
1296
+ return;
1297
+ }
1298
+ if (key.downArrow) {
1299
+ setSelectablePos((p) => Math.min(SELECTABLE_INDICES.length - 1, p + 1));
1300
+ return;
1301
+ }
1302
+ if (key.return) {
1303
+ const item = MENU_ITEMS[selectedIndex];
1304
+ if (item.key === "manageThemes") {
1305
+ onOpenThemeMenu();
1306
+ return;
1307
+ }
1308
+ if (item.key === "resetAllKeybinds") {
1309
+ if (item.apply) setLocalConfig((c) => item.apply(c));
1310
+ showToast("All keybindings reset to defaults");
1311
+ return;
1312
+ }
1313
+ if (item.type === "keybind") setRebinding(true);
1314
+ else if (item.apply) setLocalConfig((c) => item.apply(c));
1315
+ }
1316
+ });
1317
+ const contentHeight = termHeight - 6;
1318
+ const halfView = Math.floor(contentHeight / 2);
1319
+ const menuItemIndex = MENU_ITEMS.indexOf(MENU_ITEMS[selectedIndex]);
1320
+ const scrollStart = Math.max(0, Math.min(menuItemIndex - halfView, MENU_ITEMS.length - contentHeight));
1321
+ const visibleItems = MENU_ITEMS.slice(Math.max(0, scrollStart), Math.max(0, scrollStart) + contentHeight);
1322
+ const visibleStartIndex = Math.max(0, scrollStart);
1323
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsxs8(
1324
+ Box8,
1325
+ {
1326
+ borderStyle: "round",
1327
+ borderColor: colors.primary,
1328
+ flexDirection: "column",
1329
+ paddingX: 2,
1330
+ paddingY: 1,
1331
+ height: termHeight,
1332
+ children: [
1333
+ /* @__PURE__ */ jsxs8(Box8, { justifyContent: "space-between", marginBottom: 1, children: [
1334
+ /* @__PURE__ */ jsx8(Text8, { color: colors.header, bold: true, children: "SETTINGS" }),
1335
+ /* @__PURE__ */ jsxs8(Text8, { color: colors.muted, children: [
1336
+ "esc to save & close ",
1337
+ rebinding ? "| backspace to reset" : ""
1338
+ ] })
1339
+ ] }),
1340
+ visibleItems.map((item, vi) => {
1341
+ const realIndex = visibleStartIndex + vi;
1342
+ const isSelected = realIndex === selectedIndex;
1343
+ if (item.type === "header") {
1344
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: realIndex > 0 ? 1 : 0, children: /* @__PURE__ */ jsxs8(Text8, { color: colors.accent, bold: true, children: [
1345
+ " ",
1346
+ item.label
1347
+ ] }) }, `h-${item.label}`);
1348
+ }
1349
+ const value = item.getValue(localConfig);
1350
+ const isRebindingThis = rebinding && isSelected;
1351
+ const displayValue = isRebindingThis ? "[press key...]" : value;
1352
+ const valueColor = isRebindingThis ? colors.warning : value === "ON" ? colors.secondary : value === "OFF" ? colors.error : colors.bright;
1353
+ const isReset = item.key === "resetAllKeybinds";
1354
+ const dots = isReset ? "" : ".".repeat(Math.max(2, maxLabelLen - item.label.length + 4)) + " ";
1355
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
1356
+ /* @__PURE__ */ jsxs8(Text8, { color: isSelected ? colors.primary : colors.text, children: [
1357
+ isSelected ? "> " : " ",
1358
+ " ",
1359
+ item.label,
1360
+ " ",
1361
+ dots
1362
+ ] }),
1363
+ !isReset && /* @__PURE__ */ jsx8(Text8, { color: valueColor, children: displayValue })
1364
+ ] }, `${item.section}-${item.key}`);
1365
+ }),
1366
+ toast && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx8(Text8, { color: colors.warning, children: toast }) })
1367
+ ]
1368
+ }
1369
+ ) });
1370
+ });
1371
+
1372
+ // src/ui/components/ThemeMenu.tsx
1373
+ import React10, { useState as useState5, useCallback, useRef as useRef2, useEffect as useEffect3 } from "react";
1374
+ import { Box as Box10, Text as Text10, useInput as useInput4, useStdout as useStdout2 } from "ink";
1375
+
1376
+ // src/ui/components/ThemeEditor.tsx
1377
+ import React9, { useState as useState4 } from "react";
1378
+ import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
1379
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1380
+ var ALL_KEYS = [
1381
+ ...COLOR_KEYS.map((key) => ({ group: "colors", key })),
1382
+ ...TOOL_COLOR_KEYS.map((key) => ({ group: "toolColors", key }))
1383
+ ];
1384
+ var isValidHex = (v) => /^#[0-9A-Fa-f]{6}$/.test(v);
1385
+ var ThemeEditor = React9.memo(({ theme, onSave, onCancel }) => {
1386
+ const [editColors, setEditColors] = useState4({ ...theme.colors });
1387
+ const [editToolColors, setEditToolColors] = useState4({ ...theme.toolColors });
1388
+ const [selectedIdx, setSelectedIdx] = useState4(0);
1389
+ const [editing, setEditing] = useState4(false);
1390
+ const [inputValue, setInputValue] = useState4("");
1391
+ const currentTarget = ALL_KEYS[selectedIdx];
1392
+ const getCurrentValue = (t) => t.group === "colors" ? editColors[t.key] : editToolColors[t.key];
1393
+ useInput3((input, key) => {
1394
+ if (editing) {
1395
+ if (key.escape) {
1396
+ setEditing(false);
1397
+ setInputValue("");
1398
+ return;
1399
+ }
1400
+ if (key.return) {
1401
+ const val = inputValue.startsWith("#") ? inputValue : `#${inputValue}`;
1402
+ if (isValidHex(val)) {
1403
+ if (currentTarget.group === "colors") {
1404
+ setEditColors((c) => ({ ...c, [currentTarget.key]: val }));
1405
+ } else {
1406
+ setEditToolColors((c) => ({ ...c, [currentTarget.key]: val }));
1407
+ }
1408
+ }
1409
+ setEditing(false);
1410
+ setInputValue("");
1411
+ return;
1412
+ }
1413
+ if (key.backspace || key.delete) {
1414
+ setInputValue((v) => v.slice(0, -1));
1415
+ return;
1416
+ }
1417
+ if (input && input.length === 1 && /[0-9A-Fa-f#]/.test(input)) {
1418
+ setInputValue((v) => v + input);
1419
+ }
1420
+ return;
1421
+ }
1422
+ if (key.escape) {
1423
+ onCancel();
1424
+ return;
1425
+ }
1426
+ if (key.upArrow) {
1427
+ setSelectedIdx((i) => Math.max(0, i - 1));
1428
+ return;
1429
+ }
1430
+ if (key.downArrow) {
1431
+ setSelectedIdx((i) => Math.min(ALL_KEYS.length - 1, i + 1));
1432
+ return;
1433
+ }
1434
+ if (key.return) {
1435
+ setEditing(true);
1436
+ setInputValue(getCurrentValue(currentTarget));
1437
+ return;
1438
+ }
1439
+ if (input === "s" || input === "S") {
1440
+ onSave({ ...theme, colors: editColors, toolColors: editToolColors });
1441
+ }
1442
+ });
1443
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1444
+ /* @__PURE__ */ jsxs9(Box9, { justifyContent: "space-between", marginBottom: 1, children: [
1445
+ /* @__PURE__ */ jsxs9(Text9, { color: colors.header, bold: true, children: [
1446
+ "EDIT THEME: ",
1447
+ theme.name
1448
+ ] }),
1449
+ /* @__PURE__ */ jsx9(Text9, { color: colors.muted, children: "enter:edit s:save esc:cancel" })
1450
+ ] }),
1451
+ /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: colors.accent, bold: true, children: [
1452
+ " ",
1453
+ "COLORS"
1454
+ ] }) }),
1455
+ COLOR_KEYS.map((k, i) => {
1456
+ const isSelected = selectedIdx === i;
1457
+ const value = editColors[k];
1458
+ const isEditingThis = editing && isSelected;
1459
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
1460
+ /* @__PURE__ */ jsxs9(Text9, { color: isSelected ? colors.primary : colors.text, children: [
1461
+ isSelected ? "> " : " ",
1462
+ " ",
1463
+ k.padEnd(12)
1464
+ ] }),
1465
+ /* @__PURE__ */ jsx9(Text9, { color: value, children: isEditingThis ? inputValue + "_" : value }),
1466
+ /* @__PURE__ */ jsxs9(Text9, { color: value, children: [
1467
+ " ",
1468
+ "\u2588\u2588"
1469
+ ] })
1470
+ ] }, k);
1471
+ }),
1472
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: colors.accent, bold: true, children: [
1473
+ " ",
1474
+ "TOOL COLORS"
1475
+ ] }) }),
1476
+ TOOL_COLOR_KEYS.map((k, i) => {
1477
+ const realIdx = COLOR_KEYS.length + i;
1478
+ const isSelected = selectedIdx === realIdx;
1479
+ const value = editToolColors[k];
1480
+ const isEditingThis = editing && isSelected;
1481
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
1482
+ /* @__PURE__ */ jsxs9(Text9, { color: isSelected ? colors.primary : colors.text, children: [
1483
+ isSelected ? "> " : " ",
1484
+ " ",
1485
+ k.padEnd(12)
1486
+ ] }),
1487
+ /* @__PURE__ */ jsx9(Text9, { color: value, children: isEditingThis ? inputValue + "_" : value }),
1488
+ /* @__PURE__ */ jsxs9(Text9, { color: value, children: [
1489
+ " ",
1490
+ "\u2588\u2588"
1491
+ ] })
1492
+ ] }, k);
1493
+ })
1494
+ ] });
1495
+ });
1496
+
1497
+ // src/ui/components/ThemeMenu.tsx
1498
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1499
+ var ThemeMenu = React10.memo(({ config, onClose }) => {
1500
+ const { stdout } = useStdout2();
1501
+ const termHeight = stdout?.rows ?? 40;
1502
+ const [localConfig, setLocalConfig] = useState5(() => JSON.parse(JSON.stringify(config)));
1503
+ const [selectedIdx, setSelectedIdx] = useState5(0);
1504
+ const [view, setView] = useState5("list");
1505
+ const [editingTheme, setEditingTheme] = useState5(null);
1506
+ const [nameInput, setNameInput] = useState5("");
1507
+ const [namingAction, setNamingAction] = useState5("copy");
1508
+ const [toast, setToast] = useState5("");
1509
+ const toastTimer = useRef2(null);
1510
+ const showToast = (msg) => {
1511
+ if (toastTimer.current) clearTimeout(toastTimer.current);
1512
+ setToast(msg);
1513
+ toastTimer.current = setTimeout(() => setToast(""), 2500);
1514
+ };
1515
+ useEffect3(
1516
+ () => () => {
1517
+ if (toastTimer.current) clearTimeout(toastTimer.current);
1518
+ },
1519
+ []
1520
+ );
1521
+ const themes = getAllThemes(localConfig.customThemes);
1522
+ const previewTheme = useCallback(
1523
+ (idx) => {
1524
+ const t = themes[idx];
1525
+ if (t) applyTheme(t);
1526
+ },
1527
+ [themes]
1528
+ );
1529
+ const updateSelection = useCallback(
1530
+ (newIdx) => {
1531
+ setSelectedIdx(newIdx);
1532
+ previewTheme(newIdx);
1533
+ },
1534
+ [previewTheme]
1535
+ );
1536
+ useInput4((input, key) => {
1537
+ if (view === "naming") {
1538
+ if (key.escape) {
1539
+ setView("list");
1540
+ setNameInput("");
1541
+ return;
1542
+ }
1543
+ if (key.return && nameInput.trim()) {
1544
+ const name = nameInput.trim();
1545
+ if (themes.some((t) => t.name === name)) {
1546
+ showToast(`Theme '${name}' already exists`);
1547
+ return;
1548
+ }
1549
+ if (namingAction === "copy") {
1550
+ const source = themes[selectedIdx];
1551
+ const newTheme = {
1552
+ name,
1553
+ colors: { ...source.colors },
1554
+ toolColors: { ...source.toolColors }
1555
+ };
1556
+ setLocalConfig((c) => ({
1557
+ ...c,
1558
+ theme: name,
1559
+ customThemes: {
1560
+ ...c.customThemes,
1561
+ [name]: { name, colors: newTheme.colors, toolColors: newTheme.toolColors }
1562
+ }
1563
+ }));
1564
+ applyTheme(newTheme);
1565
+ showToast(`Created '${name}'`);
1566
+ } else {
1567
+ const old = themes[selectedIdx];
1568
+ const updated = { ...localConfig.customThemes };
1569
+ delete updated[old.name];
1570
+ updated[name] = { name, colors: old.colors, toolColors: old.toolColors };
1571
+ const newActive = localConfig.theme === old.name ? name : localConfig.theme;
1572
+ setLocalConfig((c) => ({ ...c, theme: newActive, customThemes: updated }));
1573
+ showToast(`Renamed to '${name}'`);
1574
+ }
1575
+ setView("list");
1576
+ setNameInput("");
1577
+ return;
1578
+ }
1579
+ if (key.backspace || key.delete) {
1580
+ setNameInput((v) => v.slice(0, -1));
1581
+ return;
1582
+ }
1583
+ if (input && input.length === 1) setNameInput((v) => v + input);
1584
+ return;
1585
+ }
1586
+ if (view === "editor" && editingTheme) return;
1587
+ if (key.escape) {
1588
+ const restoreTheme = resolveTheme(localConfig.theme, localConfig.customThemes);
1589
+ applyTheme(restoreTheme);
1590
+ onClose(localConfig);
1591
+ return;
1592
+ }
1593
+ if (key.upArrow) {
1594
+ updateSelection(Math.max(0, selectedIdx - 1));
1595
+ return;
1596
+ }
1597
+ if (key.downArrow) {
1598
+ updateSelection(Math.min(themes.length - 1, selectedIdx + 1));
1599
+ return;
1600
+ }
1601
+ if (key.return) {
1602
+ setLocalConfig((c) => ({ ...c, theme: themes[selectedIdx].name }));
1603
+ applyTheme(themes[selectedIdx]);
1604
+ onClose({ ...localConfig, theme: themes[selectedIdx].name });
1605
+ return;
1606
+ }
1607
+ if (input === "c") {
1608
+ setNamingAction("copy");
1609
+ setNameInput(themes[selectedIdx].name + "-custom");
1610
+ setView("naming");
1611
+ return;
1612
+ }
1613
+ const selected = themes[selectedIdx];
1614
+ if (input === "e" && !selected.builtin) {
1615
+ setEditingTheme(selected);
1616
+ setView("editor");
1617
+ return;
1618
+ }
1619
+ if (input === "r" && !selected.builtin) {
1620
+ setNamingAction("rename");
1621
+ setNameInput(selected.name);
1622
+ setView("naming");
1623
+ return;
1624
+ }
1625
+ if (input === "d" && !selected.builtin) {
1626
+ const updated = { ...localConfig.customThemes };
1627
+ delete updated[selected.name];
1628
+ const newTheme = localConfig.theme === selected.name ? "one-dark" : localConfig.theme;
1629
+ setLocalConfig((c) => ({ ...c, theme: newTheme, customThemes: updated }));
1630
+ if (localConfig.theme === selected.name) applyTheme(getDefaultTheme());
1631
+ setSelectedIdx((i) => Math.min(i, themes.length - 2));
1632
+ showToast(`Deleted '${selected.name}'`);
1633
+ return;
1634
+ }
1635
+ if (key.backspace || key.delete) {
1636
+ setLocalConfig((c) => ({ ...c, theme: "one-dark" }));
1637
+ applyTheme(getDefaultTheme());
1638
+ showToast("Restored default theme");
1639
+ }
1640
+ });
1641
+ const handleEditorSave = useCallback((updated) => {
1642
+ setLocalConfig((c) => ({
1643
+ ...c,
1644
+ customThemes: {
1645
+ ...c.customThemes,
1646
+ [updated.name]: { name: updated.name, colors: updated.colors, toolColors: updated.toolColors }
1647
+ }
1648
+ }));
1649
+ applyTheme(updated);
1650
+ setView("list");
1651
+ setEditingTheme(null);
1652
+ showToast(`Saved '${updated.name}'`);
1653
+ }, []);
1654
+ const handleEditorCancel = useCallback(() => {
1655
+ const restoreTheme = resolveTheme(localConfig.theme, localConfig.customThemes);
1656
+ applyTheme(restoreTheme);
1657
+ setView("list");
1658
+ setEditingTheme(null);
1659
+ }, [localConfig]);
1660
+ if (view === "editor" && editingTheme) {
1661
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsx10(Box10, { borderStyle: "round", borderColor: colors.primary, flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsx10(ThemeEditor, { theme: editingTheme, onSave: handleEditorSave, onCancel: handleEditorCancel }) }) });
1662
+ }
1663
+ const contentHeight = termHeight - 8;
1664
+ const halfView = Math.floor(contentHeight / 2);
1665
+ const scrollStart = Math.max(0, Math.min(selectedIdx - halfView, themes.length - contentHeight));
1666
+ const visibleThemes = themes.slice(Math.max(0, scrollStart), Math.max(0, scrollStart) + contentHeight);
1667
+ const visibleStartIdx = Math.max(0, scrollStart);
1668
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsxs10(
1669
+ Box10,
1670
+ {
1671
+ borderStyle: "round",
1672
+ borderColor: colors.primary,
1673
+ flexDirection: "column",
1674
+ paddingX: 2,
1675
+ paddingY: 1,
1676
+ height: termHeight,
1677
+ children: [
1678
+ /* @__PURE__ */ jsxs10(Box10, { justifyContent: "space-between", marginBottom: 1, children: [
1679
+ /* @__PURE__ */ jsx10(Text10, { color: colors.header, bold: true, children: "THEMES" }),
1680
+ /* @__PURE__ */ jsx10(Text10, { color: colors.muted, children: "enter:apply c:copy e:edit r:rename d:delete esc:back" })
1681
+ ] }),
1682
+ view === "naming" && /* @__PURE__ */ jsxs10(Box10, { marginBottom: 1, children: [
1683
+ /* @__PURE__ */ jsxs10(Text10, { color: colors.warning, children: [
1684
+ namingAction === "copy" ? "New name" : "Rename to",
1685
+ ": "
1686
+ ] }),
1687
+ /* @__PURE__ */ jsxs10(Text10, { color: colors.bright, children: [
1688
+ nameInput,
1689
+ "_"
1690
+ ] })
1691
+ ] }),
1692
+ visibleThemes.map((theme, vi) => {
1693
+ const realIdx = visibleStartIdx + vi;
1694
+ const isSelected = realIdx === selectedIdx;
1695
+ const isActive = theme.name === localConfig.theme;
1696
+ return /* @__PURE__ */ jsxs10(Box10, { children: [
1697
+ /* @__PURE__ */ jsxs10(Text10, { color: isSelected ? colors.primary : colors.text, children: [
1698
+ isSelected ? "> " : " ",
1699
+ isActive ? "* " : " ",
1700
+ theme.name
1701
+ ] }),
1702
+ /* @__PURE__ */ jsx10(Text10, { color: colors.muted, children: theme.builtin ? " (built-in)" : " (custom)" }),
1703
+ /* @__PURE__ */ jsx10(Text10, { children: " " }),
1704
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.primary, children: "\u2588" }),
1705
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.secondary, children: "\u2588" }),
1706
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.accent, children: "\u2588" }),
1707
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.warning, children: "\u2588" }),
1708
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.error, children: "\u2588" })
1709
+ ] }, theme.name);
1710
+ }),
1711
+ toast && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx10(Text10, { color: colors.warning, children: toast }) })
1712
+ ]
1713
+ }
1714
+ ) });
1715
+ });
1716
+
1717
+ // src/ui/components/ConfirmModal.tsx
1718
+ import React11 from "react";
1719
+ import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
1720
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1721
+ var ConfirmModal = React11.memo(({ title, message, onConfirm, onCancel }) => {
1722
+ useInput5((input, key) => {
1723
+ if (input === "y" || input === "Y") {
1724
+ onConfirm();
1725
+ } else if (input === "n" || input === "N" || key.escape) {
1726
+ onCancel();
1727
+ }
1728
+ });
1729
+ return /* @__PURE__ */ jsxs11(
1730
+ Box11,
1731
+ {
1732
+ borderStyle: "round",
1733
+ borderColor: colors.warning,
1734
+ flexDirection: "column",
1735
+ paddingX: 2,
1736
+ paddingY: 1,
1737
+ alignSelf: "center",
1738
+ children: [
1739
+ /* @__PURE__ */ jsx11(Text11, { color: colors.warning, bold: true, children: title }),
1740
+ /* @__PURE__ */ jsx11(Text11, { color: colors.text, children: message }),
1741
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: colors.muted, children: "[y] confirm [n/esc] cancel" }) })
1742
+ ]
1743
+ }
1744
+ );
1745
+ });
1746
+
1747
+ // src/ui/components/SplitPanel.tsx
1748
+ import React12 from "react";
1749
+ import { Box as Box12 } from "ink";
1750
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1751
+ var SplitPanel = React12.memo(
1752
+ ({
1753
+ activePanel,
1754
+ leftSession,
1755
+ rightSession,
1756
+ leftEvents,
1757
+ rightEvents,
1758
+ leftScroll,
1759
+ rightScroll,
1760
+ leftFilter,
1761
+ rightFilter,
1762
+ leftShowDetail,
1763
+ rightShowDetail,
1764
+ height
1765
+ }) => {
1766
+ const left = leftShowDetail && leftSession ? /* @__PURE__ */ jsx12(SessionDetail, { session: leftSession, focused: activePanel === "left", height }) : /* @__PURE__ */ jsx12(
1767
+ ActivityFeed,
1768
+ {
1769
+ events: leftEvents,
1770
+ sessionSlug: leftSession?.slug ?? null,
1771
+ focused: activePanel === "left",
1772
+ height,
1773
+ scrollOffset: leftScroll,
1774
+ filter: leftFilter || void 0
1775
+ }
1776
+ );
1777
+ const right = rightShowDetail && rightSession ? /* @__PURE__ */ jsx12(SessionDetail, { session: rightSession, focused: activePanel === "right", height }) : /* @__PURE__ */ jsx12(
1778
+ ActivityFeed,
1779
+ {
1780
+ events: rightEvents,
1781
+ sessionSlug: rightSession?.slug ?? null,
1782
+ focused: activePanel === "right",
1783
+ height,
1784
+ scrollOffset: rightScroll,
1785
+ filter: rightFilter || void 0
1786
+ }
1787
+ );
1788
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "row", flexGrow: 1, children: [
1789
+ left,
1790
+ right
1791
+ ] });
1792
+ }
1793
+ );
689
1794
 
690
1795
  // src/ui/hooks/useSessions.ts
691
- import { useState as useState3, useEffect as useEffect2, useCallback, useRef } from "react";
1796
+ import { useState as useState6, useEffect as useEffect4, useCallback as useCallback2, useRef as useRef3 } from "react";
692
1797
  var ACTIVE_POLL_MS = 1e4;
693
1798
  var IDLE_POLL_MS = 3e4;
694
- var useSessions = (allUsers, filter) => {
695
- const [sessions, setSessions] = useState3([]);
696
- const [selectedIndex, setSelectedIndex] = useState3(0);
697
- const usageOverrides = useRef(/* @__PURE__ */ new Map());
698
- const refresh = useCallback(() => {
1799
+ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
1800
+ const [sessions, setSessions] = useState6([]);
1801
+ const [selectedIndex, setSelectedIndex] = useState6(0);
1802
+ const usageOverrides = useRef3(/* @__PURE__ */ new Map());
1803
+ const refresh = useCallback2(() => {
699
1804
  const found = discoverSessions(allUsers);
700
1805
  const nicknames = getNicknames();
701
1806
  const enriched = found.map((s) => {
@@ -712,31 +1817,40 @@ var useSessions = (allUsers, filter) => {
712
1817
  };
713
1818
  });
714
1819
  let filtered = enriched;
1820
+ if (archivedIds && archivedIds.size > 0) {
1821
+ if (viewingArchive) {
1822
+ filtered = filtered.filter((s) => archivedIds.has(s.sessionId));
1823
+ } else {
1824
+ filtered = filtered.filter((s) => !archivedIds.has(s.sessionId));
1825
+ }
1826
+ } else if (viewingArchive) {
1827
+ filtered = [];
1828
+ }
715
1829
  if (filter) {
716
1830
  const lower = filter.toLowerCase();
717
- filtered = enriched.filter(
1831
+ filtered = filtered.filter(
718
1832
  (s) => s.slug.toLowerCase().includes(lower) || s.nickname?.toLowerCase().includes(lower) || s.project.toLowerCase().includes(lower) || s.model.toLowerCase().includes(lower)
719
1833
  );
720
1834
  }
721
1835
  setSessions(filtered);
722
- }, [allUsers, filter]);
723
- useEffect2(() => {
1836
+ }, [allUsers, filter, archivedIds, viewingArchive]);
1837
+ useEffect4(() => {
724
1838
  refresh();
725
1839
  const pollMs = sessions.length > 0 ? ACTIVE_POLL_MS : IDLE_POLL_MS;
726
1840
  const interval = setInterval(refresh, pollMs);
727
1841
  return () => clearInterval(interval);
728
1842
  }, [refresh, sessions.length > 0]);
729
1843
  const selectedSession = sessions[selectedIndex] ?? null;
730
- const selectNext = useCallback(() => {
1844
+ const selectNext = useCallback2(() => {
731
1845
  setSelectedIndex((i) => Math.min(i + 1, Math.max(0, sessions.length - 1)));
732
1846
  }, [sessions.length]);
733
- const selectPrev = useCallback(() => {
1847
+ const selectPrev = useCallback2(() => {
734
1848
  setSelectedIndex((i) => Math.max(i - 1, 0));
735
1849
  }, []);
736
- const selectIndex = useCallback((i) => {
1850
+ const selectIndex = useCallback2((i) => {
737
1851
  setSelectedIndex(i);
738
1852
  }, []);
739
- const addUsage = useCallback((sessionId, usage) => {
1853
+ const addUsage = useCallback2((sessionId, usage) => {
740
1854
  const existing = usageOverrides.current.get(sessionId);
741
1855
  if (existing) {
742
1856
  usageOverrides.current.set(sessionId, {
@@ -753,12 +1867,12 @@ var useSessions = (allUsers, filter) => {
753
1867
  };
754
1868
 
755
1869
  // src/ui/hooks/useActivityStream.ts
756
- import { useState as useState4, useEffect as useEffect3, useRef as useRef2 } from "react";
1870
+ import { useState as useState7, useEffect as useEffect5, useRef as useRef4 } from "react";
757
1871
  var MAX_EVENTS = 200;
758
1872
  var useActivityStream = (session, allUsers) => {
759
- const [events, setEvents] = useState4([]);
760
- const watcherRef = useRef2(null);
761
- useEffect3(() => {
1873
+ const [events, setEvents] = useState7([]);
1874
+ const watcherRef = useRef4(null);
1875
+ useEffect5(() => {
762
1876
  setEvents([]);
763
1877
  if (!session) return;
764
1878
  const existingCalls = [];
@@ -784,8 +1898,19 @@ var useActivityStream = (session, allUsers) => {
784
1898
  return events;
785
1899
  };
786
1900
 
1901
+ // src/ui/hooks/useFilteredEvents.ts
1902
+ import { useMemo as useMemo2 } from "react";
1903
+ var applyFilter = (events, filter) => {
1904
+ if (!filter) return events;
1905
+ const lower = filter.toLowerCase();
1906
+ return events.filter(
1907
+ (e) => e.toolName.toLowerCase().includes(lower) || JSON.stringify(e.toolInput).toLowerCase().includes(lower)
1908
+ );
1909
+ };
1910
+ var useFilteredEvents = (rawEvents, filter) => useMemo2(() => applyFilter(rawEvents, filter), [rawEvents, filter]);
1911
+
787
1912
  // src/ui/hooks/useAlerts.ts
788
- import { useState as useState5, useEffect as useEffect4, useRef as useRef3 } from "react";
1913
+ import { useState as useState8, useEffect as useEffect6, useRef as useRef5 } from "react";
789
1914
 
790
1915
  // src/notifications.ts
791
1916
  import { exec as exec2 } from "child_process";
@@ -853,11 +1978,11 @@ var AlertLogger = class {
853
1978
  // src/ui/hooks/useAlerts.ts
854
1979
  var MAX_ALERTS = 100;
855
1980
  var useAlerts = (enabled, alertLevel, allUsers, config) => {
856
- const [alerts, setAlerts] = useState5([]);
857
- const engineRef = useRef3(new SecurityEngine(alertLevel));
858
- const watcherRef = useRef3(null);
859
- const loggerRef = useRef3(null);
860
- useEffect4(() => {
1981
+ const [alerts, setAlerts] = useState8([]);
1982
+ const engineRef = useRef5(new SecurityEngine(alertLevel));
1983
+ const watcherRef = useRef5(null);
1984
+ const loggerRef = useRef5(null);
1985
+ useEffect6(() => {
861
1986
  if (!enabled) return;
862
1987
  engineRef.current = new SecurityEngine(alertLevel);
863
1988
  if (config?.alerts.enabled) {
@@ -893,26 +2018,27 @@ var useAlerts = (enabled, alertLevel, allUsers, config) => {
893
2018
  };
894
2019
 
895
2020
  // src/ui/hooks/useTextInput.ts
896
- import { useState as useState6, useCallback as useCallback2 } from "react";
897
- var useTextInput = (onConfirm) => {
898
- const [value, setValue] = useState6("");
899
- const [isActive, setIsActive] = useState6(false);
900
- const start = useCallback2((initial = "") => {
2021
+ import { useState as useState9, useCallback as useCallback3 } from "react";
2022
+ var useTextInput = (onConfirm, onCancel) => {
2023
+ const [value, setValue] = useState9("");
2024
+ const [isActive, setIsActive] = useState9(false);
2025
+ const start = useCallback3((initial = "") => {
901
2026
  setValue(initial);
902
2027
  setIsActive(true);
903
2028
  }, []);
904
- const cancel = useCallback2(() => {
2029
+ const cancel = useCallback3(() => {
905
2030
  setValue("");
906
2031
  setIsActive(false);
907
- }, []);
908
- const confirm = useCallback2(() => {
2032
+ onCancel?.();
2033
+ }, [onCancel]);
2034
+ const confirm = useCallback3(() => {
909
2035
  const result = value;
910
2036
  setIsActive(false);
911
2037
  setValue("");
912
2038
  onConfirm?.(result);
913
2039
  return result;
914
2040
  }, [value, onConfirm]);
915
- const handleInput = useCallback2(
2041
+ const handleInput = useCallback3(
916
2042
  (input, key) => {
917
2043
  if (!isActive) return false;
918
2044
  if (key.escape) {
@@ -924,7 +2050,13 @@ var useTextInput = (onConfirm) => {
924
2050
  return true;
925
2051
  }
926
2052
  if (key.backspace || key.delete) {
927
- setValue((v) => v.slice(0, -1));
2053
+ setValue((v) => {
2054
+ if (v.length === 0) {
2055
+ cancel();
2056
+ return v;
2057
+ }
2058
+ return v.slice(0, -1);
2059
+ });
928
2060
  return true;
929
2061
  }
930
2062
  if (input && input.length === 1 && input >= " ") {
@@ -938,43 +2070,227 @@ var useTextInput = (onConfirm) => {
938
2070
  return { value, isActive, start, cancel, confirm, handleInput };
939
2071
  };
940
2072
 
941
- // src/ui/App.tsx
942
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2073
+ // src/ui/hooks/useKeyHandler.ts
2074
+ import { useInput as useInput6 } from "ink";
943
2075
  var matchKey = (binding, input, key) => {
944
2076
  if (binding === "tab") return Boolean(key.tab);
945
2077
  if (binding === "shift+tab") return Boolean(key.shift && key.tab);
946
2078
  if (binding === "enter") return Boolean(key.return);
947
2079
  return input === binding;
948
2080
  };
949
- var App = ({ options, config, version, firstRun }) => {
950
- const { exit } = useApp();
951
- const { stdout } = useStdout();
952
- const termHeight = stdout?.rows ?? 40;
953
- const kb = config.keybindings;
954
- const [activePanel, setActivePanel] = useState7("sessions");
955
- const [activityScroll, setActivityScroll] = useState7(0);
956
- const [inputMode, setInputMode] = useState7("normal");
957
- const [showSetup, setShowSetup] = useState7(firstRun);
958
- const [filter, setFilter] = useState7("");
959
- const [updateInfo, setUpdateInfo] = useState7(null);
960
- const [updateStatus, setUpdateStatus] = useState7("");
961
- const [showDetail, setShowDetail] = useState7(false);
962
- const { sessions, selectedSession, selectedIndex, selectNext, selectPrev } = useSessions(
963
- options.allUsers,
964
- filter || void 0
965
- );
966
- const events = useActivityStream(selectedSession, options.allUsers);
967
- const { alerts } = useAlerts(!options.noSecurity, options.alertLevel, options.allUsers, config);
968
- const nicknameInput = useTextInput((value) => {
969
- if (selectedSession && value.trim()) setNickname(selectedSession.sessionId, value.trim());
970
- setInputMode("normal");
971
- });
972
- const filterInput = useTextInput((value) => {
973
- setFilter(value);
974
- setInputMode("normal");
2081
+ var useKeyHandler = (deps) => {
2082
+ const d = deps;
2083
+ useInput6((input, key) => {
2084
+ if (d.showSetup || d.showSettings || d.confirmAction) return;
2085
+ if (d.inputMode === "nickname") {
2086
+ d.nicknameInput.handleInput(input, key);
2087
+ return;
2088
+ }
2089
+ if (d.inputMode === "filter") {
2090
+ if (key.escape) {
2091
+ if (d.activePanel === "sessions") d.setFilter("");
2092
+ else if (d.activePanel === "left") d.setLeftFilter("");
2093
+ else if (d.activePanel === "right") d.setRightFilter("");
2094
+ else d.setActivityFilter("");
2095
+ d.setInputMode("normal");
2096
+ d.filterInput.cancel();
2097
+ return;
2098
+ }
2099
+ d.filterInput.handleInput(input, key);
2100
+ return;
2101
+ }
2102
+ if (matchKey(d.kb.quit, input, key)) {
2103
+ d.exit();
2104
+ return;
2105
+ }
2106
+ if (d.showDetail && !d.splitMode) {
2107
+ if (key.escape || key.return || key.leftArrow) d.setShowDetail(false);
2108
+ return;
2109
+ }
2110
+ if (d.splitMode && (d.leftShowDetail || d.rightShowDetail)) {
2111
+ if (key.escape || key.return) {
2112
+ if (d.activePanel === "left") d.setLeftShowDetail(false);
2113
+ else if (d.activePanel === "right") d.setRightShowDetail(false);
2114
+ else {
2115
+ d.setLeftShowDetail(false);
2116
+ d.setRightShowDetail(false);
2117
+ }
2118
+ return;
2119
+ }
2120
+ if (key.leftArrow) {
2121
+ if (d.activePanel === "left") d.setLeftShowDetail(false);
2122
+ else if (d.activePanel === "right") d.setRightShowDetail(false);
2123
+ return;
2124
+ }
2125
+ }
2126
+ if (matchKey(d.kb.split, input, key)) {
2127
+ if (d.splitMode) {
2128
+ d.clearSplitState();
2129
+ } else {
2130
+ d.setSplitMode(true);
2131
+ d.setLeftSession(d.selectedSession);
2132
+ d.setActivePanel("left");
2133
+ }
2134
+ return;
2135
+ }
2136
+ if (d.splitMode && d.activePanel === "sessions" && d.selectedSession) {
2137
+ if (matchKey(d.kb.pinLeft, input, key)) {
2138
+ d.resetPanel("left");
2139
+ d.setLeftSession(d.selectedSession);
2140
+ return;
2141
+ }
2142
+ if (matchKey(d.kb.pinRight, input, key)) {
2143
+ d.resetPanel("right");
2144
+ d.setRightSession(d.selectedSession);
2145
+ return;
2146
+ }
2147
+ }
2148
+ if (d.splitMode && matchKey(d.kb.swapPanels, input, key)) {
2149
+ const tmpLs = d.leftSession;
2150
+ const tmpRs = d.rightSession;
2151
+ d.setLeftSession(tmpRs);
2152
+ d.setRightSession(tmpLs);
2153
+ const tmpScroll = d.leftScroll;
2154
+ d.setLeftScroll(d.rightScroll);
2155
+ d.setRightScroll(tmpScroll);
2156
+ const tmpFilt = d.leftFilter;
2157
+ d.setLeftFilter(d.rightFilter);
2158
+ d.setRightFilter(tmpFilt);
2159
+ const tmpDetail = d.leftShowDetail;
2160
+ d.setLeftShowDetail(d.rightShowDetail);
2161
+ d.setRightShowDetail(tmpDetail);
2162
+ return;
2163
+ }
2164
+ if (d.splitMode && matchKey(d.kb.closePanel, input, key)) {
2165
+ if (d.activePanel === "left") {
2166
+ d.resetPanel("left");
2167
+ if (!d.rightSession) d.clearSplitState();
2168
+ } else if (d.activePanel === "right") {
2169
+ d.resetPanel("right");
2170
+ if (!d.leftSession) d.clearSplitState();
2171
+ }
2172
+ return;
2173
+ }
2174
+ if (matchKey(d.kb.detail, input, key) && d.selectedSession) {
2175
+ if (d.splitMode) {
2176
+ if (d.activePanel === "left") {
2177
+ d.setLeftShowDetail((v) => !v);
2178
+ return;
2179
+ }
2180
+ if (d.activePanel === "right") {
2181
+ d.setRightShowDetail((v) => !v);
2182
+ return;
2183
+ }
2184
+ if (d.activePanel === "sessions") {
2185
+ if (!d.leftSession) {
2186
+ d.setLeftSession(d.selectedSession);
2187
+ d.setLeftShowDetail(true);
2188
+ d.setActivePanel("left");
2189
+ } else if (!d.rightSession) {
2190
+ d.setRightSession(d.selectedSession);
2191
+ d.setRightShowDetail(true);
2192
+ d.setActivePanel("right");
2193
+ }
2194
+ return;
2195
+ }
2196
+ } else if (d.activePanel === "sessions") {
2197
+ d.setShowDetail(true);
2198
+ return;
2199
+ }
2200
+ }
2201
+ if (matchKey(d.kb.panelNext, input, key) || key.rightArrow) {
2202
+ d.switchPanel("next");
2203
+ return;
2204
+ }
2205
+ if (matchKey(d.kb.panelPrev, input, key) || key.leftArrow) {
2206
+ d.switchPanel("prev");
2207
+ return;
2208
+ }
2209
+ if (matchKey(d.kb.nickname, input, key) && d.selectedSession) {
2210
+ d.setInputMode("nickname");
2211
+ d.nicknameInput.start(d.selectedSession.nickname || "");
2212
+ return;
2213
+ }
2214
+ if (matchKey(d.kb.clearNickname, input, key) && d.selectedSession) {
2215
+ d.onClearNickname(d.selectedSession.sessionId);
2216
+ return;
2217
+ }
2218
+ if (matchKey(d.kb.filter, input, key)) {
2219
+ d.setInputMode("filter");
2220
+ d.filterInput.start(d.getActiveFilter());
2221
+ return;
2222
+ }
2223
+ if (key.escape) {
2224
+ if (d.activePanel === "sessions" && d.filter) {
2225
+ d.setFilter("");
2226
+ return;
2227
+ }
2228
+ if (d.activePanel === "activity" && d.activityFilter) {
2229
+ d.setActivityFilter("");
2230
+ return;
2231
+ }
2232
+ if (d.activePanel === "left" && d.leftFilter) {
2233
+ d.setLeftFilter("");
2234
+ return;
2235
+ }
2236
+ if (d.activePanel === "right" && d.rightFilter) {
2237
+ d.setRightFilter("");
2238
+ return;
2239
+ }
2240
+ return;
2241
+ }
2242
+ if (matchKey(d.kb.settings, input, key)) {
2243
+ d.setShowSettings(true);
2244
+ return;
2245
+ }
2246
+ if (matchKey(d.kb.viewArchive, input, key)) {
2247
+ d.setViewingArchive((v) => !v);
2248
+ return;
2249
+ }
2250
+ if (matchKey(d.kb.archive, input, key) && d.selectedSession) {
2251
+ if (d.viewingArchive) d.onUnarchive(d.selectedSession.sessionId);
2252
+ else d.onArchive(d.selectedSession.sessionId);
2253
+ return;
2254
+ }
2255
+ if (matchKey(d.kb.delete, input, key) && d.selectedSession) {
2256
+ d.onDelete(d.selectedSession);
2257
+ return;
2258
+ }
2259
+ if (matchKey(d.kb.update, input, key) && d.updateInfo?.available) {
2260
+ d.onUpdate();
2261
+ return;
2262
+ }
2263
+ if (d.activePanel === "sessions") {
2264
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.selectNext();
2265
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.selectPrev();
2266
+ }
2267
+ if (d.activePanel === "activity") {
2268
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.setActivityScroll((s) => Math.min(s + 1, d.maxScroll));
2269
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.setActivityScroll((s) => Math.max(s - 1, 0));
2270
+ if (matchKey(d.kb.scrollBottom, input, key)) d.setActivityScroll(0);
2271
+ if (matchKey(d.kb.scrollTop, input, key)) d.setActivityScroll(d.maxScroll);
2272
+ }
2273
+ if (d.activePanel === "left") {
2274
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.setLeftScroll((s) => Math.min(s + 1, d.leftMaxScroll));
2275
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.setLeftScroll((s) => Math.max(s - 1, 0));
2276
+ if (matchKey(d.kb.scrollBottom, input, key)) d.setLeftScroll(0);
2277
+ if (matchKey(d.kb.scrollTop, input, key)) d.setLeftScroll(d.leftMaxScroll);
2278
+ }
2279
+ if (d.activePanel === "right") {
2280
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.setRightScroll((s) => Math.min(s + 1, d.rightMaxScroll));
2281
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.setRightScroll((s) => Math.max(s - 1, 0));
2282
+ if (matchKey(d.kb.scrollBottom, input, key)) d.setRightScroll(0);
2283
+ if (matchKey(d.kb.scrollTop, input, key)) d.setRightScroll(d.rightMaxScroll);
2284
+ }
975
2285
  });
976
- useEffect5(() => {
977
- if (options.noUpdates || !config.updates.checkOnLaunch) return;
2286
+ };
2287
+
2288
+ // src/ui/hooks/useUpdateChecker.ts
2289
+ import { useState as useState10, useEffect as useEffect7 } from "react";
2290
+ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
2291
+ const [updateInfo, setUpdateInfo] = useState10(null);
2292
+ useEffect7(() => {
2293
+ if (disabled || !checkOnLaunch) return;
978
2294
  try {
979
2295
  const i = checkForUpdate();
980
2296
  if (i.available) setUpdateInfo(i);
@@ -986,19 +2302,117 @@ var App = ({ options, config, version, firstRun }) => {
986
2302
  if (i.available) setUpdateInfo(i);
987
2303
  } catch {
988
2304
  }
989
- }, config.updates.checkInterval);
2305
+ }, checkInterval);
990
2306
  return () => clearInterval(iv);
991
2307
  }, []);
2308
+ return updateInfo;
2309
+ };
2310
+
2311
+ // src/ui/App.tsx
2312
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2313
+ var App = ({ options, config: initialConfig, version, firstRun }) => {
2314
+ const { exit } = useApp();
2315
+ const { stdout } = useStdout3();
2316
+ const termHeight = stdout?.rows ?? 40;
2317
+ const [liveConfig, setLiveConfig] = useState11(initialConfig);
2318
+ const kb = liveConfig.keybindings;
2319
+ const [activePanel, setActivePanel] = useState11("sessions");
2320
+ const [activityScroll, setActivityScroll] = useState11(0);
2321
+ const [inputMode, setInputMode] = useState11("normal");
2322
+ const [showSetup, setShowSetup] = useState11(firstRun);
2323
+ const [filter, setFilter] = useState11("");
2324
+ const [activityFilter, setActivityFilter] = useState11("");
2325
+ const [updateStatus, setUpdateStatus] = useState11("");
2326
+ const [showDetail, setShowDetail] = useState11(false);
2327
+ const [showSettings, setShowSettings] = useState11(false);
2328
+ const [showThemeMenu, setShowThemeMenu] = useState11(false);
2329
+ const [viewingArchive, setViewingArchive] = useState11(false);
2330
+ const [confirmAction, setConfirmAction] = useState11(
2331
+ null
2332
+ );
2333
+ const [archivedIds, setArchivedIds] = useState11(() => new Set(Object.keys(getArchived())));
2334
+ const [splitMode, setSplitMode] = useState11(false);
2335
+ const [leftSession, setLeftSession] = useState11(null);
2336
+ const [rightSession, setRightSession] = useState11(null);
2337
+ const [leftScroll, setLeftScroll] = useState11(0);
2338
+ const [rightScroll, setRightScroll] = useState11(0);
2339
+ const [leftFilter, setLeftFilter] = useState11("");
2340
+ const [rightFilter, setRightFilter] = useState11("");
2341
+ const [leftShowDetail, setLeftShowDetail] = useState11(false);
2342
+ const [rightShowDetail, setRightShowDetail] = useState11(false);
2343
+ const refreshArchived = useCallback4(() => setArchivedIds(new Set(Object.keys(getArchived()))), []);
2344
+ const updateInfo = useUpdateChecker(
2345
+ options.noUpdates,
2346
+ liveConfig.updates.checkOnLaunch,
2347
+ liveConfig.updates.checkInterval
2348
+ );
2349
+ useEffect8(() => {
2350
+ applyTheme(resolveTheme(liveConfig.theme, liveConfig.customThemes));
2351
+ }, [liveConfig.theme, liveConfig.customThemes]);
2352
+ const { sessions, selectedSession, selectedIndex, selectNext, selectPrev, refresh } = useSessions(
2353
+ options.allUsers,
2354
+ filter || void 0,
2355
+ archivedIds,
2356
+ viewingArchive
2357
+ );
2358
+ const rawEvents = useActivityStream(splitMode ? null : selectedSession, options.allUsers);
2359
+ const leftRawEvents = useActivityStream(splitMode ? leftSession : null, options.allUsers);
2360
+ const rightRawEvents = useActivityStream(splitMode ? rightSession : null, options.allUsers);
2361
+ const events = useFilteredEvents(rawEvents, activityFilter);
2362
+ const leftEvents = useFilteredEvents(leftRawEvents, leftFilter);
2363
+ const rightEvents = useFilteredEvents(rightRawEvents, rightFilter);
2364
+ const { alerts } = useAlerts(!options.noSecurity, options.alertLevel, options.allUsers, liveConfig);
2365
+ const nicknameInput = useTextInput(
2366
+ (value) => {
2367
+ if (selectedSession && value.trim()) {
2368
+ setNickname(selectedSession.sessionId, value.trim());
2369
+ refresh();
2370
+ }
2371
+ setInputMode("normal");
2372
+ },
2373
+ () => setInputMode("normal")
2374
+ );
2375
+ const filterInput = useTextInput(
2376
+ (value) => {
2377
+ if (activePanel === "sessions") setFilter(value);
2378
+ else if (activePanel === "left") setLeftFilter(value);
2379
+ else if (activePanel === "right") setRightFilter(value);
2380
+ else setActivityFilter(value);
2381
+ setInputMode("normal");
2382
+ },
2383
+ () => {
2384
+ if (activePanel === "sessions") setFilter("");
2385
+ else if (activePanel === "left") setLeftFilter("");
2386
+ else if (activePanel === "right") setRightFilter("");
2387
+ else setActivityFilter("");
2388
+ setInputMode("normal");
2389
+ }
2390
+ );
2391
+ useEffect8(() => {
2392
+ purgeExpiredArchives();
2393
+ refreshArchived();
2394
+ }, []);
2395
+ useEffect8(() => {
2396
+ setActivityScroll(0);
2397
+ }, [selectedSession?.sessionId]);
992
2398
  const alertHeight = options.noSecurity ? 0 : 6;
993
2399
  const mainHeight = termHeight - 3 - alertHeight - 1 - (inputMode !== "normal" ? 1 : 0);
994
2400
  const viewportRows = mainHeight - 2;
995
- const maxScroll = Math.max(0, events.length - viewportRows);
996
- useEffect5(() => {
997
- setActivityScroll(0);
998
- }, [selectedSession?.sessionId]);
999
- const handleSetupComplete = useCallback3(
2401
+ const handleSettingsClose = useCallback4((c) => {
2402
+ setLiveConfig(c);
2403
+ saveConfig(c);
2404
+ setShowSettings(false);
2405
+ }, []);
2406
+ const handleThemeMenuClose = useCallback4((c) => {
2407
+ setLiveConfig(c);
2408
+ saveConfig(c);
2409
+ setShowThemeMenu(false);
2410
+ setShowSettings(true);
2411
+ }, []);
2412
+ const handleOpenThemeMenu = useCallback4(() => setShowThemeMenu(true), []);
2413
+ const handleSetupComplete = useCallback4(
1000
2414
  (results) => {
1001
- const nc = { ...config };
2415
+ const nc = { ...liveConfig };
1002
2416
  const [hc, mc] = results;
1003
2417
  if (hc === "yes") {
1004
2418
  try {
@@ -1017,137 +2431,237 @@ var App = ({ options, config, version, firstRun }) => {
1017
2431
  saveConfig(nc);
1018
2432
  setShowSetup(false);
1019
2433
  },
1020
- [config]
2434
+ [liveConfig]
1021
2435
  );
1022
- const switchPanel = useCallback3((_dir) => {
1023
- setActivePanel((p) => p === "sessions" ? "activity" : "sessions");
1024
- }, []);
1025
- useInput2((input, key) => {
1026
- if (showSetup) return;
1027
- if (inputMode === "nickname") {
1028
- nicknameInput.handleInput(input, key);
1029
- return;
1030
- }
1031
- if (inputMode === "filter") {
1032
- if (key.escape) {
1033
- setFilter("");
1034
- setInputMode("normal");
1035
- filterInput.cancel();
1036
- return;
2436
+ const switchPanel = useCallback4(
2437
+ (dir) => {
2438
+ if (splitMode) {
2439
+ const order = ["sessions", "left", "right"];
2440
+ setActivePanel((p) => {
2441
+ const idx = order.indexOf(p);
2442
+ if (idx === -1) return "sessions";
2443
+ return dir === "next" ? order[(idx + 1) % order.length] : order[(idx - 1 + order.length) % order.length];
2444
+ });
2445
+ } else {
2446
+ setActivePanel((p) => p === "sessions" ? "activity" : "sessions");
1037
2447
  }
1038
- filterInput.handleInput(input, key);
1039
- return;
1040
- }
1041
- if (matchKey(kb.quit, input, key)) {
1042
- exit();
1043
- return;
2448
+ },
2449
+ [splitMode]
2450
+ );
2451
+ const getActiveFilter = useCallback4(() => {
2452
+ if (activePanel === "sessions") return filter;
2453
+ if (activePanel === "left") return leftFilter;
2454
+ if (activePanel === "right") return rightFilter;
2455
+ return activityFilter;
2456
+ }, [activePanel, filter, leftFilter, rightFilter, activityFilter]);
2457
+ const clearSplitState = useCallback4(() => {
2458
+ setSplitMode(false);
2459
+ setLeftSession(null);
2460
+ setRightSession(null);
2461
+ setLeftScroll(0);
2462
+ setRightScroll(0);
2463
+ setLeftFilter("");
2464
+ setRightFilter("");
2465
+ setLeftShowDetail(false);
2466
+ setRightShowDetail(false);
2467
+ setActivePanel("sessions");
2468
+ }, []);
2469
+ const resetPanel = useCallback4((side) => {
2470
+ if (side === "left") {
2471
+ setLeftSession(null);
2472
+ setLeftScroll(0);
2473
+ setLeftFilter("");
2474
+ setLeftShowDetail(false);
2475
+ } else {
2476
+ setRightSession(null);
2477
+ setRightScroll(0);
2478
+ setRightFilter("");
2479
+ setRightShowDetail(false);
1044
2480
  }
1045
- if (showDetail) {
1046
- if (key.escape || key.return || key.leftArrow) {
1047
- setShowDetail(false);
2481
+ }, []);
2482
+ useKeyHandler({
2483
+ kb,
2484
+ activePanel,
2485
+ splitMode,
2486
+ inputMode,
2487
+ showSetup,
2488
+ showSettings: showSettings || showThemeMenu,
2489
+ showDetail,
2490
+ leftShowDetail,
2491
+ rightShowDetail,
2492
+ confirmAction,
2493
+ selectedSession,
2494
+ leftSession,
2495
+ rightSession,
2496
+ leftScroll,
2497
+ rightScroll,
2498
+ leftFilter,
2499
+ rightFilter,
2500
+ filter,
2501
+ activityFilter,
2502
+ viewingArchive,
2503
+ archivedIds,
2504
+ updateInfo,
2505
+ maxScroll: Math.max(0, events.length - viewportRows),
2506
+ leftMaxScroll: Math.max(0, leftEvents.length - viewportRows),
2507
+ rightMaxScroll: Math.max(0, rightEvents.length - viewportRows),
2508
+ exit,
2509
+ selectNext,
2510
+ selectPrev,
2511
+ refresh,
2512
+ switchPanel,
2513
+ clearSplitState,
2514
+ resetPanel,
2515
+ getActiveFilter,
2516
+ setActivePanel,
2517
+ setInputMode,
2518
+ setFilter,
2519
+ setActivityFilter,
2520
+ setLeftFilter,
2521
+ setRightFilter,
2522
+ setShowDetail,
2523
+ setLeftShowDetail,
2524
+ setRightShowDetail,
2525
+ setShowSettings,
2526
+ setViewingArchive,
2527
+ setSplitMode,
2528
+ setLeftSession,
2529
+ setRightSession,
2530
+ setLeftScroll,
2531
+ setRightScroll,
2532
+ setActivityScroll,
2533
+ setConfirmAction,
2534
+ setUpdateStatus,
2535
+ nicknameInput,
2536
+ filterInput,
2537
+ onNickname: (id) => {
2538
+ clearNickname(id);
2539
+ refresh();
2540
+ },
2541
+ onClearNickname: (id) => {
2542
+ clearNickname(id);
2543
+ refresh();
2544
+ },
2545
+ onArchive: (id) => {
2546
+ archiveSession(id);
2547
+ refreshArchived();
2548
+ refresh();
2549
+ },
2550
+ onUnarchive: (id) => {
2551
+ unarchiveSession(id);
2552
+ refreshArchived();
2553
+ refresh();
2554
+ },
2555
+ onDelete: (sess) => setConfirmAction({
2556
+ title: "Delete session?",
2557
+ message: `Delete ${sess.nickname || sess.slug}? Output files will be removed.`,
2558
+ onConfirm: () => {
2559
+ deleteSessionFiles(sess.outputFiles);
2560
+ clearNickname(sess.sessionId);
2561
+ if (archivedIds.has(sess.sessionId)) {
2562
+ unarchiveSession(sess.sessionId);
2563
+ refreshArchived();
2564
+ }
2565
+ refresh();
2566
+ setConfirmAction(null);
1048
2567
  }
1049
- return;
1050
- }
1051
- if (matchKey(kb.detail, input, key) && selectedSession && activePanel === "sessions") {
1052
- setShowDetail(true);
1053
- return;
1054
- }
1055
- if (matchKey(kb.panelNext, input, key) || key.rightArrow) {
1056
- switchPanel("next");
1057
- return;
1058
- }
1059
- if (matchKey(kb.panelPrev, input, key) || key.leftArrow) {
1060
- switchPanel("prev");
1061
- return;
1062
- }
1063
- if (matchKey(kb.nickname, input, key) && selectedSession) {
1064
- setInputMode("nickname");
1065
- nicknameInput.start(selectedSession.nickname || "");
1066
- return;
1067
- }
1068
- if (matchKey(kb.clearNickname, input, key) && selectedSession) {
1069
- clearNickname(selectedSession.sessionId);
1070
- return;
1071
- }
1072
- if (matchKey(kb.filter, input, key)) {
1073
- setInputMode("filter");
1074
- filterInput.start(filter);
1075
- return;
1076
- }
1077
- if (key.escape && filter) {
1078
- setFilter("");
1079
- return;
1080
- }
1081
- if (matchKey(kb.update, input, key) && updateInfo?.available) {
2568
+ }),
2569
+ onUpdate: () => {
1082
2570
  setUpdateStatus("updating...");
1083
- installUpdate().then(() => setUpdateStatus(`updated to v${updateInfo.latest} \u2014 restart to apply`)).catch(() => setUpdateStatus("update failed"));
1084
- return;
1085
- }
1086
- if (activePanel === "sessions") {
1087
- if (matchKey(kb.navDown, input, key) || key.downArrow) selectNext();
1088
- if (matchKey(kb.navUp, input, key) || key.upArrow) selectPrev();
1089
- }
1090
- if (activePanel === "activity") {
1091
- if (matchKey(kb.navUp, input, key) || key.upArrow) setActivityScroll((s) => Math.min(s + 1, maxScroll));
1092
- if (matchKey(kb.navDown, input, key) || key.downArrow) setActivityScroll((s) => Math.max(s - 1, 0));
1093
- if (matchKey(kb.scrollBottom, input, key) || key.end) setActivityScroll(0);
1094
- if (matchKey(kb.scrollTop, input, key) || key.home) setActivityScroll(maxScroll);
2571
+ installUpdate().then(() => setUpdateStatus(`updated to v${updateInfo?.latest} \u2014 restart to apply`)).catch(() => setUpdateStatus("update failed"));
1095
2572
  }
1096
2573
  });
1097
2574
  if (showSetup) {
1098
- const steps = [];
1099
- if (config.prompts.hook === "pending")
1100
- steps.push({
1101
- title: "Install Claude Code hook?",
1102
- description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time."
1103
- });
1104
- if (config.prompts.mcp === "pending")
1105
- steps.push({
1106
- title: "Install MCP server?",
1107
- description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts."
1108
- });
2575
+ const steps = [
2576
+ ...liveConfig.prompts.hook === "pending" ? [
2577
+ {
2578
+ title: "Install Claude Code hook?",
2579
+ description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time."
2580
+ }
2581
+ ] : [],
2582
+ ...liveConfig.prompts.mcp === "pending" ? [
2583
+ {
2584
+ title: "Install MCP server?",
2585
+ description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts."
2586
+ }
2587
+ ] : []
2588
+ ];
1109
2589
  if (steps.length === 0) {
1110
2590
  setShowSetup(false);
1111
2591
  return null;
1112
2592
  }
1113
- return /* @__PURE__ */ jsx8(SetupModal, { steps, onComplete: handleSetupComplete });
2593
+ return /* @__PURE__ */ jsx13(SetupModal, { steps, onComplete: handleSetupComplete });
2594
+ }
2595
+ if (showThemeMenu) return /* @__PURE__ */ jsx13(ThemeMenu, { config: liveConfig, onClose: handleThemeMenuClose });
2596
+ if (showSettings)
2597
+ return /* @__PURE__ */ jsx13(SettingsMenu, { config: liveConfig, onClose: handleSettingsClose, onOpenThemeMenu: handleOpenThemeMenu });
2598
+ if (confirmAction) {
2599
+ return /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx13(
2600
+ ConfirmModal,
2601
+ {
2602
+ title: confirmAction.title,
2603
+ message: confirmAction.message,
2604
+ onConfirm: confirmAction.onConfirm,
2605
+ onCancel: () => setConfirmAction(null)
2606
+ }
2607
+ ) });
1114
2608
  }
1115
- const rightPanel = showDetail && selectedSession ? /* @__PURE__ */ jsx8(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx8(
2609
+ const filterLabel = activePanel === "sessions" ? "sessions" : activePanel === "left" ? "left" : activePanel === "right" ? "right" : "activity";
2610
+ const rightPanel = splitMode ? /* @__PURE__ */ jsx13(
2611
+ SplitPanel,
2612
+ {
2613
+ activePanel,
2614
+ leftSession,
2615
+ rightSession,
2616
+ leftEvents,
2617
+ rightEvents,
2618
+ leftScroll,
2619
+ rightScroll,
2620
+ leftFilter,
2621
+ rightFilter,
2622
+ leftShowDetail,
2623
+ rightShowDetail,
2624
+ height: mainHeight
2625
+ }
2626
+ ) : showDetail && selectedSession ? /* @__PURE__ */ jsx13(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx13(
1116
2627
  ActivityFeed,
1117
2628
  {
1118
2629
  events,
1119
2630
  sessionSlug: selectedSession?.slug ?? null,
1120
2631
  focused: activePanel === "activity",
1121
2632
  height: mainHeight,
1122
- scrollOffset: activityScroll
2633
+ scrollOffset: activityScroll,
2634
+ filter: activityFilter || void 0
1123
2635
  }
1124
2636
  );
1125
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", height: termHeight, children: [
1126
- /* @__PURE__ */ jsx8(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
1127
- /* @__PURE__ */ jsxs8(Box8, { flexGrow: 1, height: mainHeight, children: [
1128
- /* @__PURE__ */ jsx8(
2637
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", height: termHeight, children: [
2638
+ /* @__PURE__ */ jsx13(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
2639
+ /* @__PURE__ */ jsxs13(Box13, { flexGrow: 1, height: mainHeight, children: [
2640
+ /* @__PURE__ */ jsx13(
1129
2641
  SessionList,
1130
2642
  {
1131
2643
  sessions,
1132
2644
  selectedIndex,
1133
2645
  focused: activePanel === "sessions",
1134
- filter: filter || void 0
2646
+ filter: filter || void 0,
2647
+ viewingArchive
1135
2648
  }
1136
2649
  ),
1137
2650
  rightPanel
1138
2651
  ] }),
1139
- !options.noSecurity && /* @__PURE__ */ jsx8(AlertBar, { alerts }),
1140
- inputMode === "nickname" && /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
1141
- /* @__PURE__ */ jsx8(Text8, { color: colors.primary, children: "nickname: " }),
1142
- /* @__PURE__ */ jsx8(Text8, { color: colors.bright, children: nicknameInput.value }),
1143
- /* @__PURE__ */ jsx8(Text8, { color: colors.muted, children: "_" })
2652
+ !options.noSecurity && /* @__PURE__ */ jsx13(AlertBar, { alerts }),
2653
+ inputMode === "nickname" && /* @__PURE__ */ jsxs13(Box13, { paddingX: 1, children: [
2654
+ /* @__PURE__ */ jsx13(Text12, { color: colors.primary, children: "nickname: " }),
2655
+ /* @__PURE__ */ jsx13(Text12, { color: colors.bright, children: nicknameInput.value }),
2656
+ /* @__PURE__ */ jsx13(Text12, { color: colors.muted, children: "_" })
1144
2657
  ] }),
1145
- inputMode === "filter" && /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
1146
- /* @__PURE__ */ jsx8(Text8, { color: colors.primary, children: "/" }),
1147
- /* @__PURE__ */ jsx8(Text8, { color: colors.bright, children: filterInput.value }),
1148
- /* @__PURE__ */ jsx8(Text8, { color: colors.muted, children: "_" })
2658
+ inputMode === "filter" && /* @__PURE__ */ jsxs13(Box13, { paddingX: 1, children: [
2659
+ /* @__PURE__ */ jsx13(Text12, { color: colors.muted, children: filterLabel }),
2660
+ /* @__PURE__ */ jsx13(Text12, { color: colors.primary, children: "/" }),
2661
+ /* @__PURE__ */ jsx13(Text12, { color: colors.bright, children: filterInput.value }),
2662
+ /* @__PURE__ */ jsx13(Text12, { color: colors.muted, children: "_" })
1149
2663
  ] }),
1150
- inputMode === "normal" && /* @__PURE__ */ jsx8(FooterBar, { keybindings: kb, updateStatus })
2664
+ inputMode === "normal" && /* @__PURE__ */ jsx13(FooterBar, { keybindings: kb, updateStatus, viewingArchive, splitMode })
1151
2665
  ] });
1152
2666
  };
1153
2667
 
@@ -1384,7 +2898,7 @@ var main = () => {
1384
2898
  if (options.noUpdates) config.updates.checkOnLaunch = false;
1385
2899
  if (options.noSecurity) config.security.enabled = false;
1386
2900
  if (firstRun) saveConfig(config);
1387
- render(React9.createElement(App, { options, config, version: VERSION, firstRun }));
2901
+ render(React14.createElement(App, { options, config, version: VERSION, firstRun }));
1388
2902
  };
1389
2903
  main();
1390
2904
  //# sourceMappingURL=index.js.map