agenttop 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -12,18 +12,18 @@ import {
12
12
  saveConfig,
13
13
  setNickname,
14
14
  startMcpServer
15
- } from "./chunk-4I4UZNKS.js";
15
+ } from "./chunk-H2JOTO54.js";
16
16
 
17
17
  // src/index.tsx
18
18
  import { readFileSync as readFileSync4 } from "fs";
19
19
  import { join as join4, dirname as dirname4 } from "path";
20
20
  import { fileURLToPath as fileURLToPath3 } from "url";
21
- import React9 from "react";
21
+ import React10 from "react";
22
22
  import { render } from "ink";
23
23
 
24
24
  // src/ui/App.tsx
25
- import { useState as useState7, useEffect as useEffect5, useCallback as useCallback3 } from "react";
26
- import { Box as Box8, Text as Text8, useApp, useInput as useInput2, useStdout } from "ink";
25
+ import { useState as useState8, useEffect as useEffect5, useCallback as useCallback3 } from "react";
26
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput3, useStdout as useStdout2 } from "ink";
27
27
 
28
28
  // src/hooks/installer.ts
29
29
  import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, chmodSync } from "fs";
@@ -273,76 +273,88 @@ var formatModel = (model) => {
273
273
  if (model.includes("haiku")) return "haiku";
274
274
  return model.slice(0, 8);
275
275
  };
276
- var formatProject = (project) => {
276
+ var formatProject = (project, max) => {
277
277
  const parts = project.split("/");
278
278
  const last = parts[parts.length - 1] || project;
279
- return last.length > 18 ? last.slice(0, 17) + "\u2026" : last;
279
+ return last.length > max ? last.slice(0, max - 1) + "\u2026" : last;
280
280
  };
281
281
  var formatTokens = (n) => {
282
282
  if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
283
283
  if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
284
284
  return String(n);
285
285
  };
286
+ var truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
287
+ var SIDEBAR_WIDTH = 28;
288
+ var INNER_WIDTH = SIDEBAR_WIDTH - 4;
286
289
  var SessionList = React2.memo(({ sessions, selectedIndex, focused, filter }) => {
287
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: 28, borderStyle: "single", borderColor: focused ? colors.primary : colors.border, children: [
288
- /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
289
- /* @__PURE__ */ jsx2(Text2, { color: colors.header, bold: true, children: "SESSIONS" }),
290
- filter && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
291
- " [",
292
- filter,
293
- "]"
294
- ] })
295
- ] }),
296
- sessions.length === 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: colors.muted, italic: true, children: filter ? "No matching sessions" : "No active sessions" }) }),
297
- sessions.map((session, i) => {
298
- const isSelected = i === selectedIndex;
299
- const indicator = isSelected ? ">" : " ";
300
- const displayName = session.nickname || session.slug;
301
- const totalIn = session.usage.inputTokens + session.usage.cacheReadTokens;
302
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
303
- /* @__PURE__ */ jsxs2(
304
- Text2,
305
- {
306
- color: isSelected ? colors.bright : colors.text,
307
- bold: isSelected,
308
- backgroundColor: isSelected ? colors.selected : void 0,
309
- children: [
310
- indicator,
311
- " ",
312
- displayName
313
- ]
314
- }
315
- ),
316
- session.nickname && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
317
- " ",
318
- session.slug
319
- ] }),
320
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
321
- " ",
322
- formatProject(session.project),
323
- " | ",
324
- formatModel(session.model)
325
- ] }),
326
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
327
- " ",
328
- "CPU ",
329
- session.cpu,
330
- "% | ",
331
- session.memMB,
332
- "MB | ",
333
- session.agentCount,
334
- " ag"
290
+ return /* @__PURE__ */ jsxs2(
291
+ Box2,
292
+ {
293
+ flexDirection: "column",
294
+ width: SIDEBAR_WIDTH,
295
+ borderStyle: "single",
296
+ borderColor: focused ? colors.primary : colors.border,
297
+ overflow: "hidden",
298
+ children: [
299
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
300
+ /* @__PURE__ */ jsx2(Text2, { color: colors.header, bold: true, children: "SESSIONS" }),
301
+ filter && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
302
+ " [",
303
+ truncate(filter, 10),
304
+ "]"
305
+ ] })
335
306
  ] }),
