agenttop 0.10.7 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ STATUS_PRIORITY,
3
4
  SecurityEngine,
4
5
  Watcher,
5
6
  archiveSession,
@@ -7,30 +8,34 @@ import {
7
8
  deleteSessionFiles,
8
9
  discoverSessions,
9
10
  getArchived,
11
+ getClaudeProcessesAsync,
10
12
  getNicknames,
11
13
  getProjectsDirs,
12
14
  getTaskDirs,
13
15
  isFirstRun,
14
16
  loadConfig,
17
+ movePinned,
18
+ pinSession,
15
19
  purgeExpiredArchives,
16
20
  resolveAlertLogPath,
17
21
  rotateLogFile,
18
22
  saveConfig,
19
23
  setNickname,
20
24
  startMcpServer,
21
- unarchiveSession
22
- } from "./chunk-CXXCDFJ5.js";
25
+ unarchiveSession,
26
+ unpinSession
27
+ } from "./chunk-27WRQSJY.js";
23
28
 
24
29
  // src/index.tsx
25
30
  import { readFileSync as readFileSync3 } from "fs";
26
31
  import { join as join5, dirname as dirname4 } from "path";
27
32
  import { fileURLToPath as fileURLToPath3 } from "url";
28
- import React18 from "react";
33
+ import React19 from "react";
29
34
  import { render } from "ink";
30
35
 
31
36
  // src/ui/App.tsx
32
- import { useState as useState17, useEffect as useEffect9, useCallback as useCallback7 } from "react";
33
- import { Box as Box17, Text as Text16, useApp, useStdout as useStdout3 } from "ink";
37
+ import { useState as useState18, useEffect as useEffect10, useCallback as useCallback8 } from "react";
38
+ import { Box as Box18, Text as Text17, useApp, useStdout as useStdout4 } from "ink";
34
39
 
35
40
  // src/config/themes.ts
