agenttop 0.4.0 → 0.7.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-H2JOTO54.js";
18
+ startMcpServer,
19
+ unarchiveSession
20
+ } from "./chunk-FCCWHOJO.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 React10 from "react";
26
+ import React16 from "react";
22
27
  import { render } from "ink";
23
28
 
24
29
  // src/ui/App.tsx
25
- import { useState as useState8, useEffect as useEffect5, useCallback as useCallback3 } from "react";
26
- import { Box as Box9, Text as Text9, useApp, useInput as useInput3, useStdout as useStdout2 } from "ink";
30
+ import { useState as useState13, useEffect as useEffect9, useCallback as useCallback4 } from "react";
31
+ import { Box as Box15, Text as Text14, 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";
@@ -286,76 +613,82 @@ var formatTokens = (n) => {
286
613
  var truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
287
614
  var SIDEBAR_WIDTH = 28;
288
615
  var INNER_WIDTH = SIDEBAR_WIDTH - 4;
289
- var SessionList = React2.memo(({ sessions, selectedIndex, focused, filter }) => {
290
- return /* @__PURE__ */ jsxs2(
291
- Box2,
292
- {
293
- flexDirection: "column",
294
- width: SIDEBAR_WIDTH,
295
- borderStyle: "single",
296
- borderColor: focused ? colors.primary : colors.border,
297
- overflow: "hidden",
298
- children: [
299
- /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
300
- /* @__PURE__ */ jsx2(Text2, { color: colors.header, bold: true, children: "SESSIONS" }),
301
- filter && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
302
- " [",
303
- truncate(filter, 10),
304
- "]"
305
- ] })
306
- ] }),
307
- sessions.length === 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: colors.muted, italic: true, children: filter ? "No matches" : "No sessions" }) }),
308
- sessions.map((session, i) => {
309
- const isSelected = i === selectedIndex;
310
- const indicator = isSelected ? ">" : " ";
311
- const nameMaxLen = INNER_WIDTH - 2;
312
- const displayName = truncate(session.nickname || session.slug, nameMaxLen);
313
- const totalIn = session.usage.inputTokens + session.usage.cacheReadTokens;
314
- const proj = formatProject(session.project, 12);
315
- const model = formatModel(session.model);
316
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
317
- /* @__PURE__ */ jsxs2(
318
- Text2,
319
- {
320
- color: isSelected ? colors.bright : colors.text,
321
- bold: isSelected,
322
- backgroundColor: isSelected ? colors.selected : void 0,
323
- wrap: "truncate",
324
- children: [
325
- indicator,
326
- " ",
327
- displayName
328
- ]
329
- }
330
- ),
331
- session.nickname && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
332
- " ",
333
- truncate(session.slug, nameMaxLen)
334
- ] }),
335
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
336
- " ",
337
- proj,
338
- " ",
339
- model,
340
- " ",
341
- session.agentCount,
342
- "ag"
343
- ] }),
344
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
345
- " ",
346
- formatTokens(totalIn),
347
- "in ",
348
- formatTokens(session.usage.outputTokens),
349
- "out ",
350
- session.cpu,
351
- "%"
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
+ "]"
352
634
  ] })
353
- ] }, session.sessionId);
354
- })
355
- ]
356
- }
357
- );
358
- });
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
+ );
359
692
 
360
693
  // src/ui/components/ActivityFeed.tsx
361
694
  import React3 from "react";
@@ -388,7 +721,7 @@ var summarizeInput = (call) => {
388
721
  }
389
722
  };
390
723
  var ActivityFeed = React3.memo(
391
- ({ events, sessionSlug, focused, height, scrollOffset }) => {
724
+ ({ events, sessionSlug, focused, height, scrollOffset, filter }) => {
392
725
  const viewportRows = height - 2;
393
726
  const totalEvents = events.length;
394
727
  const start = Math.max(0, totalEvents - viewportRows - scrollOffset);
@@ -412,6 +745,11 @@ var ActivityFeed = React3.memo(
412
745
  " (",
413
746
  sessionSlug,
414
747
  ")"
748
+ ] }),
749
+ filter && /* @__PURE__ */ jsxs3(Text3, { color: colors.muted, children: [
750
+ " [",
751
+ filter.length > 10 ? filter.slice(0, 9) + "\u2026" : filter,
752
+ "]"
415
753
  ] })
416
754
  ] }),
417
755
  focused && canScroll && !isAtBottom && /* @__PURE__ */ jsxs3(Text3, { color: colors.muted, children: [
@@ -662,49 +1000,81 @@ var SetupModal = React6.memo(({ steps, onComplete }) => {
662
1000
  // src/ui/components/FooterBar.tsx
663
1001
  import React7 from "react";
664
1002
  import { Box as Box7, Text as Text7 } from "ink";
665
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1003
+ import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
666
1004
  var label = (key) => {
667
1005
  if (key === "tab") return "tab";
668
1006
  if (key === "shift+tab") return "S-tab";
669
1007
  if (key === "enter") return "enter";
670
1008
  return key;
671
1009
  };
672
- var FooterBar = React7.memo(({ keybindings, updateStatus }) => /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
673
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
674
- label(keybindings.quit),
675
- ":quit"
676
- ] }) }),
677
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
678
- label(keybindings.navDown),
679
- "/",
680
- label(keybindings.navUp),
681
- ":nav"
682
- ] }) }),
683
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
684
- label(keybindings.panelNext),
685
- ":panel"
686
- ] }) }),
687
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
688
- label(keybindings.filter),
689
- ":filter"
690
- ] }) }),
691
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
692
- label(keybindings.nickname),
693
- ":name"
694
- ] }) }),
695
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
696
- label(keybindings.detail),
697
- ":detail"
698
- ] }) }),
699
- /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
700
- label(keybindings.settings),
701
- ":settings"
702
- ] }) }),
703
- updateStatus && /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsx7(Text7, { color: colors.secondary, children: updateStatus }) })
704
- ] }));
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
+ );
705
1075
 
706
1076
  // src/ui/components/SettingsMenu.tsx
707
- import React8, { useState as useState3, useMemo } from "react";
1077
+ import React8, { useState as useState3, useMemo, useEffect as useEffect2, useRef } from "react";
708
1078
  import { Box as Box8, Text as Text8, useInput as useInput2, useStdout } from "ink";