336
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
337
- " ",
338
- formatTokens(totalIn),
339
- " in | ",
340
- formatTokens(session.usage.outputTokens),
341
- " out"
342
- ] })
343
- ] }, session.sessionId);
344
- })
345
- ] });
307
+ sessions.length === 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: colors.muted, italic: true, children: filter ? "No matches" : "No sessions" }) }),
308
+ sessions.map((session, i) => {
309
+ const isSelected = i === selectedIndex;
310
+ const indicator = isSelected ? ">" : " ";
311
+ const nameMaxLen = INNER_WIDTH - 2;
312
+ const displayName = truncate(session.nickname || session.slug, nameMaxLen);
313
+ const totalIn = session.usage.inputTokens + session.usage.cacheReadTokens;
314
+ const proj = formatProject(session.project, 12);
315
+ const model = formatModel(session.model);
316
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, paddingY: 0, children: [
317
+ /* @__PURE__ */ jsxs2(
318
+ Text2,
319
+ {
320
+ color: isSelected ? colors.bright : colors.text,
321
+ bold: isSelected,
322
+ backgroundColor: isSelected ? colors.selected : void 0,
323
+ wrap: "truncate",
324
+ children: [
325
+ indicator,
326
+ " ",
327
+ displayName
328
+ ]
329
+ }
330
+ ),
331
+ session.nickname && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
332
+ " ",
333
+ truncate(session.slug, nameMaxLen)
334
+ ] }),
335
+ /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
336
+ " ",
337
+ proj,
338
+ " ",
339
+ model,
340
+ " ",
341
+ session.agentCount,
342
+ "ag"
343
+ ] }),
344
+ /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
345
+ " ",
346
+ formatTokens(totalIn),
347
+ "in ",
348
+ formatTokens(session.usage.outputTokens),
349
+ "out ",
350
+ session.cpu,
351
+ "%"
352
+ ] })
353
+ ] }, session.sessionId);
354
+ })
355
+ ]
356
+ }
357
+ );
346
358
  });
347
359
 
348
360
  // src/ui/components/ActivityFeed.tsx
@@ -684,16 +696,234 @@ var FooterBar = React7.memo(({ keybindings, updateStatus }) => /* @__PURE__ */ j
684
696
  label(keybindings.detail),
685
697
  ":detail"
686
698
  ] }) }),
699
+ /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsxs7(Text7, { color: "#5C6370", children: [
700
+ label(keybindings.settings),
701
+ ":settings"
702
+ ] }) }),
687
703
  updateStatus && /* @__PURE__ */ jsx7(Box7, { marginRight: 2, children: /* @__PURE__ */ jsx7(Text7, { color: colors.secondary, children: updateStatus }) })
688
704
  ] }));
689
705
 