36
41
  var COLOR_KEYS = [
@@ -45,7 +50,9 @@ var COLOR_KEYS = [
45
50
  "bright",
46
51
  "border",
47
52
  "selected",
48
- "header"
53
+ "header",
54
+ "waiting",
55
+ "stale"
49
56
  ];
50
57
  var TOOL_COLOR_KEYS = [
51
58
  "Bash",
@@ -82,11 +89,28 @@ var fromTuple = ([
82
89
  bright,
83
90
  border,
84
91
  selected,
85
- header
92
+ header,
93
+ waiting,
94
+ stale
86
95
  ]) => ({
87
96
  name,
88
97
  builtin: true,
89
- colors: { primary, secondary, accent, warning, error, critical, muted, text, bright, border, selected, header },
98
+ colors: {
99
+ primary,
100
+ secondary,
101
+ accent,
102
+ warning,
103
+ error,
104
+ critical,
105
+ muted,
106
+ text,
107
+ bright,
108
+ border,
109
+ selected,
110
+ header,
111
+ waiting,
112
+ stale
113
+ },
90
114
  toolColors: deriveToolColors({
91
115
  primary,
92
116
  secondary,
@@ -99,7 +123,9 @@ var fromTuple = ([
99
123
  bright,
100
124
  border,
101
125
  selected,
102
- header
126
+ header,
127
+ waiting,
128
+ stale
103
129
  })
104
130
  });
105
131
  var PRESETS = [
@@ -116,7 +142,9 @@ var PRESETS = [
116
142
  "#FFFFFF",
117
143
  "#3E4451",
118
144
  "#2C313A",
119
- "#61AFEF"
145
+ "#61AFEF",
146
+ "#E5C07B",
147
+ "#D19A66"
120
148
  ],
121
149
  [
122
150
  "dracula",
@@ -131,7 +159,9 @@ var PRESETS = [
131
159
  "#FFFFFF",
132
160
  "#44475A",
133
161
  "#383A59",
134
- "#BD93F9"
162
+ "#BD93F9",
163
+ "#F1FA8C",
164
+ "#FFB86C"
135
165
  ],
136
166
  [
137
167
  "monokai-pro",
@@ -146,7 +176,9 @@ var PRESETS = [
146
176
  "#FFFFFF",
147
177
  "#403E41",
148
178
  "#2D2A2E",
149
- "#78DCE8"
179
+ "#78DCE8",
180
+ "#FFD866",
181
+ "#FC9867"
150
182
  ],
151
183
  [
152
184
  "solarized-dark",
@@ -161,7 +193,9 @@ var PRESETS = [
161
193
  "#FDF6E3",
162
194
  "#073642",
163
195
  "#002B36",
164
- "#268BD2"
196
+ "#268BD2",
197
+ "#B58900",
198
+ "#CB4B16"
165
199
  ],
166
200
  [
167
201
  "solarized-light",
@@ -176,7 +210,9 @@ var PRESETS = [
176
210
  "#002B36",
177
211
  "#EEE8D5",
178
212
  "#FDF6E3",
179
- "#268BD2"
213
+ "#268BD2",
214
+ "#B58900",
215
+ "#CB4B16"
180
216
  ],
181
217
  [
182
218
  "nord",
@@ -191,7 +227,9 @@ var PRESETS = [
191
227
  "#ECEFF4",
192
228
  "#3B4252",
193
229
  "#2E3440",
194
- "#88C0D0"
230
+ "#88C0D0",
231
+ "#EBCB8B",
232
+ "#D08770"
195
233
  ],
196
234
  [
197
235
  "gruvbox-dark",
@@ -206,7 +244,9 @@ var PRESETS = [
206
244
  "#FBF1C7",
207
245
  "#3C3836",
208
246
  "#282828",
209
- "#83A598"
247
+ "#83A598",
248
+ "#FABD2F",
249
+ "#FE8019"
210
250
  ],
211
251
  [
212
252
  "tokyo-night",
@@ -221,7 +261,9 @@ var PRESETS = [
221
261
  "#C0CAF5",
222
262
  "#292E42",
223
263
  "#1A1B26",
224
- "#7AA2F7"
264
+ "#7AA2F7",
265
+ "#E0AF68",
266
+ "#FF9E64"
225
267
  ],
226
268
  [
227
269
  "catppuccin-mocha",
@@ -236,7 +278,9 @@ var PRESETS = [
236
278
  "#FFFFFF",
237
279
  "#313244",
238
280
  "#1E1E2E",
239
- "#89B4FA"
281
+ "#89B4FA",
282
+ "#F9E2AF",
283
+ "#FAB387"
240
284
  ],
241
285
  [
242
286
  "catppuccin-latte",
@@ -251,7 +295,9 @@ var PRESETS = [
251
295
  "#11111B",
252
296
  "#E6E9EF",
253
297
  "#EFF1F5",
254
- "#1E66F5"
298
+ "#1E66F5",
299
+ "#DF8E1D",
300
+ "#FE640B"
255
301
  ],
256
302
  [
257
303
  "rose-pine",
@@ -266,7 +312,9 @@ var PRESETS = [
266
312
  "#E0DEF4",
267
313
  "#26233A",
268
314
  "#191724",
269
- "#9CCFD8"
315
+ "#9CCFD8",
316
+ "#F6C177",
317
+ "#EA9D34"
270
318
  ],
271
319
  [
272
320
  "rose-pine-moon",
@@ -281,7 +329,9 @@ var PRESETS = [
281
329
  "#E0DEF4",
282
330
  "#2A273F",
283
331
  "#232136",
284
- "#9CCFD8"
332
+ "#9CCFD8",
333
+ "#F6C177",
334
+ "#EA9D34"
285
335
  ],
286
336
  [
287
337
  "pastel-dark",
@@ -296,7 +346,9 @@ var PRESETS = [
296
346
  "#FFFFFF",
297
347
  "#3A3A4A",
298
348
  "#2B2B3A",
299
- "#89CFF0"
349
+ "#89CFF0",
350
+ "#FFD580",
351
+ "#FFAA5E"
300
352
  ],
301
353
  [
302
354
  "kanagawa",
@@ -311,7 +363,9 @@ var PRESETS = [
311
363
  "#FFFFFF",
312
364
  "#2A2A37",
313
365
  "#1F1F28",
314
- "#7E9CD8"
366
+ "#7E9CD8",
367
+ "#E6C384",
368
+ "#FFA066"
315
369
  ],
316
370
  [
317
371
  "everforest",
@@ -326,7 +380,213 @@ var PRESETS = [
326
380
  "#FFFFFF",
327
381
  "#374145",
328
382
  "#2D353B",
329
- "#7FBBB3"
383
+ "#7FBBB3",
384
+ "#DBBC7F",
385
+ "#E69875"
386
+ ],
387
+ [
388
+ "hi-feline",
389
+ "#FF6B8A",
390
+ "#FFB3C6",
391
+ "#FF1744",
392
+ "#FFE082",
393
+ "#FF5252",
394
+ "#FF0000",
395
+ "#C48B9F",
396
+ "#FFE4EC",
397
+ "#FFFFFF",
398
+ "#4A2030",
399
+ "#3A1525",
400
+ "#FF6B8A",
401
+ "#FFE082",
402
+ "#FFA726"
403
+ ],
404
+ [
405
+ "plumber-bros",
406
+ "#4CAF50",
407
+ "#F44336",
408
+ "#2196F3",
409
+ "#FFD700",
410
+ "#FF5722",
411
+ "#FF0000",
412
+ "#795548",
413
+ "#EFEBE9",
414
+ "#FFFFFF",
415
+ "#1B5E20",
416
+ "#0D3010",
417
+ "#4CAF50",
418
+ "#FFD700",
419
+ "#FF9800"
420
+ ],
421
+ [
422
+ "hedgehog-speed",
423
+ "#1565C0",
424
+ "#FFD700",
425
+ "#4CAF50",
426
+ "#FFEB3B",
427
+ "#F44336",
428
+ "#FF0000",
429
+ "#5C6BC0",
430
+ "#E8EAF6",
431
+ "#FFFFFF",
432
+ "#0D47A1",
433
+ "#0A2E6E",
434
+ "#1565C0",
435
+ "#FFEB3B",
436
+ "#FF9800"
437
+ ],
438
+ [
439
+ "block-craft",
440
+ "#4CAF50",
441
+ "#8D6E63",
442
+ "#795548",
443
+ "#FFEB3B",
444
+ "#F44336",
445
+ "#FF0000",
446
+ "#78909C",
447
+ "#BCAAA4",
448
+ "#FFFFFF",
449
+ "#33691E",
450
+ "#1B4400",
451
+ "#4CAF50",
452
+ "#FFEB3B",
453
+ "#FF9800"
454
+ ],
455
+ [
456
+ "galaxy-conflicts",
457
+ "#F44336",
458
+ "#2196F3",
459
+ "#9C27B0",
460
+ "#FFD54F",
461
+ "#FF1744",
462
+ "#FF0000",
463
+ "#546E7A",
464
+ "#ECEFF1",
465
+ "#FFFFFF",
466
+ "#1A1A2E",
467
+ "#0D0D1A",
468
+ "#F44336",
469
+ "#FFD54F",
470
+ "#FF6D00"
471
+ ],
472
+ [
473
+ "pocket-creatures",
474
+ "#F44336",
475
+ "#FFFFFF",
476
+ "#FFEB3B",
477
+ "#FFC107",
478
+ "#E53935",
479
+ "#FF0000",
480
+ "#90A4AE",
481
+ "#ECEFF1",
482
+ "#FFFFFF",
483
+ "#B71C1C",
484
+ "#7F0000",
485
+ "#F44336",
486
+ "#FFC107",
487
+ "#FF9800"
488
+ ],
489
+ [
490
+ "brick-wizard",
491
+ "#7B1FA2",
492
+ "#FFD700",
493
+ "#880E4F",
494
+ "#FFC107",
495
+ "#F44336",
496
+ "#FF0000",
497
+ "#6A1B9A",
498
+ "#E1BEE7",
499
+ "#FFFFFF",
500
+ "#311B92",
501
+ "#1A0A52",
502
+ "#7B1FA2",
503
+ "#FFC107",
504
+ "#FF9800"
505
+ ],
506
+ [
507
+ "caped-knight",
508
+ "#616161",
509
+ "#FDD835",
510
+ "#424242",
511
+ "#FFC107",
512
+ "#F44336",
513
+ "#FF0000",
514
+ "#757575",
515
+ "#E0E0E0",
516
+ "#FFFFFF",
517
+ "#212121",
518
+ "#0A0A0A",
519
+ "#616161",
520
+ "#FFC107",
521
+ "#FF9800"
522
+ ],
523
+ [
524
+ "web-crawler",
525
+ "#F44336",
526
+ "#1565C0",
527
+ "#FFFFFF",
528
+ "#FFEB3B",
529
+ "#D50000",
530
+ "#FF0000",
531
+ "#78909C",
532
+ "#E3F2FD",
533
+ "#FFFFFF",
534
+ "#B71C1C",
535
+ "#7F0000",
536
+ "#F44336",
537
+ "#FFEB3B",
538
+ "#FF9800"
539
+ ],
540
+ [
541
+ "frozen-kingdom",
542
+ "#81D4FA",
543
+ "#CE93D8",
544
+ "#B3E5FC",
545
+ "#FFF9C4",
546
+ "#EF9A9A",
547
+ "#FF0000",
548
+ "#90CAF9",
549
+ "#E1F5FE",
550
+ "#FFFFFF",
551
+ "#1A237E",
552
+ "#0D1252",
553
+ "#81D4FA",
554
+ "#FFF9C4",
555
+ "#FFCC80"
556
+ ],
557
+ [
558
+ "coral-reef",
559
+ "#FF7043",
560
+ "#29B6F6",
561
+ "#AB47BC",
562
+ "#FFEE58",
563
+ "#EF5350",
564
+ "#FF0000",
565
+ "#4DB6AC",
566
+ "#E0F7FA",
567
+ "#FFFFFF",
568
+ "#BF360C",
569
+ "#7F2008",
570
+ "#FF7043",
571
+ "#FFEE58",
572
+ "#FFA726"
573
+ ],
574
+ [
575
+ "toy-ranch",
576
+ "#8D6E63",
577
+ "#7CB342",
578
+ "#7E57C2",
579
+ "#FFEE58",
580
+ "#EF5350",
581
+ "#FF0000",
582
+ "#90A4AE",
583
+ "#EFEBE9",
584
+ "#FFFFFF",
585
+ "#4E342E",
586
+ "#2E1C15",
587
+ "#8D6E63",
588
+ "#FFEE58",
589
+ "#FFA726"
330
590
  ]
331
591
  ];
332
592
  var BUILTIN_THEMES = PRESETS.map(fromTuple);
@@ -350,7 +610,7 @@ var deriveSeverityColors = (c) => ({
350
610
  });
351
611
 
352
612
  // src/updates.ts
353
- import { execFile, exec, spawn } from "child_process";
613
+ import { execFile, spawn } from "child_process";
354
614
  import { readFile } from "fs/promises";
355
615
  import { join, dirname } from "path";
356
616
  import { fileURLToPath } from "url";
@@ -366,7 +626,7 @@ var getPackageVersion = async () => {
366
626
  };
367
627
  var getNpmPath = () => {
368
628
  const nodeDir = dirname(process.execPath);
369
- return join(nodeDir, "npm");
629
+ return join(nodeDir, process.platform === "win32" ? "npm.cmd" : "npm");
370
630
  };
371
631
  var checkForUpdate = () => new Promise((resolve) => {
372
632
  const npm = getNpmPath();
@@ -388,7 +648,7 @@ var checkForUpdate = () => new Promise((resolve) => {
388
648
  var installUpdate = () => {
389
649
  const npm = getNpmPath();
390
650
  return new Promise((resolve, reject) => {
391
- exec(`${npm} install -g agenttop@latest`, { timeout: 6e4 }, (err, stdout) => {
651
+ execFile(npm, ["install", "-g", "agenttop@latest"], { timeout: 6e4 }, (err, stdout) => {
392
652
  if (err) {
393
653
  reject(err);
394
654
  } else {
@@ -418,7 +678,7 @@ var compareVersions = (a, b) => {
418
678
  };
419
679
 
420
680
  // src/ui/components/StatusBar.tsx
421
- import React, { useState, useEffect } from "react";
681
+ import React, { useState, useEffect, useRef } from "react";
422
682
  import { Box, Text } from "ink";
423
683
 
424
684
  // src/ui/theme.ts
@@ -429,12 +689,15 @@ var colors = {
429
689
  warning: "#E5C07B",
430
690
  error: "#E06C75",
431
691
  critical: "#FF0000",
692
+ success: "#98C379",
432
693
  muted: "#5C6370",
433
694
  text: "#ABB2BF",
434
695
  bright: "#FFFFFF",
435
696
  border: "#3E4451",
436
697
  selected: "#2C313A",
437
- header: "#61AFEF"
698
+ header: "#61AFEF",
699
+ waiting: "#E5C07B",
700
+ stale: "#D19A66"
438
701
  };
439
702
  var severityColors = {
440
703
  info: colors.muted,
@@ -456,19 +719,27 @@ var toolColors = {
456
719
  var getToolColor = (toolName) => toolColors[toolName] || colors.text;
457
720
  var applyTheme = (theme) => {
458
721
  Object.assign(colors, theme.colors);
722
+ colors.success = theme.colors.secondary;
459
723
  Object.assign(toolColors, theme.toolColors);
460
724
  Object.assign(severityColors, deriveSeverityColors(theme.colors));
461
725
  };
462
726
 
463
727
  // src/ui/components/StatusBar.tsx
464
728
  import { jsx, jsxs } from "react/jsx-runtime";
729
+ var formatTime = () => (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
465
730
  var StatusBar = React.memo(({ sessionCount, alertCount, version, updateInfo }) => {
466
- const [time, setTime] = useState(/* @__PURE__ */ new Date());
731
+ const [timeStr, setTimeStr] = useState(formatTime);
732
+ const lastRef = useRef(timeStr);
467
733
  useEffect(() => {
468
- const interval = setInterval(() => setTime(/* @__PURE__ */ new Date()), 1e3);
734
+ const interval = setInterval(() => {
735
+ const next = formatTime();
736
+ if (next !== lastRef.current) {
737
+ lastRef.current = next;
738
+ setTimeStr(next);
739
+ }
740
+ }, 1e3);
469
741
  return () => clearInterval(interval);
470
742
  }, []);
471
- const timeStr = time.toLocaleTimeString("en-GB", { hour12: false });
472
743
  return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: colors.border, paddingX: 1, justifyContent: "space-between", children: [
473
744
  /* @__PURE__ */ jsxs(Text, { color: colors.header, bold: true, children: [
474
745
  "agenttop v",
@@ -496,18 +767,32 @@ var StatusBar = React.memo(({ sessionCount, alertCount, version, updateInfo }) =
496
767
  // src/ui/components/SessionList.tsx
497
768
  import React2 from "react";
498
769
  import { Box as Box2, Text as Text2 } from "ink";
499
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
770
+
771
+ // src/ui/format.ts
772
+ var formatTokens = (n) => {
773
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
774
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
775
+ return String(n);
776
+ };
777
+ var formatTime2 = (ts) => {
778
+ const d = new Date(ts);
779
+ return d.toLocaleTimeString("en-GB", { hour12: false });
780
+ };
500
781
  var formatModel = (model) => {
782
+ if (model.includes("opus")) return "opus";
783
+ if (model.includes("sonnet")) return "sonnet";
784
+ if (model.includes("haiku")) return "haiku";
785
+ return model.slice(0, 4);
786
+ };
787
+ var formatModelShort = (model) => {
501
788
  if (model.includes("opus")) return "opus";
502
789
  if (model.includes("sonnet")) return "son";
503
790
  if (model.includes("haiku")) return "hai";
504
791
  return model.slice(0, 4);
505
792
  };
506
- var formatTokens = (n) => {
507
- if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
508
- if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
509
- return String(n);
510
- };
793
+
794
+ // src/ui/components/SessionList.tsx
795
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
511
796
  var truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
512
797
  var getDisplayName = (s) => {
513
798
  if (s.nickname) return s.nickname;
@@ -567,11 +852,14 @@ var SessionList = React2.memo(
567
852
  if (item.type === "group") {
568
853
  const g = item.group;
569
854
  const arrow = g.expanded ? "\u25BE" : "\u25B8";
570
- const dotColor2 = g.isActive ? colors.success : colors.muted;
571
- const statusDot2 = g.isActive ? "\u25CF" : "\u25CB";
572
- const nameColor2 = isSelected ? colors.bright : g.isActive ? colors.secondary : colors.text;
573
- const label2 = truncate(`${g.key} (${g.sessions.length})`, INNER_WIDTH - 4);
574
- const model2 = formatModel(g.latestModel);
855
+ const dotColor2 = g.status === "waiting" ? colors.waiting : g.status === "stale" ? colors.stale : g.status === "active" ? colors.success : colors.muted;
856
+ const statusDot2 = g.status === "inactive" ? "\u25CB" : "\u25CF";
857
+ const nameColor2 = isSelected ? colors.bright : g.status !== "inactive" ? colors.secondary : colors.text;
858
+ const groupPinned = g.sessions.some((s) => s.pinned);
859
+ const pinMarker2 = groupPinned ? "* " : " ";
860
+ const statusTag2 = g.status === "waiting" ? " [waiting]" : g.status === "stale" ? " [stale]" : "";
861
+ const label2 = truncate(`${g.key} (${g.sessions.length})${statusTag2}`, INNER_WIDTH - 4);
862
+ const model2 = formatModelShort(g.latestModel);
575
863
  return /* @__PURE__ */ jsxs2(
576
864
  Box2,
577
865
  {
@@ -584,6 +872,7 @@ var SessionList = React2.memo(
584
872
  " ",
585
873
  /* @__PURE__ */ jsx2(Text2, { color: dotColor2, children: statusDot2 }),
586
874
  " ",
875
+ pinMarker2,
587
876
  label2
588
877
  ] }),
589
878
  /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? colors.text : colors.muted, wrap: "truncate", children: [
@@ -602,14 +891,15 @@ var SessionList = React2.memo(
602
891
  );
603
892
  }
604
893
  const session = item.type === "session" ? item.session : item.session;
605
- const isActive = session.pid !== null;
606
- const statusDot = isActive ? "\u25CF" : "\u25CB";
607
- const dotColor = isActive ? colors.success : colors.muted;
894
+ const dotColor = session.status === "waiting" ? colors.waiting : session.status === "stale" ? colors.stale : session.status === "active" ? colors.success : colors.muted;
895
+ const statusDot = session.status === "inactive" ? "\u25CB" : "\u25CF";
896
+ const pinMarker = session.pinned ? "* " : " ";
897
+ const statusTag = session.status === "waiting" ? " [waiting]" : session.status === "stale" ? " [stale]" : "";
608
898
  const totalIn = session.usage.inputTokens + session.usage.cacheReadTokens;
609
- const model = formatModel(session.model);
899
+ const model = formatModelShort(session.model);
610
900
  if (item.type === "session") {
611
- const nameColor2 = isSelected ? colors.bright : isActive ? colors.secondary : colors.muted;
612
- const displayName2 = truncate(session.nickname || session.slug, INNER_WIDTH - 6);
901
+ const nameColor2 = isSelected ? colors.bright : session.status !== "inactive" ? colors.secondary : colors.muted;
902
+ const displayName2 = truncate(`${session.nickname || session.slug}${statusTag}`, INNER_WIDTH - 6);
613
903
  return /* @__PURE__ */ jsxs2(
614
904
  Box2,
615
905
  {
@@ -622,6 +912,7 @@ var SessionList = React2.memo(
622
912
  " ",
623
913
  /* @__PURE__ */ jsx2(Text2, { color: dotColor, children: statusDot }),
624
914
  " ",
915
+ pinMarker,
625
916
  displayName2
626
917
  ] }),
627
918
  /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? colors.text : colors.muted, wrap: "truncate", children: [
@@ -640,8 +931,8 @@ var SessionList = React2.memo(
640
931
  );
641
932
  }
642
933
  const indicator = isSelected ? "\u25B8" : " ";
643
- const nameColor = isSelected ? colors.bright : isActive ? colors.secondary : colors.text;
644
- const displayName = truncate(getDisplayName(session), INNER_WIDTH - 4);
934
+ const nameColor = isSelected ? colors.bright : session.status !== "inactive" ? colors.secondary : colors.text;
935
+ const displayName = truncate(`${getDisplayName(session)}${statusTag}`, INNER_WIDTH - 4);
645
936
  return /* @__PURE__ */ jsxs2(
646
937
  Box2,
647
938
  {
@@ -654,6 +945,7 @@ var SessionList = React2.memo(
654
945
  " ",
655
946
  /* @__PURE__ */ jsx2(Text2, { color: dotColor, children: statusDot }),
656
947
  " ",
948
+ pinMarker,
657
949
  displayName
658
950
  ] }),
659
951
  /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? colors.text : colors.muted, wrap: "truncate", children: [
@@ -682,10 +974,6 @@ import React3 from "react";
682
974
  import { Box as Box3, Text as Text3 } from "ink";
683
975
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
684
976
  var TAG_COLORS = ["#61AFEF", "#98C379", "#C678DD", "#E5C07B", "#E06C75", "#56B6C2", "#D19A66", "#BE5046"];
685
- var formatTime = (ts) => {
686
- const d = new Date(ts);
687
- return d.toLocaleTimeString("en-GB", { hour12: false });
688
- };
689
977
  var summarizeInput = (call) => {
690
978
  const input = call.toolInput;
691
979
  switch (call.toolName) {
@@ -713,7 +1001,7 @@ var ActivityFeed = React3.memo(
713
1001
  events,
714
1002
  sessionSlug,
715
1003
  sessionId,
716
- isActive,
1004
+ status,
717
1005
  focused,
718
1006
  height,
719
1007
  scrollOffset,
@@ -780,7 +1068,7 @@ var ActivityFeed = React3.memo(
780
1068
  const tagColor = merged ? slugColorMap.get(call.sessionId) || colors.muted : void 0;
781
1069
  return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
782
1070
  tag && /* @__PURE__ */ jsx3(Text3, { color: tagColor, children: tag.padEnd(5) }),
783
- /* @__PURE__ */ jsx3(Text3, { color: isSelected ? colors.bright : colors.muted, underline: isSelected, children: formatTime(call.timestamp) }),
1071
+ /* @__PURE__ */ jsx3(Text3, { color: isSelected ? colors.bright : colors.muted, underline: isSelected, children: formatTime2(call.timestamp) }),
784
1072
  " ",
785
1073
  /* @__PURE__ */ jsx3(Text3, { color: getToolColor(call.toolName), bold: true, underline: isSelected, children: call.toolName.padEnd(8) }),
786
1074
  /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? colors.bright : colors.text, underline: isSelected, children: [
@@ -790,7 +1078,7 @@ var ActivityFeed = React3.memo(
790
1078
  ] }) }, `${call.timestamp}-${i}`);
791
1079
  }),
792
1080
  focused && canScroll && !isAtTop && visible.length > 0 && /* @__PURE__ */ jsx3(Box3, { paddingX: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx3(Text3, { color: colors.muted, children: isAtBottom ? "" : "G:bottom " }) }),
793
- !merged && sessionId && isActive === false && /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, flexDirection: "column", children: [
1081
+ !merged && sessionId && status === "inactive" && /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, flexDirection: "column", children: [
794
1082
  /* @__PURE__ */ jsx3(Text3, { color: colors.muted, children: " " }),
795
1083
  /* @__PURE__ */ jsxs3(Text3, { color: colors.muted, children: [
796
1084
  "resume: ",
@@ -815,10 +1103,6 @@ var ActivityFeed = React3.memo(
815
1103
  import React4 from "react";
816
1104
  import { Box as Box4, Text as Text4 } from "ink";
817
1105
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
818
- var formatTime2 = (ts) => {
819
- const d = new Date(ts);
820
- return d.toLocaleTimeString("en-GB", { hour12: false });
821
- };
822
1106
  var severityIcon = {
823
1107
  info: "i",
824
1108
  warn: "!",
@@ -856,11 +1140,6 @@ var AlertBar = React4.memo(({ alerts, maxVisible = 4 }) => {
856
1140
  import React5 from "react";
857
1141
  import { Box as Box5, Text as Text5 } from "ink";
858
1142
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
859
- var formatTokens2 = (n) => {
860
- if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
861
- if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
862
- return String(n);
863
- };
864
1143
  var formatUptime = (startTime) => {
865
1144
  const ms = Date.now() - startTime;
866
1145
  const secs = Math.floor(ms / 1e3);
@@ -870,12 +1149,6 @@ var formatUptime = (startTime) => {
870
1149
  if (mins > 0) return `${mins}m ${secs % 60}s`;
871
1150
  return `${secs}s`;
872
1151
  };
873
- var formatModel2 = (model) => {
874
- if (model.includes("opus")) return "opus";
875
- if (model.includes("sonnet")) return "sonnet";
876
- if (model.includes("haiku")) return "haiku";
877
- return model.slice(0, 20);
878
- };
879
1152
  var SessionDetail = React5.memo(({ session, focused }) => {
880
1153
  const totalInput = session.usage.inputTokens + session.usage.cacheCreationTokens + session.usage.cacheReadTokens;
881
1154
  const totalTokens = totalInput + session.usage.outputTokens;
@@ -903,7 +1176,7 @@ var SessionDetail = React5.memo(({ session, focused }) => {
903
1176
  ] }),
904
1177
  /* @__PURE__ */ jsxs5(Box5, { children: [
905
1178
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: "model: " }),
906
- /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatModel2(session.model) })
1179
+ /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatModel(session.model) })
907
1180
  ] }),
908
1181
  /* @__PURE__ */ jsxs5(Box5, { children: [
909
1182
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: "cwd: " }),
@@ -946,19 +1219,19 @@ var SessionDetail = React5.memo(({ session, focused }) => {
946
1219
  /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: colors.header, bold: true, children: "Token usage" }) }),
947
1220
  /* @__PURE__ */ jsxs5(Box5, { children: [
948
1221
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: " input: " }),
949
- /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens2(session.usage.inputTokens) })
1222
+ /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens(session.usage.inputTokens) })
950
1223
  ] }),
951
1224
  /* @__PURE__ */ jsxs5(Box5, { children: [
952
1225
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: " output: " }),
953
- /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens2(session.usage.outputTokens) })
1226
+ /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens(session.usage.outputTokens) })
954
1227
  ] }),
955
1228
  /* @__PURE__ */ jsxs5(Box5, { children: [
956
1229
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: " cache write: " }),
957
- /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens2(session.usage.cacheCreationTokens) })
1230
+ /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens(session.usage.cacheCreationTokens) })
958
1231
  ] }),
959
1232
  /* @__PURE__ */ jsxs5(Box5, { children: [
960
1233
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: " cache read: " }),
961
- /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens2(session.usage.cacheReadTokens) })
1234
+ /* @__PURE__ */ jsx5(Text5, { color: colors.text, children: formatTokens(session.usage.cacheReadTokens) })
962
1235
  ] }),
963
1236
  /* @__PURE__ */ jsxs5(Box5, { children: [
964
1237
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: " cache hit: " }),
@@ -969,7 +1242,7 @@ var SessionDetail = React5.memo(({ session, focused }) => {
969
1242
  ] }),
970
1243
  /* @__PURE__ */ jsxs5(Box5, { children: [
971
1244
  /* @__PURE__ */ jsx5(Text5, { color: colors.muted, children: " total: " }),
972
- /* @__PURE__ */ jsx5(Text5, { color: colors.bright, bold: true, children: formatTokens2(totalTokens) })
1245
+ /* @__PURE__ */ jsx5(Text5, { color: colors.bright, bold: true, children: formatTokens(totalTokens) })
973
1246
  ] })
974
1247
  ] })
975
1248
  ]
@@ -1109,10 +1382,6 @@ var FooterBar = React7.memo(
1109
1382
  import React8, { useState as useState3 } from "react";
1110
1383
  import { Box as Box8, Text as Text8, useInput as useInput2 } from "ink";
1111
1384
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1112
- var formatTime3 = (ts) => {
1113
- const d = new Date(ts);
1114
- return d.toLocaleTimeString("en-GB", { hour12: false });
1115
- };
1116
1385
  var renderBash = (event) => {
1117
1386
  const lines = [];
1118
1387
  const cmd = String(event.call.toolInput.command || "");
@@ -1286,7 +1555,7 @@ var ToolCallDetail = React8.memo(({ event, focused, height }) => {
1286
1555
  /* @__PURE__ */ jsx8(Text8, { color: getToolColor(event.call.toolName), bold: true, children: event.call.toolName }),
1287
1556
  /* @__PURE__ */ jsxs8(Text8, { color: colors.muted, children: [
1288
1557
  " ",
1289
- formatTime3(event.call.timestamp)
1558
+ formatTime2(event.call.timestamp)
1290
1559
  ] }),
1291
1560
  /* @__PURE__ */ jsxs8(Text8, { color: colors.muted, children: [
1292
1561
  " ",
@@ -1320,7 +1589,7 @@ var ToolCallDetail = React8.memo(({ event, focused, height }) => {
1320
1589
  });
1321
1590
 
1322
1591
  // src/ui/components/SettingsMenu.tsx
1323
- import React9, { useState as useState4, useMemo, useEffect as useEffect2, useRef } from "react";
1592
+ import React9, { useState as useState4, useMemo, useEffect as useEffect2, useRef as useRef2 } from "react";
1324
1593
  import { Box as Box9, Text as Text9, useInput as useInput3, useStdout } from "ink";
1325
1594
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1326
1595
  var KEYBIND_LABELS = {
@@ -1489,7 +1758,7 @@ var SettingsMenu = React9.memo(({ config, onClose, onOpenThemeMenu }) => {
1489
1758
  const [selectablePos, setSelectablePos] = useState4(0);
1490
1759
  const [rebinding, setRebinding] = useState4(false);
1491
1760
  const [toast, setToast] = useState4("");
1492
- const toastTimer = useRef(null);
1761
+ const toastTimer = useRef2(null);
1493
1762
  const showToast = (msg) => {
1494
1763
  if (toastTimer.current) clearTimeout(toastTimer.current);
1495
1764
  setToast(msg);
@@ -1620,7 +1889,7 @@ var SettingsMenu = React9.memo(({ config, onClose, onOpenThemeMenu }) => {
1620
1889
  });
1621
1890
 
1622
1891
  // src/ui/components/ThemeMenu.tsx
1623
- import React11, { useState as useState6, useCallback, useRef as useRef2, useEffect as useEffect3 } from "react";
1892
+ import React11, { useState as useState6, useCallback, useRef as useRef3, useEffect as useEffect3 } from "react";
1624
1893
  import { Box as Box11, Text as Text11, useInput as useInput5, useStdout as useStdout2 } from "ink";
1625
1894
 
1626
1895
  // src/ui/components/ThemeEditor.tsx
@@ -1756,7 +2025,7 @@ var ThemeMenu = React11.memo(({ config, onClose }) => {
1756
2025
  const [nameInput, setNameInput] = useState6("");
1757
2026
  const [namingAction, setNamingAction] = useState6("copy");
1758
2027
  const [toast, setToast] = useState6("");
1759
- const toastTimer = useRef2(null);
2028
+ const toastTimer = useRef3(null);
1760
2029
  const showToast = (msg) => {
1761
2030
  if (toastTimer.current) clearTimeout(toastTimer.current);
1762
2031
  setToast(msg);
@@ -1970,23 +2239,451 @@ var ThemeMenu = React11.memo(({ config, onClose }) => {
1970
2239
  /* @__PURE__ */ jsx11(Text11, { color: theme.colors.error, children: "\u2588" })
1971
2240
  ] }, theme.name);
1972
2241
  }),
1973
- toast && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx11(Text11, { color: colors.warning, children: toast }) })
2242
+ toast && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx11(Text11, { color: colors.warning, children: toast }) }),
2243
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: colors.muted, dimColor: true, italic: true, wrap: "truncate", children: "Some themes were inspired by pop culture. Names have been changed to protect the innocent (and our legal team)." }) })
1974
2244
  ]
1975
2245
  }
1976
2246
  ) });
1977
2247
  });
1978
2248
 
1979
- // src/ui/components/ThemePickerModal.tsx
1980
- import React12, { useState as useState7, useEffect as useEffect4 } from "react";
1981
- import { Box as Box12, Text as Text12, useInput as useInput6 } from "ink";
2249
+ // src/ui/components/AlertRulesMenu.tsx
2250
+ import React12, { useState as useState7, useRef as useRef4, useEffect as useEffect4 } from "react";
2251
+ import { Box as Box12, Text as Text12, useInput as useInput6, useStdout as useStdout3 } from "ink";
1982
2252
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1983
- var ThemePickerModal = React12.memo(({ onSelect, onSkip, onDismiss }) => {
1984
- const [selectedIndex, setSelectedIndex] = useState7(0);
2253
+ var MATCH_OPTIONS = ["input", "output", "toolName", "all"];
2254
+ var SEVERITY_OPTIONS2 = ["info", "warn", "high", "critical"];
2255
+ var RULE_LABELS2 = {
2256
+ network: "Network detection",
2257
+ exfiltration: "Exfiltration detection",
2258
+ sensitiveFiles: "Sensitive files",
2259
+ shellEscape: "Shell escape",
2260
+ injection: "Prompt injection"
2261
+ };
2262
+ var BUILTIN_RULE_KEYS = Object.keys(RULE_LABELS2);
2263
+ var FORM_FIELDS = ["name", "pattern", "match", "severity", "message"];
2264
+ var FORM_LABELS = {
2265
+ name: "Name",
2266
+ pattern: "Pattern (regex)",
2267
+ match: "Match target",
2268
+ severity: "Severity",
2269
+ message: "Message"
2270
+ };
2271
+ var emptyForm = () => ({
2272
+ name: "",
2273
+ pattern: "",
2274
+ match: "all",
2275
+ severity: "warn",
2276
+ message: ""
2277
+ });
2278
+ var formFromRule = (rule) => ({
2279
+ name: rule.name,
2280
+ pattern: rule.pattern,
2281
+ match: rule.match,
2282
+ severity: rule.severity,
2283
+ message: rule.message
2284
+ });
2285
+ var validatePattern = (pattern) => {
2286
+ if (!pattern.trim()) return "Pattern cannot be empty";
2287
+ try {
2288
+ new RegExp(pattern);
2289
+ return null;
2290
+ } catch {
2291
+ return "Invalid regex";
2292
+ }
2293
+ };
2294
+ var AlertRulesMenu = React12.memo(({ config, onClose, onSave }) => {
2295
+ const { stdout } = useStdout3();
2296
+ const termHeight = stdout?.rows ?? 40;
2297
+ const [localConfig, setLocalConfig] = useState7(() => JSON.parse(JSON.stringify(config)));
2298
+ const [selectedIdx, setSelectedIdx] = useState7(0);
2299
+ const [view, setView] = useState7("list");
2300
+ const [toast, setToast] = useState7("");
2301
+ const toastTimer = useRef4(null);
2302
+ const [formData, setFormData] = useState7(emptyForm());
2303
+ const [formField, setFormField] = useState7(0);
2304
+ const [formError, setFormError] = useState7("");
2305
+ const [editingIndex, setEditingIndex] = useState7(null);
2306
+ useEffect4(
2307
+ () => () => {
2308
+ if (toastTimer.current) clearTimeout(toastTimer.current);
2309
+ },
2310
+ []
2311
+ );
2312
+ const showToast = (msg) => {
2313
+ if (toastTimer.current) clearTimeout(toastTimer.current);
2314
+ setToast(msg);
2315
+ toastTimer.current = setTimeout(() => setToast(""), 2500);
2316
+ };
2317
+ const customRules = localConfig.alerts.custom || [];
2318
+ const totalItems = 1 + BUILTIN_RULE_KEYS.length + customRules.length;
2319
+ const isStaleRow = (idx) => idx === 0;
2320
+ const isBuiltinRow = (idx) => idx >= 1 && idx <= BUILTIN_RULE_KEYS.length;
2321
+ const isCustomRow = (idx) => idx > BUILTIN_RULE_KEYS.length;
2322
+ const getBuiltinKey = (idx) => BUILTIN_RULE_KEYS[idx - 1];
2323
+ const getCustomIndex = (idx) => idx - BUILTIN_RULE_KEYS.length - 1;
2324
+ useInput6((input, key) => {
2325
+ if (view === "form") {
2326
+ const currentField = FORM_FIELDS[formField];
2327
+ if (key.escape) {
2328
+ setView("list");
2329
+ setFormError("");
2330
+ return;
2331
+ }
2332
+ if (currentField === "match") {
2333
+ if (key.return || input === " ") {
2334
+ const idx = MATCH_OPTIONS.indexOf(formData.match);
2335
+ setFormData((f) => ({ ...f, match: MATCH_OPTIONS[(idx + 1) % MATCH_OPTIONS.length] }));
2336
+ return;
2337
+ }
2338
+ if (key.downArrow) {
2339
+ setFormField((f) => Math.min(f + 1, FORM_FIELDS.length - 1));
2340
+ return;
2341
+ }
2342
+ if (key.upArrow) {
2343
+ setFormField((f) => Math.max(f - 1, 0));
2344
+ return;
2345
+ }
2346
+ if (key.tab) {
2347
+ if (formField < FORM_FIELDS.length - 1) {
2348
+ setFormField((f) => f + 1);
2349
+ }
2350
+ return;
2351
+ }
2352
+ return;
2353
+ }
2354
+ if (currentField === "severity") {
2355
+ if (key.return || input === " ") {
2356
+ const idx = SEVERITY_OPTIONS2.indexOf(formData.severity);
2357
+ setFormData((f) => ({ ...f, severity: SEVERITY_OPTIONS2[(idx + 1) % SEVERITY_OPTIONS2.length] }));
2358
+ return;
2359
+ }
2360
+ if (key.downArrow) {
2361
+ setFormField((f) => Math.min(f + 1, FORM_FIELDS.length - 1));
2362
+ return;
2363
+ }
2364
+ if (key.upArrow) {
2365
+ setFormField((f) => Math.max(f - 1, 0));
2366
+ return;
2367
+ }
2368
+ if (key.tab) {
2369
+ if (formField < FORM_FIELDS.length - 1) {
2370
+ setFormField((f) => f + 1);
2371
+ }
2372
+ return;
2373
+ }
2374
+ return;
2375
+ }
2376
+ if (key.return) {
2377
+ if (currentField === "pattern") {
2378
+ const err = validatePattern(formData.pattern);
2379
+ if (err) {
2380
+ setFormError(err);
2381
+ return;
2382
+ }
2383
+ setFormError("");
2384
+ }
2385
+ if (formField < FORM_FIELDS.length - 1) {
2386
+ setFormField((f) => f + 1);
2387
+ return;
2388
+ }
2389
+ if (!formData.name.trim()) {
2390
+ setFormError("Name cannot be empty");
2391
+ return;
2392
+ }
2393
+ const patErr = validatePattern(formData.pattern);
2394
+ if (patErr) {
2395
+ setFormError(patErr);
2396
+ return;
2397
+ }
2398
+ const newRule = {
2399
+ name: formData.name.trim(),
2400
+ pattern: formData.pattern,
2401
+ match: formData.match,
2402
+ severity: formData.severity,
2403
+ message: formData.message.trim(),
2404
+ enabled: true
2405
+ };
2406
+ setLocalConfig((c) => {
2407
+ const customs = [...c.alerts.custom || []];
2408
+ if (editingIndex !== null) {
2409
+ newRule.enabled = customs[editingIndex].enabled;
2410
+ customs[editingIndex] = newRule;
2411
+ } else {
2412
+ customs.push(newRule);
2413
+ }
2414
+ const updated = { ...c, alerts: { ...c.alerts, custom: customs } };
2415
+ onSave(updated);
2416
+ return updated;
2417
+ });
2418
+ showToast(editingIndex !== null ? `Updated '${newRule.name}'` : `Added '${newRule.name}'`);
2419
+ setView("list");
2420
+ setFormError("");
2421
+ return;
2422
+ }
2423
+ if (key.backspace || key.delete) {
2424
+ setFormData((f) => ({ ...f, [currentField]: f[currentField].slice(0, -1) }));
2425
+ setFormError("");
2426
+ return;
2427
+ }
2428
+ if (key.upArrow) {
2429
+ setFormField((f) => Math.max(f - 1, 0));
2430
+ return;
2431
+ }
2432
+ if (key.downArrow || key.tab) {
2433
+ if (currentField === "pattern") {
2434
+ const err = validatePattern(formData.pattern);
2435
+ if (err && formData.pattern.trim()) {
2436
+ setFormError(err);
2437
+ return;
2438
+ }
2439
+ }
2440
+ setFormField((f) => Math.min(f + 1, FORM_FIELDS.length - 1));
2441
+ return;
2442
+ }
2443
+ if (input && input.length === 1) {
2444
+ setFormData((f) => ({ ...f, [currentField]: f[currentField] + input }));
2445
+ setFormError("");
2446
+ return;
2447
+ }
2448
+ return;
2449
+ }
2450
+ if (key.escape) {
2451
+ onSave(localConfig);
2452
+ onClose();
2453
+ return;
2454
+ }
2455
+ if (key.upArrow) {
2456
+ setSelectedIdx((i) => Math.max(0, i - 1));
2457
+ return;
2458
+ }
2459
+ if (key.downArrow) {
2460
+ setSelectedIdx((i) => Math.min(totalItems - 1, i + 1));
2461
+ return;
2462
+ }
2463
+ if (input === " ") {
2464
+ if (isStaleRow(selectedIdx)) return;
2465
+ if (isBuiltinRow(selectedIdx)) {
2466
+ const ruleKey = getBuiltinKey(selectedIdx);
2467
+ setLocalConfig((c) => {
2468
+ const updated = {
2469
+ ...c,
2470
+ security: { ...c.security, rules: { ...c.security.rules, [ruleKey]: !c.security.rules[ruleKey] } }
2471
+ };
2472
+ onSave(updated);
2473
+ return updated;
2474
+ });
2475
+ return;
2476
+ }
2477
+ if (isCustomRow(selectedIdx)) {
2478
+ const ci = getCustomIndex(selectedIdx);
2479
+ setLocalConfig((c) => {
2480
+ const customs = [...c.alerts.custom || []];
2481
+ customs[ci] = { ...customs[ci], enabled: !customs[ci].enabled };
2482
+ const updated = { ...c, alerts: { ...c.alerts, custom: customs } };
2483
+ onSave(updated);
2484
+ return updated;
2485
+ });
2486
+ return;
2487
+ }
2488
+ return;
2489
+ }
2490
+ if (isStaleRow(selectedIdx)) {
2491
+ if (input === "+" || input === "=") {
2492
+ setLocalConfig((c) => {
2493
+ const updated = { ...c, alerts: { ...c.alerts, staleTimeout: c.alerts.staleTimeout + 15 } };
2494
+ onSave(updated);
2495
+ return updated;
2496
+ });
2497
+ return;
2498
+ }
2499
+ if (input === "-" || input === "_") {
2500
+ setLocalConfig((c) => {
2501
+ const newTimeout = Math.max(15, c.alerts.staleTimeout - 15);
2502
+ const updated = { ...c, alerts: { ...c.alerts, staleTimeout: newTimeout } };
2503
+ onSave(updated);
2504
+ return updated;
2505
+ });
2506
+ return;
2507
+ }
2508
+ }
2509
+ if (input === "n") {
2510
+ setFormData(emptyForm());
2511
+ setFormField(0);
2512
+ setEditingIndex(null);
2513
+ setFormError("");
2514
+ setView("form");
2515
+ return;
2516
+ }
2517
+ if (input === "e" && isCustomRow(selectedIdx)) {
2518
+ const ci = getCustomIndex(selectedIdx);
2519
+ const rule = customRules[ci];
2520
+ setFormData(formFromRule(rule));
2521
+ setFormField(0);
2522
+ setEditingIndex(ci);
2523
+ setFormError("");
2524
+ setView("form");
2525
+ return;
2526
+ }
2527
+ if (input === "d" && isCustomRow(selectedIdx)) {
2528
+ const ci = getCustomIndex(selectedIdx);
2529
+ const name = customRules[ci].name;
2530
+ setLocalConfig((c) => {
2531
+ const customs = [...c.alerts.custom || []];
2532
+ customs.splice(ci, 1);
2533
+ const updated = { ...c, alerts: { ...c.alerts, custom: customs } };
2534
+ onSave(updated);
2535
+ return updated;
2536
+ });
2537
+ setSelectedIdx((i) => Math.min(i, totalItems - 2));
2538
+ showToast(`Deleted '${name}'`);
2539
+ return;
2540
+ }
2541
+ });
2542
+ if (view === "form") {
2543
+ const currentField = FORM_FIELDS[formField];
2544
+ return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsxs12(
2545
+ Box12,
2546
+ {
2547
+ borderStyle: "round",
2548
+ borderColor: colors.primary,
2549
+ flexDirection: "column",
2550
+ paddingX: 2,
2551
+ paddingY: 1,
2552
+ height: termHeight,
2553
+ children: [
2554
+ /* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", marginBottom: 1, children: [
2555
+ /* @__PURE__ */ jsx12(Text12, { color: colors.header, bold: true, children: editingIndex !== null ? "EDIT RULE" : "NEW RULE" }),
2556
+ /* @__PURE__ */ jsx12(Text12, { color: colors.muted, children: "enter:next/save esc:cancel" })
2557
+ ] }),
2558
+ FORM_FIELDS.map((field, fi) => {
2559
+ const isCurrent = fi === formField;
2560
+ const value = formData[field];
2561
+ const isTextInput = field === "name" || field === "pattern" || field === "message";
2562
+ const displayValue = isTextInput ? isCurrent ? `${value}_` : value || "(empty)" : field === "match" ? `${value} (space to cycle)` : `${value} (space to cycle)`;
2563
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
2564
+ /* @__PURE__ */ jsxs12(Text12, { color: isCurrent ? colors.primary : colors.text, children: [
2565
+ isCurrent ? "> " : " ",
2566
+ FORM_LABELS[field],
2567
+ ":",
2568
+ " "
2569
+ ] }),
2570
+ /* @__PURE__ */ jsx12(Text12, { color: isCurrent ? colors.bright : colors.muted, children: displayValue })
2571
+ ] }, field);
2572
+ }),
2573
+ formError && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx12(Text12, { color: colors.error, children: formError }) }),
2574
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx12(Text12, { color: colors.muted, children: currentField === "match" || currentField === "severity" ? "space/enter: cycle options | up/down: move fields" : "type to edit | enter: next field | up/down: move fields" }) })
2575
+ ]
2576
+ }
2577
+ ) });
2578
+ }
2579
+ const contentHeight = termHeight - 6;
2580
+ const halfView = Math.floor(contentHeight / 2);
2581
+ const scrollStart = Math.max(0, Math.min(selectedIdx - halfView, totalItems - contentHeight));
2582
+ const visibleStart = Math.max(0, scrollStart);
2583
+ const visibleEnd = Math.min(totalItems, visibleStart + contentHeight);
2584
+ return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsxs12(
2585
+ Box12,
2586
+ {
2587
+ borderStyle: "round",
2588
+ borderColor: colors.primary,
2589
+ flexDirection: "column",
2590
+ paddingX: 2,
2591
+ paddingY: 1,
2592
+ height: termHeight,
2593
+ children: [
2594
+ /* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", marginBottom: 1, children: [
2595
+ /* @__PURE__ */ jsx12(Text12, { color: colors.header, bold: true, children: "ALERT RULES" }),
2596
+ /* @__PURE__ */ jsx12(Text12, { color: colors.muted, children: "space:toggle n:new e:edit d:delete esc:close" })
2597
+ ] }),
2598
+ visibleStart === 0 && /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs12(Text12, { color: colors.accent, bold: true, children: [
2599
+ " ",
2600
+ "STALE SESSION"
2601
+ ] }) }) }),
2602
+ Array.from({ length: visibleEnd - visibleStart }, (_, vi) => {
2603
+ const idx = visibleStart + vi;
2604
+ const isSelected = idx === selectedIdx;
2605
+ if (isStaleRow(idx)) {
2606
+ const timeout = localConfig.alerts.staleTimeout;
2607
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
2608
+ /* @__PURE__ */ jsxs12(Text12, { color: isSelected ? colors.primary : colors.text, children: [
2609
+ isSelected ? "> " : " ",
2610
+ " Stale timeout ",
2611
+ "............ "
2612
+ ] }),
2613
+ /* @__PURE__ */ jsxs12(Text12, { color: colors.bright, children: [
2614
+ timeout,
2615
+ "s"
2616
+ ] }),
2617
+ /* @__PURE__ */ jsx12(Text12, { color: colors.muted, children: " (+/-)" })
2618
+ ] }, "stale-timeout");
2619
+ }
2620
+ if (isBuiltinRow(idx)) {
2621
+ const ruleKey = getBuiltinKey(idx);
2622
+ const label2 = RULE_LABELS2[ruleKey];
2623
+ const enabled = localConfig.security.rules[ruleKey];
2624
+ const showHeader = idx === 1 && visibleStart <= 1;
2625
+ return /* @__PURE__ */ jsxs12(React12.Fragment, { children: [
2626
+ showHeader && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text12, { color: colors.accent, bold: true, children: [
2627
+ " ",
2628
+ "BUILT-IN RULES"
2629
+ ] }) }),
2630
+ /* @__PURE__ */ jsxs12(Box12, { children: [
2631
+ /* @__PURE__ */ jsxs12(Text12, { color: isSelected ? colors.primary : colors.text, children: [
2632
+ isSelected ? "> " : " ",
2633
+ " ",
2634
+ label2,
2635
+ " ",
2636
+ ".".repeat(Math.max(2, 28 - label2.length)),
2637
+ " "
2638
+ ] }),
2639
+ /* @__PURE__ */ jsx12(Text12, { color: enabled ? colors.secondary : colors.error, children: enabled ? "ON" : "OFF" })
2640
+ ] })
2641
+ ] }, `builtin-${ruleKey}`);
2642
+ }
2643
+ if (isCustomRow(idx)) {
2644
+ const ci = getCustomIndex(idx);
2645
+ const rule = customRules[ci];
2646
+ const showHeader = ci === 0;
2647
+ return /* @__PURE__ */ jsxs12(React12.Fragment, { children: [
2648
+ showHeader && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text12, { color: colors.accent, bold: true, children: [
2649
+ " ",
2650
+ "CUSTOM RULES"
2651
+ ] }) }),
2652
+ /* @__PURE__ */ jsxs12(Box12, { children: [
2653
+ /* @__PURE__ */ jsxs12(Text12, { color: isSelected ? colors.primary : colors.text, children: [
2654
+ isSelected ? "> " : " ",
2655
+ " ",
2656
+ rule.name,
2657
+ " "
2658
+ ] }),
2659
+ /* @__PURE__ */ jsxs12(Text12, { color: colors.muted, children: [
2660
+ "/",
2661
+ rule.pattern,
2662
+ "/ "
2663
+ ] }),
2664
+ /* @__PURE__ */ jsx12(Text12, { color: rule.enabled ? colors.secondary : colors.error, children: rule.enabled ? "ON" : "OFF" })
2665
+ ] })
2666
+ ] }, `custom-${ci}`);
2667
+ }
2668
+ return null;
2669
+ }),
2670
+ toast && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsx12(Text12, { color: colors.warning, children: toast }) })
2671
+ ]
2672
+ }
2673
+ ) });
2674
+ });
2675
+
2676
+ // src/ui/components/ThemePickerModal.tsx
2677
+ import React13, { useState as useState8, useEffect as useEffect5 } from "react";
2678
+ import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
2679
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2680
+ var ThemePickerModal = React13.memo(({ onSelect, onSkip, onDismiss }) => {
2681
+ const [selectedIndex, setSelectedIndex] = useState8(0);
1985
2682
  const themes = BUILTIN_THEMES;
1986
- useEffect4(() => {
2683
+ useEffect5(() => {
1987
2684
  applyTheme(themes[selectedIndex]);
1988
2685
  }, [selectedIndex]);
1989
- useInput6((input, key) => {
2686
+ useInput7((input, key) => {
1990
2687
  if (key.upArrow) setSelectedIndex((i) => Math.max(0, i - 1));
1991
2688
  if (key.downArrow) setSelectedIndex((i) => Math.min(themes.length - 1, i + 1));
1992
2689
  if (key.return) onSelect(themes[selectedIndex].name);
@@ -1995,33 +2692,33 @@ var ThemePickerModal = React12.memo(({ onSelect, onSkip, onDismiss }) => {
1995
2692
  });
1996
2693
  const renderSwatch = (theme) => {
1997
2694
  const c = theme.colors;
1998
- return /* @__PURE__ */ jsxs12(Text12, { children: [
1999
- /* @__PURE__ */ jsx12(Text12, { color: c.primary, children: "\u2588" }),
2000
- /* @__PURE__ */ jsx12(Text12, { color: c.secondary, children: "\u2588" }),
2001
- /* @__PURE__ */ jsx12(Text12, { color: c.accent, children: "\u2588" }),
2002
- /* @__PURE__ */ jsx12(Text12, { color: c.warning, children: "\u2588" }),
2003
- /* @__PURE__ */ jsx12(Text12, { color: c.error, children: "\u2588" })
2695
+ return /* @__PURE__ */ jsxs13(Text13, { children: [
2696
+ /* @__PURE__ */ jsx13(Text13, { color: c.primary, children: "\u2588" }),
2697
+ /* @__PURE__ */ jsx13(Text13, { color: c.secondary, children: "\u2588" }),
2698
+ /* @__PURE__ */ jsx13(Text13, { color: c.accent, children: "\u2588" }),
2699
+ /* @__PURE__ */ jsx13(Text13, { color: c.warning, children: "\u2588" }),
2700
+ /* @__PURE__ */ jsx13(Text13, { color: c.error, children: "\u2588" })
2004
2701
  ] });
2005
2702
  };
2006
- 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: [
2007
- /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { color: colors.header, bold: true, children: "Choose a theme" }) }),
2008
- /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { color: colors.text, children: "Select a theme to get started. You can change this later in settings (s)." }) }),
2009
- themes.map((theme, i) => /* @__PURE__ */ jsxs12(Box12, { children: [
2010
- /* @__PURE__ */ jsx12(Text12, { color: i === selectedIndex ? colors.primary : colors.muted, children: i === selectedIndex ? "> " : " " }),
2703
+ return /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", paddingX: 4, paddingY: 1, children: /* @__PURE__ */ jsxs13(Box13, { borderStyle: "round", borderColor: colors.primary, flexDirection: "column", paddingX: 3, paddingY: 1, children: [
2704
+ /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.header, bold: true, children: "Choose a theme" }) }),
2705
+ /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.text, children: "Select a theme to get started. You can change this later in settings (s)." }) }),
2706
+ themes.map((theme, i) => /* @__PURE__ */ jsxs13(Box13, { children: [
2707
+ /* @__PURE__ */ jsx13(Text13, { color: i === selectedIndex ? colors.primary : colors.muted, children: i === selectedIndex ? "> " : " " }),
2011
2708
  renderSwatch(theme),
2012
- /* @__PURE__ */ jsxs12(Text12, { color: i === selectedIndex ? colors.bright : colors.text, children: [
2709
+ /* @__PURE__ */ jsxs13(Text13, { color: i === selectedIndex ? colors.bright : colors.text, children: [
2013
2710
  " ",
2014
2711
  theme.name
2015
2712
  ] })
2016
2713
  ] }, theme.name)),
2017
- /* @__PURE__ */ jsx12(Box12, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(Text12, { color: colors.muted, children: "Enter = select | n = not now | d = don't ask again" }) })
2714
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx13(Text13, { color: colors.muted, children: "Enter = select | n = not now | d = don't ask again" }) })
2018
2715
  ] }) });