709
1079
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
710
1080
  var KEYBIND_LABELS = {
@@ -715,12 +1085,20 @@ var KEYBIND_LABELS = {
715
1085
  panelPrev: "Previous panel",
716
1086
  scrollTop: "Scroll to top",
717
1087
  scrollBottom: "Scroll to bottom",
718
- filter: "Filter sessions",
1088
+ filter: "Filter",
719
1089
  nickname: "Set nickname",
720
1090
  clearNickname: "Clear nickname",
721
1091
  detail: "Detail view",
722
1092
  update: "Install update",
723
- settings: "Settings"
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"
724
1102
  };
725
1103
  var RULE_LABELS = {
726
1104
  network: "Network detection",
@@ -729,7 +1107,32 @@ var RULE_LABELS = {
729
1107
  shellEscape: "Shell escape",
730
1108
  injection: "Prompt injection"
731
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
+ };
732
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`;
733
1136
  var displayKey = (key) => {
734
1137
  if (key === "tab") return "tab";
735
1138
  if (key === "shift+tab") return "S-tab";
@@ -738,6 +1141,27 @@ var displayKey = (key) => {
738
1141
  };
739
1142
  var buildMenuItems = () => {
740
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
+ });
741
1165
  items.push({ type: "header", label: "KEYBINDINGS", section: "keybindings", getValue: () => "", key: void 0 });
742
1166
  for (const [k, label2] of Object.entries(KEYBIND_LABELS)) {
743
1167
  const kbKey = k;
@@ -747,12 +1171,17 @@ var buildMenuItems = () => {
747
1171
  section: "keybindings",
748
1172
  key: kbKey,
749
1173
  getValue: (cfg) => displayKey(cfg.keybindings[kbKey]),
750
- apply: (cfg, newValue) => ({
751
- ...cfg,
752
- keybindings: { ...cfg.keybindings, [kbKey]: newValue }
753
- })
1174
+ apply: (cfg, newValue) => ({ ...cfg, keybindings: { ...cfg.keybindings, [kbKey]: newValue } })
754
1175
  });
755
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
+ });
756
1185
  items.push({ type: "header", label: "SECURITY RULES", section: "security", getValue: () => "", key: void 0 });
757
1186
  for (const [k, label2] of Object.entries(RULE_LABELS)) {
758
1187
  const ruleKey = k;
@@ -764,30 +1193,18 @@ var buildMenuItems = () => {
764
1193
  getValue: (cfg) => cfg.security.rules[ruleKey] ? "ON" : "OFF",
765
1194
  apply: (cfg) => ({
766
1195
  ...cfg,
767
- security: {
768
- ...cfg.security,
769
- rules: { ...cfg.security.rules, [ruleKey]: !cfg.security.rules[ruleKey] }
770
- }
1196
+ security: { ...cfg.security, rules: { ...cfg.security.rules, [ruleKey]: !cfg.security.rules[ruleKey] } }
771
1197
  })
772
1198
  });
773
1199
  }
774
- items.push({
775
- type: "header",
776
- label: "NOTIFICATIONS",
777
- section: "notifications",
778
- getValue: () => "",
779
- key: void 0
780
- });
1200
+ items.push({ type: "header", label: "NOTIFICATIONS", section: "notifications", getValue: () => "", key: void 0 });
781
1201
  items.push({
782
1202
  type: "toggle",
783
1203
  label: "Terminal bell",
784
1204
  section: "notifications",
785
1205
  key: "bell",
786
1206
  getValue: (cfg) => cfg.notifications.bell ? "ON" : "OFF",
787
- apply: (cfg) => ({
788
- ...cfg,
789
- notifications: { ...cfg.notifications, bell: !cfg.notifications.bell }
790
- })
1207
+ apply: (cfg) => ({ ...cfg, notifications: { ...cfg.notifications, bell: !cfg.notifications.bell } })
791
1208
  });
792
1209
  items.push({
793
1210
  type: "toggle",
@@ -795,10 +1212,7 @@ var buildMenuItems = () => {
795
1212
  section: "notifications",
796
1213
  key: "desktop",
797
1214
  getValue: (cfg) => cfg.notifications.desktop ? "ON" : "OFF",
798
- apply: (cfg) => ({
799
- ...cfg,
800
- notifications: { ...cfg.notifications, desktop: !cfg.notifications.desktop }
801
- })
1215
+ apply: (cfg) => ({ ...cfg, notifications: { ...cfg.notifications, desktop: !cfg.notifications.desktop } })
802
1216
  });
803
1217
  items.push({
804
1218
  type: "cycle",
@@ -808,20 +1222,35 @@ var buildMenuItems = () => {
808
1222
  getValue: (cfg) => cfg.notifications.minSeverity,
809
1223
  apply: (cfg) => {
810
1224
  const idx = SEVERITY_OPTIONS.indexOf(cfg.notifications.minSeverity);
811
- const next = SEVERITY_OPTIONS[(idx + 1) % SEVERITY_OPTIONS.length];
812
- return { ...cfg, notifications: { ...cfg.notifications, minSeverity: next } };
1225
+ return {
1226
+ ...cfg,
1227
+ notifications: { ...cfg.notifications, minSeverity: SEVERITY_OPTIONS[(idx + 1) % SEVERITY_OPTIONS.length] }
1228
+ };
813
1229
  }
814
1230
  });
815
1231
  return items;
816
1232
  };
817
1233
  var MENU_ITEMS = buildMenuItems();
818
1234
  var SELECTABLE_INDICES = MENU_ITEMS.map((item, i) => item.type !== "header" ? i : -1).filter((i) => i >= 0);
819
- var SettingsMenu = React8.memo(({ config, onClose }) => {
1235
+ var SettingsMenu = React8.memo(({ config, onClose, onOpenThemeMenu }) => {
820
1236
  const { stdout } = useStdout();
821
1237
  const termHeight = stdout?.rows ?? 40;
822
1238
  const [localConfig, setLocalConfig] = useState3(() => JSON.parse(JSON.stringify(config)));
823
1239
  const [selectablePos, setSelectablePos] = useState3(0);
824
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
+ );
825
1254
  const selectedIndex = SELECTABLE_INDICES[selectablePos];
826
1255
  const maxLabelLen = useMemo(
827
1256
  () => Math.max(...MENU_ITEMS.filter((i) => i.type !== "header").map((i) => i.label.length)),
@@ -837,11 +1266,24 @@ var SettingsMenu = React8.memo(({ config, onClose }) => {
837
1266
  else if (key.escape) {
838
1267
  setRebinding(false);
839
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;
840
1276
  } else if (input && input.length === 1) newKey = input;
841
1277
  else return;
842
- if (item.apply) {
843
- setLocalConfig((c) => item.apply(c, newKey));
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;
844
1285
  }
1286
+ if (item.apply) setLocalConfig((c) => item.apply(c, newKey));
845
1287
  setRebinding(false);
846
1288
  return;
847
1289
  }
@@ -859,11 +1301,17 @@ var SettingsMenu = React8.memo(({ config, onClose }) => {
859
1301
  }
860
1302
  if (key.return) {
861
1303
  const item = MENU_ITEMS[selectedIndex];
862
- if (item.type === "keybind") {
863
- setRebinding(true);
864
- } else if (item.apply) {
865
- setLocalConfig((c) => item.apply(c));
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;
866
1312
  }
1313
+ if (item.type === "keybind") setRebinding(true);
1314
+ else if (item.apply) setLocalConfig((c) => item.apply(c));
867
1315
  }
868
1316
  });
869
1317
  const contentHeight = termHeight - 6;
@@ -884,7 +1332,10 @@ var SettingsMenu = React8.memo(({ config, onClose }) => {
884
1332
  children: [
885
1333
  /* @__PURE__ */ jsxs8(Box8, { justifyContent: "space-between", marginBottom: 1, children: [
886
1334
  /* @__PURE__ */ jsx8(Text8, { color: colors.header, bold: true, children: "SETTINGS" }),
887
- /* @__PURE__ */ jsx8(Text8, { color: colors.muted, children: "esc to save & close" })
1335
+ /* @__PURE__ */ jsxs8(Text8, { color: colors.muted, children: [
1336
+ "esc to save & close ",
1337
+ rebinding ? "| backspace to reset" : ""
1338
+ ] })
888
1339
  ] }),
889
1340
  visibleItems.map((item, vi) => {
890
1341
  const realIndex = visibleStartIndex + vi;
@@ -896,36 +1347,579 @@ var SettingsMenu = React8.memo(({ config, onClose }) => {
896
1347
  ] }) }, `h-${item.label}`);
897
1348
  }
898
1349
  const value = item.getValue(localConfig);
899
- const dots = ".".repeat(Math.max(2, maxLabelLen - item.label.length + 4));
900
1350
  const isRebindingThis = rebinding && isSelected;
901
1351
  const displayValue = isRebindingThis ? "[press key...]" : value;
902
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)) + " ";
903
1355
  return /* @__PURE__ */ jsxs8(Box8, { children: [
904
1356
  /* @__PURE__ */ jsxs8(Text8, { color: isSelected ? colors.primary : colors.text, children: [
905
1357
  isSelected ? "> " : " ",
906
- " ",
1358
+ " ",
907
1359
  item.label,
908
1360
  " ",
909
- dots,
910
- " "
1361
+ dots
911
1362
  ] }),
912
- /* @__PURE__ */ jsx8(Text8, { color: valueColor, children: displayValue })
1363
+ !isReset && /* @__PURE__ */ jsx8(Text8, { color: valueColor, children: displayValue })
913
1364
  ] }, `${item.section}-${item.key}`);
914
- })
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 }) })
915
1712
  ]
916
1713
  }
917
1714
  ) });
918
1715
  });
919
1716
 
1717
+ // src/ui/components/ThemePickerModal.tsx
1718
+ import React11, { useState as useState6, useEffect as useEffect4 } 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 ThemePickerModal = React11.memo(({ onSelect, onSkip, onDismiss }) => {
1722
+ const [selectedIndex, setSelectedIndex] = useState6(0);
1723
+ const themes = BUILTIN_THEMES;
1724
+ useEffect4(() => {
1725
+ applyTheme(themes[selectedIndex]);
1726
+ }, [selectedIndex]);
1727
+ useInput5((input, key) => {
1728
+ if (key.upArrow) setSelectedIndex((i) => Math.max(0, i - 1));
1729
+ if (key.downArrow) setSelectedIndex((i) => Math.min(themes.length - 1, i + 1));
1730
+ if (key.return) onSelect(themes[selectedIndex].name);
1731
+ if (input === "n") onSkip();
1732
+ if (input === "d") onDismiss();
1733
+ });
1734
+ const renderSwatch = (theme) => {
1735
+ const c = theme.colors;
1736
+ return /* @__PURE__ */ jsxs11(Text11, { children: [
1737
+ /* @__PURE__ */ jsx11(Text11, { color: c.primary, children: "\u2588" }),
1738
+ /* @__PURE__ */ jsx11(Text11, { color: c.secondary, children: "\u2588" }),
1739
+ /* @__PURE__ */ jsx11(Text11, { color: c.accent, children: "\u2588" }),
1740
+ /* @__PURE__ */ jsx11(Text11, { color: c.warning, children: "\u2588" }),
1741
+ /* @__PURE__ */ jsx11(Text11, { color: c.error, children: "\u2588" })
1742
+ ] });
1743
+ };
1744
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 4, paddingY: 1, children: /* @__PURE__ */ jsxs11(Box11, { borderStyle: "round", borderColor: colors.primary, flexDirection: "column", paddingX: 3, paddingY: 1, children: [
1745
+ /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: colors.header, bold: true, children: "Choose a theme" }) }),
1746
+ /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: colors.text, children: "Select a theme to get started. You can change this later in settings (s)." }) }),
1747
+ themes.map((theme, i) => /* @__PURE__ */ jsxs11(Box11, { children: [
1748
+ /* @__PURE__ */ jsx11(Text11, { color: i === selectedIndex ? colors.primary : colors.muted, children: i === selectedIndex ? "> " : " " }),
1749
+ renderSwatch(theme),
1750
+ /* @__PURE__ */ jsxs11(Text11, { color: i === selectedIndex ? colors.bright : colors.text, children: [
1751
+ " ",
1752
+ theme.name
1753
+ ] })
1754
+ ] }, theme.name)),
1755
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(Text11, { color: colors.muted, children: "Enter = select | n = not now | d = don't ask again" }) })
1756
+ ] }) });
1757
+ });
1758
+
1759
+ // src/ui/components/GuidedTour.tsx
1760
+ import React12, { useState as useState7 } from "react";
1761
+ import { Box as Box12, Text as Text12, useInput as useInput6 } from "ink";
1762
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1763
+ var STEPS = [
1764
+ {
1765
+ title: "Session list",
1766
+ body: "The left panel shows all active Claude Code sessions. Use j/k to navigate and Enter to view details."
1767
+ },
1768
+ {
1769
+ title: "Activity feed",
1770
+ body: "The right panel streams tool calls in real-time. Press Tab to switch between panels."
1771
+ },
1772
+ {
1773
+ title: "Split view",
1774
+ body: "Press x to split the activity panel. Pin sessions to left (1) or right (2) panels. Press S to swap."
1775
+ },
1776
+ {
1777
+ title: "Filtering",
1778
+ body: "Press / to filter sessions or activity by keyword. Works in any panel."
1779
+ },
1780
+ {
1781
+ title: "Session management",
1782
+ body: "Press n to nickname a session, a to archive it, d to delete it. Press A to view archived sessions."
1783
+ },
1784
+ {
1785
+ title: "Security alerts",
1786
+ body: "The alert bar highlights suspicious tool calls like network access or sensitive file reads."
1787
+ },
1788
+ {
1789
+ title: "Themes & settings",
1790
+ body: "Press s to open settings. Customise keybindings, manage themes, and configure updates."
1791
+ }
1792
+ ];
1793
+ var GuidedTour = React12.memo(({ onComplete, onSkip }) => {
1794
+ const [stepIndex, setStepIndex] = useState7(0);
1795
+ const step = STEPS[stepIndex];
1796
+ const isLast = stepIndex === STEPS.length - 1;
1797
+ useInput6((input, key) => {
1798
+ if (key.return || key.rightArrow) {
1799
+ if (isLast) onComplete();
1800
+ else setStepIndex((i) => i + 1);
1801
+ }
1802
+ if (key.leftArrow && stepIndex > 0) setStepIndex((i) => i - 1);
1803
+ if (input === "q" || key.escape) onSkip();
1804
+ });
1805
+ return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", paddingX: 4, paddingY: 1, children: /* @__PURE__ */ jsxs12(Box12, { borderStyle: "round", borderColor: colors.primary, flexDirection: "column", paddingX: 3, paddingY: 1, children: [
1806
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Text12, { color: colors.header, bold: true, children: [
1807
+ "Quick tour (",
1808
+ stepIndex + 1,
1809
+ "/",
1810
+ STEPS.length,
1811
+ ")"
1812
+ ] }) }),
1813
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { color: colors.bright, bold: true, children: step.title }) }),
1814
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { color: colors.text, children: step.body }) }),
1815
+ /* @__PURE__ */ jsxs12(Text12, { color: colors.muted, children: [
1816
+ isLast ? "Enter = finish" : "Enter/\u2192 = next",
1817
+ " | ",
1818
+ stepIndex > 0 ? "\u2190 = back | " : "",
1819
+ "q = skip tour"
1820
+ ] })
1821
+ ] }) });
1822
+ });
1823
+
1824
+ // src/ui/components/ConfirmModal.tsx
1825
+ import React13 from "react";
1826
+ import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
1827
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1828
+ var ConfirmModal = React13.memo(({ title, message, onConfirm, onCancel }) => {
1829
+ useInput7((input, key) => {
1830
+ if (input === "y" || input === "Y") {
1831
+ onConfirm();
1832
+ } else if (input === "n" || input === "N" || key.escape) {
1833
+ onCancel();
1834
+ }
1835
+ });
1836
+ return /* @__PURE__ */ jsxs13(
1837
+ Box13,
1838
+ {
1839
+ borderStyle: "round",
1840
+ borderColor: colors.warning,
1841
+ flexDirection: "column",
1842
+ paddingX: 2,
1843
+ paddingY: 1,
1844
+ alignSelf: "center",
1845
+ children: [
1846
+ /* @__PURE__ */ jsx13(Text13, { color: colors.warning, bold: true, children: title }),
1847
+ /* @__PURE__ */ jsx13(Text13, { color: colors.text, children: message }),
1848
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.muted, children: "[y] confirm [n/esc] cancel" }) })
1849
+ ]
1850
+ }
1851
+ );
1852
+ });
1853
+
1854
+ // src/ui/components/SplitPanel.tsx
1855
+ import React14 from "react";
1856
+ import { Box as Box14 } from "ink";
1857
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1858
+ var SplitPanel = React14.memo(
1859
+ ({
1860
+ activePanel,
1861
+ leftSession,
1862
+ rightSession,
1863
+ leftEvents,
1864
+ rightEvents,
1865
+ leftScroll,
1866
+ rightScroll,
1867
+ leftFilter,
1868
+ rightFilter,
1869
+ leftShowDetail,
1870
+ rightShowDetail,
1871
+ height
1872
+ }) => {
1873
+ const left = leftShowDetail && leftSession ? /* @__PURE__ */ jsx14(SessionDetail, { session: leftSession, focused: activePanel === "left", height }) : /* @__PURE__ */ jsx14(
1874
+ ActivityFeed,
1875
+ {
1876
+ events: leftEvents,
1877
+ sessionSlug: leftSession?.slug ?? null,
1878
+ focused: activePanel === "left",
1879
+ height,
1880
+ scrollOffset: leftScroll,
1881
+ filter: leftFilter || void 0
1882
+ }
1883
+ );
1884
+ const right = rightShowDetail && rightSession ? /* @__PURE__ */ jsx14(SessionDetail, { session: rightSession, focused: activePanel === "right", height }) : /* @__PURE__ */ jsx14(
1885
+ ActivityFeed,
1886
+ {
1887
+ events: rightEvents,
1888
+ sessionSlug: rightSession?.slug ?? null,
1889
+ focused: activePanel === "right",
1890
+ height,
1891
+ scrollOffset: rightScroll,
1892
+ filter: rightFilter || void 0
1893
+ }
1894
+ );
1895
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "row", flexGrow: 1, children: [
1896
+ /* @__PURE__ */ jsx14(
1897
+ Box14,
1898
+ {
1899
+ flexGrow: 1,
1900
+ borderStyle: "single",
1901
+ borderRight: true,
1902
+ borderTop: false,
1903
+ borderBottom: false,
1904
+ borderLeft: false,
1905
+ borderColor: colors.border,
1906
+ children: left
1907
+ }
1908
+ ),
1909
+ right
1910
+ ] });
1911
+ }
1912
+ );
1913
+
920
1914
  // src/ui/hooks/useSessions.ts
921
- import { useState as useState4, useEffect as useEffect2, useCallback, useRef } from "react";
1915
+ import { useState as useState8, useEffect as useEffect5, useCallback as useCallback2, useRef as useRef3 } from "react";
922
1916
  var ACTIVE_POLL_MS = 1e4;
923
1917
  var IDLE_POLL_MS = 3e4;
924
- var useSessions = (allUsers, filter) => {
925
- const [sessions, setSessions] = useState4([]);
926
- const [selectedIndex, setSelectedIndex] = useState4(0);
927
- const usageOverrides = useRef(/* @__PURE__ */ new Map());
928
- const refresh = useCallback(() => {
1918
+ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
1919
+ const [sessions, setSessions] = useState8([]);
1920
+ const [selectedIndex, setSelectedIndex] = useState8(0);
1921
+ const usageOverrides = useRef3(/* @__PURE__ */ new Map());
1922
+ const refresh = useCallback2(() => {
929
1923
  const found = discoverSessions(allUsers);
930
1924
  const nicknames = getNicknames();
931
1925
  const enriched = found.map((s) => {
@@ -942,31 +1936,40 @@ var useSessions = (allUsers, filter) => {
942
1936
  };
943
1937
  });
944
1938
  let filtered = enriched;
1939
+ if (archivedIds && archivedIds.size > 0) {
1940
+ if (viewingArchive) {
1941
+ filtered = filtered.filter((s) => archivedIds.has(s.sessionId));
1942
+ } else {
1943
+ filtered = filtered.filter((s) => !archivedIds.has(s.sessionId));
1944
+ }
1945
+ } else if (viewingArchive) {
1946
+ filtered = [];
1947
+ }
945
1948
  if (filter) {
946
1949
  const lower = filter.toLowerCase();
947
- filtered = enriched.filter(
1950
+ filtered = filtered.filter(
948
1951
  (s) => s.slug.toLowerCase().includes(lower) || s.nickname?.toLowerCase().includes(lower) || s.project.toLowerCase().includes(lower) || s.model.toLowerCase().includes(lower)
949
1952
  );
950
1953
  }
951
1954
  setSessions(filtered);
952
- }, [allUsers, filter]);
953
- useEffect2(() => {
1955
+ }, [allUsers, filter, archivedIds, viewingArchive]);
1956
+ useEffect5(() => {
954
1957
  refresh();
955
1958
  const pollMs = sessions.length > 0 ? ACTIVE_POLL_MS : IDLE_POLL_MS;
956
1959
  const interval = setInterval(refresh, pollMs);
957
1960
  return () => clearInterval(interval);
958
1961
  }, [refresh, sessions.length > 0]);
959
1962
  const selectedSession = sessions[selectedIndex] ?? null;
960
- const selectNext = useCallback(() => {
1963
+ const selectNext = useCallback2(() => {
961
1964
  setSelectedIndex((i) => Math.min(i + 1, Math.max(0, sessions.length - 1)));
962
1965
  }, [sessions.length]);
963
- const selectPrev = useCallback(() => {
1966
+ const selectPrev = useCallback2(() => {
964
1967
  setSelectedIndex((i) => Math.max(i - 1, 0));
965
1968
  }, []);
966
- const selectIndex = useCallback((i) => {
1969
+ const selectIndex = useCallback2((i) => {
967
1970
  setSelectedIndex(i);
968
1971
  }, []);
969
- const addUsage = useCallback((sessionId, usage) => {
1972
+ const addUsage = useCallback2((sessionId, usage) => {
970
1973
  const existing = usageOverrides.current.get(sessionId);
971
1974
  if (existing) {
972
1975
  usageOverrides.current.set(sessionId, {
@@ -983,12 +1986,12 @@ var useSessions = (allUsers, filter) => {
983
1986
  };
984
1987
 
985
1988
  // src/ui/hooks/useActivityStream.ts
986
- import { useState as useState5, useEffect as useEffect3, useRef as useRef2 } from "react";
1989
+ import { useState as useState9, useEffect as useEffect6, useRef as useRef4 } from "react";
987
1990
  var MAX_EVENTS = 200;
988
1991
  var useActivityStream = (session, allUsers) => {
989
- const [events, setEvents] = useState5([]);
990
- const watcherRef = useRef2(null);
991
- useEffect3(() => {
1992
+ const [events, setEvents] = useState9([]);
1993
+ const watcherRef = useRef4(null);
1994
+ useEffect6(() => {
992
1995
  setEvents([]);
993
1996
  if (!session) return;
994
1997
  const existingCalls = [];
@@ -1014,8 +2017,19 @@ var useActivityStream = (session, allUsers) => {
1014
2017
  return events;
1015
2018
  };
1016
2019
 
2020
+ // src/ui/hooks/useFilteredEvents.ts
2021
+ import { useMemo as useMemo2 } from "react";
2022
+ var applyFilter = (events, filter) => {
2023
+ if (!filter) return events;
2024
+ const lower = filter.toLowerCase();
2025
+ return events.filter(
2026
+ (e) => e.toolName.toLowerCase().includes(lower) || JSON.stringify(e.toolInput).toLowerCase().includes(lower)
2027
+ );
2028
+ };
2029
+ var useFilteredEvents = (rawEvents, filter) => useMemo2(() => applyFilter(rawEvents, filter), [rawEvents, filter]);
2030
+
1017
2031
  // src/ui/hooks/useAlerts.ts
1018
- import { useState as useState6, useEffect as useEffect4, useRef as useRef3 } from "react";
2032
+ import { useState as useState10, useEffect as useEffect7, useRef as useRef5 } from "react";
1019
2033
 
1020
2034
  // src/notifications.ts
1021
2035
  import { exec as exec2 } from "child_process";
@@ -1083,11 +2097,11 @@ var AlertLogger = class {
1083
2097
  // src/ui/hooks/useAlerts.ts
1084
2098
  var MAX_ALERTS = 100;
1085
2099
  var useAlerts = (enabled, alertLevel, allUsers, config) => {
1086
- const [alerts, setAlerts] = useState6([]);
1087
- const engineRef = useRef3(new SecurityEngine(alertLevel));
1088
- const watcherRef = useRef3(null);
1089
- const loggerRef = useRef3(null);
1090
- useEffect4(() => {
2100
+ const [alerts, setAlerts] = useState10([]);
2101
+ const engineRef = useRef5(new SecurityEngine(alertLevel));
2102
+ const watcherRef = useRef5(null);
2103
+ const loggerRef = useRef5(null);
2104
+ useEffect7(() => {
1091
2105
  if (!enabled) return;
1092
2106
  engineRef.current = new SecurityEngine(alertLevel);
1093
2107
  if (config?.alerts.enabled) {
@@ -1123,27 +2137,27 @@ var useAlerts = (enabled, alertLevel, allUsers, config) => {
1123
2137
  };
1124
2138
 
1125
2139
  // src/ui/hooks/useTextInput.ts
1126
- import { useState as useState7, useCallback as useCallback2 } from "react";
2140
+ import { useState as useState11, useCallback as useCallback3 } from "react";
1127
2141
  var useTextInput = (onConfirm, onCancel) => {
1128
- const [value, setValue] = useState7("");
1129
- const [isActive, setIsActive] = useState7(false);
1130
- const start = useCallback2((initial = "") => {
2142
+ const [value, setValue] = useState11("");
2143
+ const [isActive, setIsActive] = useState11(false);
2144
+ const start = useCallback3((initial = "") => {
1131
2145
  setValue(initial);
1132
2146
  setIsActive(true);
1133
2147
  }, []);
1134
- const cancel = useCallback2(() => {
2148
+ const cancel = useCallback3(() => {
1135
2149
  setValue("");
1136
2150
  setIsActive(false);
1137
2151
  onCancel?.();
1138
2152
  }, [onCancel]);
1139
- const confirm = useCallback2(() => {
2153
+ const confirm = useCallback3(() => {
1140
2154
  const result = value;
1141
2155
  setIsActive(false);
1142
2156
  setValue("");
1143
2157
  onConfirm?.(result);
1144
2158
  return result;
1145
2159
  }, [value, onConfirm]);
1146
- const handleInput = useCallback2(
2160
+ const handleInput = useCallback3(
1147
2161
  (input, key) => {
1148
2162
  if (!isActive) return false;
1149
2163
  if (key.escape) {
@@ -1175,34 +2189,299 @@ var useTextInput = (onConfirm, onCancel) => {
1175
2189
  return { value, isActive, start, cancel, confirm, handleInput };
1176
2190
  };
1177
2191
 
1178
- // src/ui/App.tsx
1179
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2192
+ // src/ui/hooks/useKeyHandler.ts
2193
+ import { useInput as useInput8 } from "ink";
1180
2194
  var matchKey = (binding, input, key) => {
1181
2195
  if (binding === "tab") return Boolean(key.tab);
1182
2196
  if (binding === "shift+tab") return Boolean(key.shift && key.tab);
1183
2197
  if (binding === "enter") return Boolean(key.return);
1184
2198
  return input === binding;
1185
2199
  };
2200
+ var useKeyHandler = (deps) => {
2201
+ const d = deps;
2202
+ useInput8((input, key) => {
2203
+ if (d.showSetup || d.showSettings || d.confirmAction) return;
2204
+ if (d.inputMode === "nickname") {
2205
+ d.nicknameInput.handleInput(input, key);
2206
+ return;
2207
+ }
2208
+ if (d.inputMode === "filter") {
2209
+ if (key.escape) {
2210
+ if (d.activePanel === "sessions") d.setFilter("");
2211
+ else if (d.activePanel === "left") d.setLeftFilter("");
2212
+ else if (d.activePanel === "right") d.setRightFilter("");
2213
+ else d.setActivityFilter("");
2214
+ d.setInputMode("normal");
2215
+ d.filterInput.cancel();
2216
+ return;
2217
+ }
2218
+ d.filterInput.handleInput(input, key);
2219
+ return;
2220
+ }
2221
+ if (matchKey(d.kb.quit, input, key)) {
2222
+ d.exit();
2223
+ return;
2224
+ }
2225
+ if (d.showDetail && !d.splitMode) {
2226
+ if (key.escape || key.return || key.leftArrow) d.setShowDetail(false);
2227
+ return;
2228
+ }
2229
+ if (d.splitMode && (d.leftShowDetail || d.rightShowDetail)) {
2230
+ if (key.escape || key.return) {
2231
+ if (d.activePanel === "left") d.setLeftShowDetail(false);
2232
+ else if (d.activePanel === "right") d.setRightShowDetail(false);
2233
+ else {
2234
+ d.setLeftShowDetail(false);
2235
+ d.setRightShowDetail(false);
2236
+ }
2237
+ return;
2238
+ }
2239
+ if (key.leftArrow) {
2240
+ if (d.activePanel === "left") d.setLeftShowDetail(false);
2241
+ else if (d.activePanel === "right") d.setRightShowDetail(false);
2242
+ return;
2243
+ }
2244
+ }
2245
+ if (matchKey(d.kb.split, input, key)) {
2246
+ if (d.splitMode) {
2247
+ d.clearSplitState();
2248
+ } else {
2249
+ d.setSplitMode(true);
2250
+ d.setLeftSession(d.selectedSession);
2251
+ d.setActivePanel("left");
2252
+ }
2253
+ return;
2254
+ }
2255
+ if (d.splitMode && d.activePanel === "sessions" && d.selectedSession) {
2256
+ if (matchKey(d.kb.pinLeft, input, key)) {
2257
+ d.resetPanel("left");
2258
+ d.setLeftSession(d.selectedSession);
2259
+ return;
2260
+ }
2261
+ if (matchKey(d.kb.pinRight, input, key)) {
2262
+ d.resetPanel("right");
2263
+ d.setRightSession(d.selectedSession);
2264
+ return;
2265
+ }
2266
+ }
2267
+ if (d.splitMode && matchKey(d.kb.swapPanels, input, key)) {
2268
+ const tmpLs = d.leftSession;
2269
+ const tmpRs = d.rightSession;
2270
+ d.setLeftSession(tmpRs);
2271
+ d.setRightSession(tmpLs);
2272
+ const tmpScroll = d.leftScroll;
2273
+ d.setLeftScroll(d.rightScroll);
2274
+ d.setRightScroll(tmpScroll);
2275
+ const tmpFilt = d.leftFilter;
2276
+ d.setLeftFilter(d.rightFilter);
2277
+ d.setRightFilter(tmpFilt);
2278
+ const tmpDetail = d.leftShowDetail;
2279
+ d.setLeftShowDetail(d.rightShowDetail);
2280
+ d.setRightShowDetail(tmpDetail);
2281
+ return;
2282
+ }
2283
+ if (d.splitMode && matchKey(d.kb.closePanel, input, key)) {
2284
+ if (d.activePanel === "left") {
2285
+ d.resetPanel("left");
2286
+ if (!d.rightSession) d.clearSplitState();
2287
+ } else if (d.activePanel === "right") {
2288
+ d.resetPanel("right");
2289
+ if (!d.leftSession) d.clearSplitState();
2290
+ }
2291
+ return;
2292
+ }
2293
+ if (matchKey(d.kb.detail, input, key) && d.selectedSession) {
2294
+ if (d.splitMode) {
2295
+ if (d.activePanel === "left") {
2296
+ d.setLeftShowDetail((v) => !v);
2297
+ return;
2298
+ }
2299
+ if (d.activePanel === "right") {
2300
+ d.setRightShowDetail((v) => !v);
2301
+ return;
2302
+ }
2303
+ if (d.activePanel === "sessions") {
2304
+ if (!d.leftSession) {
2305
+ d.setLeftSession(d.selectedSession);
2306
+ d.setLeftShowDetail(true);
2307
+ d.setActivePanel("left");
2308
+ } else if (!d.rightSession) {
2309
+ d.setRightSession(d.selectedSession);
2310
+ d.setRightShowDetail(true);
2311
+ d.setActivePanel("right");
2312
+ }
2313
+ return;
2314
+ }
2315
+ } else if (d.activePanel === "sessions") {
2316
+ d.setShowDetail(true);
2317
+ return;
2318
+ }
2319
+ }
2320
+ if (matchKey(d.kb.panelNext, input, key) || key.rightArrow) {
2321
+ d.switchPanel("next");
2322
+ return;
2323
+ }
2324
+ if (matchKey(d.kb.panelPrev, input, key) || key.leftArrow) {
2325
+ d.switchPanel("prev");
2326
+ return;
2327
+ }
2328
+ if (matchKey(d.kb.nickname, input, key) && d.selectedSession) {
2329
+ d.setInputMode("nickname");
2330
+ d.nicknameInput.start(d.selectedSession.nickname || "");
2331
+ return;
2332
+ }
2333
+ if (matchKey(d.kb.clearNickname, input, key) && d.selectedSession) {
2334
+ d.onClearNickname(d.selectedSession.sessionId);
2335
+ return;
2336
+ }
2337
+ if (matchKey(d.kb.filter, input, key)) {
2338
+ d.setInputMode("filter");
2339
+ d.filterInput.start(d.getActiveFilter());
2340
+ return;
2341
+ }
2342
+ if (key.escape) {
2343
+ if (d.activePanel === "sessions" && d.filter) {
2344
+ d.setFilter("");
2345
+ return;
2346
+ }
2347
+ if (d.activePanel === "activity" && d.activityFilter) {
2348
+ d.setActivityFilter("");
2349
+ return;
2350
+ }
2351
+ if (d.activePanel === "left" && d.leftFilter) {
2352
+ d.setLeftFilter("");
2353
+ return;
2354
+ }
2355
+ if (d.activePanel === "right" && d.rightFilter) {
2356
+ d.setRightFilter("");
2357
+ return;
2358
+ }
2359
+ return;
2360
+ }
2361
+ if (matchKey(d.kb.settings, input, key)) {
2362
+ d.setShowSettings(true);
2363
+ return;
2364
+ }
2365
+ if (matchKey(d.kb.viewArchive, input, key)) {
2366
+ d.setViewingArchive((v) => !v);
2367
+ return;
2368
+ }
2369
+ if (matchKey(d.kb.archive, input, key) && d.selectedSession) {
2370
+ if (d.viewingArchive) d.onUnarchive(d.selectedSession.sessionId);
2371
+ else d.onArchive(d.selectedSession.sessionId);
2372
+ return;
2373
+ }
2374
+ if (matchKey(d.kb.delete, input, key) && d.selectedSession) {
2375
+ d.onDelete(d.selectedSession);
2376
+ return;
2377
+ }
2378
+ if (matchKey(d.kb.update, input, key) && d.updateInfo?.available) {
2379
+ d.onUpdate();
2380
+ return;
2381
+ }
2382
+ if (d.activePanel === "sessions") {
2383
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.selectNext();
2384
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.selectPrev();
2385
+ }
2386
+ if (d.activePanel === "activity") {
2387
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.setActivityScroll((s) => Math.min(s + 1, d.maxScroll));
2388
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.setActivityScroll((s) => Math.max(s - 1, 0));
2389
+ if (matchKey(d.kb.scrollBottom, input, key)) d.setActivityScroll(0);
2390
+ if (matchKey(d.kb.scrollTop, input, key)) d.setActivityScroll(d.maxScroll);
2391
+ }
2392
+ if (d.activePanel === "left") {
2393
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.setLeftScroll((s) => Math.min(s + 1, d.leftMaxScroll));
2394
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.setLeftScroll((s) => Math.max(s - 1, 0));
2395
+ if (matchKey(d.kb.scrollBottom, input, key)) d.setLeftScroll(0);
2396
+ if (matchKey(d.kb.scrollTop, input, key)) d.setLeftScroll(d.leftMaxScroll);
2397
+ }
2398
+ if (d.activePanel === "right") {
2399
+ if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.setRightScroll((s) => Math.min(s + 1, d.rightMaxScroll));
2400
+ if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.setRightScroll((s) => Math.max(s - 1, 0));
2401
+ if (matchKey(d.kb.scrollBottom, input, key)) d.setRightScroll(0);
2402
+ if (matchKey(d.kb.scrollTop, input, key)) d.setRightScroll(d.rightMaxScroll);
2403
+ }
2404
+ });
2405
+ };
2406
+
2407
+ // src/ui/hooks/useUpdateChecker.ts
2408
+ import { useState as useState12, useEffect as useEffect8 } from "react";
2409
+ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
2410
+ const [updateInfo, setUpdateInfo] = useState12(null);
2411
+ useEffect8(() => {
2412
+ if (disabled || !checkOnLaunch) return;
2413
+ try {
2414
+ const i = checkForUpdate();
2415
+ if (i.available) setUpdateInfo(i);
2416
+ } catch {
2417
+ }
2418
+ const iv = setInterval(() => {
2419
+ try {
2420
+ const i = checkForUpdate();
2421
+ if (i.available) setUpdateInfo(i);
2422
+ } catch {
2423
+ }
2424
+ }, checkInterval);
2425
+ return () => clearInterval(iv);
2426
+ }, []);
2427
+ return updateInfo;
2428
+ };
2429
+
2430
+ // src/ui/App.tsx
2431
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1186
2432
  var App = ({ options, config: initialConfig, version, firstRun }) => {
1187
2433
  const { exit } = useApp();
1188
- const { stdout } = useStdout2();
2434
+ const { stdout } = useStdout3();
1189
2435
  const termHeight = stdout?.rows ?? 40;
1190
- const [liveConfig, setLiveConfig] = useState8(initialConfig);
2436
+ const [liveConfig, setLiveConfig] = useState13(initialConfig);
1191
2437
  const kb = liveConfig.keybindings;
1192
- const [activePanel, setActivePanel] = useState8("sessions");
1193
- const [activityScroll, setActivityScroll] = useState8(0);
1194
- const [inputMode, setInputMode] = useState8("normal");
1195
- const [showSetup, setShowSetup] = useState8(firstRun);
1196
- const [filter, setFilter] = useState8("");
1197
- const [updateInfo, setUpdateInfo] = useState8(null);
1198
- const [updateStatus, setUpdateStatus] = useState8("");
1199
- const [showDetail, setShowDetail] = useState8(false);
1200
- const [showSettings, setShowSettings] = useState8(false);
2438
+ const [activePanel, setActivePanel] = useState13("sessions");
2439
+ const [activityScroll, setActivityScroll] = useState13(0);
2440
+ const [inputMode, setInputMode] = useState13("normal");
2441
+ const [showSetup, setShowSetup] = useState13(firstRun);
2442
+ const [showThemePicker, setShowThemePicker] = useState13(false);
2443
+ const [showTour, setShowTour] = useState13(false);
2444
+ const [filter, setFilter] = useState13("");
2445
+ const [activityFilter, setActivityFilter] = useState13("");
2446
+ const [updateStatus, setUpdateStatus] = useState13("");
2447
+ const [showDetail, setShowDetail] = useState13(false);
2448
+ const [showSettings, setShowSettings] = useState13(false);
2449
+ const [showThemeMenu, setShowThemeMenu] = useState13(false);
2450
+ const [viewingArchive, setViewingArchive] = useState13(false);
2451
+ const [confirmAction, setConfirmAction] = useState13(
2452
+ null
2453
+ );
2454
+ const [archivedIds, setArchivedIds] = useState13(() => new Set(Object.keys(getArchived())));
2455
+ const [splitMode, setSplitMode] = useState13(false);
2456
+ const [leftSession, setLeftSession] = useState13(null);
2457
+ const [rightSession, setRightSession] = useState13(null);
2458
+ const [leftScroll, setLeftScroll] = useState13(0);
2459
+ const [rightScroll, setRightScroll] = useState13(0);
2460
+ const [leftFilter, setLeftFilter] = useState13("");
2461
+ const [rightFilter, setRightFilter] = useState13("");
2462
+ const [leftShowDetail, setLeftShowDetail] = useState13(false);
2463
+ const [rightShowDetail, setRightShowDetail] = useState13(false);
2464
+ const refreshArchived = useCallback4(() => setArchivedIds(new Set(Object.keys(getArchived()))), []);
2465
+ const updateInfo = useUpdateChecker(
2466
+ options.noUpdates,
2467
+ liveConfig.updates.checkOnLaunch,
2468
+ liveConfig.updates.checkInterval
2469
+ );
2470
+ useEffect9(() => {
2471
+ applyTheme(resolveTheme(liveConfig.theme, liveConfig.customThemes));
2472
+ }, [liveConfig.theme, liveConfig.customThemes]);
1201
2473
  const { sessions, selectedSession, selectedIndex, selectNext, selectPrev, refresh } = useSessions(
1202
2474
  options.allUsers,
1203
- filter || void 0
2475
+ filter || void 0,
2476
+ archivedIds,
2477
+ viewingArchive
1204
2478
  );
1205
- const events = useActivityStream(selectedSession, options.allUsers);
2479
+ const rawEvents = useActivityStream(splitMode ? null : selectedSession, options.allUsers);
2480
+ const leftRawEvents = useActivityStream(splitMode ? leftSession : null, options.allUsers);
2481
+ const rightRawEvents = useActivityStream(splitMode ? rightSession : null, options.allUsers);
2482
+ const events = useFilteredEvents(rawEvents, activityFilter);
2483
+ const leftEvents = useFilteredEvents(leftRawEvents, leftFilter);
2484
+ const rightEvents = useFilteredEvents(rightRawEvents, rightFilter);
1206
2485
  const { alerts } = useAlerts(!options.noSecurity, options.alertLevel, options.allUsers, liveConfig);
1207
2486
  const nicknameInput = useTextInput(
1208
2487
  (value) => {
@@ -1216,43 +2495,43 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
1216
2495
  );
1217
2496
  const filterInput = useTextInput(
1218
2497
  (value) => {
1219
- setFilter(value);
2498
+ if (activePanel === "sessions") setFilter(value);
2499
+ else if (activePanel === "left") setLeftFilter(value);
2500
+ else if (activePanel === "right") setRightFilter(value);
2501
+ else setActivityFilter(value);
1220
2502
  setInputMode("normal");
1221
2503
  },
1222
2504
  () => {
1223
- setFilter("");
2505
+ if (activePanel === "sessions") setFilter("");
2506
+ else if (activePanel === "left") setLeftFilter("");
2507
+ else if (activePanel === "right") setRightFilter("");
2508
+ else setActivityFilter("");
1224
2509
  setInputMode("normal");
1225
2510
  }
1226
2511
  );
1227
- useEffect5(() => {
1228
- if (options.noUpdates || !liveConfig.updates.checkOnLaunch) return;
1229
- try {
1230
- const i = checkForUpdate();
1231
- if (i.available) setUpdateInfo(i);
1232
- } catch {
1233
- }
1234
- const iv = setInterval(() => {
1235
- try {
1236
- const i = checkForUpdate();
1237
- if (i.available) setUpdateInfo(i);
1238
- } catch {
1239
- }
1240
- }, liveConfig.updates.checkInterval);
1241
- return () => clearInterval(iv);
2512
+ useEffect9(() => {
2513
+ purgeExpiredArchives();
2514
+ refreshArchived();
1242
2515
  }, []);
2516
+ useEffect9(() => {
2517
+ setActivityScroll(0);
2518
+ }, [selectedSession?.sessionId]);
1243
2519
  const alertHeight = options.noSecurity ? 0 : 6;
1244
2520
  const mainHeight = termHeight - 3 - alertHeight - 1 - (inputMode !== "normal" ? 1 : 0);
1245
2521
  const viewportRows = mainHeight - 2;
1246
- const maxScroll = Math.max(0, events.length - viewportRows);
1247
- useEffect5(() => {
1248
- setActivityScroll(0);
1249
- }, [selectedSession?.sessionId]);
1250
- const handleSettingsClose = useCallback3((updatedConfig) => {
1251
- setLiveConfig(updatedConfig);
1252
- saveConfig(updatedConfig);
2522
+ const handleSettingsClose = useCallback4((c) => {
2523
+ setLiveConfig(c);
2524
+ saveConfig(c);
1253
2525
  setShowSettings(false);
1254
2526
  }, []);
1255
- const handleSetupComplete = useCallback3(
2527
+ const handleThemeMenuClose = useCallback4((c) => {
2528
+ setLiveConfig(c);
2529
+ saveConfig(c);
2530
+ setShowThemeMenu(false);
2531
+ setShowSettings(true);
2532
+ }, []);
2533
+ const handleOpenThemeMenu = useCallback4(() => setShowThemeMenu(true), []);
2534
+ const handleSetupComplete = useCallback4(
1256
2535
  (results) => {
1257
2536
  const nc = { ...liveConfig };
1258
2537
  const [hc, mc] = results;
@@ -1272,146 +2551,283 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
1272
2551
  } else if (mc === "dismiss") nc.prompts.mcp = "dismissed";
1273
2552
  saveConfig(nc);
1274
2553
  setShowSetup(false);
2554
+ if (nc.prompts.theme === "pending") setShowThemePicker(true);
2555
+ else if (nc.prompts.tour === "pending") setShowTour(true);
2556
+ },
2557
+ [liveConfig]
2558
+ );
2559
+ const handleThemePickerSelect = useCallback4(
2560
+ (themeName) => {
2561
+ const nc = { ...liveConfig, theme: themeName, prompts: { ...liveConfig.prompts, theme: "done" } };
2562
+ setLiveConfig(nc);
2563
+ saveConfig(nc);
2564
+ setShowThemePicker(false);
2565
+ if (nc.prompts.tour === "pending") setShowTour(true);
1275
2566
  },
1276
2567
  [liveConfig]
1277
2568
  );
1278
- const switchPanel = useCallback3((_dir) => {
1279
- setActivePanel((p) => p === "sessions" ? "activity" : "sessions");
2569
+ const handleThemePickerSkip = useCallback4(() => {
2570
+ setShowThemePicker(false);
2571
+ if (liveConfig.prompts.tour === "pending") setShowTour(true);
2572
+ }, [liveConfig]);
2573
+ const handleThemePickerDismiss = useCallback4(() => {
2574
+ const nc = { ...liveConfig, prompts: { ...liveConfig.prompts, theme: "dismissed" } };
2575
+ setLiveConfig(nc);
2576
+ saveConfig(nc);
2577
+ setShowThemePicker(false);
2578
+ if (nc.prompts.tour === "pending") setShowTour(true);
2579
+ }, [liveConfig]);
2580
+ const handleTourComplete = useCallback4(() => {
2581
+ const nc = { ...liveConfig, prompts: { ...liveConfig.prompts, tour: "done" } };
2582
+ setLiveConfig(nc);
2583
+ saveConfig(nc);
2584
+ setShowTour(false);
2585
+ }, [liveConfig]);
2586
+ const handleTourSkip = useCallback4(() => {
2587
+ setShowTour(false);
1280
2588
  }, []);
1281
- useInput3((input, key) => {
1282
- if (showSetup || showSettings) return;
1283
- if (inputMode === "nickname") {
1284
- nicknameInput.handleInput(input, key);
1285
- return;
1286
- }
1287
- if (inputMode === "filter") {
1288
- if (key.escape) {
1289
- setFilter("");
1290
- setInputMode("normal");
1291
- filterInput.cancel();
1292
- return;
1293
- }
1294
- filterInput.handleInput(input, key);
1295
- return;
1296
- }
1297
- if (matchKey(kb.quit, input, key)) {
1298
- exit();
1299
- return;
1300
- }
1301
- if (showDetail) {
1302
- if (key.escape || key.return || key.leftArrow) {
1303
- setShowDetail(false);
2589
+ const switchPanel = useCallback4(
2590
+ (dir) => {
2591
+ if (splitMode) {
2592
+ const order = ["sessions", "left", "right"];
2593
+ setActivePanel((p) => {
2594
+ const idx = order.indexOf(p);
2595
+ if (idx === -1) return "sessions";
2596
+ return dir === "next" ? order[(idx + 1) % order.length] : order[(idx - 1 + order.length) % order.length];
2597
+ });
2598
+ } else {
2599
+ setActivePanel((p) => p === "sessions" ? "activity" : "sessions");
1304
2600
  }
1305
- return;
1306
- }
1307
- if (matchKey(kb.detail, input, key) && selectedSession && activePanel === "sessions") {
1308
- setShowDetail(true);
1309
- return;
1310
- }
1311
- if (matchKey(kb.panelNext, input, key) || key.rightArrow) {
1312
- switchPanel("next");
1313
- return;
1314
- }
1315
- if (matchKey(kb.panelPrev, input, key) || key.leftArrow) {
1316
- switchPanel("prev");
1317
- return;
1318
- }
1319
- if (matchKey(kb.nickname, input, key) && selectedSession) {
1320
- setInputMode("nickname");
1321
- nicknameInput.start(selectedSession.nickname || "");
1322
- return;
2601
+ },
2602
+ [splitMode]
2603
+ );
2604
+ const getActiveFilter = useCallback4(() => {
2605
+ if (activePanel === "sessions") return filter;
2606
+ if (activePanel === "left") return leftFilter;
2607
+ if (activePanel === "right") return rightFilter;
2608
+ return activityFilter;
2609
+ }, [activePanel, filter, leftFilter, rightFilter, activityFilter]);
2610
+ const clearSplitState = useCallback4(() => {
2611
+ setSplitMode(false);
2612
+ setLeftSession(null);
2613
+ setRightSession(null);
2614
+ setLeftScroll(0);
2615
+ setRightScroll(0);
2616
+ setLeftFilter("");
2617
+ setRightFilter("");
2618
+ setLeftShowDetail(false);
2619
+ setRightShowDetail(false);
2620
+ setActivePanel("sessions");
2621
+ }, []);
2622
+ const resetPanel = useCallback4((side) => {
2623
+ if (side === "left") {
2624
+ setLeftSession(null);
2625
+ setLeftScroll(0);
2626
+ setLeftFilter("");
2627
+ setLeftShowDetail(false);
2628
+ } else {
2629
+ setRightSession(null);
2630
+ setRightScroll(0);
2631
+ setRightFilter("");
2632
+ setRightShowDetail(false);
1323
2633
  }
1324
- if (matchKey(kb.clearNickname, input, key) && selectedSession) {
1325
- clearNickname(selectedSession.sessionId);
2634
+ }, []);
2635
+ useKeyHandler({
2636
+ kb,
2637
+ activePanel,
2638
+ splitMode,
2639
+ inputMode,
2640
+ showSetup,
2641
+ showSettings: showSettings || showThemeMenu || showThemePicker || showTour,
2642
+ showDetail,
2643
+ leftShowDetail,
2644
+ rightShowDetail,
2645
+ confirmAction,
2646
+ selectedSession,
2647
+ leftSession,
2648
+ rightSession,
2649
+ leftScroll,
2650
+ rightScroll,
2651
+ leftFilter,
2652
+ rightFilter,
2653
+ filter,
2654
+ activityFilter,
2655
+ viewingArchive,
2656
+ archivedIds,
2657
+ updateInfo,
2658
+ maxScroll: Math.max(0, events.length - viewportRows),
2659
+ leftMaxScroll: Math.max(0, leftEvents.length - viewportRows),
2660
+ rightMaxScroll: Math.max(0, rightEvents.length - viewportRows),
2661
+ exit,
2662
+ selectNext,
2663
+ selectPrev,
2664
+ refresh,
2665
+ switchPanel,
2666
+ clearSplitState,
2667
+ resetPanel,
2668
+ getActiveFilter,
2669
+ setActivePanel,
2670
+ setInputMode,
2671
+ setFilter,
2672
+ setActivityFilter,
2673
+ setLeftFilter,
2674
+ setRightFilter,
2675
+ setShowDetail,
2676
+ setLeftShowDetail,
2677
+ setRightShowDetail,
2678
+ setShowSettings,
2679
+ setViewingArchive,
2680
+ setSplitMode,
2681
+ setLeftSession,
2682
+ setRightSession,
2683
+ setLeftScroll,
2684
+ setRightScroll,
2685
+ setActivityScroll,
2686
+ setConfirmAction,
2687
+ setUpdateStatus,
2688
+ nicknameInput,
2689
+ filterInput,
2690
+ onNickname: (id) => {
2691
+ clearNickname(id);
1326
2692
  refresh();
1327
- return;
1328
- }
1329
- if (matchKey(kb.filter, input, key)) {
1330
- setInputMode("filter");
1331
- filterInput.start(filter);
1332
- return;
1333
- }
1334
- if (key.escape && filter) {
1335
- setFilter("");
1336
- return;
1337
- }
1338
- if (matchKey(kb.settings, input, key)) {
1339
- setShowSettings(true);
1340
- return;
1341
- }
1342
- if (matchKey(kb.update, input, key) && updateInfo?.available) {
2693
+ },
2694
+ onClearNickname: (id) => {
2695
+ clearNickname(id);
2696
+ refresh();
2697
+ },
2698
+ onArchive: (id) => {
2699
+ archiveSession(id);
2700
+ refreshArchived();
2701
+ refresh();
2702
+ },
2703
+ onUnarchive: (id) => {
2704
+ unarchiveSession(id);
2705
+ refreshArchived();
2706
+ refresh();
2707
+ },
2708
+ onDelete: (sess) => setConfirmAction({
2709
+ title: "Delete session?",
2710
+ message: `Delete ${sess.nickname || sess.slug}? Output files will be removed.`,
2711
+ onConfirm: () => {
2712
+ deleteSessionFiles(sess.outputFiles);
2713
+ clearNickname(sess.sessionId);
2714
+ if (archivedIds.has(sess.sessionId)) {
2715
+ unarchiveSession(sess.sessionId);
2716
+ refreshArchived();
2717
+ }
2718
+ refresh();
2719
+ setConfirmAction(null);
2720
+ }
2721
+ }),
2722
+ onUpdate: () => {
1343
2723
  setUpdateStatus("updating...");
1344
- installUpdate().then(() => setUpdateStatus(`updated to v${updateInfo.latest} \u2014 restart to apply`)).catch(() => setUpdateStatus("update failed"));
1345
- return;
1346
- }
1347
- if (activePanel === "sessions") {
1348
- if (matchKey(kb.navDown, input, key) || key.downArrow) selectNext();
1349
- if (matchKey(kb.navUp, input, key) || key.upArrow) selectPrev();
1350
- }
1351
- if (activePanel === "activity") {
1352
- if (matchKey(kb.navUp, input, key) || key.upArrow) setActivityScroll((s) => Math.min(s + 1, maxScroll));
1353
- if (matchKey(kb.navDown, input, key) || key.downArrow) setActivityScroll((s) => Math.max(s - 1, 0));
1354
- if (matchKey(kb.scrollBottom, input, key) || key.end) setActivityScroll(0);
1355
- if (matchKey(kb.scrollTop, input, key) || key.home) setActivityScroll(maxScroll);
2724
+ installUpdate().then(() => setUpdateStatus(`updated to v${updateInfo?.latest} \u2014 restart to apply`)).catch(() => setUpdateStatus("update failed"));
1356
2725
  }
1357
2726
  });
1358
2727
  if (showSetup) {
1359
- const steps = [];
1360
- if (liveConfig.prompts.hook === "pending")
1361
- steps.push({
1362
- title: "Install Claude Code hook?",
1363
- description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time."
1364
- });
1365
- if (liveConfig.prompts.mcp === "pending")
1366
- steps.push({
1367
- title: "Install MCP server?",
1368
- description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts."
1369
- });
2728
+ const steps = [
2729
+ ...liveConfig.prompts.hook === "pending" ? [
2730
+ {
2731
+ title: "Install Claude Code hook?",
2732
+ description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time."
2733
+ }
2734
+ ] : [],
2735
+ ...liveConfig.prompts.mcp === "pending" ? [
2736
+ {
2737
+ title: "Install MCP server?",
2738
+ description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts."
2739
+ }
2740
+ ] : []
2741
+ ];
1370
2742
  if (steps.length === 0) {
1371
2743
  setShowSetup(false);
2744
+ if (liveConfig.prompts.theme === "pending") setShowThemePicker(true);
2745
+ else if (liveConfig.prompts.tour === "pending") setShowTour(true);
1372
2746
  return null;
1373
2747
  }
1374
- return /* @__PURE__ */ jsx9(SetupModal, { steps, onComplete: handleSetupComplete });
2748
+ return /* @__PURE__ */ jsx15(SetupModal, { steps, onComplete: handleSetupComplete });
1375
2749
  }
1376
- if (showSettings) {
1377
- return /* @__PURE__ */ jsx9(SettingsMenu, { config: liveConfig, onClose: handleSettingsClose });
2750
+ if (showThemePicker) {
2751
+ return /* @__PURE__ */ jsx15(
2752
+ ThemePickerModal,
2753
+ {
2754
+ onSelect: handleThemePickerSelect,
2755
+ onSkip: handleThemePickerSkip,
2756
+ onDismiss: handleThemePickerDismiss
2757
+ }
2758
+ );
2759
+ }
2760
+ if (showTour) return /* @__PURE__ */ jsx15(GuidedTour, { onComplete: handleTourComplete, onSkip: handleTourSkip });
2761
+ if (showThemeMenu) return /* @__PURE__ */ jsx15(ThemeMenu, { config: liveConfig, onClose: handleThemeMenuClose });
2762
+ if (showSettings)
2763
+ return /* @__PURE__ */ jsx15(SettingsMenu, { config: liveConfig, onClose: handleSettingsClose, onOpenThemeMenu: handleOpenThemeMenu });
2764
+ if (confirmAction) {
2765
+ return /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx15(
2766
+ ConfirmModal,
2767
+ {
2768
+ title: confirmAction.title,
2769
+ message: confirmAction.message,
2770
+ onConfirm: confirmAction.onConfirm,
2771
+ onCancel: () => setConfirmAction(null)
2772
+ }
2773
+ ) });
1378
2774
  }
1379
- const rightPanel = showDetail && selectedSession ? /* @__PURE__ */ jsx9(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx9(
2775
+ const filterLabel = activePanel === "sessions" ? "sessions" : activePanel === "left" ? "left" : activePanel === "right" ? "right" : "activity";
2776
+ const rightPanel = splitMode ? /* @__PURE__ */ jsx15(
2777
+ SplitPanel,
2778
+ {
2779
+ activePanel,
2780
+ leftSession,
2781
+ rightSession,
2782
+ leftEvents,
2783
+ rightEvents,
2784
+ leftScroll,
2785
+ rightScroll,
2786
+ leftFilter,
2787
+ rightFilter,
2788
+ leftShowDetail,
2789
+ rightShowDetail,
2790
+ height: mainHeight
2791
+ }
2792
+ ) : showDetail && selectedSession ? /* @__PURE__ */ jsx15(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx15(
1380
2793
  ActivityFeed,
1381
2794
  {
1382
2795
  events,
1383
2796
  sessionSlug: selectedSession?.slug ?? null,
1384
2797
  focused: activePanel === "activity",
1385
2798
  height: mainHeight,
1386
- scrollOffset: activityScroll
2799
+ scrollOffset: activityScroll,
2800
+ filter: activityFilter || void 0
1387
2801
  }
1388
2802
  );
1389
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", height: termHeight, children: [
1390
- /* @__PURE__ */ jsx9(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
1391
- /* @__PURE__ */ jsxs9(Box9, { flexGrow: 1, height: mainHeight, children: [
1392
- /* @__PURE__ */ jsx9(
2803
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: termHeight, children: [
2804
+ /* @__PURE__ */ jsx15(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
2805
+ /* @__PURE__ */ jsxs15(Box15, { flexGrow: 1, height: mainHeight, children: [
2806
+ /* @__PURE__ */ jsx15(
1393
2807
  SessionList,
1394
2808
  {
1395
2809
  sessions,
1396
2810
  selectedIndex,
1397
2811
  focused: activePanel === "sessions",
1398
- filter: filter || void 0
2812
+ filter: filter || void 0,
2813
+ viewingArchive
1399
2814
  }
1400
2815
  ),
1401
2816
  rightPanel
1402
2817
  ] }),
1403
- !options.noSecurity && /* @__PURE__ */ jsx9(AlertBar, { alerts }),
1404
- inputMode === "nickname" && /* @__PURE__ */ jsxs9(Box9, { paddingX: 1, children: [
1405
- /* @__PURE__ */ jsx9(Text9, { color: colors.primary, children: "nickname: " }),
1406
- /* @__PURE__ */ jsx9(Text9, { color: colors.bright, children: nicknameInput.value }),
1407
- /* @__PURE__ */ jsx9(Text9, { color: colors.muted, children: "_" })
2818
+ !options.noSecurity && /* @__PURE__ */ jsx15(AlertBar, { alerts }),
2819
+ inputMode === "nickname" && /* @__PURE__ */ jsxs15(Box15, { paddingX: 1, children: [
2820
+ /* @__PURE__ */ jsx15(Text14, { color: colors.primary, children: "nickname: " }),
2821
+ /* @__PURE__ */ jsx15(Text14, { color: colors.bright, children: nicknameInput.value }),
2822
+ /* @__PURE__ */ jsx15(Text14, { color: colors.muted, children: "_" })
1408
2823
  ] }),
1409
- inputMode === "filter" && /* @__PURE__ */ jsxs9(Box9, { paddingX: 1, children: [
1410
- /* @__PURE__ */ jsx9(Text9, { color: colors.primary, children: "/" }),
1411
- /* @__PURE__ */ jsx9(Text9, { color: colors.bright, children: filterInput.value }),
1412
- /* @__PURE__ */ jsx9(Text9, { color: colors.muted, children: "_" })
2824
+ inputMode === "filter" && /* @__PURE__ */ jsxs15(Box15, { paddingX: 1, children: [
2825
+ /* @__PURE__ */ jsx15(Text14, { color: colors.muted, children: filterLabel }),
2826
+ /* @__PURE__ */ jsx15(Text14, { color: colors.primary, children: "/" }),
2827
+ /* @__PURE__ */ jsx15(Text14, { color: colors.bright, children: filterInput.value }),
2828
+ /* @__PURE__ */ jsx15(Text14, { color: colors.muted, children: "_" })
1413
2829
  ] }),
1414
- inputMode === "normal" && /* @__PURE__ */ jsx9(FooterBar, { keybindings: kb, updateStatus })
2830
+ inputMode === "normal" && /* @__PURE__ */ jsx15(FooterBar, { keybindings: kb, updateStatus, viewingArchive, splitMode })
1415
2831
  ] });
1416
2832
  };
1417
2833
 
@@ -1648,7 +3064,7 @@ var main = () => {
1648
3064
  if (options.noUpdates) config.updates.checkOnLaunch = false;
1649
3065
  if (options.noSecurity) config.security.enabled = false;
1650
3066
  if (firstRun) saveConfig(config);
1651
- render(React10.createElement(App, { options, config, version: VERSION, firstRun }));
3067
+ render(React16.createElement(App, { options, config, version: VERSION, firstRun }));
1652
3068
  };
1653
3069
  main();
1654
3070
  //# sourceMappingURL=index.js.map