706
+ // src/ui/components/SettingsMenu.tsx
707
+ import React8, { useState as useState3, useMemo } from "react";
708
+ import { Box as Box8, Text as Text8, useInput as useInput2, useStdout } from "ink";
709
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
710
+ var KEYBIND_LABELS = {
711
+ quit: "Quit",
712
+ navUp: "Navigate up",
713
+ navDown: "Navigate down",
714
+ panelNext: "Next panel",
715
+ panelPrev: "Previous panel",
716
+ scrollTop: "Scroll to top",
717
+ scrollBottom: "Scroll to bottom",
718
+ filter: "Filter sessions",
719
+ nickname: "Set nickname",
720
+ clearNickname: "Clear nickname",
721
+ detail: "Detail view",
722
+ update: "Install update",
723
+ settings: "Settings"
724
+ };
725
+ var RULE_LABELS = {
726
+ network: "Network detection",
727
+ exfiltration: "Exfiltration detection",
728
+ sensitiveFiles: "Sensitive files",
729
+ shellEscape: "Shell escape",
730
+ injection: "Prompt injection"
731
+ };
732
+ var SEVERITY_OPTIONS = ["info", "warn", "high", "critical"];
733
+ var displayKey = (key) => {
734
+ if (key === "tab") return "tab";
735
+ if (key === "shift+tab") return "S-tab";
736
+ if (key === "enter") return "enter";
737
+ return key;
738
+ };
739
+ var buildMenuItems = () => {
740
+ const items = [];
741
+ items.push({ type: "header", label: "KEYBINDINGS", section: "keybindings", getValue: () => "", key: void 0 });
742
+ for (const [k, label2] of Object.entries(KEYBIND_LABELS)) {
743
+ const kbKey = k;
744
+ items.push({
745
+ type: "keybind",
746
+ label: label2,
747
+ section: "keybindings",
748
+ key: kbKey,
749
+ getValue: (cfg) => displayKey(cfg.keybindings[kbKey]),
750
+ apply: (cfg, newValue) => ({
751
+ ...cfg,
752
+ keybindings: { ...cfg.keybindings, [kbKey]: newValue }
753
+ })
754
+ });
755
+ }
756
+ items.push({ type: "header", label: "SECURITY RULES", section: "security", getValue: () => "", key: void 0 });
757
+ for (const [k, label2] of Object.entries(RULE_LABELS)) {
758
+ const ruleKey = k;
759
+ items.push({
760
+ type: "toggle",
761
+ label: label2,
762
+ section: "security",
763
+ key: ruleKey,
764
+ getValue: (cfg) => cfg.security.rules[ruleKey] ? "ON" : "OFF",
765
+ apply: (cfg) => ({
766
+ ...cfg,
767
+ security: {
768
+ ...cfg.security,
769
+ rules: { ...cfg.security.rules, [ruleKey]: !cfg.security.rules[ruleKey] }
770
+ }
771
+ })
772
+ });
773
+ }
774
+ items.push({
775
+ type: "header",
776
+ label: "NOTIFICATIONS",
777
+ section: "notifications",
778
+ getValue: () => "",
779
+ key: void 0
780
+ });
781
+ items.push({
782
+ type: "toggle",
783
+ label: "Terminal bell",
784
+ section: "notifications",
785
+ key: "bell",
786
+ getValue: (cfg) => cfg.notifications.bell ? "ON" : "OFF",
787
+ apply: (cfg) => ({
788
+ ...cfg,
789
+ notifications: { ...cfg.notifications, bell: !cfg.notifications.bell }
790
+ })
791
+ });
792
+ items.push({
793
+ type: "toggle",
794
+ label: "Desktop notifications",
795
+ section: "notifications",
796
+ key: "desktop",
797
+ getValue: (cfg) => cfg.notifications.desktop ? "ON" : "OFF",
798
+ apply: (cfg) => ({
799
+ ...cfg,
800
+ notifications: { ...cfg.notifications, desktop: !cfg.notifications.desktop }
801
+ })
802
+ });
803
+ items.push({
804
+ type: "cycle",
805
+ label: "Minimum severity",
806
+ section: "notifications",
807
+ key: "minSeverity",
808
+ getValue: (cfg) => cfg.notifications.minSeverity,
809
+ apply: (cfg) => {
810
+ const idx = SEVERITY_OPTIONS.indexOf(cfg.notifications.minSeverity);
811
+ const next = SEVERITY_OPTIONS[(idx + 1) % SEVERITY_OPTIONS.length];
812
+ return { ...cfg, notifications: { ...cfg.notifications, minSeverity: next } };
813
+ }
814
+ });
815
+ return items;
816
+ };
817
+ var MENU_ITEMS = buildMenuItems();
818
+ var SELECTABLE_INDICES = MENU_ITEMS.map((item, i) => item.type !== "header" ? i : -1).filter((i) => i >= 0);
819
+ var SettingsMenu = React8.memo(({ config, onClose }) => {
820
+ const { stdout } = useStdout();
821
+ const termHeight = stdout?.rows ?? 40;
822
+ const [localConfig, setLocalConfig] = useState3(() => JSON.parse(JSON.stringify(config)));
823
+ const [selectablePos, setSelectablePos] = useState3(0);
824
+ const [rebinding, setRebinding] = useState3(false);
825
+ const selectedIndex = SELECTABLE_INDICES[selectablePos];
826
+ const maxLabelLen = useMemo(
827
+ () => Math.max(...MENU_ITEMS.filter((i) => i.type !== "header").map((i) => i.label.length)),
828
+ []
829
+ );
830
+ useInput2((input, key) => {
831
+ if (rebinding) {
832
+ const item = MENU_ITEMS[selectedIndex];
833
+ let newKey;
834
+ if (key.tab && key.shift) newKey = "shift+tab";
835
+ else if (key.tab) newKey = "tab";
836
+ else if (key.return) newKey = "enter";
837
+ else if (key.escape) {
838
+ setRebinding(false);
839
+ return;
840
+ } else if (input && input.length === 1) newKey = input;
841
+ else return;
842
+ if (item.apply) {
843
+ setLocalConfig((c) => item.apply(c, newKey));
844
+ }
845
+ setRebinding(false);
846
+ return;
847
+ }
848
+ if (key.escape) {
849
+ onClose(localConfig);
850
+ return;
851
+ }
852
+ if (key.upArrow) {
853
+ setSelectablePos((p) => Math.max(0, p - 1));
854
+ return;
855
+ }
856
+ if (key.downArrow) {
857
+ setSelectablePos((p) => Math.min(SELECTABLE_INDICES.length - 1, p + 1));
858
+ return;
859
+ }
860
+ if (key.return) {
861
+ const item = MENU_ITEMS[selectedIndex];
862
+ if (item.type === "keybind") {
863
+ setRebinding(true);
864
+ } else if (item.apply) {
865
+ setLocalConfig((c) => item.apply(c));
866
+ }
867
+ }
868
+ });
869
+ const contentHeight = termHeight - 6;
870
+ const halfView = Math.floor(contentHeight / 2);
871
+ const menuItemIndex = MENU_ITEMS.indexOf(MENU_ITEMS[selectedIndex]);
872
+ const scrollStart = Math.max(0, Math.min(menuItemIndex - halfView, MENU_ITEMS.length - contentHeight));
873
+ const visibleItems = MENU_ITEMS.slice(Math.max(0, scrollStart), Math.max(0, scrollStart) + contentHeight);
874
+ const visibleStartIndex = Math.max(0, scrollStart);
875
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: termHeight, children: /* @__PURE__ */ jsxs8(
876
+ Box8,
877
+ {
878
+ borderStyle: "round",
879
+ borderColor: colors.primary,
880
+ flexDirection: "column",
881
+ paddingX: 2,
882
+ paddingY: 1,
883
+ height: termHeight,
884
+ children: [
885
+ /* @__PURE__ */ jsxs8(Box8, { justifyContent: "space-between", marginBottom: 1, children: [
886
+ /* @__PURE__ */ jsx8(Text8, { color: colors.header, bold: true, children: "SETTINGS" }),
887
+ /* @__PURE__ */ jsx8(Text8, { color: colors.muted, children: "esc to save & close" })
888
+ ] }),
889
+ visibleItems.map((item, vi) => {
890
+ const realIndex = visibleStartIndex + vi;
891
+ const isSelected = realIndex === selectedIndex;
892
+ if (item.type === "header") {
893
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: realIndex > 0 ? 1 : 0, children: /* @__PURE__ */ jsxs8(Text8, { color: colors.accent, bold: true, children: [
894
+ " ",
895
+ item.label
896
+ ] }) }, `h-${item.label}`);
897
+ }
898
+ const value = item.getValue(localConfig);
899
+ const dots = ".".repeat(Math.max(2, maxLabelLen - item.label.length + 4));
900
+ const isRebindingThis = rebinding && isSelected;
901
+ const displayValue = isRebindingThis ? "[press key...]" : value;
902
+ const valueColor = isRebindingThis ? colors.warning : value === "ON" ? colors.secondary : value === "OFF" ? colors.error : colors.bright;
903
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
904
+ /* @__PURE__ */ jsxs8(Text8, { color: isSelected ? colors.primary : colors.text, children: [
905
+ isSelected ? "> " : " ",
906
+ " ",
907
+ item.label,
908
+ " ",
909
+ dots,
910
+ " "
911
+ ] }),
912
+ /* @__PURE__ */ jsx8(Text8, { color: valueColor, children: displayValue })
913
+ ] }, `${item.section}-${item.key}`);
914
+ })
915
+ ]
916
+ }
917
+ ) });
918
+ });
919
+
690
920
  // src/ui/hooks/useSessions.ts
