agenttop 0.9.1 → 0.9.2

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
@@ -660,16 +660,7 @@ var SessionList = React2.memo(
660
660
  import React3 from "react";
661
661
  import { Box as Box3, Text as Text3 } from "ink";
662
662
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
663
- var TAG_COLORS = [
664
- "#61AFEF",
665
- "#98C379",
666
- "#C678DD",
667
- "#E5C07B",
668
- "#E06C75",
669
- "#56B6C2",
670
- "#D19A66",
671
- "#BE5046"
672
- ];
663
+ var TAG_COLORS = ["#61AFEF", "#98C379", "#C678DD", "#E5C07B", "#E06C75", "#56B6C2", "#D19A66", "#BE5046"];
673
664
  var formatTime = (ts) => {
674
665
  const d = new Date(ts);
675
666
  return d.toLocaleTimeString("en-GB", { hour12: false });
@@ -1938,7 +1929,7 @@ var SplitPanel = React14.memo(
1938
1929
  );
1939
1930
 
1940
1931
  // src/ui/hooks/useSessions.ts
1941
- import { useState as useState8, useEffect as useEffect5, useCallback as useCallback2, useRef as useRef3 } from "react";
1932
+ import { useState as useState8, useEffect as useEffect5, useCallback as useCallback2, useRef as useRef3, useMemo as useMemo2 } from "react";
1942
1933
  var ACTIVE_POLL_MS = 1e4;
1943
1934
  var IDLE_POLL_MS = 3e4;
1944
1935
  var getDisplayName = (session) => {
@@ -1953,21 +1944,32 @@ var getDisplayName = (session) => {
1953
1944
  }
1954
1945
  return session.slug;
1955
1946
  };
1947
+ var getGroupKey = (session) => {
1948
+ if (session.cwd) {
1949
+ const parts = session.cwd.replace(/\/+$/, "").split("/");
1950
+ return parts[parts.length - 1] || session.slug;
1951
+ }
1952
+ if (session.project) {
1953
+ const parts = session.project.replace(/\/+$/, "").split("/");
1954
+ return parts[parts.length - 1] || session.slug;
1955
+ }
1956
+ return session.slug;
1957
+ };
1956
1958
  var buildGroups = (sessions, expandedKeys) => {
1957
- const byName = /* @__PURE__ */ new Map();
1959
+ const byKey = /* @__PURE__ */ new Map();
1958
1960
  for (const s of sessions) {
1959
- const name = getDisplayName(s);
1960
- const list = byName.get(name);
1961
- if (list) list.push(s);
1962
- else byName.set(name, [s]);
1961
+ const key = getGroupKey(s);
1962
+ const existing = byKey.get(key);
1963
+ if (existing) {
1964
+ existing.sessions.push(s);
1965
+ } else {
1966
+ byKey.set(key, { displayName: getDisplayName(s), sessions: [s] });
1967
+ }
1963
1968
  }
1964
1969
  const groups = [];
1965
- for (const [key, list] of byName) {
1970
+ for (const [key, { displayName, sessions: list }] of byKey) {
1966
1971
  list.sort((a, b) => b.lastActivity - a.lastActivity);
1967
- const totalIn = list.reduce(
1968
- (sum, s) => sum + s.usage.inputTokens + s.usage.cacheReadTokens,
1969
- 0
1970
- );
1972
+ const totalIn = list.reduce((sum, s) => sum + s.usage.inputTokens + s.usage.cacheReadTokens, 0);
1971
1973
  const totalOut = list.reduce((sum, s) => sum + s.usage.outputTokens, 0);
1972
1974
  groups.push({
1973
1975
  key,
@@ -2045,14 +2047,16 @@ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
2045
2047
  const interval = setInterval(refresh, pollMs);
2046
2048
  return () => clearInterval(interval);
2047
2049
  }, [refresh, sessions.length > 0]);
2048
- const groups = buildGroups(sessions, expandedKeys);
2049
- const visibleItems = buildVisibleItems(groups);
2050
+ const groups = useMemo2(() => buildGroups(sessions, expandedKeys), [sessions, expandedKeys]);
2051
+ const visibleItems = useMemo2(() => buildVisibleItems(groups), [groups]);
2052
+ const itemCountRef = useRef3(visibleItems.length);
2053
+ itemCountRef.current = visibleItems.length;
2050
2054
  const selectedItem = visibleItems[selectedIndex] ?? null;
2051
2055
  const selectedSession = selectedItem?.type === "ungrouped" ? selectedItem.session : selectedItem?.type === "session" ? selectedItem.session : null;
2052
2056
  const selectedGroup = selectedItem?.type === "group" ? selectedItem.group : null;
2053
2057
  const selectNext = useCallback2(() => {
2054
- setSelectedIndex((i) => Math.min(i + 1, Math.max(0, visibleItems.length - 1)));
2055
- }, [visibleItems.length]);
2058
+ setSelectedIndex((i) => Math.min(i + 1, Math.max(0, itemCountRef.current - 1)));
2059
+ }, []);
2056
2060
  const selectPrev = useCallback2(() => {
2057
2061
  setSelectedIndex((i) => Math.max(i - 1, 0));
2058
2062
  }, []);
@@ -2137,7 +2141,7 @@ var useActivityStream = (session, allUsers) => {
2137
2141
  };
2138
2142
 
2139
2143
  // src/ui/hooks/useFilteredEvents.ts
2140
- import { useMemo as useMemo2 } from "react";
2144
+ import { useMemo as useMemo3 } from "react";
2141
2145
  var applyFilter = (events, filter) => {
2142
2146
  if (!filter) return events;
2143
2147
  const lower = filter.toLowerCase();
@@ -2145,7 +2149,7 @@ var applyFilter = (events, filter) => {
2145
2149
  (e) => e.toolName.toLowerCase().includes(lower) || JSON.stringify(e.toolInput).toLowerCase().includes(lower)
2146
2150
  );
2147
2151
  };
2148
- var useFilteredEvents = (rawEvents, filter) => useMemo2(() => applyFilter(rawEvents, filter), [rawEvents, filter]);
2152
+ var useFilteredEvents = (rawEvents, filter) => useMemo3(() => applyFilter(rawEvents, filter), [rawEvents, filter]);
2149
2153
 
2150
2154
  // src/ui/hooks/useAlerts.ts
2151
2155
  import { useState as useState10, useEffect as useEffect7, useRef as useRef5 } from "react";
@@ -2859,14 +2863,30 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
2859
2863
  const [updateStatus, setUpdateStatus] = useState15("");
2860
2864
  const [showDetail, setShowDetail] = useState15(false);
2861
2865
  const [viewingArchive, setViewingArchive] = useState15(false);
2862
- const [confirmAction, setConfirmAction] = useState15(null);
2866
+ const [confirmAction, setConfirmAction] = useState15(
2867
+ null
2868
+ );
2863
2869
  const [archivedIds, setArchivedIds] = useState15(() => new Set(Object.keys(getArchived())));
2864
2870
  const refreshArchived = useCallback6(() => setArchivedIds(new Set(Object.keys(getArchived()))), []);
2865
- const updateInfo = useUpdateChecker(options.noUpdates, setup.liveConfig.updates.checkOnLaunch, setup.liveConfig.updates.checkInterval);
2871
+ const updateInfo = useUpdateChecker(
2872
+ options.noUpdates,
2873
+ setup.liveConfig.updates.checkOnLaunch,
2874
+ setup.liveConfig.updates.checkInterval
2875
+ );
2866
2876
  useEffect9(() => {
2867
2877
  applyTheme(resolveTheme(setup.liveConfig.theme, setup.liveConfig.customThemes));
2868
2878
  }, [setup.liveConfig.theme, setup.liveConfig.customThemes]);
2869
- const { sessions, visibleItems, selectedSession, selectedGroup, selectedIndex, selectNext, selectPrev, toggleExpand, refresh } = useSessions(options.allUsers, filter || void 0, archivedIds, viewingArchive);
2879
+ const {
2880
+ sessions,
2881
+ visibleItems,
2882
+ selectedSession,
2883
+ selectedGroup,
2884
+ selectedIndex,
2885
+ selectNext,
2886
+ selectPrev,
2887
+ toggleExpand,
2888
+ refresh
2889
+ } = useSessions(options.allUsers, filter || void 0, archivedIds, viewingArchive);
2870
2890
  const activityTarget = split.splitMode ? null : selectedGroup ? selectedGroup.sessions : selectedSession;
2871
2891
  const rawEvents = useActivityStream(activityTarget, options.allUsers);
2872
2892
  const leftRawEvents = useActivityStream(split.splitMode ? split.leftSession : null, options.allUsers);
@@ -2911,7 +2931,10 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
2911
2931
  const alertHeight = options.noSecurity ? 0 : 6;
2912
2932
  const mainHeight = termHeight - 3 - alertHeight - 1 - (inputMode !== "normal" ? 1 : 0);
2913
2933
  const viewportRows = mainHeight - 2;
2914
- const switchPanel = useCallback6((dir) => split.switchPanel(dir, setActivePanel), [split.switchPanel]);
2934
+ const switchPanel = useCallback6(
2935
+ (dir) => split.switchPanel(dir, setActivePanel),
2936
+ [split.switchPanel]
2937
+ );
2915
2938
  const clearSplitState = useCallback6(() => {
2916
2939
  split.clearSplitState();
2917
2940
  setActivePanel("sessions");
@@ -3018,8 +3041,18 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
3018
3041
  });
3019
3042
  if (setup.showSetup) {
3020
3043
  const steps = [
3021
- ...setup.liveConfig.prompts.hook === "pending" ? [{ title: "Install Claude Code hook?", description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time." }] : [],
3022
- ...setup.liveConfig.prompts.mcp === "pending" ? [{ title: "Install MCP server?", description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts." }] : []
3044
+ ...setup.liveConfig.prompts.hook === "pending" ? [
3045
+ {
3046
+ title: "Install Claude Code hook?",
3047
+ description: "Adds a PostToolUse hook that blocks prompt injection attempts in real-time."
3048
+ }
3049
+ ] : [],
3050
+ ...setup.liveConfig.prompts.mcp === "pending" ? [
3051
+ {
3052
+ title: "Install MCP server?",
3053
+ description: "Registers agenttop as an MCP server so Claude Code can query session status and alerts."
3054
+ }
3055
+ ] : []
3023
3056
  ];
3024
3057
  if (steps.length === 0) {
3025
3058
  setup.setShowSetup(false);
@@ -3027,20 +3060,85 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
3027
3060
  }
3028
3061
  return /* @__PURE__ */ jsx15(SetupModal, { steps, onComplete: setup.handleSetupComplete });
3029
3062
  }
3030
- if (setup.showThemePicker) return /* @__PURE__ */ jsx15(ThemePickerModal, { onSelect: setup.handleThemePickerSelect, onSkip: setup.handleThemePickerSkip, onDismiss: setup.handleThemePickerDismiss });
3063
+ if (setup.showThemePicker)
3064
+ return /* @__PURE__ */ jsx15(
3065
+ ThemePickerModal,
3066
+ {
3067
+ onSelect: setup.handleThemePickerSelect,
3068
+ onSkip: setup.handleThemePickerSkip,
3069
+ onDismiss: setup.handleThemePickerDismiss
3070
+ }
3071
+ );
3031
3072
  if (setup.showTour) return /* @__PURE__ */ jsx15(GuidedTour, { onComplete: setup.handleTourComplete, onSkip: setup.handleTourSkip });
3032
3073
  if (setup.showThemeMenu) return /* @__PURE__ */ jsx15(ThemeMenu, { config: setup.liveConfig, onClose: setup.handleThemeMenuClose });
3033
- if (setup.showSettings) return /* @__PURE__ */ jsx15(SettingsMenu, { config: setup.liveConfig, onClose: setup.handleSettingsClose, onOpenThemeMenu: setup.handleOpenThemeMenu });
3074
+ if (setup.showSettings)
3075
+ return /* @__PURE__ */ jsx15(
3076
+ SettingsMenu,
3077
+ {
3078
+ config: setup.liveConfig,
3079
+ onClose: setup.handleSettingsClose,
3080
+ onOpenThemeMenu: setup.handleOpenThemeMenu
3081
+ }
3082
+ );
3034
3083
  if (confirmAction) {
3035
- return /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx15(ConfirmModal, { title: confirmAction.title, message: confirmAction.message, onConfirm: confirmAction.onConfirm, onCancel: () => setConfirmAction(null) }) });
3084
+ return /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", height: termHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx15(
3085
+ ConfirmModal,
3086
+ {
3087
+ title: confirmAction.title,
3088
+ message: confirmAction.message,
3089
+ onConfirm: confirmAction.onConfirm,
3090
+ onCancel: () => setConfirmAction(null)
3091
+ }
3092
+ ) });
3036
3093
  }
3037
3094
  const activitySlug = selectedGroup ? selectedGroup.key : selectedSession?.slug ?? null;
3038
3095
  const isMerged = selectedGroup !== null;
3039
- const rightPanel = split.splitMode ? /* @__PURE__ */ jsx15(SplitPanel, { activePanel, leftSession: split.leftSession, rightSession: split.rightSession, leftEvents, rightEvents, leftScroll: split.leftScroll, rightScroll: split.rightScroll, leftFilter: split.leftFilter, rightFilter: split.rightFilter, leftShowDetail: split.leftShowDetail, rightShowDetail: split.rightShowDetail, height: mainHeight }) : showDetail && selectedSession ? /* @__PURE__ */ jsx15(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx15(ActivityFeed, { events, sessionSlug: activitySlug, sessionId: selectedSession?.sessionId, isActive: selectedGroup ? selectedGroup.isActive : selectedSession ? selectedSession.pid !== null : void 0, focused: activePanel === "activity", height: mainHeight, scrollOffset: activityScroll, filter: activityFilter || void 0, merged: isMerged, mergedSessions: selectedGroup?.sessions });
3096
+ const rightPanel = split.splitMode ? /* @__PURE__ */ jsx15(
3097
+ SplitPanel,
3098
+ {
3099
+ activePanel,
3100
+ leftSession: split.leftSession,
3101
+ rightSession: split.rightSession,
3102
+ leftEvents,
3103
+ rightEvents,
3104
+ leftScroll: split.leftScroll,
3105
+ rightScroll: split.rightScroll,
3106
+ leftFilter: split.leftFilter,
3107
+ rightFilter: split.rightFilter,
3108
+ leftShowDetail: split.leftShowDetail,
3109
+ rightShowDetail: split.rightShowDetail,
3110
+ height: mainHeight
3111
+ }
3112
+ ) : showDetail && selectedSession ? /* @__PURE__ */ jsx15(SessionDetail, { session: selectedSession, focused: activePanel === "activity", height: mainHeight }) : /* @__PURE__ */ jsx15(
3113
+ ActivityFeed,
3114
+ {
3115
+ events,
3116
+ sessionSlug: activitySlug,
3117
+ sessionId: selectedSession?.sessionId,
3118
+ isActive: selectedGroup ? selectedGroup.isActive : selectedSession ? selectedSession.pid !== null : void 0,
3119
+ focused: activePanel === "activity",
3120
+ height: mainHeight,
3121
+ scrollOffset: activityScroll,
3122
+ filter: activityFilter || void 0,
3123
+ merged: isMerged,
3124
+ mergedSessions: selectedGroup?.sessions
3125
+ }
3126
+ );
3040
3127
  return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: termHeight, children: [
3041
3128
  /* @__PURE__ */ jsx15(StatusBar, { sessionCount: sessions.length, alertCount: alerts.length, version, updateInfo }),
3042
3129
  /* @__PURE__ */ jsxs15(Box15, { flexGrow: 1, height: mainHeight, children: [
3043
- /* @__PURE__ */ jsx15(SessionList, { visibleItems, selectedIndex, focused: activePanel === "sessions", height: mainHeight, filter: filter || void 0, viewingArchive, totalSessions: sessions.length }),
3130
+ /* @__PURE__ */ jsx15(
3131
+ SessionList,
3132
+ {
3133
+ visibleItems,
3134
+ selectedIndex,
3135
+ focused: activePanel === "sessions",
3136
+ height: mainHeight,
3137
+ filter: filter || void 0,
3138
+ viewingArchive,
3139
+ totalSessions: sessions.length
3140
+ }
3141
+ ),
3044
3142
  rightPanel
3045
3143
  ] }),
3046
3144
  !options.noSecurity && /* @__PURE__ */ jsx15(AlertBar, { alerts }),
@@ -3055,7 +3153,15 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
3055
3153
  /* @__PURE__ */ jsx15(Text14, { color: colors.bright, children: filterInput.value }),
3056
3154
  /* @__PURE__ */ jsx15(Text14, { color: colors.muted, children: "_" })
3057
3155
  ] }),
3058
- inputMode === "normal" && /* @__PURE__ */ jsx15(FooterBar, { keybindings: kb, updateStatus, viewingArchive, splitMode: split.splitMode })
3156
+ inputMode === "normal" && /* @__PURE__ */ jsx15(
3157
+ FooterBar,
3158
+ {
3159
+ keybindings: kb,
3160
+ updateStatus,
3161
+ viewingArchive,
3162
+ splitMode: split.splitMode
3163
+ }
3164
+ )
3059
3165
  ] });
3060
3166
  };
3061
3167