2019
2716
  });
2020
2717
 
2021
2718
  // src/ui/components/GuidedTour.tsx
2022
- import React13, { useState as useState8 } from "react";
2023
- import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
2024
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2719
+ import React14, { useState as useState9 } from "react";
2720
+ import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
2721
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2025
2722
  var STEPS = [
2026
2723
  {
2027
2724
  title: "Session list",
@@ -2052,11 +2749,11 @@ var STEPS = [
2052
2749
  body: "Press s to open settings. Customise keybindings, manage themes, and configure updates."
2053
2750
  }
2054
2751
  ];
2055
- var GuidedTour = React13.memo(({ onComplete, onSkip }) => {
2056
- const [stepIndex, setStepIndex] = useState8(0);
2752
+ var GuidedTour = React14.memo(({ onComplete, onSkip }) => {
2753
+ const [stepIndex, setStepIndex] = useState9(0);
2057
2754
  const step = STEPS[stepIndex];
2058
2755
  const isLast = stepIndex === STEPS.length - 1;
2059
- useInput7((input, key) => {
2756
+ useInput8((input, key) => {
2060
2757
  if (key.return || key.rightArrow) {
2061
2758
  if (isLast) onComplete();
2062
2759
  else setStepIndex((i) => i + 1);
@@ -2064,17 +2761,17 @@ var GuidedTour = React13.memo(({ onComplete, onSkip }) => {
2064
2761
  if (key.leftArrow && stepIndex > 0) setStepIndex((i) => i - 1);
2065
2762
  if (input === "q" || key.escape) onSkip();
2066
2763
  });
2067
- return /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", paddingX: 4, paddingY: 1, children: /* @__PURE__ */ jsxs13(Box13, { borderStyle: "round", borderColor: colors.primary, flexDirection: "column", paddingX: 3, paddingY: 1, children: [
2068
- /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs13(Text13, { color: colors.header, bold: true, children: [
2764
+ return /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", paddingX: 4, paddingY: 1, children: /* @__PURE__ */ jsxs14(Box14, { borderStyle: "round", borderColor: colors.primary, flexDirection: "column", paddingX: 3, paddingY: 1, children: [
2765
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsxs14(Text14, { color: colors.header, bold: true, children: [
2069
2766
  "Quick tour (",
2070
2767
  stepIndex + 1,
2071
2768
  "/",
2072
2769
  STEPS.length,
2073
2770
  ")"
2074
2771
  ] }) }),
2075
- /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.bright, bold: true, children: step.title }) }),
2076
- /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.text, children: step.body }) }),
2077
- /* @__PURE__ */ jsxs13(Text13, { color: colors.muted, children: [
2772
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { color: colors.bright, bold: true, children: step.title }) }),
2773
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { color: colors.text, children: step.body }) }),
2774
+ /* @__PURE__ */ jsxs14(Text14, { color: colors.muted, children: [
2078
2775
  isLast ? "Enter = finish" : "Enter/\u2192 = next",
2079
2776
  " | ",
2080
2777
  stepIndex > 0 ? "\u2190 = back | " : "",
@@ -2084,19 +2781,19 @@ var GuidedTour = React13.memo(({ onComplete, onSkip }) => {
2084
2781
  });
2085
2782
 
2086
2783
  // src/ui/components/ConfirmModal.tsx
2087
- import React14 from "react";
2088
- import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
2089
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2090
- var ConfirmModal = React14.memo(({ title, message, onConfirm, onCancel }) => {
2091
- useInput8((input, key) => {
2784
+ import React15 from "react";
2785
+ import { Box as Box15, Text as Text15, useInput as useInput9 } from "ink";
2786
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2787
+ var ConfirmModal = React15.memo(({ title, message, onConfirm, onCancel }) => {
2788
+ useInput9((input, key) => {
2092
2789
  if (input === "y" || input === "Y") {
2093
2790
  onConfirm();
2094
2791
  } else if (input === "n" || input === "N" || key.escape) {
2095
2792
  onCancel();
2096
2793
  }
2097
2794
  });
2098
- return /* @__PURE__ */ jsxs14(
2099
- Box14,
2795
+ return /* @__PURE__ */ jsxs15(
2796
+ Box15,
2100
2797
  {
2101
2798
  borderStyle: "round",
2102
2799
  borderColor: colors.warning,
@@ -2105,27 +2802,27 @@ var ConfirmModal = React14.memo(({ title, message, onConfirm, onCancel }) => {
2105
2802
  paddingY: 1,
2106
2803
  alignSelf: "center",
2107
2804
  children: [
2108
- /* @__PURE__ */ jsx14(Text14, { color: colors.warning, bold: true, children: title }),
2109
- /* @__PURE__ */ jsx14(Text14, { color: colors.text, children: message }),
2110
- /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: colors.muted, children: "[y] confirm [n/esc] cancel" }) })
2805
+ /* @__PURE__ */ jsx15(Text15, { color: colors.warning, bold: true, children: title }),
2806
+ /* @__PURE__ */ jsx15(Text15, { color: colors.text, children: message }),
2807
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: colors.muted, children: "[y] confirm [n/esc] cancel" }) })
2111
2808
  ]
2112
2809
  }
2113
2810
  );
2114
2811
  });
2115
2812
 
2116
2813
  // src/ui/components/UpdateModal.tsx
2117
- import React15, { useState as useState9, useCallback as useCallback2 } from "react";
2118
- import { Box as Box15, Text as Text15, useInput as useInput9 } from "ink";
2119
- import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2814
+ import React16, { useState as useState10, useCallback as useCallback2 } from "react";
2815
+ import { Box as Box16, Text as Text16, useInput as useInput10 } from "ink";
2816
+ import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2120
2817
  var options = [
2121
2818
  { key: "now", label: "Update now" },
2122
2819
  { key: "later", label: "Not now" },
2123
2820
  { key: "never", label: "Don't ask again" }
2124
2821
  ];
2125
- var UpdateModal = React15.memo(({ current, latest, onNotNow, onDontAskAgain }) => {
2126
- const [selected, setSelected] = useState9(0);
2127
- const [status, setStatus] = useState9("idle");
2128
- const [errorMsg, setErrorMsg] = useState9("");
2822
+ var UpdateModal = React16.memo(({ current, latest, onNotNow, onDontAskAgain }) => {
2823
+ const [selected, setSelected] = useState10(0);
2824
+ const [status, setStatus] = useState10("idle");
2825
+ const [errorMsg, setErrorMsg] = useState10("");
2129
2826
  const doUpdate = useCallback2(() => {
2130
2827
  setStatus("updating");
2131
2828
  installUpdate().then(() => {
@@ -2136,7 +2833,7 @@ var UpdateModal = React15.memo(({ current, latest, onNotNow, onDontAskAgain }) =
2136
2833
  setStatus("error");
2137
2834
  });
2138
2835
  }, []);
2139
- useInput9((input, key) => {
2836
+ useInput10((input, key) => {
2140
2837
  if (status === "updating" || status === "restarting") return;
2141
2838
  if (status === "error") {
2142
2839
  if (key.escape || key.return) {
@@ -2168,8 +2865,8 @@ var UpdateModal = React15.memo(({ current, latest, onNotNow, onDontAskAgain }) =
2168
2865
  }
2169
2866
  }
2170
2867
  });
2171
- return /* @__PURE__ */ jsxs15(
2172
- Box15,
2868
+ return /* @__PURE__ */ jsxs16(
2869
+ Box16,
2173
2870
  {
2174
2871
  borderStyle: "round",
2175
2872
  borderColor: colors.primary,
@@ -2178,8 +2875,8 @@ var UpdateModal = React15.memo(({ current, latest, onNotNow, onDontAskAgain }) =
2178
2875
  paddingY: 1,
2179
2876
  alignSelf: "center",
2180
2877
  children: [
2181
- /* @__PURE__ */ jsx15(Text15, { color: colors.primary, bold: true, children: "Update available" }),
2182
- /* @__PURE__ */ jsxs15(Text15, { color: colors.text, children: [
2878
+ /* @__PURE__ */ jsx16(Text16, { color: colors.primary, bold: true, children: "Update available" }),
2879
+ /* @__PURE__ */ jsxs16(Text16, { color: colors.text, children: [
2183
2880
  "v",
2184
2881
  current,
2185
2882
  " ",
@@ -2187,10 +2884,10 @@ var UpdateModal = React15.memo(({ current, latest, onNotNow, onDontAskAgain }) =
2187
2884
  " v",
2188
2885
  latest
2189
2886
  ] }),
2190
- /* @__PURE__ */ jsx15(Box15, { marginTop: 1, flexDirection: "column", children: status === "updating" ? /* @__PURE__ */ jsx15(Text15, { color: colors.warning, children: "updating..." }) : status === "restarting" ? /* @__PURE__ */ jsx15(Text15, { color: colors.success, children: "restarting..." }) : status === "error" ? /* @__PURE__ */ jsxs15(Fragment2, { children: [
2191
- /* @__PURE__ */ jsx15(Text15, { color: colors.error, children: errorMsg }),
2192
- /* @__PURE__ */ jsx15(Text15, { color: colors.muted, children: "press enter to continue" })
2193
- ] }) : options.map((opt, i) => /* @__PURE__ */ jsxs15(Text15, { color: i === selected ? colors.primary : colors.muted, children: [
2887
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, flexDirection: "column", children: status === "updating" ? /* @__PURE__ */ jsx16(Text16, { color: colors.warning, children: "updating..." }) : status === "restarting" ? /* @__PURE__ */ jsx16(Text16, { color: colors.secondary, children: "restarting..." }) : status === "error" ? /* @__PURE__ */ jsxs16(Fragment2, { children: [
2888
+ /* @__PURE__ */ jsx16(Text16, { color: colors.error, children: errorMsg }),
2889
+ /* @__PURE__ */ jsx16(Text16, { color: colors.muted, children: "press enter to continue" })
2890
+ ] }) : options.map((opt, i) => /* @__PURE__ */ jsxs16(Text16, { color: i === selected ? colors.primary : colors.muted, children: [
2194
2891
  i === selected ? "> " : " ",
2195
2892
  opt.label
2196
2893
  ] }, opt.key)) })
@@ -2200,10 +2897,10 @@ var UpdateModal = React15.memo(({ current, latest, onNotNow, onDontAskAgain }) =
2200
2897
  });
2201
2898
 
2202
2899
  // src/ui/components/SplitPanel.tsx
2203
- import React16 from "react";
2204
- import { Box as Box16 } from "ink";
2205
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2206
- var SplitPanel = React16.memo(
2900
+ import React17 from "react";
2901
+ import { Box as Box17 } from "ink";
2902
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2903
+ var SplitPanel = React17.memo(
2207
2904
  ({
2208
2905
  activePanel,
2209
2906
  leftSession,
@@ -2218,35 +2915,35 @@ var SplitPanel = React16.memo(
2218
2915
  rightShowDetail,
2219
2916
  height
2220
2917
  }) => {
2221
- const left = leftShowDetail && leftSession ? /* @__PURE__ */ jsx16(SessionDetail, { session: leftSession, focused: activePanel === "left", height }) : /* @__PURE__ */ jsx16(
2918
+ const left = leftShowDetail && leftSession ? /* @__PURE__ */ jsx17(SessionDetail, { session: leftSession, focused: activePanel === "left", height }) : /* @__PURE__ */ jsx17(
2222
2919
  ActivityFeed,
2223
2920
  {
2224
2921
  events: leftEvents,
2225
2922
  sessionSlug: leftSession?.slug ?? null,
2226
2923
  sessionId: leftSession?.sessionId,
2227
- isActive: leftSession ? leftSession.pid !== null : void 0,
2924
+ status: leftSession ? leftSession.status : void 0,
2228
2925
  focused: activePanel === "left",
2229
2926
  height,
2230
2927
  scrollOffset: leftScroll,
2231
2928
  filter: leftFilter || void 0
2232
2929
  }
2233
2930
  );
2234
- const right = rightShowDetail && rightSession ? /* @__PURE__ */ jsx16(SessionDetail, { session: rightSession, focused: activePanel === "right", height }) : /* @__PURE__ */ jsx16(
2931
+ const right = rightShowDetail && rightSession ? /* @__PURE__ */ jsx17(SessionDetail, { session: rightSession, focused: activePanel === "right", height }) : /* @__PURE__ */ jsx17(
2235
2932
  ActivityFeed,
2236
2933
  {
2237
2934
  events: rightEvents,
2238
2935
  sessionSlug: rightSession?.slug ?? null,
2239
2936
  sessionId: rightSession?.sessionId,
2240
- isActive: rightSession ? rightSession.pid !== null : void 0,
2937
+ status: rightSession ? rightSession.status : void 0,
2241
2938
  focused: activePanel === "right",
2242
2939
  height,
2243
2940
  scrollOffset: rightScroll,
2244
2941
  filter: rightFilter || void 0
2245
2942
  }
2246
2943
  );
2247
- return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "row", flexGrow: 1, children: [
2248
- /* @__PURE__ */ jsx16(
2249
- Box16,
2944
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "row", flexGrow: 1, children: [
2945
+ /* @__PURE__ */ jsx17(
2946
+ Box17,
2250
2947
  {
2251
2948
  flexGrow: 1,
2252
2949
  borderStyle: "single",
@@ -2264,10 +2961,10 @@ var SplitPanel = React16.memo(
2264
2961
  );
2265
2962
 
2266
2963
  // src/ui/hooks/useSessions.ts
2267
- import { useState as useState10, useEffect as useEffect5, useCallback as useCallback3, useRef as useRef3, useMemo as useMemo2 } from "react";
2964
+ import { useState as useState11, useEffect as useEffect6, useCallback as useCallback3, useRef as useRef5, useMemo as useMemo2 } from "react";
2268
2965
 
2269
2966
  // src/discovery/sessionsAsync.ts
2270
- import { readdir, stat as stat2 } from "fs/promises";
2967
+ import { readdir, stat as stat2, open as open2 } from "fs/promises";
2271
2968
  import { join as join2, basename } from "path";
2272
2969
 
2273
2970
  // src/discovery/cache.ts
@@ -2306,44 +3003,9 @@ var pruneFileMetaCache = (validPaths) => {
2306
3003
  };
2307
3004
 
2308
3005
  // src/discovery/asyncHelpers.ts
2309
- import { execFile as execFile2 } from "child_process";
2310
- import { open, stat, readlink } from "fs/promises";
2311
- var normalisePath = (p) => p.replace(/\/+$/, "");
2312
- var getClaudeProcessesAsync = async () => {
2313
- const stdout = await new Promise((resolve) => {
2314
- execFile2("ps", ["aux"], { encoding: "utf-8", timeout: 5e3, maxBuffer: 4 * 1024 * 1024 }, (err, out) => {
2315
- resolve(err || !out ? "" : out);
2316
- });
2317
- });
2318
- if (!stdout) return [];
2319
- const procs = [];
2320
- for (const line of stdout.split("\n")) {
2321
- if (!line.includes("/claude") || line.includes("grep") || line.includes("agenttop")) continue;
2322
- const parts = line.trim().split(/\s+/);
2323
- const pid = parseInt(parts[1], 10);
2324
- if (isNaN(pid)) continue;
2325
- const command = parts.slice(10).join(" ");
2326
- if (command.startsWith("sudo")) continue;
2327
- procs.push({
2328
- pid,
2329
- cpu: parseFloat(parts[2]) || 0,
2330
- mem: parseFloat(parts[3]) || 0,
2331
- memKB: parseInt(parts[5], 10) || 0,
2332
- startTime: parts[8] || "",
2333
- command,
2334
- cwd: ""
2335
- });
2336
- }
2337
- await Promise.all(
2338
- procs.map(async (p) => {
2339
- try {
2340
- p.cwd = await readlink(`/proc/${p.pid}/cwd`);
2341
- } catch {
2342
- }
2343
- })
2344
- );
2345
- return procs;
2346
- };
3006
+ import { open, stat } from "fs/promises";
3007
+ import { normalize } from "path";
3008
+ var normalisePath = (p) => normalize(p).replace(/[\\/]+$/, "");
2347
3009
  var readFirstLinesAsync = async (filePath, bytes) => {
2348
3010
  let fh;
2349
3011
  try {
@@ -2461,7 +3123,47 @@ var findModelAndUsageAsync = async (filePath) => {
2461
3123
  };
2462
3124
 
2463
3125
  // src/discovery/sessionsAsync.ts
2464
- var discoverFromProjectsAsync = async (allUsers, processes, sessionMap, seenFiles) => {
3126
+ var readTailBytesAsync = async (filePath, bytes) => {
3127
+ let fh;
3128
+ try {
3129
+ fh = await open2(filePath, "r");
3130
+ const fstat = await stat2(filePath);
3131
+ const start = Math.max(0, fstat.size - bytes);
3132
+ const readSize = Math.min(bytes, fstat.size);
3133
+ const buf = Buffer.alloc(readSize);
3134
+ await fh.read(buf, 0, readSize, start);
3135
+ return buf.toString("utf-8");
3136
+ } catch {
3137
+ return "";
3138
+ } finally {
3139
+ await fh?.close();
3140
+ }
3141
+ };
3142
+ var detectStatusAsync = async (filePath, hasPid, lastActivity, staleTimeout) => {
3143
+ if (!hasPid) return "inactive";
3144
+ const tail = await readTailBytesAsync(filePath, 4096);
3145
+ const lines = tail.split("\n").filter(Boolean);
3146
+ if (lines.length > 0) {
3147
+ try {
3148
+ const lastEvent = JSON.parse(lines[lines.length - 1]);
3149
+ if (lastEvent.type === "assistant") {
3150
+ const content = lastEvent.message?.content;
3151
+ if (Array.isArray(content)) {
3152
+ const hasAskUser = content.some(
3153
+ (b) => b.type === "tool_use" && b.name === "AskUserQuestion"
3154
+ );
3155
+ if (hasAskUser) return "waiting";
3156
+ const hasToolUse = content.some((b) => b.type === "tool_use");
3157
+ if (!hasToolUse) return "waiting";
3158
+ }
3159
+ }
3160
+ } catch {
3161
+ }
3162
+ }
3163
+ if (Date.now() - lastActivity > staleTimeout * 1e3) return "stale";
3164
+ return "active";
3165
+ };
3166
+ var discoverFromProjectsAsync = async (allUsers, processes, sessionMap, seenFiles, staleTimeout, pinnedOrder) => {
2465
3167
  const projectsDirs = getProjectsDirs(allUsers);
2466
3168
  for (const projectsDir of projectsDirs) {
2467
3169
  let projectNames;
@@ -2516,14 +3218,16 @@ var discoverFromProjectsAsync = async (allUsers, processes, sessionMap, seenFile
2516
3218
  outputFiles: [filePath],
2517
3219
  startTime: fstat.birthtimeMs || fstat.ctimeMs,
2518
3220
  lastActivity: fstat.mtimeMs,
2519
- usage: meta.usage
3221
+ usage: meta.usage,
3222
+ status: await detectStatusAsync(filePath, matchingProcess !== void 0, fstat.mtimeMs, staleTimeout),
3223
+ pinned: pinnedOrder.includes(meta.sessionId)
2520
3224
  };
2521
3225
  sessionMap.set(meta.sessionId, session);
2522
3226
  }
2523
3227
  }
2524
3228
  }
2525
3229
  };
2526
- var discoverFromTmpAsync = async (allUsers, processes, sessionMap, seenFiles) => {
3230
+ var discoverFromTmpAsync = async (allUsers, processes, sessionMap, seenFiles, staleTimeout, pinnedOrder) => {
2527
3231
  const taskDirs = getTaskDirs(allUsers);
2528
3232
  for (const taskDir of taskDirs) {
2529
3233
  let projectDirs;
@@ -2613,6 +3317,13 @@ var discoverFromTmpAsync = async (allUsers, processes, sessionMap, seenFiles) =>
2613
3317
  if (sessionMap.has(sessionId || projectName)) continue;
2614
3318
  const normCwd = normalisePath(cwd);
2615
3319
  const matchingProcess = processes.find((p) => p.cwd && normalisePath(p.cwd) === normCwd);
3320
+ let latestFile = outputFiles[0];
3321
+ for (const f of outputFiles) {
3322
+ try {
3323
+ if ((await stat2(f)).mtimeMs > (await stat2(latestFile)).mtimeMs) latestFile = f;
3324
+ } catch {
3325
+ }
3326
+ }
2616
3327
  const session = {
2617
3328
  sessionId,
2618
3329
  slug: slug || sessionId.slice(0, 12),
@@ -2630,7 +3341,9 @@ var discoverFromTmpAsync = async (allUsers, processes, sessionMap, seenFiles) =>
2630
3341
  outputFiles,
2631
3342
  startTime: startTime === Infinity ? Date.now() : startTime,
2632
3343
  lastActivity,
2633
- usage: totalUsage
3344
+ usage: totalUsage,
3345
+ status: await detectStatusAsync(latestFile, matchingProcess !== void 0, lastActivity, staleTimeout),
3346
+ pinned: pinnedOrder.includes(sessionId)
2634
3347
  };
2635
3348
  sessionMap.set(sessionId || projectName, session);
2636
3349
  }
@@ -2638,16 +3351,26 @@ var discoverFromTmpAsync = async (allUsers, processes, sessionMap, seenFiles) =>
2638
3351
  }
2639
3352
  };
2640
3353
  var discoverSessionsAsync = async (allUsers) => {
3354
+ const config = loadConfig();
3355
+ const staleTimeout = config.alerts.staleTimeout ?? 60;
3356
+ const pinnedOrder = config.pinnedSessions ?? [];
2641
3357
  const processes = await getClaudeProcessesAsync();
2642
3358
  const sessionMap = /* @__PURE__ */ new Map();
2643
3359
  const seenFiles = /* @__PURE__ */ new Set();
2644
- await discoverFromProjectsAsync(allUsers, processes, sessionMap, seenFiles);
2645
- await discoverFromTmpAsync(allUsers, processes, sessionMap, seenFiles);
3360
+ await discoverFromProjectsAsync(allUsers, processes, sessionMap, seenFiles, staleTimeout, pinnedOrder);
3361
+ await discoverFromTmpAsync(allUsers, processes, sessionMap, seenFiles, staleTimeout, pinnedOrder);
2646
3362
  pruneFileMetaCache(seenFiles);
2647
3363
  return Array.from(sessionMap.values()).sort((a, b) => {
2648
- const aActive = a.pid !== null ? 1 : 0;
2649
- const bActive = b.pid !== null ? 1 : 0;
2650
- if (aActive !== bActive) return bActive - aActive;
3364
+ const aPin = pinnedOrder.indexOf(a.sessionId);
3365
+ const bPin = pinnedOrder.indexOf(b.sessionId);
3366
+ const aIsPinned = aPin !== -1;
3367
+ const bIsPinned = bPin !== -1;
3368
+ if (aIsPinned && !bIsPinned) return -1;
3369
+ if (!aIsPinned && bIsPinned) return 1;
3370
+ if (aIsPinned && bIsPinned) return aPin - bPin;
3371
+ const aPri = STATUS_PRIORITY[a.status];
3372
+ const bPri = STATUS_PRIORITY[b.status];
3373
+ if (aPri !== bPri) return aPri - bPri;
2651
3374
  return b.lastActivity - a.lastActivity;
2652
3375
  });
2653
3376
  };
@@ -2692,7 +3415,9 @@ var buildGroups = (sessions2, expandedKeys) => {
2692
3415
  totalInputTokens: totalIn,
2693
3416
  totalOutputTokens: totalOut,
2694
3417
  latestModel: list[0].model,
2695
- isActive: list.some((s) => s.pid !== null),
3418
+ status: list.reduce((best, s) => {
3419
+ return (STATUS_PRIORITY[s.status] ?? 3) < (STATUS_PRIORITY[best] ?? 3) ? s.status : best;
3420
+ }, "inactive"),
2696
3421
  latestActivity: list[0].lastActivity,
2697
3422
  earliestStart: Math.min(...list.map((s) => s.startTime))
2698
3423
  });
@@ -2749,17 +3474,17 @@ var enrichAndFilter = (found, usageOverrides, filter, archivedIds, viewingArchiv
2749
3474
  return enriched;
2750
3475
  };
2751
3476
  var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
2752
- const [sessions2, setSessions] = useState10([]);
2753
- const [selectedIndex, setSelectedIndex] = useState10(0);
2754
- const [expandedKeys, setExpandedKeys] = useState10(/* @__PURE__ */ new Set());
2755
- const usageOverrides = useRef3(/* @__PURE__ */ new Map());
3477
+ const [sessions2, setSessions] = useState11([]);
3478
+ const [selectedIndex, setSelectedIndex] = useState11(0);
3479
+ const [expandedKeys, setExpandedKeys] = useState11(/* @__PURE__ */ new Set());
3480
+ const usageOverrides = useRef5(/* @__PURE__ */ new Map());
2756
3481
  const refresh = useCallback3(() => {
2757
3482
  const found = getCachedSessions();
2758
3483
  const filtered = enrichAndFilter(found, usageOverrides.current, filter, archivedIds, viewingArchive);
2759
3484
  setSessions(filtered);
2760
3485
  triggerRefresh(allUsers);
2761
3486
  }, [allUsers, filter, archivedIds, viewingArchive]);
2762
- useEffect5(() => {
3487
+ useEffect6(() => {
2763
3488
  const unsubscribe = subscribe(() => {
2764
3489
  const found = getCachedSessions();
2765
3490
  const filtered = enrichAndFilter(found, usageOverrides.current, filter, archivedIds, viewingArchive);
@@ -2767,7 +3492,7 @@ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
2767
3492
  });
2768
3493
  return unsubscribe;
2769
3494
  }, [filter, archivedIds, viewingArchive]);
2770
- useEffect5(() => {
3495
+ useEffect6(() => {
2771
3496
  refresh();
2772
3497
  const pollMs = sessions2.length > 0 ? ACTIVE_POLL_MS : IDLE_POLL_MS;
2773
3498
  const interval = setInterval(() => triggerRefresh(allUsers), pollMs);
@@ -2775,7 +3500,7 @@ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
2775
3500
  }, [refresh, sessions2.length > 0]);
2776
3501
  const groups = useMemo2(() => buildGroups(sessions2, expandedKeys), [sessions2, expandedKeys]);
2777
3502
  const visibleItems = useMemo2(() => buildVisibleItems(groups), [groups]);
2778
- const itemCountRef = useRef3(visibleItems.length);
3503
+ const itemCountRef = useRef5(visibleItems.length);
2779
3504
  itemCountRef.current = visibleItems.length;
2780
3505
  const selectedItem = visibleItems[selectedIndex] ?? null;
2781
3506
  const selectedSession = selectedItem?.type === "ungrouped" ? selectedItem.session : selectedItem?.type === "session" ? selectedItem.session : null;
@@ -2827,18 +3552,42 @@ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
2827
3552
  };
2828
3553
 
2829
3554
  // src/ui/hooks/useActivityStream.ts
2830
- import { useState as useState11, useEffect as useEffect6, useRef as useRef4, useMemo as useMemo3 } from "react";
3555
+ import { useState as useState12, useEffect as useEffect7, useRef as useRef6, useMemo as useMemo3, useCallback as useCallback4 } from "react";
2831
3556
  var MAX_EVENTS = 200;
3557
+ var DEBOUNCE_MS = 80;
2832
3558
  var useActivityStream = (session, allUsers) => {
2833
- const [calls, setCalls] = useState11([]);
2834
- const [results, setResults] = useState11([]);
2835
- const watcherRef = useRef4(null);
3559
+ const [calls, setCalls] = useState12([]);
3560
+ const [results, setResults] = useState12([]);
3561
+ const watcherRef = useRef6(null);
3562
+ const pendingCallsRef = useRef6([]);
3563
+ const pendingResultsRef = useRef6([]);
3564
+ const flushTimerRef = useRef6(null);
2836
3565
  const sessions2 = session === null ? [] : Array.isArray(session) ? session : [session];
2837
3566
  const sessionKey = sessions2.map((s) => s.sessionId).sort().join(",");
2838
3567
  const sessionIdSet = new Set(sessions2.map((s) => s.sessionId));
2839
- useEffect6(() => {
3568
+ const flush = useCallback4(() => {
3569
+ flushTimerRef.current = null;
3570
+ const pc = pendingCallsRef.current;
3571
+ const pr = pendingResultsRef.current;
3572
+ if (pc.length > 0) {
3573
+ const batch = pc.splice(0);
3574
+ setCalls((prev) => [...prev, ...batch].slice(-MAX_EVENTS));
3575
+ }
3576
+ if (pr.length > 0) {
3577
+ const batch = pr.splice(0);
3578
+ setResults((prev) => [...prev, ...batch].slice(-MAX_EVENTS * 2));
3579
+ }
3580
+ }, []);
3581
+ const scheduleFlush = useCallback4(() => {
3582
+ if (!flushTimerRef.current) {
3583
+ flushTimerRef.current = setTimeout(flush, DEBOUNCE_MS);
3584
+ }
3585
+ }, [flush]);
3586
+ useEffect7(() => {
2840
3587
  setCalls([]);
2841
3588
  setResults([]);
3589
+ pendingCallsRef.current = [];
3590
+ pendingResultsRef.current = [];
2842
3591
  if (sessions2.length === 0) return;
2843
3592
  let cancelled = false;
2844
3593
  const loadExisting = async () => {
@@ -2865,18 +3614,21 @@ var useActivityStream = (session, allUsers) => {
2865
3614
  const callHandler = (newCalls) => {
2866
3615
  const matched = newCalls.filter((c) => sessionIdSet.has(c.sessionId));
2867
3616
  if (matched.length === 0) return;
2868
- setCalls((prev) => [...prev, ...matched].slice(-MAX_EVENTS));
3617
+ pendingCallsRef.current.push(...matched);
3618
+ scheduleFlush();
2869
3619
  };
2870
3620
  const resultHandler = (newResults) => {
2871
3621
  const matched = newResults.filter((r) => sessionIdSet.has(r.sessionId));
2872
3622
  if (matched.length === 0) return;
2873
- setResults((prev) => [...prev, ...matched]);
3623
+ pendingResultsRef.current.push(...matched);
3624
+ scheduleFlush();
2874
3625
  };
2875
3626
  const watcher = new Watcher(callHandler, allUsers, void 0, void 0, resultHandler);
2876
3627
  watcherRef.current = watcher;
2877
3628
  watcher.start();
2878
3629
  return () => {
2879
3630
  cancelled = true;
3631
+ if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
2880
3632
  watcher.stop();
2881
3633
  watcherRef.current = null;
2882
3634
  };
@@ -2905,10 +3657,10 @@ var applyFilter = (events, filter) => {
2905
3657
  var useFilteredEvents = (rawEvents, filter) => useMemo4(() => applyFilter(rawEvents, filter), [rawEvents, filter]);
2906
3658
 
2907
3659
  // src/ui/hooks/useAlerts.ts
2908
- import { useState as useState12, useEffect as useEffect7, useRef as useRef5 } from "react";
3660
+ import { useState as useState13, useEffect as useEffect8, useRef as useRef7 } from "react";
2909
3661
 
2910
3662
  // src/notifications.ts
2911
- import { exec as exec2 } from "child_process";
3663
+ import { execFile as execFile2 } from "child_process";
2912
3664
  import { platform } from "os";
2913
3665
  var SEVERITY_ORDER = {
2914
3666
  info: 0,
@@ -2931,11 +3683,23 @@ var notify = (alert, config) => {
2931
3683
  const body = `${alert.sessionSlug}: ${alert.message.slice(0, 100)}`;
2932
3684
  const os = platform();
2933
3685
  if (os === "linux") {
2934
- exec2(`notify-send ${JSON.stringify(title)} ${JSON.stringify(body)}`, { timeout: 3e3 });
3686
+ execFile2("notify-send", [title, body], { timeout: 3e3 });
2935
3687
  } else if (os === "darwin") {
2936
- exec2(`osascript -e 'display notification ${JSON.stringify(body)} with title ${JSON.stringify(title)}'`, {
2937
- timeout: 3e3
2938
- });
3688
+ execFile2(
3689
+ "osascript",
3690
+ ["-e", `display notification ${JSON.stringify(body)} with title ${JSON.stringify(title)}`],
3691
+ { timeout: 3e3 }
3692
+ );
3693
+ } else if (os === "win32") {
3694
+ execFile2(
3695
+ "powershell",
3696
+ [
3697
+ "-NoProfile",
3698
+ "-Command",
3699
+ `Add-Type -AssemblyName System.Windows.Forms; $n = New-Object System.Windows.Forms.NotifyIcon; $n.Icon = [System.Drawing.SystemIcons]::Information; $n.Visible = $true; $n.ShowBalloonTip(5000, ${JSON.stringify(title)}, ${JSON.stringify(body)}, 'Warning'); Start-Sleep -Seconds 6; $n.Dispose()`
3700
+ ],
3701
+ { timeout: 1e4 }
3702
+ );
2939
3703
  }
2940
3704
  }
2941
3705
  };
@@ -2973,11 +3737,11 @@ var AlertLogger = class {
2973
3737
  // src/ui/hooks/useAlerts.ts
2974
3738
  var MAX_ALERTS = 100;
2975
3739
  var useAlerts = (enabled, alertLevel, allUsers, config) => {
2976
- const [alerts, setAlerts] = useState12([]);
2977
- const engineRef = useRef5(new SecurityEngine(alertLevel));
2978
- const watcherRef = useRef5(null);
2979
- const loggerRef = useRef5(null);
2980
- useEffect7(() => {
3740
+ const [alerts, setAlerts] = useState13([]);
3741
+ const engineRef = useRef7(new SecurityEngine(alertLevel));
3742
+ const watcherRef = useRef7(null);
3743
+ const loggerRef = useRef7(null);
3744
+ useEffect8(() => {
2981
3745
  if (!enabled) return;
2982
3746
  engineRef.current = new SecurityEngine(alertLevel);
2983
3747
  if (config?.alerts.enabled) {
@@ -3007,33 +3771,33 @@ var useAlerts = (enabled, alertLevel, allUsers, config) => {
3007
3771
  watcherRef.current = null;
3008
3772
  loggerRef.current = null;
3009
3773
  };
3010
- }, [enabled, alertLevel, allUsers]);
3774
+ }, [enabled, alertLevel, allUsers, config]);
3011
3775
  const clearAlerts = () => setAlerts([]);
3012
3776
  return { alerts, clearAlerts };
3013
3777
  };
3014
3778
 
3015
3779
  // src/ui/hooks/useTextInput.ts
3016
- import { useState as useState13, useCallback as useCallback4 } from "react";
3780
+ import { useState as useState14, useCallback as useCallback5 } from "react";
3017
3781
  var useTextInput = (onConfirm, onCancel) => {
3018
- const [value, setValue] = useState13("");
3019
- const [isActive, setIsActive] = useState13(false);
3020
- const start = useCallback4((initial = "") => {
3782
+ const [value, setValue] = useState14("");
3783
+ const [isActive, setIsActive] = useState14(false);
3784
+ const start = useCallback5((initial = "") => {
3021
3785
  setValue(initial);
3022
3786
  setIsActive(true);
3023
3787
  }, []);
3024
- const cancel = useCallback4(() => {
3788
+ const cancel = useCallback5(() => {
3025
3789
  setValue("");
3026
3790
  setIsActive(false);
3027
3791
  onCancel?.();
3028
3792
  }, [onCancel]);
3029
- const confirm = useCallback4(() => {
3793
+ const confirm = useCallback5(() => {
3030
3794
  const result = value;
3031
3795
  setIsActive(false);
3032
3796
  setValue("");
3033
3797
  onConfirm?.(result);
3034
3798
  return result;
3035
3799
  }, [value, onConfirm]);
3036
- const handleInput = useCallback4(
3800
+ const handleInput = useCallback5(
3037
3801
  (input, key) => {
3038
3802
  if (!isActive) return false;
3039
3803
  if (key.escape) {
@@ -3066,16 +3830,17 @@ var useTextInput = (onConfirm, onCancel) => {
3066
3830
  };
3067
3831
 
3068
3832
  // src/ui/hooks/useKeyHandler.ts
3069
- import { useInput as useInput10 } from "ink";
3833
+ import { useInput as useInput11 } from "ink";
3070
3834
  var matchKey = (binding, input, key) => {
3071
3835
  if (binding === "tab") return Boolean(key.tab);
3072
3836
  if (binding === "shift+tab") return Boolean(key.shift && key.tab);
3073
3837
  if (binding === "enter") return Boolean(key.return);
3838
+ if (binding.startsWith("ctrl+")) return Boolean(key.ctrl) && input === binding.slice(5);
3074
3839
  return input === binding;
3075
3840
  };
3076
3841
  var useKeyHandler = (deps) => {
3077
3842
  const d = deps;
3078
- useInput10((input, key) => {
3843
+ useInput11((input, key) => {
3079
3844
  if (d.showSetup || d.showSettings || d.confirmAction || d.showUpdateModal) return;
3080
3845
  if (d.inputMode === "nickname") {
3081
3846
  d.nicknameInput.handleInput(input, key);
@@ -3246,10 +4011,26 @@ var useKeyHandler = (deps) => {
3246
4011
  d.setShowSettings(true);
3247
4012
  return;
3248
4013
  }
4014
+ if (matchKey(d.kb.alertRules, input, key)) {
4015
+ d.setShowAlertRules(true);
4016
+ return;
4017
+ }
3249
4018
  if (matchKey(d.kb.viewArchive, input, key)) {
3250
4019
  d.setViewingArchive((v) => !v);
3251
4020
  return;
3252
4021
  }
4022
+ if (matchKey(d.kb.pin, input, key) && d.selectedSession) {
4023
+ d.onTogglePin(d.selectedSession.sessionId);
4024
+ return;
4025
+ }
4026
+ if (matchKey(d.kb.pinMoveUp, input, key) && d.selectedSession?.pinned) {
4027
+ d.onMovePinned(d.selectedSession.sessionId, "up");
4028
+ return;
4029
+ }
4030
+ if (matchKey(d.kb.pinMoveDown, input, key) && d.selectedSession?.pinned) {
4031
+ d.onMovePinned(d.selectedSession.sessionId, "down");
4032
+ return;
4033
+ }
3253
4034
  if (matchKey(d.kb.archive, input, key) && d.selectedSession) {
3254
4035
  if (d.viewingArchive) d.onUnarchive(d.selectedSession.sessionId);
3255
4036
  else d.onArchive(d.selectedSession.sessionId);
@@ -3308,10 +4089,10 @@ var useKeyHandler = (deps) => {
3308
4089
  };
3309
4090
 
3310
4091
  // src/ui/hooks/useUpdateChecker.ts
3311
- import { useState as useState14, useEffect as useEffect8 } from "react";
4092
+ import { useState as useState15, useEffect as useEffect9 } from "react";
3312
4093
  var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
3313
- const [updateInfo, setUpdateInfo] = useState14(null);
3314
- useEffect8(() => {
4094
+ const [updateInfo, setUpdateInfo] = useState15(null);
4095
+ useEffect9(() => {
3315
4096
  if (disabled || !checkOnLaunch) return;
3316
4097
  let cancelled = false;
3317
4098
  const check = () => {
@@ -3326,12 +4107,12 @@ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
3326
4107
  cancelled = true;
3327
4108
  clearInterval(iv);
3328
4109
  };
3329
- }, []);
4110
+ }, [disabled, checkOnLaunch, checkInterval]);
3330
4111
  return updateInfo;
3331
4112
  };
3332
4113
 
3333
4114
  // src/ui/hooks/useSetupFlow.ts
3334
- import { useState as useState15, useCallback as useCallback5 } from "react";
4115
+ import { useState as useState16, useCallback as useCallback6 } from "react";
3335
4116
 
3336
4117
  // src/hooks/installer.ts
3337
4118
  import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync as mkdirSync2, chmodSync } from "fs";
@@ -3451,25 +4232,25 @@ var installMcpConfig = () => {
3451
4232
 
3452
4233
  // src/ui/hooks/useSetupFlow.ts
3453
4234
  var useSetupFlow = (initialConfig, firstRun) => {
3454
- const [liveConfig, setLiveConfig] = useState15(initialConfig);
3455
- const [showSetup, setShowSetup] = useState15(firstRun);
3456
- const [showThemePicker, setShowThemePicker] = useState15(false);
3457
- const [showTour, setShowTour] = useState15(false);
3458
- const [showSettings, setShowSettings] = useState15(false);
3459
- const [showThemeMenu, setShowThemeMenu] = useState15(false);
3460
- const handleSettingsClose = useCallback5((c) => {
4235
+ const [liveConfig, setLiveConfig] = useState16(initialConfig);
4236
+ const [showSetup, setShowSetup] = useState16(firstRun);
4237
+ const [showThemePicker, setShowThemePicker] = useState16(false);
4238
+ const [showTour, setShowTour] = useState16(false);
4239
+ const [showSettings, setShowSettings] = useState16(false);
4240
+ const [showThemeMenu, setShowThemeMenu] = useState16(false);
4241
+ const handleSettingsClose = useCallback6((c) => {
3461
4242
  setLiveConfig(c);
3462
4243
  saveConfig(c);
3463
4244
  setShowSettings(false);
3464
4245
  }, []);
3465
- const handleThemeMenuClose = useCallback5((c) => {
4246
+ const handleThemeMenuClose = useCallback6((c) => {
3466
4247
  setLiveConfig(c);
3467
4248
  saveConfig(c);
3468
4249
  setShowThemeMenu(false);
3469
4250
  setShowSettings(true);
3470
4251
  }, []);
3471
- const handleOpenThemeMenu = useCallback5(() => setShowThemeMenu(true), []);
3472
- const handleSetupComplete = useCallback5(
4252
+ const handleOpenThemeMenu = useCallback6(() => setShowThemeMenu(true), []);
4253
+ const handleSetupComplete = useCallback6(
3473
4254
  (results) => {
3474
4255
  const nc = { ...liveConfig };
3475
4256
  const [hc, mc] = results;
@@ -3494,7 +4275,7 @@ var useSetupFlow = (initialConfig, firstRun) => {
3494
4275
  },
3495
4276
  [liveConfig]
3496
4277
  );
3497
- const handleThemePickerSelect = useCallback5(
4278
+ const handleThemePickerSelect = useCallback6(
3498
4279
  (themeName) => {
3499
4280
  const nc = { ...liveConfig, theme: themeName, prompts: { ...liveConfig.prompts, theme: "done" } };
3500
4281
  setLiveConfig(nc);
@@ -3504,24 +4285,24 @@ var useSetupFlow = (initialConfig, firstRun) => {
3504
4285
  },
3505
4286
  [liveConfig]
3506
4287
  );
3507
- const handleThemePickerSkip = useCallback5(() => {
4288
+ const handleThemePickerSkip = useCallback6(() => {
3508
4289
  setShowThemePicker(false);
3509
4290
  if (liveConfig.prompts.tour === "pending") setShowTour(true);
3510
4291
  }, [liveConfig]);
3511
- const handleThemePickerDismiss = useCallback5(() => {
4292
+ const handleThemePickerDismiss = useCallback6(() => {
3512
4293
  const nc = { ...liveConfig, prompts: { ...liveConfig.prompts, theme: "dismissed" } };
3513
4294
  setLiveConfig(nc);
3514
4295
  saveConfig(nc);
3515
4296
  setShowThemePicker(false);
3516
4297
  if (nc.prompts.tour === "pending") setShowTour(true);
3517
4298
  }, [liveConfig]);
3518
- const handleTourComplete = useCallback5(() => {
4299
+ const handleTourComplete = useCallback6(() => {
3519
4300
  const nc = { ...liveConfig, prompts: { ...liveConfig.prompts, tour: "done" } };
3520
4301
  setLiveConfig(nc);
3521
4302
  saveConfig(nc);
3522
4303
  setShowTour(false);
3523
4304
  }, [liveConfig]);
3524
- const handleTourSkip = useCallback5(() => {
4305
+ const handleTourSkip = useCallback6(() => {
3525
4306
  setShowTour(false);
3526
4307
  }, []);
3527
4308
  return {
@@ -3547,18 +4328,18 @@ var useSetupFlow = (initialConfig, firstRun) => {
3547
4328
  };
3548
4329
 
3549
4330
  // src/ui/hooks/useSplitPanel.ts
3550
- import { useState as useState16, useCallback as useCallback6 } from "react";
4331
+ import { useState as useState17, useCallback as useCallback7 } from "react";
3551
4332
  var useSplitPanel = () => {
3552
- const [splitMode, setSplitMode] = useState16(false);
3553
- const [leftSession, setLeftSession] = useState16(null);
3554
- const [rightSession, setRightSession] = useState16(null);
3555
- const [leftScroll, setLeftScroll] = useState16(0);
3556
- const [rightScroll, setRightScroll] = useState16(0);
3557
- const [leftFilter, setLeftFilter] = useState16("");
3558
- const [rightFilter, setRightFilter] = useState16("");
3559
- const [leftShowDetail, setLeftShowDetail] = useState16(false);
3560
- const [rightShowDetail, setRightShowDetail] = useState16(false);
3561
- const clearSplitState = useCallback6(() => {
4333
+ const [splitMode, setSplitMode] = useState17(false);
4334
+ const [leftSession, setLeftSession] = useState17(null);
4335
+ const [rightSession, setRightSession] = useState17(null);
4336
+ const [leftScroll, setLeftScroll] = useState17(0);
4337
+ const [rightScroll, setRightScroll] = useState17(0);
4338
+ const [leftFilter, setLeftFilter] = useState17("");
4339
+ const [rightFilter, setRightFilter] = useState17("");
4340
+ const [leftShowDetail, setLeftShowDetail] = useState17(false);
4341
+ const [rightShowDetail, setRightShowDetail] = useState17(false);
4342
+ const clearSplitState = useCallback7(() => {
3562
4343
  setSplitMode(false);
3563
4344
  setLeftSession(null);
3564
4345
  setRightSession(null);
@@ -3569,7 +4350,7 @@ var useSplitPanel = () => {
3569
4350
  setLeftShowDetail(false);
3570
4351
  setRightShowDetail(false);
3571
4352
  }, []);
3572
- const resetPanel = useCallback6((side) => {
4353
+ const resetPanel = useCallback7((side) => {
3573
4354
  if (side === "left") {
3574
4355
  setLeftSession(null);
3575
4356
  setLeftScroll(0);
@@ -3582,7 +4363,7 @@ var useSplitPanel = () => {
3582
4363
  setRightShowDetail(false);
3583
4364
  }
3584
4365
  }, []);
3585
- const switchPanel = useCallback6(
4366
+ const switchPanel = useCallback7(
3586
4367
  (dir, setActivePanel) => {
3587
4368
  if (splitMode) {
3588
4369
  const order = ["sessions", "left", "right"];
@@ -3623,32 +4404,33 @@ var useSplitPanel = () => {
3623
4404
  };
3624
4405
 
3625
4406
  // src/ui/App.tsx
3626
- import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
4407
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
3627
4408
  var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3628
4409
  const { exit } = useApp();
3629
- const { stdout } = useStdout3();
4410
+ const { stdout } = useStdout4();
3630
4411
  const termHeight = stdout?.rows ?? 40;
3631
4412
  const setup = useSetupFlow(initialConfig, firstRun);
3632
4413
  const split = useSplitPanel();
3633
4414
  const kb = setup.liveConfig.keybindings;
3634
- const [activePanel, setActivePanel] = useState17("sessions");
3635
- const [activityScroll, setActivityScroll] = useState17(0);
3636
- const [inputMode, setInputMode] = useState17("normal");
3637
- const [filter, setFilter] = useState17("");
3638
- const [activityFilter, setActivityFilter] = useState17("");
3639
- const [updateStatus, setUpdateStatus] = useState17("");
3640
- const [showDetail, setShowDetail] = useState17(false);
3641
- const [viewingArchive, setViewingArchive] = useState17(false);
3642
- const [confirmAction, setConfirmAction] = useState17(
4415
+ const [activePanel, setActivePanel] = useState18("sessions");
4416
+ const [activityScroll, setActivityScroll] = useState18(0);
4417
+ const [inputMode, setInputMode] = useState18("normal");
4418
+ const [filter, setFilter] = useState18("");
4419
+ const [activityFilter, setActivityFilter] = useState18("");
4420
+ const [updateStatus, setUpdateStatus] = useState18("");
4421
+ const [showDetail, setShowDetail] = useState18(false);
4422
+ const [viewingArchive, setViewingArchive] = useState18(false);
4423
+ const [confirmAction, setConfirmAction] = useState18(
3643
4424
  null
3644
4425
  );
3645
- const [archivedIds, setArchivedIds] = useState17(() => new Set(Object.keys(getArchived())));
3646
- const [sidebarWidth, setSidebarWidth] = useState17(() => initialConfig.sidebarWidth ?? 30);
3647
- const [selectedEventIndex, setSelectedEventIndex] = useState17(0);
3648
- const [showEventDetail, setShowEventDetail] = useState17(false);
3649
- const [showUpdateModal, setShowUpdateModal] = useState17(false);
3650
- const refreshArchived = useCallback7(() => setArchivedIds(new Set(Object.keys(getArchived()))), []);
3651
- const persistSidebarWidth = useCallback7((v) => {
4426
+ const [archivedIds, setArchivedIds] = useState18(() => new Set(Object.keys(getArchived())));
4427
+ const [sidebarWidth, setSidebarWidth] = useState18(() => initialConfig.sidebarWidth ?? 30);
4428
+ const [selectedEventIndex, setSelectedEventIndex] = useState18(0);
4429
+ const [showEventDetail, setShowEventDetail] = useState18(false);
4430
+ const [showUpdateModal, setShowUpdateModal] = useState18(false);
4431
+ const [showAlertRules, setShowAlertRules] = useState18(false);
4432
+ const refreshArchived = useCallback8(() => setArchivedIds(new Set(Object.keys(getArchived()))), []);
4433
+ const persistSidebarWidth = useCallback8((v) => {
3652
4434
  setSidebarWidth((prev) => {
3653
4435
  const next = typeof v === "function" ? v(prev) : v;
3654
4436
  if (next !== prev) {
@@ -3664,10 +4446,10 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3664
4446
  setup.liveConfig.updates.checkOnLaunch,
3665
4447
  setup.liveConfig.updates.checkInterval
3666
4448
  );
3667
- useEffect9(() => {
4449
+ useEffect10(() => {
3668
4450
  applyTheme(resolveTheme(setup.liveConfig.theme, setup.liveConfig.customThemes));
3669
4451
  }, [setup.liveConfig.theme, setup.liveConfig.customThemes]);
3670
- useEffect9(() => {
4452
+ useEffect10(() => {
3671
4453
  if (updateInfo?.available && setup.liveConfig.prompts.autoUpdate === "pending") {
3672
4454
  setShowUpdateModal(true);
3673
4455
  }
@@ -3717,16 +4499,16 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3717
4499
  setInputMode("normal");
3718
4500
  }
3719
4501
  );
3720
- useEffect9(() => {
4502
+ useEffect10(() => {
3721
4503
  purgeExpiredArchives();
3722
4504
  refreshArchived();
3723
4505
  }, []);
3724
- useEffect9(() => {
4506
+ useEffect10(() => {
3725
4507
  setActivityScroll(0);
3726
4508
  setSelectedEventIndex(0);
3727
4509
  setShowEventDetail(false);
3728
4510
  }, [selectedSession?.sessionId, selectedGroup?.key]);
3729
- useEffect9(() => {
4511
+ useEffect10(() => {
3730
4512
  const totalEvents = events.length;
3731
4513
  const vRows = termHeight - 3 - (options2.noSecurity ? 0 : 6) - 1 - (inputMode !== "normal" ? 1 : 0) - 2;
3732
4514
  if (vRows <= 0 || totalEvents === 0) return;
@@ -3738,7 +4520,7 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3738
4520
  setActivityScroll(totalEvents - vRows - (selectedEventIndex - vRows + 1));
3739
4521
  }
3740
4522
  }, [selectedEventIndex, events.length]);
3741
- useEffect9(() => {
4523
+ useEffect10(() => {
3742
4524
  if (events.length > 0 && selectedEventIndex >= events.length) {
3743
4525
  setSelectedEventIndex(events.length - 1);
3744
4526
  }
@@ -3746,27 +4528,46 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3746
4528
  const alertHeight = options2.noSecurity ? 0 : 6;
3747
4529
  const mainHeight = termHeight - 3 - alertHeight - 1 - (inputMode !== "normal" ? 1 : 0);
3748
4530
  const viewportRows = mainHeight - 2;
3749
- const switchPanel = useCallback7(
4531
+ const switchPanel = useCallback8(
3750
4532
  (dir) => split.switchPanel(dir, setActivePanel),
3751
4533
  [split.switchPanel]
3752
4534
  );
3753
- const clearSplitState = useCallback7(() => {
4535
+ const clearSplitState = useCallback8(() => {
3754
4536
  split.clearSplitState();
3755
4537
  setActivePanel("sessions");
3756
4538
  }, [split.clearSplitState]);
3757
- const getActiveFilter = useCallback7(() => {
4539
+ const getActiveFilter = useCallback8(() => {
3758
4540
  if (activePanel === "sessions") return filter;
3759
4541
  if (activePanel === "left") return split.leftFilter;
3760
4542
  if (activePanel === "right") return split.rightFilter;
3761
4543
  return activityFilter;
3762
4544
  }, [activePanel, filter, split.leftFilter, split.rightFilter, activityFilter]);
4545
+ const handleTogglePin = useCallback8(
4546
+ (sessionId) => {
4547
+ const cfg = loadConfig();
4548
+ if (cfg.pinnedSessions.includes(sessionId)) {
4549
+ unpinSession(sessionId);
4550
+ } else {
4551
+ pinSession(sessionId);
4552
+ }
4553
+ refresh();
4554
+ },
4555
+ [refresh]
4556
+ );
4557
+ const handleMovePinned = useCallback8(
4558
+ (sessionId, dir) => {
4559
+ movePinned(sessionId, dir);
4560
+ refresh();
4561
+ },
4562
+ [refresh]
4563
+ );
3763
4564
  useKeyHandler({
3764
4565
  kb,
3765
4566
  activePanel,
3766
4567
  splitMode: split.splitMode,
3767
4568
  inputMode,
3768
4569
  showSetup: setup.showSetup,
3769
- showSettings: setup.showSettings || setup.showThemeMenu || setup.showThemePicker || setup.showTour,
4570
+ showSettings: setup.showSettings || setup.showThemeMenu || setup.showThemePicker || setup.showTour || showAlertRules,
3770
4571
  showDetail,
3771
4572
  showEventDetail,
3772
4573
  leftShowDetail: split.leftShowDetail,
@@ -3810,6 +4611,8 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3810
4611
  setLeftShowDetail: split.setLeftShowDetail,
3811
4612
  setRightShowDetail: split.setRightShowDetail,
3812
4613
  setShowSettings: setup.setShowSettings,
4614
+ showAlertRules,
4615
+ setShowAlertRules,
3813
4616
  setViewingArchive,
3814
4617
  setSplitMode: split.setSplitMode,
3815
4618
  setLeftSession: split.setLeftSession,
@@ -3824,14 +4627,12 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3824
4627
  setSidebarWidth: persistSidebarWidth,
3825
4628
  nicknameInput,
3826
4629
  filterInput,
3827
- onNickname: (id) => {
3828
- clearNickname(id);
3829
- refresh();
3830
- },
3831
4630
  onClearNickname: (id) => {
3832
4631
  clearNickname(id);
3833
4632
  refresh();
3834
4633
  },
4634
+ onTogglePin: handleTogglePin,
4635
+ onMovePinned: handleMovePinned,
3835
4636
  onArchive: (id) => {
3836
4637
  archiveSession(id);
3837
4638
  refreshArchived();
@@ -3883,10 +4684,10 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3883
4684
  setup.setShowSetup(false);
3884
4685
  return null;
3885
4686
  }
3886
- return /* @__PURE__ */ jsx17(SetupModal, { steps, onComplete: setup.handleSetupComplete });
4687
+ return /* @__PURE__ */ jsx18(SetupModal, { steps, onComplete: setup.handleSetupComplete });
3887
4688
  }
3888
4689
  if (setup.showThemePicker)
3889
- return /* @__PURE__ */ jsx17(
4690
+ return /* @__PURE__ */ jsx18(
3890
4691
  ThemePickerModal,
3891
4692
  {
3892
4693
  onSelect: setup.handleThemePickerSelect,
@@ -3894,10 +4695,22 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3894
4695
  onDismiss: setup.handleThemePickerDismiss
3895
4696
  }
3896
4697
  );
3897
- if (setup.showTour) return /* @__PURE__ */ jsx17(GuidedTour, { onComplete: setup.handleTourComplete, onSkip: setup.handleTourSkip });
3898
- if (setup.showThemeMenu) return /* @__PURE__ */ jsx17(ThemeMenu, { config: setup.liveConfig, onClose: setup.handleThemeMenuClose });
4698
+ if (setup.showTour) return /* @__PURE__ */ jsx18(GuidedTour, { onComplete: setup.handleTourComplete, onSkip: setup.handleTourSkip });
4699
+ if (setup.showThemeMenu) return /* @__PURE__ */ jsx18(ThemeMenu, { config: setup.liveConfig, onClose: setup.handleThemeMenuClose });
4700
+ if (showAlertRules)
4701
+ return /* @__PURE__ */ jsx18(
4702
+ AlertRulesMenu,
4703
+ {
4704
+ config: setup.liveConfig,
4705
+ onClose: () => setShowAlertRules(false),
4706
+ onSave: (newConfig) => {
4707
+ saveConfig(newConfig);
4708
+ setup.setLiveConfig(newConfig);
4709
+ }
4710
+ }
4711
+ );
3899
4712
  if (setup.showSettings)
3900
- return /* @__PURE__ */ jsx17(
4713
+ return /* @__PURE__ */ jsx18(
3901
4714
  SettingsMenu,
3902
4715
  {
3903
4716
  config: setup.liveConfig,
@@ -3906,7 +4719,7 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3906
4719
  }
3907
4720
  );
3908
4721
  if (showUpdateModal && updateInfo?.available) {
3909
- return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx17(
4722
+ return /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx18(
3910
4723
  UpdateModal,
3911
4724
  {
3912
4725
  current: updateInfo.current,
@@ -3923,7 +4736,7 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3923
4736
  ) });
3924
4737
  }
3925
4738
  if (confirmAction) {
3926
- return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx17(
4739
+ return /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx18(
3927
4740
  ConfirmModal,
3928
4741
  {
3929
4742
  title: confirmAction.title,
@@ -3936,7 +4749,7 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3936
4749
  const activitySlug = selectedGroup ? selectedGroup.key : selectedSession?.slug ?? null;
3937
4750
  const isMerged = selectedGroup !== null;
3938
4751
  const selectedEvent = events[selectedEventIndex];
3939
- const rightPanel = split.splitMode ? /* @__PURE__ */ jsx17(
4752
+ const rightPanel = split.splitMode ? /* @__PURE__ */ jsx18(
3940
4753
  SplitPanel,
3941
4754
  {
3942
4755
  activePanel,
@@ -3952,13 +4765,13 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3952
4765
  rightShowDetail: split.rightShowDetail,
3953
4766
  height: mainHeight
3954
4767
  }
3955
- ) : showEventDetail && selectedEvent ? /* @__PURE__ */ jsx17(ToolCallDetail, { event: selectedEvent, focused: activePanel === "activity", height: mainHeight }) : showDetail && selectedSession ? /* @__PURE__ */ jsx17(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx17(
4768
+ ) : showEventDetail && selectedEvent ? /* @__PURE__ */ jsx18(ToolCallDetail, { event: selectedEvent, focused: activePanel === "activity", height: mainHeight }) : showDetail && selectedSession ? /* @__PURE__ */ jsx18(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx18(
3956
4769
  ActivityFeed,
3957
4770
  {
3958
4771
  events,
3959
4772
  sessionSlug: activitySlug,
3960
4773
  sessionId: selectedSession?.sessionId,
3961
- isActive: selectedGroup ? selectedGroup.isActive : selectedSession ? selectedSession.pid !== null : void 0,
4774
+ status: selectedGroup ? selectedGroup.status : selectedSession ? selectedSession.status : void 0,
3962
4775
  focused: activePanel === "activity",
3963
4776
  height: mainHeight,
3964
4777
  scrollOffset: activityScroll,
@@ -3968,10 +4781,10 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3968
4781
  selectedEventIndex
3969
4782
  }
3970
4783
  );
3971
- return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", height: termHeight, children: [
3972
- /* @__PURE__ */ jsx17(StatusBar, { sessionCount: sessions2.length, alertCount: alerts.length, version, updateInfo }),
3973
- /* @__PURE__ */ jsxs17(Box17, { flexGrow: 1, height: mainHeight, children: [
3974
- /* @__PURE__ */ jsx17(
4784
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", height: termHeight, children: [
4785
+ /* @__PURE__ */ jsx18(StatusBar, { sessionCount: sessions2.length, alertCount: alerts.length, version, updateInfo }),
4786
+ /* @__PURE__ */ jsxs18(Box18, { flexGrow: 1, height: mainHeight, children: [
4787
+ /* @__PURE__ */ jsx18(
3975
4788
  SessionList,
3976
4789
  {
3977
4790
  visibleItems,
@@ -3984,21 +4797,21 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
3984
4797
  sidebarWidth
3985
4798
  }
3986
4799
  ),
3987
- /* @__PURE__ */ jsx17(Box17, { flexGrow: 1, flexShrink: 1, minWidth: 0, overflow: "hidden", children: rightPanel })
4800
+ /* @__PURE__ */ jsx18(Box18, { flexGrow: 1, flexShrink: 1, minWidth: 0, overflow: "hidden", children: rightPanel })
3988
4801
  ] }),
3989
- !options2.noSecurity && /* @__PURE__ */ jsx17(AlertBar, { alerts }),
3990
- inputMode === "nickname" && /* @__PURE__ */ jsxs17(Box17, { paddingX: 1, children: [
3991
- /* @__PURE__ */ jsx17(Text16, { color: colors.primary, children: "nickname: " }),
3992
- /* @__PURE__ */ jsx17(Text16, { color: colors.bright, children: nicknameInput.value }),
3993
- /* @__PURE__ */ jsx17(Text16, { color: colors.muted, children: "_" })
4802
+ !options2.noSecurity && /* @__PURE__ */ jsx18(AlertBar, { alerts }),
4803
+ inputMode === "nickname" && /* @__PURE__ */ jsxs18(Box18, { paddingX: 1, children: [
4804
+ /* @__PURE__ */ jsx18(Text17, { color: colors.primary, children: "nickname: " }),
4805
+ /* @__PURE__ */ jsx18(Text17, { color: colors.bright, children: nicknameInput.value }),
4806
+ /* @__PURE__ */ jsx18(Text17, { color: colors.muted, children: "_" })
3994
4807
  ] }),
3995
- inputMode === "filter" && /* @__PURE__ */ jsxs17(Box17, { paddingX: 1, children: [
3996
- /* @__PURE__ */ jsx17(Text16, { color: colors.muted, children: activePanel === "sessions" ? "sessions" : activePanel === "left" ? "left" : activePanel === "right" ? "right" : "activity" }),
3997
- /* @__PURE__ */ jsx17(Text16, { color: colors.primary, children: "/" }),
3998
- /* @__PURE__ */ jsx17(Text16, { color: colors.bright, children: filterInput.value }),
3999
- /* @__PURE__ */ jsx17(Text16, { color: colors.muted, children: "_" })
4808
+ inputMode === "filter" && /* @__PURE__ */ jsxs18(Box18, { paddingX: 1, children: [
4809
+ /* @__PURE__ */ jsx18(Text17, { color: colors.muted, children: activePanel === "sessions" ? "sessions" : activePanel === "left" ? "left" : activePanel === "right" ? "right" : "activity" }),
4810
+ /* @__PURE__ */ jsx18(Text17, { color: colors.primary, children: "/" }),
4811
+ /* @__PURE__ */ jsx18(Text17, { color: colors.bright, children: filterInput.value }),
4812
+ /* @__PURE__ */ jsx18(Text17, { color: colors.muted, children: "_" })
4000
4813
  ] }),
4001
- inputMode === "normal" && /* @__PURE__ */ jsx17(
4814
+ inputMode === "normal" && /* @__PURE__ */ jsx18(
4002
4815
  FooterBar,
4003
4816
  {
4004
4817
  keybindings: kb,
@@ -4014,11 +4827,6 @@ var App = ({ options: options2, config: initialConfig, version, firstRun }) => {
4014
4827
  var write = (msg) => {
4015
4828
  process.stdout.write(msg + "\n");
4016
4829
  };
4017
- var formatTokens3 = (n) => {
4018
- if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
4019
- if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
4020
- return String(n);
4021
- };
4022
4830
  var runStreamMode = (options2, isJson) => {
4023
4831
  const engine = options2.noSecurity ? null : new SecurityEngine(options2.alertLevel);
4024
4832
  const sessions2 = discoverSessions(options2.allUsers);
@@ -4027,7 +4835,7 @@ var runStreamMode = (options2, isJson) => {
4027
4835
  } else {
4028
4836
  for (const s of sessions2) {
4029
4837
  write(
4030
- `SESSION ${s.slug} | ${s.model} | ${s.cwd} | CPU ${s.cpu}% | ${s.memMB}MB | ${formatTokens3(s.usage.inputTokens)} in / ${formatTokens3(s.usage.outputTokens)} out`
4838
+ `SESSION ${s.slug} | ${s.status} | ${s.model} | ${s.cwd} | CPU ${s.cpu}% | ${s.memMB}MB | ${formatTokens(s.usage.inputTokens)} in / ${formatTokens(s.usage.outputTokens)} out`
4031
4839
  );
4032
4840
  }
4033
4841
  }
@@ -4060,7 +4868,7 @@ var runStreamMode = (options2, isJson) => {
4060
4868
  write(JSON.stringify({ type: "usage", data: { sessionId, usage } }));
4061
4869
  } else {
4062
4870
  write(
4063
- `USAGE ${sessionId.slice(0, 12)} +${formatTokens3(usage.inputTokens)} in / +${formatTokens3(usage.outputTokens)} out`
4871
+ `USAGE ${sessionId.slice(0, 12)} +${formatTokens(usage.inputTokens)} in / +${formatTokens(usage.outputTokens)} out`
4064
4872
  );
4065
4873
  }
4066
4874
  };
@@ -4244,7 +5052,7 @@ var main = () => {
4244
5052
  if (options2.noUpdates) config.updates.checkOnLaunch = false;
4245
5053
  if (options2.noSecurity) config.security.enabled = false;
4246
5054
  if (firstRun) saveConfig(config);
4247
- render(React18.createElement(App, { options: options2, config, version: VERSION, firstRun }));
5055
+ render(React19.createElement(App, { options: options2, config, version: VERSION, firstRun }));
4248
5056
  };
4249
5057
  main();
4250
5058
  //# sourceMappingURL=index.js.map