691
- import { useState as useState3, useEffect as useEffect2, useCallback, useRef } from "react";
921
+ import { useState as useState4, useEffect as useEffect2, useCallback, useRef } from "react";
692
922
  var ACTIVE_POLL_MS = 1e4;
693
923
  var IDLE_POLL_MS = 3e4;
694
924
  var useSessions = (allUsers, filter) => {
695
- const [sessions, setSessions] = useState3([]);
696
- const [selectedIndex, setSelectedIndex] = useState3(0);
925
+ const [sessions, setSessions] = useState4([]);
926
+ const [selectedIndex, setSelectedIndex] = useState4(0);
697
927
  const usageOverrides = useRef(/* @__PURE__ */ new Map());
698
928
  const refresh = useCallback(() => {
699
929
  const found = discoverSessions(allUsers);
@@ -753,10 +983,10 @@ var useSessions = (allUsers, filter) => {
753
983
  };
754
984
 
755
985
  // src/ui/hooks/useActivityStream.ts
756
- import { useState as useState4, useEffect as useEffect3, useRef as useRef2 } from "react";
986
+ import { useState as useState5, useEffect as useEffect3, useRef as useRef2 } from "react";
757
987
  var MAX_EVENTS = 200;
758
988
  var useActivityStream = (session, allUsers) => {
759
- const [events, setEvents] = useState4([]);
989
+ const [events, setEvents] = useState5([]);
760
990
  const watcherRef = useRef2(null);
761
991
  useEffect3(() => {
762
992
  setEvents([]);
@@ -785,7 +1015,7 @@ var useActivityStream = (session, allUsers) => {
785
1015
  };
786
1016
 
787
1017
  // src/ui/hooks/useAlerts.ts
788
- import { useState as useState5, useEffect as useEffect4, useRef as useRef3 } from "react";
1018
+ import { useState as useState6, useEffect as useEffect4, useRef as useRef3 } from "react";
789
1019
 
790
1020
  // src/notifications.ts
791
1021
  import { exec as exec2 } from "child_process";
@@ -853,7 +1083,7 @@ var AlertLogger = class {
853
1083
  // src/ui/hooks/useAlerts.ts
854
1084
  var MAX_ALERTS = 100;
855
1085
  var useAlerts = (enabled, alertLevel, allUsers, config) => {
856
- const [alerts, setAlerts] = useState5([]);
1086
+ const [alerts, setAlerts] = useState6([]);
857
1087
  const engineRef = useRef3(new SecurityEngine(alertLevel));
858
1088
  const watcherRef = useRef3(null);
859
1089
  const loggerRef = useRef3(null);
@@ -893,10 +1123,10 @@ var useAlerts = (enabled, alertLevel, allUsers, config) => {
893
1123
  };
894
1124
 
895
1125
  // src/ui/hooks/useTextInput.ts
896
- import { useState as useState6, useCallback as useCallback2 } from "react";
897
- var useTextInput = (onConfirm) => {
898
- const [value, setValue] = useState6("");
899
- const [isActive, setIsActive] = useState6(false);
1126
+ import { useState as useState7, useCallback as useCallback2 } from "react";
1127
+ var useTextInput = (onConfirm, onCancel) => {
1128
+ const [value, setValue] = useState7("");
1129
+ const [isActive, setIsActive] = useState7(false);
900
1130
  const start = useCallback2((initial = "") => {
901
1131
  setValue(initial);
902
1132
  setIsActive(true);
@@ -904,7 +1134,8 @@ var useTextInput = (onConfirm) => {
904
1134
  const cancel = useCallback2(() => {
905
1135
  setValue("");
906
1136
  setIsActive(false);
907
- }, []);
1137
+ onCancel?.();
1138
+ }, [onCancel]);
908
1139
  const confirm = useCallback2(() => {
909
1140
  const result = value;
910
1141
  setIsActive(false);
@@ -924,7 +1155,13 @@ var useTextInput = (onConfirm) => {
924
1155
  return true;
925
1156
  }
926
1157
  if (key.backspace || key.delete) {
927
- setValue((v) => v.slice(0, -1));
1158
+ setValue((v) => {
1159
+ if (v.length === 0) {
1160
+ cancel();
1161
+ return v;
1162
+ }
1163
+ return v.slice(0, -1);
1164
+ });
928
1165
  return true;
929
1166
  }
930
1167
  if (input && input.length === 1 && input >= " ") {
@@ -939,42 +1176,56 @@ var useTextInput = (onConfirm) => {
939
1176
  };
940
1177
 
941
1178
  // src/ui/App.tsx
942
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1179
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
943
1180
  var matchKey = (binding, input, key) => {
944
1181
  if (binding === "tab") return Boolean(key.tab);
945
1182
  if (binding === "shift+tab") return Boolean(key.shift && key.tab);
946
1183
  if (binding === "enter") return Boolean(key.return);
947
1184
  return input === binding;
948
1185
  };
949
- var App = ({ options, config, version, firstRun }) => {
1186
+ var App = ({ options, config: initialConfig, version, firstRun }) => {
950
1187
  const { exit } = useApp();
951
- const { stdout } = useStdout();
1188
+ const { stdout } = useStdout2();
952
1189
  const termHeight = stdout?.rows ?? 40;
953
- const kb = config.keybindings;
954
- const [activePanel, setActivePanel] = useState7("sessions");
955
- const [activityScroll, setActivityScroll] = useState7(0);
956
- const [inputMode, setInputMode] = useState7("normal");
957
- const [showSetup, setShowSetup] = useState7(firstRun);
958
- const [filter, setFilter] = useState7("");
959
- const [updateInfo, setUpdateInfo] = useState7(null);
960
- const [updateStatus, setUpdateStatus] = useState7("");
961
- const [showDetail, setShowDetail] = useState7(false);
962
- const { sessions, selectedSession, selectedIndex, selectNext, selectPrev } = useSessions(
1190
+ const [liveConfig, setLiveConfig] = useState8(initialConfig);
1191
+ const kb = liveConfig.keybindings;
1192
+ const [activePanel, setActivePanel] = useState8("sessions");
1193
+ const [activityScroll, setActivityScroll] = useState8(0);
1194
+ const [inputMode, setInputMode] = useState8("normal");
1195
+ const [showSetup, setShowSetup] = useState8(firstRun);
1196
+ const [filter, setFilter] = useState8("");
1197
+ const [updateInfo, setUpdateInfo] = useState8(null);
1198
+ const [updateStatus, setUpdateStatus] = useState8("");
1199
+ const [showDetail, setShowDetail] = useState8(false);
1200
+ const [showSettings, setShowSettings] = useState8(false);
1201
+ const { sessions, selectedSession, selectedIndex, selectNext, selectPrev, refresh } = useSessions(
963
1202
  options.allUsers,
964
1203
  filter || void 0
965
1204
  );
966
1205
  const events = useActivityStream(selectedSession, options.allUsers);
967
- const { alerts } = useAlerts(!options.noSecurity, options.alertLevel, options.allUsers, config);
968
- const nicknameInput = useTextInput((value) => {
969
- if (selectedSession && value.trim()) setNickname(selectedSession.sessionId, value.trim());
970
- setInputMode("normal");
971
- });
972
- const filterInput = useTextInput((value) => {
973
- setFilter(value);
974
- setInputMode("normal");
975
- });
1206
+ const { alerts } = useAlerts(!options.noSecurity, options.alertLevel, options.allUsers, liveConfig);
1207
+ const nicknameInput = useTextInput(
1208
+ (value) => {
1209
+ if (selectedSession && value.trim()) {
1210
+ setNickname(selectedSession.sessionId, value.trim());
1211
+ refresh();
1212
+ }
1213
+ setInputMode("normal");
1214
+ },
1215
+ () => setInputMode("normal")
1216
+ );
1217
+ const filterInput = useTextInput(
1218
+ (value) => {
1219
+ setFilter(value);
1220
+ setInputMode("normal");
1221
+ },
1222
+ () => {
1223
+ setFilter("");
1224
+ setInputMode("normal");
1225
+ }
1226
+ );
976
1227
  useEffect5(() => {
977
- if (options.noUpdates || !config.updates.checkOnLaunch) return;
1228
+ if (options.noUpdates || !liveConfig.updates.checkOnLaunch) return;
978
1229
  try {
979
1230
  const i = checkForUpdate();
980
1231
  if (i.available) setUpdateInfo(i);
@@ -986,7 +1237,7 @@ var App = ({ options, config, version, firstRun }) => {
986
1237
  if (i.available) setUpdateInfo(i);
987
1238
  } catch {
988
1239
  }
989
- }, config.updates.checkInterval);
1240
+ }, liveConfig.updates.checkInterval);
990
1241
  return () => clearInterval(iv);
991
1242
  }, []);
992
1243
  const alertHeight = options.noSecurity ? 0 : 6;
@@ -996,9 +1247,14 @@ var App = ({ options, config, version, firstRun }) => {
996
1247
  useEffect5(() => {
997
1248
  setActivityScroll(0);
998
1249
  }, [selectedSession?.sessionId]);
1250
+ const handleSettingsClose = useCallback3((updatedConfig) => {
1251
+ setLiveConfig(updatedConfig);
1252
+ saveConfig(updatedConfig);
1253
+ setShowSettings(false);
1254
+ }, []);
999
1255
  const handleSetupComplete = useCallback3(
1000
1256
  (results) => {
1001
- const nc = { ...config };
1257
+ const nc = { ...liveConfig };
1002
1258
  const [hc, mc] = results;
1003
1259
  if (hc === "yes") {
1004
1260
  try {
@@ -1017,13 +1273,13 @@ var App = ({ options, config, version, firstRun }) => {
1017
1273
  saveConfig(nc);
1018
1274
  setShowSetup(false);
1019
1275
  },
1020
- [config]
1276
+ [liveConfig]
1021
1277
  );
1022
1278
  const switchPanel = useCallback3((_dir) => {
1023
1279
  setActivePanel((p) => p === "sessions" ? "activity" : "sessions");
1024
1280
  }, []);
1025
- useInput2((input, key) => {
1026
- if (showSetup) return;
1281
+ useInput3((input, key) => {
1282
+ if (showSetup || showSettings) return;
1027
1283
  if (inputMode === "nickname") {
1028
1284
  nicknameInput.handleInput(input, key);
1029
1285
  return;
@@ -1067,6 +1323,7 @@ var App = ({ options, config, version, firstRun }) => {
1067
1323
  }
1068
1324
  if (matchKey(kb.clearNickname, input, key) && selectedSession) {
1069
1325
  clearNickname(selectedSession.sessionId);
1326
+ refresh();
1070
1327
  return;
1071
1328
  }
1072
1329
  if (matchKey(kb.filter, input, key)) {
@@ -1078,6 +1335,10 @@ var App = ({ options, config, version, firstRun }) => {
1078
1335
  setFilter("");
1079
1336
  return;
1080
1337
  }
1338
+ if (matchKey(kb.settings, input, key)) {
1339
+ setShowSettings(true);
1340
+ return;
1341
+ }
1081
1342
  if (matchKey(kb.update, input, key) && updateInfo?.available) {
1082
1343
  setUpdateStatus("updating...");
1083
1344
  installUpdate().then(() => setUpdateStatus(`updated to v${updateInfo.latest} \u2014 restart to apply`)).catch(() => setUpdateStatus("update failed"));
@@ -1096,12 +1357,12 @@ var App = ({ options, config, version, firstRun }) => {
1096
1357
  });
1097
1358
  if (showSetup) {
1098
1359
  const steps = [];
1099
- if (config.prompts.hook === "pending")
1360
+ if (liveConfig.prompts.hook === "pending")
1100
1361
  steps.push({
1101
1362
  title: "Install Claude Code hook?",
1102
1363
  description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time."
1103
1364
  });
1104
- if (config.prompts.mcp === "pending")
1365
+ if (liveConfig.prompts.mcp === "pending")
1105
1366
  steps.push({
1106
1367
  title: "Install MCP server?",
1107
1368
  description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts."
@@ -1110,9 +1371,12 @@ var App = ({ options, config, version, firstRun }) => {
1110
1371
  setShowSetup(false);
1111
1372
  return null;
1112
1373
  }
1113
- return /* @__PURE__ */ jsx8(SetupModal, { steps, onComplete: handleSetupComplete });
1374
+ return /* @__PURE__ */ jsx9(SetupModal, { steps, onComplete: handleSetupComplete });
1375
+ }
1376
+ if (showSettings) {
1377
+ return /* @__PURE__ */ jsx9(SettingsMenu, { config: liveConfig, onClose: handleSettingsClose });
1114
1378
  }
1115
- const rightPanel = showDetail && selectedSession ? /* @__PURE__ */ jsx8(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx8(
1379
+ const rightPanel = showDetail && selectedSession ? /* @__PURE__ */ jsx9(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx9(
1116
1380
  ActivityFeed,
1117
1381
  {
1118
1382
  events,
@@ -1122,10 +1386,10 @@ var App = ({ options, config, version, firstRun }) => {
1122
1386
  scrollOffset: activityScroll
1123
1387
  }
1124
1388
  );
1125
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", height: termHeight, children: [
1126
- /* @__PURE__ */ jsx8(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
1127
- /* @__PURE__ */ jsxs8(Box8, { flexGrow: 1, height: mainHeight, children: [
1128
- /* @__PURE__ */ jsx8(
1389
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", height: termHeight, children: [
1390
+ /* @__PURE__ */ jsx9(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
1391
+ /* @__PURE__ */ jsxs9(Box9, { flexGrow: 1, height: mainHeight, children: [
1392
+ /* @__PURE__ */ jsx9(
1129
1393
  SessionList,
1130
1394
  {
1131
1395
  sessions,
@@ -1136,18 +1400,18 @@ var App = ({ options, config, version, firstRun }) => {
1136
1400
  ),
1137
1401
  rightPanel
1138
1402
  ] }),
1139
- !options.noSecurity && /* @__PURE__ */ jsx8(AlertBar, { alerts }),
1140
- inputMode === "nickname" && /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
1141
- /* @__PURE__ */ jsx8(Text8, { color: colors.primary, children: "nickname: " }),
1142
- /* @__PURE__ */ jsx8(Text8, { color: colors.bright, children: nicknameInput.value }),
1143
- /* @__PURE__ */ jsx8(Text8, { color: colors.muted, children: "_" })
1403
+ !options.noSecurity && /* @__PURE__ */ jsx9(AlertBar, { alerts }),
1404
+ inputMode === "nickname" && /* @__PURE__ */ jsxs9(Box9, { paddingX: 1, children: [
1405
+ /* @__PURE__ */ jsx9(Text9, { color: colors.primary, children: "nickname: " }),
1406
+ /* @__PURE__ */ jsx9(Text9, { color: colors.bright, children: nicknameInput.value }),
1407
+ /* @__PURE__ */ jsx9(Text9, { color: colors.muted, children: "_" })
1144
1408
  ] }),
1145
- inputMode === "filter" && /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
1146
- /* @__PURE__ */ jsx8(Text8, { color: colors.primary, children: "/" }),
1147
- /* @__PURE__ */ jsx8(Text8, { color: colors.bright, children: filterInput.value }),
1148
- /* @__PURE__ */ jsx8(Text8, { color: colors.muted, children: "_" })
1409
+ inputMode === "filter" && /* @__PURE__ */ jsxs9(Box9, { paddingX: 1, children: [
1410
+ /* @__PURE__ */ jsx9(Text9, { color: colors.primary, children: "/" }),
1411
+ /* @__PURE__ */ jsx9(Text9, { color: colors.bright, children: filterInput.value }),
1412
+ /* @__PURE__ */ jsx9(Text9, { color: colors.muted, children: "_" })
1149
1413
  ] }),
1150
- inputMode === "normal" && /* @__PURE__ */ jsx8(FooterBar, { keybindings: kb, updateStatus })
1414
+ inputMode === "normal" && /* @__PURE__ */ jsx9(FooterBar, { keybindings: kb, updateStatus })
1151
1415
  ] });
1152
1416
  };
1153
1417
 
@@ -1384,7 +1648,7 @@ var main = () => {
1384
1648
  if (options.noUpdates) config.updates.checkOnLaunch = false;
1385
1649
  if (options.noSecurity) config.security.enabled = false;
1386
1650
  if (firstRun) saveConfig(config);
1387
- render(React9.createElement(App, { options, config, version: VERSION, firstRun }));
1651
+ render(React10.createElement(App, { options, config, version: VERSION, firstRun }));
1388
1652
  };
1389
1653
  main();
1390
1654
  //# sourceMappingURL=index.js.map