agenttop 0.9.0 → 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/{chunk-2GBTSKHW.js → chunk-6D2UXDV4.js} +29 -14
- package/dist/chunk-6D2UXDV4.js.map +1 -0
- package/dist/index.js +146 -40
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2GBTSKHW.js.map +0 -1
|
@@ -204,39 +204,54 @@ var resolvePath = (p) => {
|
|
|
204
204
|
}
|
|
205
205
|
};
|
|
206
206
|
var getUid = () => process.getuid?.() ?? 0;
|
|
207
|
+
var isRoot = () => getUid() === 0;
|
|
207
208
|
var getTmpDir = () => resolvePath(platform() === "darwin" ? "/private/tmp" : "/tmp");
|
|
208
209
|
var getTaskDirs = (allUsers) => {
|
|
209
210
|
const tmp = getTmpDir();
|
|
210
211
|
const uid = getUid();
|
|
211
212
|
if (allUsers) {
|
|
212
213
|
try {
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
214
|
+
const dirs2 = readdirSync(tmp).filter((d) => d.startsWith("claude-")).filter((d) => !d.endsWith("-cwd")).map((d) => join2(tmp, d));
|
|
215
|
+
if (dirs2.length > 0) return dirs2;
|
|
215
216
|
} catch {
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
|
-
|
|
219
|
+
const dirs = [join2(tmp, `claude-${uid}`)];
|
|
220
|
+
if (isRoot()) {
|
|
221
|
+
try {
|
|
222
|
+
const sudoUid = process.env["SUDO_UID"];
|
|
223
|
+
if (sudoUid && sudoUid !== String(uid)) {
|
|
224
|
+
dirs.push(join2(tmp, `claude-${sudoUid}`));
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return dirs;
|
|
219
230
|
};
|
|
220
231
|
var getProjectsDirs = (allUsers) => {
|
|
221
232
|
const dirs = [];
|
|
233
|
+
const addDir = (d) => {
|
|
234
|
+
if (!dirs.includes(d) && existsSync2(d)) dirs.push(d);
|
|
235
|
+
};
|
|
222
236
|
const home = homedir2();
|
|
223
|
-
|
|
224
|
-
if (
|
|
237
|
+
addDir(join2(home, ".claude", "projects"));
|
|
238
|
+
if (isRoot()) {
|
|
239
|
+
addDir(join2("/root", ".claude", "projects"));
|
|
240
|
+
const sudoUser = process.env["SUDO_USER"];
|
|
241
|
+
if (sudoUser) {
|
|
242
|
+
const homeBase = platform() === "darwin" ? "/Users" : "/home";
|
|
243
|
+
addDir(join2(homeBase, sudoUser, ".claude", "projects"));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
225
246
|
if (allUsers) {
|
|
226
247
|
try {
|
|
227
248
|
const homeBase = platform() === "darwin" ? "/Users" : "/home";
|
|
228
249
|
for (const user of readdirSync(homeBase)) {
|
|
229
|
-
|
|
230
|
-
if (userProjects !== primary && existsSync2(userProjects)) {
|
|
231
|
-
dirs.push(userProjects);
|
|
232
|
-
}
|
|
250
|
+
addDir(join2(homeBase, user, ".claude", "projects"));
|
|
233
251
|
}
|
|
234
252
|
} catch {
|
|
235
253
|
}
|
|
236
|
-
|
|
237
|
-
if (!dirs.includes(rootProjects) && existsSync2(rootProjects)) {
|
|
238
|
-
dirs.push(rootProjects);
|
|
239
|
-
}
|
|
254
|
+
addDir(join2("/root", ".claude", "projects"));
|
|
240
255
|
}
|
|
241
256
|
return dirs;
|
|
242
257
|
};
|
|
@@ -1206,4 +1221,4 @@ export {
|
|
|
1206
1221
|
SecurityEngine,
|
|
1207
1222
|
startMcpServer
|
|
1208
1223
|
};
|
|
1209
|
-
//# sourceMappingURL=chunk-
|
|
1224
|
+
//# sourceMappingURL=chunk-6D2UXDV4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/store.ts","../src/mcp/server.ts","../src/discovery/sessions.ts","../src/config.ts","../src/discovery/types.ts","../src/analysis/rules/network.ts","../src/analysis/rules/exfiltration.ts","../src/analysis/rules/sensitive-files.ts","../src/analysis/rules/shell-escape.ts","../src/analysis/rules/injection.ts","../src/analysis/security.ts","../src/ingestion/watcher.ts","../src/ingestion/tail.ts","../src/ingestion/parser.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, renameSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\nimport type { ThemeColors, ToolColors } from './themes.js';\n\nexport interface SecurityRulesConfig {\n network: boolean;\n exfiltration: boolean;\n sensitiveFiles: boolean;\n shellEscape: boolean;\n injection: boolean;\n}\n\nexport interface NotificationsConfig {\n bell: boolean;\n desktop: boolean;\n minSeverity: 'info' | 'warn' | 'high' | 'critical';\n}\n\nexport interface AlertsConfig {\n logFile: string;\n enabled: boolean;\n}\n\nexport interface UpdatesConfig {\n checkOnLaunch: boolean;\n checkInterval: number;\n}\n\nexport interface PromptsConfig {\n hook: 'pending' | 'installed' | 'dismissed';\n mcp: 'pending' | 'installed' | 'dismissed';\n theme: 'pending' | 'done' | 'dismissed';\n tour: 'pending' | 'done' | 'dismissed';\n}\n\nexport interface KeybindingsConfig {\n quit: string;\n navUp: string;\n navDown: string;\n panelNext: string;\n panelPrev: string;\n scrollTop: string;\n scrollBottom: string;\n filter: string;\n nickname: string;\n clearNickname: string;\n detail: string;\n update: string;\n settings: string;\n archive: string;\n delete: string;\n viewArchive: string;\n split: string;\n pinLeft: string;\n pinRight: string;\n swapPanels: string;\n closePanel: string;\n}\n\nexport interface Config {\n pollInterval: number;\n maxEvents: number;\n maxAlerts: number;\n alertLevel: 'info' | 'warn' | 'high' | 'critical';\n notifications: NotificationsConfig;\n alerts: AlertsConfig;\n updates: UpdatesConfig;\n nicknames: Record<string, string>;\n keybindings: KeybindingsConfig;\n security: {\n enabled: boolean;\n rules: SecurityRulesConfig;\n };\n prompts: PromptsConfig;\n archived: Record<string, number>;\n archiveExpiryDays: number;\n theme: string;\n customThemes: Record<string, { name: string; colors: ThemeColors; toolColors: ToolColors }>;\n}\n\nexport const getConfigDir = (): string => {\n const xdg = process.env.XDG_CONFIG_HOME;\n return xdg ? join(xdg, 'agenttop') : join(homedir(), '.config', 'agenttop');\n};\n\nexport const getConfigPath = (): string => join(getConfigDir(), 'config.json');\n\nconst defaultConfig = (): Config => ({\n pollInterval: 10000,\n maxEvents: 200,\n maxAlerts: 100,\n alertLevel: 'warn',\n notifications: {\n bell: true,\n desktop: false,\n minSeverity: 'high',\n },\n alerts: {\n logFile: join(getConfigDir(), 'alerts.jsonl'),\n enabled: true,\n },\n updates: {\n checkOnLaunch: true,\n checkInterval: 21600000,\n },\n nicknames: {},\n keybindings: {\n quit: 'q',\n navUp: 'k',\n navDown: 'j',\n panelNext: 'tab',\n panelPrev: 'shift+tab',\n scrollTop: 'g',\n scrollBottom: 'G',\n filter: '/',\n nickname: 'n',\n clearNickname: 'N',\n detail: 'enter',\n update: 'u',\n settings: 's',\n archive: 'a',\n delete: 'd',\n viewArchive: 'A',\n split: 'x',\n pinLeft: '1',\n pinRight: '2',\n swapPanels: 'S',\n closePanel: 'X',\n },\n security: {\n enabled: true,\n rules: {\n network: true,\n exfiltration: true,\n sensitiveFiles: true,\n shellEscape: true,\n injection: true,\n },\n },\n prompts: {\n hook: 'pending',\n mcp: 'pending',\n theme: 'pending',\n tour: 'pending',\n },\n archived: {},\n archiveExpiryDays: 0,\n theme: 'one-dark',\n customThemes: {},\n});\n\nconst deepMerge = (target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> => {\n const result = { ...target };\n for (const key of Object.keys(target)) {\n if (key in source) {\n const tVal = target[key];\n const sVal = source[key];\n if (tVal && sVal && typeof tVal === 'object' && typeof sVal === 'object' && !Array.isArray(tVal)) {\n result[key] = deepMerge(tVal as Record<string, unknown>, sVal as Record<string, unknown>);\n } else {\n result[key] = sVal;\n }\n }\n }\n for (const key of Object.keys(source)) {\n if (!(key in target)) {\n result[key] = source[key];\n }\n }\n return result;\n};\n\nexport const loadConfig = (): Config => {\n const configPath = getConfigPath();\n const defaults = defaultConfig();\n\n if (!existsSync(configPath)) {\n return defaults;\n }\n\n try {\n const raw = JSON.parse(readFileSync(configPath, 'utf-8'));\n return deepMerge(defaults as unknown as Record<string, unknown>, raw) as unknown as Config;\n } catch {\n return defaults;\n }\n};\n\nexport const saveConfig = (config: Config): void => {\n const configDir = getConfigDir();\n mkdirSync(configDir, { recursive: true });\n writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\\n');\n};\n\nexport const isFirstRun = (): boolean => !existsSync(getConfigPath());\n\nexport const setNickname = (sessionId: string, nickname: string): void => {\n const config = loadConfig();\n config.nicknames[sessionId] = nickname;\n saveConfig(config);\n};\n\nexport const clearNickname = (sessionId: string): void => {\n const config = loadConfig();\n delete config.nicknames[sessionId];\n saveConfig(config);\n};\n\nexport const getNicknames = (): Record<string, string> => {\n return loadConfig().nicknames;\n};\n\nexport const resolveAlertLogPath = (config: Config): string => {\n const logFile = config.alerts.logFile;\n if (logFile.startsWith('~')) {\n return join(homedir(), logFile.slice(1));\n }\n return logFile;\n};\n\nexport const rotateLogFile = (filePath: string, maxBytes = 10 * 1024 * 1024): void => {\n try {\n const stat = statSync(filePath);\n if (stat.size > maxBytes) {\n const rotated = filePath + '.1';\n if (existsSync(rotated)) {\n try {\n renameSync(rotated, filePath + '.2');\n } catch {\n /* ignore */\n }\n }\n renameSync(filePath, rotated);\n }\n } catch {\n // file doesn't exist yet\n }\n};\n\nexport const archiveSession = (sessionId: string): void => {\n const config = loadConfig();\n config.archived[sessionId] = Date.now();\n saveConfig(config);\n};\n\nexport const unarchiveSession = (sessionId: string): void => {\n const config = loadConfig();\n delete config.archived[sessionId];\n saveConfig(config);\n};\n\nexport const getArchived = (): Record<string, number> => {\n return loadConfig().archived;\n};\n\nexport const purgeExpiredArchives = (): void => {\n const config = loadConfig();\n if (config.archiveExpiryDays <= 0) return;\n const cutoff = Date.now() - config.archiveExpiryDays * 86_400_000;\n let changed = false;\n for (const [id, ts] of Object.entries(config.archived)) {\n if (ts < cutoff) {\n delete config.archived[id];\n changed = true;\n }\n }\n if (changed) saveConfig(config);\n};\n\nexport const deleteSessionFiles = (outputFiles: string[]): void => {\n for (const file of outputFiles) {\n try {\n unlinkSync(file);\n } catch {\n /* file may already be gone */\n }\n }\n};\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\n\nimport { discoverSessions } from '../discovery/sessions.js';\nimport type { Alert, AlertSeverity, SecurityEvent, ToolCall } from '../discovery/types.js';\nimport { SecurityEngine } from '../analysis/security.js';\nimport { Watcher } from '../ingestion/watcher.js';\n\nconst MAX_ALERTS = 100;\nconst MAX_ACTIVITY = 200;\n\nexport const startMcpServer = async (allUsers: boolean, noSecurity: boolean): Promise<void> => {\n const alerts: Alert[] = [];\n const activity = new Map<string, ToolCall[]>();\n const engine = noSecurity ? null : new SecurityEngine('info');\n\n const toolHandler = (calls: ToolCall[]) => {\n for (const call of calls) {\n const existing = activity.get(call.sessionId) ?? [];\n existing.push(call);\n if (existing.length > MAX_ACTIVITY) existing.splice(0, existing.length - MAX_ACTIVITY);\n activity.set(call.sessionId, existing);\n }\n };\n\n const securityHandler = engine\n ? (events: SecurityEvent[]) => {\n for (const event of events) {\n const newAlerts = engine.analyzeEvent(event);\n alerts.push(...newAlerts);\n if (alerts.length > MAX_ALERTS) alerts.splice(0, alerts.length - MAX_ALERTS);\n }\n }\n : undefined;\n\n const watcher = new Watcher(toolHandler, allUsers, securityHandler);\n watcher.start();\n\n const server = new Server(\n { name: 'agenttop', version: '0.3.0' },\n {\n capabilities: { tools: {} },\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: 'agenttop_sessions',\n description: 'List active Claude Code sessions with model, CPU, MEM, tokens, nickname',\n inputSchema: { type: 'object' as const, properties: {} },\n },\n {\n name: 'agenttop_alerts',\n description: 'Get recent security alerts, optionally filtered by severity',\n inputSchema: {\n type: 'object' as const,\n properties: {\n severity: {\n type: 'string',\n enum: ['info', 'warn', 'high', 'critical'],\n description: 'Minimum severity filter',\n },\n limit: { type: 'number', description: 'Max alerts to return (default 20)' },\n },\n },\n },\n {\n name: 'agenttop_usage',\n description: 'Get token usage for a session or all sessions',\n inputSchema: {\n type: 'object' as const,\n properties: {\n sessionId: { type: 'string', description: 'Session ID (omit for all)' },\n },\n },\n },\n {\n name: 'agenttop_activity',\n description: 'Get recent tool calls for a session',\n inputSchema: {\n type: 'object' as const,\n properties: {\n sessionId: { type: 'string', description: 'Session ID' },\n limit: { type: 'number', description: 'Max events to return (default 20)' },\n },\n required: ['sessionId'],\n },\n },\n ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n switch (name) {\n case 'agenttop_sessions': {\n const sessions = discoverSessions(allUsers);\n const data = sessions.map((s) => ({\n sessionId: s.sessionId,\n slug: s.slug,\n model: s.model,\n cwd: s.cwd,\n cpu: s.cpu,\n memMB: s.memMB,\n agents: s.agentCount,\n tokens: { input: s.usage.inputTokens, output: s.usage.outputTokens, cacheRead: s.usage.cacheReadTokens },\n }));\n return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };\n }\n\n case 'agenttop_alerts': {\n const severity = (args?.severity as AlertSeverity) ?? 'info';\n const limit = (args?.limit as number) ?? 20;\n const order: Record<string, number> = { info: 0, warn: 1, high: 2, critical: 3 };\n const minOrder = order[severity] ?? 0;\n const filtered = alerts\n .filter((a) => (order[a.severity] ?? 0) >= minOrder)\n .slice(-limit)\n .map((a) => ({\n severity: a.severity,\n rule: a.rule,\n message: a.message,\n sessionSlug: a.sessionSlug,\n timestamp: new Date(a.timestamp).toISOString(),\n }));\n return { content: [{ type: 'text', text: JSON.stringify(filtered, null, 2) }] };\n }\n\n case 'agenttop_usage': {\n const sessions = discoverSessions(allUsers);\n const sessionId = args?.sessionId as string | undefined;\n const targets = sessionId ? sessions.filter((s) => s.sessionId === sessionId) : sessions;\n const data = targets.map((s) => ({\n sessionId: s.sessionId,\n slug: s.slug,\n usage: s.usage,\n }));\n return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };\n }\n\n case 'agenttop_activity': {\n const sid = args?.sessionId as string;\n const limit = (args?.limit as number) ?? 20;\n const events = (activity.get(sid) ?? []).slice(-limit).map((e) => ({\n timestamp: new Date(e.timestamp).toISOString(),\n tool: e.toolName,\n input: e.toolInput,\n }));\n return { content: [{ type: 'text', text: JSON.stringify(events, null, 2) }] };\n }\n\n default:\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.on('SIGINT', () => {\n watcher.stop();\n process.exit(0);\n });\n process.on('SIGTERM', () => {\n watcher.stop();\n process.exit(0);\n });\n};\n","import { readdirSync, statSync, readlinkSync, openSync, readSync, closeSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { execSync } from 'node:child_process';\n\nimport { getTaskDirs, getProjectsDirs } from '../config.js';\nimport type { Session, ProcessInfo, TokenUsage } from './types.js';\n\nexport const getClaudeProcesses = (): ProcessInfo[] => {\n try {\n const output = execSync('ps aux', { encoding: 'utf-8', timeout: 5000 });\n const procs = output\n .split('\\n')\n .filter((line) => line.includes('/claude') && !line.includes('grep') && !line.includes('agenttop'))\n .map((line) => {\n const parts = line.trim().split(/\\s+/);\n const pid = parseInt(parts[1], 10);\n let cwd = '';\n try {\n cwd = readlinkSync(`/proc/${pid}/cwd`);\n } catch {\n // no access or process gone\n }\n return {\n pid,\n cpu: parseFloat(parts[2]) || 0,\n mem: parseFloat(parts[3]) || 0,\n memKB: parseInt(parts[5], 10) || 0,\n startTime: parts[8] || '',\n command: parts.slice(10).join(' '),\n cwd,\n };\n })\n .filter((p) => !isNaN(p.pid))\n .filter((p) => !p.command.startsWith('sudo'));\n return procs;\n } catch {\n return [];\n }\n};\n\nconst readFirstLines = (filePath: string, bytes: number): string[] => {\n try {\n const fd = openSync(filePath, 'r');\n const buf = Buffer.alloc(bytes);\n const bytesRead = readSync(fd, buf, 0, bytes, 0);\n closeSync(fd);\n return buf.subarray(0, bytesRead).toString('utf-8').split('\\n').filter(Boolean);\n } catch {\n return [];\n }\n};\n\nconst readFirstEvent = (filePath: string): Record<string, unknown> | null => {\n const lines = readFirstLines(filePath, 16384);\n if (lines.length === 0) return null;\n try {\n return JSON.parse(lines[0]) as Record<string, unknown>;\n } catch {\n return null;\n }\n};\n\nconst findModelAndUsage = (filePath: string): { model: string; usage: TokenUsage } => {\n const usage: TokenUsage = { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };\n let model = '';\n\n const lines = readFirstLines(filePath, 65536);\n for (const line of lines) {\n try {\n const evt = JSON.parse(line);\n if (evt.type === 'assistant') {\n if (!model && evt.message?.model) model = String(evt.message.model);\n const u = evt.message?.usage;\n if (u) {\n usage.inputTokens += u.input_tokens ?? 0;\n usage.cacheCreationTokens += u.cache_creation_input_tokens ?? 0;\n usage.cacheReadTokens += u.cache_read_input_tokens ?? 0;\n usage.outputTokens += u.output_tokens ?? 0;\n }\n }\n } catch {\n continue;\n }\n }\n\n return { model, usage };\n};\n\nconst normalisePath = (p: string): string => p.replace(/\\/+$/, '');\n\nconst extractSessionMeta = (\n filePath: string,\n): { sessionId: string; cwd: string; version: string; gitBranch: string; model: string; usage: TokenUsage } | null => {\n const lines = readFirstLines(filePath, 65536);\n let sessionId = '';\n let cwd = '';\n let version = '';\n let gitBranch = '';\n let model = '';\n const usage: TokenUsage = { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };\n\n for (const line of lines) {\n try {\n const evt = JSON.parse(line);\n if (!sessionId && evt.sessionId) sessionId = String(evt.sessionId);\n if (!cwd && evt.cwd) cwd = String(evt.cwd);\n if (!version && evt.version) version = String(evt.version);\n if (!gitBranch && evt.gitBranch) gitBranch = String(evt.gitBranch);\n if (evt.type === 'assistant') {\n if (!model && evt.message?.model) model = String(evt.message.model);\n const u = evt.message?.usage;\n if (u) {\n usage.inputTokens += u.input_tokens ?? 0;\n usage.cacheCreationTokens += u.cache_creation_input_tokens ?? 0;\n usage.cacheReadTokens += u.cache_read_input_tokens ?? 0;\n usage.outputTokens += u.output_tokens ?? 0;\n }\n }\n } catch {\n continue;\n }\n }\n\n if (!sessionId) return null;\n return { sessionId, cwd, version, gitBranch, model, usage };\n};\n\nconst discoverFromProjects = (allUsers: boolean, processes: ProcessInfo[], sessionMap: Map<string, Session>): void => {\n const projectsDirs = getProjectsDirs(allUsers);\n\n for (const projectsDir of projectsDirs) {\n let projectNames: string[];\n try {\n projectNames = readdirSync(projectsDir);\n } catch {\n continue;\n }\n\n for (const projectName of projectNames) {\n const projectPath = join(projectsDir, projectName);\n try {\n if (!statSync(projectPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n let files: string[];\n try {\n files = readdirSync(projectPath).filter((f) => f.endsWith('.jsonl'));\n } catch {\n continue;\n }\n\n for (const file of files) {\n const filePath = join(projectPath, file);\n let fstat;\n try {\n fstat = statSync(filePath);\n if (!fstat.isFile() || fstat.size === 0) continue;\n } catch {\n continue;\n }\n\n const meta = extractSessionMeta(filePath);\n if (!meta) continue;\n if (sessionMap.has(meta.sessionId)) continue;\n\n const normCwd = normalisePath(meta.cwd);\n const matchingProcess = processes.find((p) => p.cwd && normalisePath(p.cwd) === normCwd);\n\n const session: Session = {\n sessionId: meta.sessionId,\n slug: meta.sessionId.slice(0, 12),\n project: projectName.replace(/-/g, '/'),\n cwd: meta.cwd,\n model: meta.model || 'unknown',\n version: meta.version,\n gitBranch: meta.gitBranch,\n pid: matchingProcess?.pid ?? null,\n cpu: matchingProcess?.cpu ?? 0,\n mem: matchingProcess?.mem ?? 0,\n memMB: matchingProcess ? Math.round(matchingProcess.memKB / 1024) : 0,\n agentCount: 1,\n agentIds: [basename(file, '.jsonl')],\n outputFiles: [filePath],\n startTime: fstat.birthtimeMs || fstat.ctimeMs,\n lastActivity: fstat.mtimeMs,\n usage: meta.usage,\n };\n\n sessionMap.set(meta.sessionId, session);\n }\n }\n }\n};\n\nconst discoverFromTmp = (allUsers: boolean, processes: ProcessInfo[], sessionMap: Map<string, Session>): void => {\n const taskDirs = getTaskDirs(allUsers);\n\n for (const taskDir of taskDirs) {\n let projectDirs: string[];\n try {\n projectDirs = readdirSync(taskDir);\n } catch {\n continue;\n }\n\n for (const projectName of projectDirs) {\n const projectPath = join(taskDir, projectName);\n try {\n if (!statSync(projectPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n // both old (project/tasks/) and new (project/session-id/tasks/) layouts\n const tasksPaths: string[] = [];\n const tasksDir = join(projectPath, 'tasks');\n try {\n readdirSync(tasksDir);\n tasksPaths.push(tasksDir);\n } catch {\n // try nested session-id directories\n try {\n for (const sub of readdirSync(projectPath)) {\n const subTasks = join(projectPath, sub, 'tasks');\n try {\n readdirSync(subTasks);\n tasksPaths.push(subTasks);\n } catch {\n continue;\n }\n }\n } catch {\n continue;\n }\n }\n\n for (const tDir of tasksPaths) {\n let outputFiles: string[];\n try {\n outputFiles = readdirSync(tDir)\n .filter((f) => f.endsWith('.output'))\n .map((f) => join(tDir, f));\n } catch {\n continue;\n }\n\n if (outputFiles.length === 0) continue;\n\n const agentIds: string[] = [];\n let sessionId = '';\n let slug = '';\n let cwd = '';\n let model = '';\n let version = '';\n let gitBranch = '';\n let startTime = Infinity;\n let lastActivity = 0;\n const totalUsage: TokenUsage = {\n inputTokens: 0,\n cacheCreationTokens: 0,\n cacheReadTokens: 0,\n outputTokens: 0,\n };\n\n for (const outputFile of outputFiles) {\n const agentId = basename(outputFile, '.output');\n agentIds.push(agentId);\n\n const firstEvent = readFirstEvent(outputFile);\n if (firstEvent) {\n if (!sessionId) sessionId = String(firstEvent.sessionId || '');\n if (!slug) slug = String(firstEvent.slug || '');\n if (!cwd) cwd = String(firstEvent.cwd || '');\n if (!version) version = String(firstEvent.version || '');\n if (!gitBranch) gitBranch = String(firstEvent.gitBranch || '');\n }\n\n try {\n const fstat = statSync(outputFile);\n const created = fstat.birthtimeMs || fstat.ctimeMs;\n if (created < startTime) startTime = created;\n if (fstat.mtimeMs > lastActivity) lastActivity = fstat.mtimeMs;\n } catch {\n // ignore\n }\n\n const result = findModelAndUsage(outputFile);\n if (!model && result.model) model = result.model;\n totalUsage.inputTokens += result.usage.inputTokens;\n totalUsage.cacheCreationTokens += result.usage.cacheCreationTokens;\n totalUsage.cacheReadTokens += result.usage.cacheReadTokens;\n totalUsage.outputTokens += result.usage.outputTokens;\n }\n\n if (!sessionId && !slug) continue;\n if (sessionMap.has(sessionId || projectName)) continue;\n\n const normCwd = normalisePath(cwd);\n const matchingProcess = processes.find((p) => p.cwd && normalisePath(p.cwd) === normCwd);\n\n const session: Session = {\n sessionId,\n slug: slug || sessionId.slice(0, 12),\n project: projectName.replace(/-/g, '/'),\n cwd,\n model: model || 'unknown',\n version,\n gitBranch,\n pid: matchingProcess?.pid ?? null,\n cpu: matchingProcess?.cpu ?? 0,\n mem: matchingProcess?.mem ?? 0,\n memMB: matchingProcess ? Math.round(matchingProcess.memKB / 1024) : 0,\n agentCount: agentIds.length,\n agentIds,\n outputFiles,\n startTime: startTime === Infinity ? Date.now() : startTime,\n lastActivity,\n usage: totalUsage,\n };\n\n sessionMap.set(sessionId || projectName, session);\n }\n }\n }\n};\n\nexport const discoverSessions = (allUsers: boolean): Session[] => {\n const processes = getClaudeProcesses();\n const sessionMap = new Map<string, Session>();\n\n discoverFromProjects(allUsers, processes, sessionMap);\n discoverFromTmp(allUsers, processes, sessionMap);\n\n return Array.from(sessionMap.values()).sort((a, b) => {\n const aActive = a.pid !== null ? 1 : 0;\n const bActive = b.pid !== null ? 1 : 0;\n if (aActive !== bActive) return bActive - aActive;\n return b.lastActivity - a.lastActivity;\n });\n};\n","import { existsSync, realpathSync, readdirSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\n\nconst resolvePath = (p: string): string => {\n try {\n return realpathSync(p);\n } catch {\n return p;\n }\n};\n\nexport const getUid = (): number => process.getuid?.() ?? 0;\n\nexport const isRoot = (): boolean => getUid() === 0;\n\nexport const getTmpDir = (): string => resolvePath(platform() === 'darwin' ? '/private/tmp' : '/tmp');\n\nexport const getClaudeHome = (): string => join(homedir(), '.claude');\n\nexport const getHistoryPath = (): string => join(getClaudeHome(), 'history.jsonl');\n\nexport const getTaskDirs = (allUsers: boolean): string[] => {\n const tmp = getTmpDir();\n const uid = getUid();\n\n if (allUsers) {\n try {\n const dirs = readdirSync(tmp)\n .filter((d: string) => d.startsWith('claude-'))\n .filter((d: string) => !d.endsWith('-cwd'))\n .map((d: string) => join(tmp, d));\n if (dirs.length > 0) return dirs;\n } catch {\n // fall through to default\n }\n }\n\n const dirs = [join(tmp, `claude-${uid}`)];\n\n // when running as root via sudo, also include the original user's task dir\n if (isRoot()) {\n try {\n const sudoUid = process.env['SUDO_UID'];\n if (sudoUid && sudoUid !== String(uid)) {\n dirs.push(join(tmp, `claude-${sudoUid}`));\n }\n } catch {\n // ignore\n }\n }\n\n return dirs;\n};\n\nexport const getProjectsDirs = (allUsers: boolean): string[] => {\n const dirs: string[] = [];\n const addDir = (d: string) => {\n if (!dirs.includes(d) && existsSync(d)) dirs.push(d);\n };\n\n const home = homedir();\n addDir(join(home, '.claude', 'projects'));\n\n // when running as root (e.g. sudo agenttop), always include /root and the\n // original user's home so both root-owned and user-owned sessions appear\n if (isRoot()) {\n addDir(join('/root', '.claude', 'projects'));\n const sudoUser = process.env['SUDO_USER'];\n if (sudoUser) {\n const homeBase = platform() === 'darwin' ? '/Users' : '/home';\n addDir(join(homeBase, sudoUser, '.claude', 'projects'));\n }\n }\n\n if (allUsers) {\n try {\n const homeBase = platform() === 'darwin' ? '/Users' : '/home';\n for (const user of readdirSync(homeBase)) {\n addDir(join(homeBase, user, '.claude', 'projects'));\n }\n } catch {\n // can't read /home — skip\n }\n addDir(join('/root', '.claude', 'projects'));\n }\n\n return dirs;\n};\n\nexport const getPlatform = (): 'linux' | 'darwin' | 'win32' => {\n const p = platform();\n if (p === 'darwin') return 'darwin';\n if (p === 'win32') return 'win32';\n return 'linux';\n};\n\nexport { getConfigDir } from './config/store.js';\n","export interface TokenUsage {\n inputTokens: number;\n cacheCreationTokens: number;\n cacheReadTokens: number;\n outputTokens: number;\n}\n\nexport interface Session {\n sessionId: string;\n slug: string;\n project: string;\n cwd: string;\n model: string;\n version: string;\n gitBranch: string;\n pid: number | null;\n cpu: number;\n mem: number;\n memMB: number;\n agentCount: number;\n agentIds: string[];\n outputFiles: string[];\n startTime: number;\n lastActivity: number;\n usage: TokenUsage;\n nickname?: string;\n}\n\nexport interface ToolCall {\n sessionId: string;\n agentId: string;\n slug: string;\n timestamp: number;\n toolName: string;\n toolInput: Record<string, unknown>;\n cwd: string;\n}\n\nexport interface ToolResult {\n sessionId: string;\n agentId: string;\n slug: string;\n timestamp: number;\n toolUseId: string;\n content: string;\n isError: boolean;\n cwd: string;\n}\n\nexport type SecurityEvent = ToolCall | ToolResult;\n\nexport const isToolResult = (event: SecurityEvent): event is ToolResult => 'toolUseId' in event;\n\nexport const isToolCall = (event: SecurityEvent): event is ToolCall => 'toolName' in event;\n\nexport interface RawEvent {\n type: 'user' | 'assistant' | 'tool_result';\n sessionId: string;\n agentId: string;\n slug: string;\n timestamp?: string;\n cwd: string;\n version: string;\n gitBranch: string;\n message: {\n role: string;\n content: unknown;\n model?: string;\n usage?: {\n input_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n output_tokens?: number;\n };\n };\n}\n\nexport type AlertSeverity = 'info' | 'warn' | 'high' | 'critical';\n\nexport interface Alert {\n id: string;\n severity: AlertSeverity;\n rule: string;\n message: string;\n sessionSlug: string;\n sessionId: string;\n event: SecurityEvent;\n timestamp: number;\n}\n\nexport interface SessionGroup {\n key: string;\n sessions: Session[];\n expanded: boolean;\n // aggregated fields for group header\n totalInputTokens: number;\n totalOutputTokens: number;\n latestModel: string;\n isActive: boolean;\n latestActivity: number;\n earliestStart: number;\n}\n\nexport type VisibleItem =\n | { type: 'group'; group: SessionGroup }\n | { type: 'session'; session: Session; groupKey: string }\n | { type: 'ungrouped'; session: Session };\n\nexport interface ProcessInfo {\n pid: number;\n cpu: number;\n mem: number;\n memKB: number;\n command: string;\n startTime: string;\n cwd: string;\n}\n\nexport interface CLIOptions {\n allUsers: boolean;\n noSecurity: boolean;\n json: boolean;\n plain: boolean;\n alertLevel: AlertSeverity;\n installHooks: boolean;\n uninstallHooks: boolean;\n help: boolean;\n version: boolean;\n noNotify: boolean;\n noAlertLog: boolean;\n noUpdates: boolean;\n pollInterval: number;\n mcp: boolean;\n installMcp: boolean;\n}\n","import type { ToolCall, Alert } from '../../discovery/types.js';\n\nconst NETWORK_PATTERNS = [\n /\\bcurl\\b/,\n /\\bwget\\b/,\n /\\bfetch\\s*\\(/,\n /\\bnc\\b/,\n /\\bnetcat\\b/,\n /\\bpython3?\\s+-m\\s+http\\.server\\b/,\n /\\bncat\\b/,\n /\\bsocat\\b/,\n /\\btelnet\\b/,\n];\n\nconst LOCALHOST = /\\b(localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|\\[::1\\])\\b/;\n\nexport const checkNetwork = (call: ToolCall): Alert | null => {\n if (call.toolName !== 'Bash') return null;\n\n const command = String(call.toolInput.command || '');\n const matched = NETWORK_PATTERNS.some((p) => p.test(command));\n if (!matched) return null;\n\n const isLocal = LOCALHOST.test(command);\n const severity = isLocal ? 'info' : 'warn';\n\n return {\n id: `net-${call.timestamp}-${call.agentId}`,\n severity,\n rule: 'network',\n message: isLocal\n ? `Network command to localhost: ${command.slice(0, 80)}`\n : `Network command to external target: ${command.slice(0, 80)}`,\n sessionSlug: call.slug,\n sessionId: call.sessionId,\n event: call,\n timestamp: call.timestamp,\n };\n};\n","import type { ToolCall, Alert } from '../../discovery/types.js';\n\nconst EXFIL_PATTERNS = [\n /base64.*\\|\\s*(curl|wget|nc)/,\n /cat\\s+.*\\|\\s*(curl|wget|nc)/,\n /(tar|zip|gzip).*\\|\\s*(curl|wget|nc)/,\n /\\bcurl\\b.*-d\\s*@/,\n /\\bcurl\\b.*--data-binary/,\n /\\bscp\\b/,\n /\\brsync\\b.*[^/]@/,\n />\\s*\\/dev\\/tcp\\//,\n];\n\nexport const checkExfiltration = (call: ToolCall): Alert | null => {\n if (call.toolName !== 'Bash') return null;\n\n const command = String(call.toolInput.command || '');\n const matched = EXFIL_PATTERNS.some((p) => p.test(command));\n if (!matched) return null;\n\n return {\n id: `exfil-${call.timestamp}-${call.agentId}`,\n severity: 'high',\n rule: 'exfiltration',\n message: `Potential data exfiltration: ${command.slice(0, 80)}`,\n sessionSlug: call.slug,\n sessionId: call.sessionId,\n event: call,\n timestamp: call.timestamp,\n };\n};\n","import type { ToolCall, Alert } from '../../discovery/types.js';\n\nconst SENSITIVE_PATTERNS = [\n /\\.env\\b/,\n /\\.env\\.\\w+/,\n /\\.ssh\\//,\n /id_rsa/,\n /id_ed25519/,\n /\\.pem$/,\n /\\.key$/,\n /credentials/i,\n /\\/etc\\/shadow/,\n /\\/etc\\/passwd/,\n /\\.aws\\/credentials/,\n /\\.kube\\/config/,\n /\\.docker\\/config\\.json/,\n /\\.npmrc/,\n /\\.pypirc/,\n /\\.netrc/,\n /secrets?\\.\\w+/i,\n /token\\.\\w+/i,\n];\n\nconst TOOLS_THAT_READ = ['Read', 'Bash', 'Grep', 'Glob'];\n\nexport const checkSensitiveFiles = (call: ToolCall): Alert | null => {\n if (!TOOLS_THAT_READ.includes(call.toolName)) return null;\n\n const inputs = JSON.stringify(call.toolInput);\n const matched = SENSITIVE_PATTERNS.some((p) => p.test(inputs));\n if (!matched) return null;\n\n const target = String(call.toolInput.file_path || call.toolInput.command || call.toolInput.pattern || '').slice(\n 0,\n 60,\n );\n\n return {\n id: `sens-${call.timestamp}-${call.agentId}`,\n severity: 'warn',\n rule: 'sensitive-files',\n message: `Accessing sensitive file: ${target}`,\n sessionSlug: call.slug,\n sessionId: call.sessionId,\n event: call,\n timestamp: call.timestamp,\n };\n};\n","import type { ToolCall, Alert } from '../../discovery/types.js';\n\nconst SHELL_PATTERNS: Array<{ pattern: RegExp; severity: 'high' | 'critical'; label: string }> = [\n { pattern: /\\beval\\s*[(\"']/, severity: 'high', label: 'eval execution' },\n { pattern: /\\bchmod\\s+777\\b/, severity: 'high', label: 'chmod 777' },\n { pattern: /\\bchmod\\s+\\+s\\b/, severity: 'critical', label: 'setuid chmod' },\n { pattern: /\\bsudo\\b/, severity: 'high', label: 'sudo usage' },\n { pattern: /\\bsu\\s+-?\\s*\\w/, severity: 'high', label: 'su usage' },\n { pattern: />\\s*\\/etc\\//, severity: 'critical', label: 'writing to /etc/' },\n { pattern: />\\s*\\/usr\\//, severity: 'critical', label: 'writing to /usr/' },\n { pattern: /--privileged/, severity: 'critical', label: 'privileged flag' },\n { pattern: /\\brm\\s+-rf\\s+\\/(?!\\w)/, severity: 'critical', label: 'rm -rf /' },\n { pattern: /\\bdd\\s+.*of=\\/dev\\//, severity: 'critical', label: 'dd to device' },\n { pattern: /\\bmkfs\\b/, severity: 'critical', label: 'filesystem format' },\n { pattern: /\\biptables\\b/, severity: 'high', label: 'firewall modification' },\n];\n\nexport const checkShellEscape = (call: ToolCall): Alert | null => {\n if (call.toolName !== 'Bash') return null;\n\n const command = String(call.toolInput.command || '');\n\n for (const rule of SHELL_PATTERNS) {\n if (rule.pattern.test(command)) {\n return {\n id: `shell-${call.timestamp}-${call.agentId}`,\n severity: rule.severity,\n rule: 'shell-escape',\n message: `${rule.label}: ${command.slice(0, 80)}`,\n sessionSlug: call.slug,\n sessionId: call.sessionId,\n event: call,\n timestamp: call.timestamp,\n };\n }\n }\n\n return null;\n};\n","import type { ToolCall, ToolResult, Alert, SecurityEvent } from '../../discovery/types.js';\nimport { isToolResult, isToolCall } from '../../discovery/types.js';\n\nconst INJECTION_PATTERNS = [\n /ignore\\s+(all\\s+)?previous\\s+instructions/i,\n /ignore\\s+(all\\s+)?prior\\s+instructions/i,\n /disregard\\s+(all\\s+)?previous/i,\n /you\\s+are\\s+now\\s+/i,\n /new\\s+instructions?\\s*:/i,\n /system\\s*:\\s*you/i,\n /\\bdo\\s+not\\s+follow\\s+(your|the)\\s+(original|previous)/i,\n /override\\s+(your\\s+)?(instructions|rules|guidelines)/i,\n /forget\\s+(your\\s+)?(instructions|rules|guidelines)/i,\n /act\\s+as\\s+(if\\s+)?(you\\s+are|a)\\s+/i,\n /pretend\\s+(you\\s+are|to\\s+be)\\s+/i,\n /\\bAI\\s+assistant\\b.*\\bmust\\b/i,\n /\\bhuman\\s*:\\s*/i,\n /\\bassistant\\s*:\\s*/i,\n /<\\s*system\\s*>/i,\n /\\[\\s*INST\\s*\\]/i,\n /BEGIN\\s+HIDDEN\\s+INSTRUCTIONS/i,\n];\n\nconst ENCODED_PATTERNS = [\n /aWdub3JlIHByZXZpb3Vz/, // base64 \"ignore previous\"\n /&#x[0-9a-f]+;/i, // html hex entities\n /&#\\d+;/, // html decimal entities\n /\\\\u[0-9a-f]{4}/i, // unicode escapes\n];\n\nexport const checkInjection = (event: SecurityEvent): Alert | null => {\n if (isToolCall(event)) {\n return checkToolCallInjection(event);\n }\n if (isToolResult(event)) {\n return checkToolResultInjection(event);\n }\n return null;\n};\n\nconst checkToolCallInjection = (call: ToolCall): Alert | null => {\n const inputs = JSON.stringify(call.toolInput);\n\n const matched = INJECTION_PATTERNS.some((p) => p.test(inputs));\n if (!matched) return null;\n\n return {\n id: `inject-call-${call.timestamp}-${call.agentId}`,\n severity: 'critical',\n rule: 'injection',\n message: `Prompt injection in ${call.toolName} input`,\n sessionSlug: call.slug,\n sessionId: call.sessionId,\n event: call,\n timestamp: call.timestamp,\n };\n};\n\nconst checkToolResultInjection = (result: ToolResult): Alert | null => {\n const content = result.content;\n if (!content || content.length < 10) return null;\n\n const textPatternMatch = INJECTION_PATTERNS.some((p) => p.test(content));\n const encodedMatch = ENCODED_PATTERNS.some((p) => p.test(content));\n\n if (!textPatternMatch && !encodedMatch) return null;\n\n const matchedPattern = INJECTION_PATTERNS.find((p) => p.test(content));\n const snippet = matchedPattern ? content.match(matchedPattern)?.[0]?.slice(0, 50) || '' : 'encoded pattern';\n\n return {\n id: `inject-result-${result.timestamp}-${result.agentId}`,\n severity: 'critical',\n rule: 'injection-in-result',\n message: `Prompt injection in tool result: \"${snippet}\"`,\n sessionSlug: result.slug,\n sessionId: result.sessionId,\n event: result,\n timestamp: result.timestamp,\n };\n};\n","import type { ToolCall, ToolResult, SecurityEvent, Alert, AlertSeverity } from '../discovery/types.js';\nimport { isToolCall } from '../discovery/types.js';\nimport type { SecurityRulesConfig } from '../config/store.js';\n\nimport { checkNetwork } from './rules/network.js';\nimport { checkExfiltration } from './rules/exfiltration.js';\nimport { checkSensitiveFiles } from './rules/sensitive-files.js';\nimport { checkShellEscape } from './rules/shell-escape.js';\nimport { checkInjection } from './rules/injection.js';\n\ntype ToolCallRule = (call: ToolCall) => Alert | null;\ntype SecurityEventRule = (event: SecurityEvent) => Alert | null;\n\ninterface NamedRule<T> {\n key: keyof SecurityRulesConfig;\n fn: T;\n}\n\nconst toolCallRules: NamedRule<ToolCallRule>[] = [\n { key: 'network', fn: checkNetwork },\n { key: 'exfiltration', fn: checkExfiltration },\n { key: 'sensitiveFiles', fn: checkSensitiveFiles },\n { key: 'shellEscape', fn: checkShellEscape },\n];\n\nconst allEventRules: NamedRule<SecurityEventRule>[] = [{ key: 'injection', fn: checkInjection }];\n\nconst SEVERITY_ORDER: Record<AlertSeverity, number> = {\n info: 0,\n warn: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEDUP_WINDOW_MS = 30_000;\n\nexport class SecurityEngine {\n private recentAlerts = new Map<string, number>();\n private minLevel: AlertSeverity;\n private rulesConfig: SecurityRulesConfig;\n\n constructor(minLevel: AlertSeverity = 'warn', rulesConfig?: SecurityRulesConfig) {\n this.minLevel = minLevel;\n this.rulesConfig = rulesConfig ?? {\n network: true,\n exfiltration: true,\n sensitiveFiles: true,\n shellEscape: true,\n injection: true,\n };\n }\n\n analyze(call: ToolCall): Alert[] {\n return this.analyzeEvent(call);\n }\n\n analyzeResult(result: ToolResult): Alert[] {\n return this.analyzeEvent(result);\n }\n\n analyzeEvent(event: SecurityEvent): Alert[] {\n const alerts: Alert[] = [];\n\n if (isToolCall(event)) {\n for (const rule of toolCallRules) {\n if (!this.rulesConfig[rule.key]) continue;\n const alert = rule.fn(event);\n if (alert) alerts.push(alert);\n }\n }\n\n for (const rule of allEventRules) {\n if (!this.rulesConfig[rule.key]) continue;\n const alert = rule.fn(event);\n if (alert) alerts.push(alert);\n }\n\n return alerts.filter((alert) => {\n if (SEVERITY_ORDER[alert.severity] < SEVERITY_ORDER[this.minLevel]) return false;\n\n const dedupKey = `${alert.rule}-${alert.sessionId}-${alert.message.slice(0, 40)}`;\n const lastSeen = this.recentAlerts.get(dedupKey);\n if (lastSeen && alert.timestamp - lastSeen < DEDUP_WINDOW_MS) return false;\n\n this.recentAlerts.set(dedupKey, alert.timestamp);\n return true;\n });\n }\n\n pruneOldAlerts(): void {\n const cutoff = Date.now() - DEDUP_WINDOW_MS * 2;\n for (const [key, ts] of this.recentAlerts) {\n if (ts < cutoff) this.recentAlerts.delete(key);\n }\n }\n}\n","import { watch } from 'chokidar';\nimport type { FSWatcher } from 'chokidar';\n\nimport { getTaskDirs, getProjectsDirs } from '../config.js';\nimport type { ToolCall, SecurityEvent, TokenUsage } from '../discovery/types.js';\nimport { FileTailer } from './tail.js';\nimport { parseLines, parseAllEvents, parseUsageFromLines } from './parser.js';\n\nexport type ToolCallHandler = (calls: ToolCall[]) => void;\nexport type SecurityEventHandler = (events: SecurityEvent[]) => void;\nexport type UsageHandler = (sessionId: string, usage: TokenUsage) => void;\n\nexport class Watcher {\n private watcher: FSWatcher | null = null;\n private tailer = new FileTailer();\n private handler: ToolCallHandler;\n private securityHandler: SecurityEventHandler | null;\n private usageHandler: UsageHandler | null;\n private allUsers: boolean;\n private knownFiles = new Set<string>();\n\n constructor(\n handler: ToolCallHandler,\n allUsers: boolean,\n securityHandler?: SecurityEventHandler,\n usageHandler?: UsageHandler,\n ) {\n this.handler = handler;\n this.allUsers = allUsers;\n this.securityHandler = securityHandler ?? null;\n this.usageHandler = usageHandler ?? null;\n }\n\n start(): void {\n const taskDirs = getTaskDirs(this.allUsers);\n const projectsDirs = getProjectsDirs(this.allUsers);\n const globs = [...taskDirs.map((d) => `${d}/**/tasks/*.output`), ...projectsDirs.map((d) => `${d}/**/*.jsonl`)];\n\n this.watcher = watch(globs, {\n persistent: true,\n ignoreInitial: false,\n awaitWriteFinish: false,\n usePolling: false,\n });\n\n this.watcher.on('add', (filePath: string) => {\n if (this.knownFiles.has(filePath)) return;\n this.knownFiles.add(filePath);\n this.tailer.seekToEnd(filePath);\n });\n\n this.watcher.on('change', (filePath: string) => {\n const lines = this.tailer.readNewLines(filePath);\n if (lines.length === 0) return;\n\n const calls = parseLines(lines);\n if (calls.length > 0) {\n this.handler(calls);\n }\n\n if (this.securityHandler) {\n const allEvents = parseAllEvents(lines);\n if (allEvents.length > 0) {\n this.securityHandler(allEvents);\n }\n }\n\n if (this.usageHandler) {\n const usage = parseUsageFromLines(lines);\n if (usage.inputTokens > 0 || usage.outputTokens > 0) {\n const firstCall = calls[0];\n if (firstCall) {\n this.usageHandler(firstCall.sessionId, usage);\n }\n }\n }\n });\n }\n\n stop(): void {\n this.watcher?.close();\n this.watcher = null;\n this.tailer.resetAll();\n this.knownFiles.clear();\n }\n\n readExisting(filePath: string): ToolCall[] {\n this.tailer.reset(filePath);\n const lines = this.tailer.readNewLines(filePath);\n return parseLines(lines);\n }\n}\n","import { openSync, readSync, closeSync, statSync } from 'node:fs';\n\nexport class FileTailer {\n private offsets = new Map<string, number>();\n\n readNewLines(filePath: string): string[] {\n let currentSize: number;\n try {\n currentSize = statSync(filePath).size;\n } catch {\n return [];\n }\n\n const lastOffset = this.offsets.get(filePath) ?? 0;\n if (currentSize <= lastOffset) return [];\n\n const bytesToRead = currentSize - lastOffset;\n const buf = Buffer.alloc(bytesToRead);\n\n let fd: number;\n try {\n fd = openSync(filePath, 'r');\n } catch {\n return [];\n }\n\n try {\n readSync(fd, buf, 0, bytesToRead, lastOffset);\n } finally {\n closeSync(fd);\n }\n\n this.offsets.set(filePath, currentSize);\n\n const text = buf.toString('utf-8');\n const lines = text.split('\\n').filter((l) => l.trim().length > 0);\n return lines;\n }\n\n seekToEnd(filePath: string): void {\n try {\n const size = statSync(filePath).size;\n this.offsets.set(filePath, size);\n } catch {\n // file doesn't exist yet\n }\n }\n\n reset(filePath: string): void {\n this.offsets.delete(filePath);\n }\n\n resetAll(): void {\n this.offsets.clear();\n }\n}\n","import type { RawEvent, ToolCall, ToolResult, SecurityEvent, TokenUsage } from '../discovery/types.js';\n\nconst parseEventTimestamp = (event: RawEvent): number => {\n if (event.timestamp) {\n const parsed = new Date(event.timestamp).getTime();\n if (!isNaN(parsed)) return parsed;\n }\n return Date.now();\n};\n\nexport const parseLine = (line: string): RawEvent | null => {\n try {\n return JSON.parse(line) as RawEvent;\n } catch {\n return null;\n }\n};\n\nexport const extractToolCalls = (event: RawEvent): ToolCall[] => {\n if (event.type !== 'assistant') return [];\n\n const content = event.message?.content;\n if (!Array.isArray(content)) return [];\n\n const ts = parseEventTimestamp(event);\n const calls: ToolCall[] = [];\n\n for (const block of content) {\n if (\n typeof block === 'object' &&\n block !== null &&\n 'type' in block &&\n (block as Record<string, unknown>).type === 'tool_use'\n ) {\n const toolBlock = block as Record<string, unknown>;\n calls.push({\n sessionId: event.sessionId,\n agentId: event.agentId,\n slug: ((event as Record<string, unknown>).slug as string) || '',\n timestamp: ts,\n toolName: (toolBlock.name as string) || 'unknown',\n toolInput: (toolBlock.input as Record<string, unknown>) || {},\n cwd: event.cwd,\n });\n }\n }\n\n return calls;\n};\n\nexport const extractToolResults = (event: RawEvent): ToolResult[] => {\n if (event.type !== 'user') return [];\n\n const content = event.message?.content;\n if (!Array.isArray(content)) return [];\n\n const ts = parseEventTimestamp(event);\n const results: ToolResult[] = [];\n\n for (const block of content) {\n if (\n typeof block === 'object' &&\n block !== null &&\n 'type' in block &&\n (block as Record<string, unknown>).type === 'tool_result'\n ) {\n const resultBlock = block as Record<string, unknown>;\n const resultContent = resultBlock.content;\n let text = '';\n if (typeof resultContent === 'string') {\n text = resultContent;\n } else if (Array.isArray(resultContent)) {\n text = resultContent\n .map((c) => (typeof c === 'object' && c !== null ? (c as Record<string, unknown>).text || '' : String(c)))\n .join('\\n');\n }\n\n results.push({\n sessionId: event.sessionId,\n agentId: event.agentId,\n slug: ((event as Record<string, unknown>).slug as string) || '',\n timestamp: ts,\n toolUseId: String(resultBlock.tool_use_id || ''),\n content: text,\n isError: Boolean(resultBlock.is_error),\n cwd: event.cwd,\n });\n }\n }\n\n return results;\n};\n\nexport const extractUsage = (event: RawEvent): TokenUsage | null => {\n if (event.type !== 'assistant') return null;\n\n const usage = event.message?.usage;\n if (!usage) return null;\n\n return {\n inputTokens: usage.input_tokens ?? 0,\n cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,\n cacheReadTokens: usage.cache_read_input_tokens ?? 0,\n outputTokens: usage.output_tokens ?? 0,\n };\n};\n\nexport const parseLines = (lines: string[]): ToolCall[] => {\n const calls: ToolCall[] = [];\n for (const line of lines) {\n const event = parseLine(line);\n if (event) {\n calls.push(...extractToolCalls(event));\n }\n }\n return calls;\n};\n\nexport const parseAllEvents = (lines: string[]): SecurityEvent[] => {\n const events: SecurityEvent[] = [];\n for (const line of lines) {\n const event = parseLine(line);\n if (event) {\n events.push(...extractToolCalls(event));\n events.push(...extractToolResults(event));\n }\n }\n return events;\n};\n\nexport const parseUsageFromLines = (lines: string[]): TokenUsage => {\n const total: TokenUsage = { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };\n for (const line of lines) {\n const event = parseLine(line);\n if (event) {\n const usage = extractUsage(event);\n if (usage) {\n total.inputTokens += usage.inputTokens;\n total.cacheCreationTokens += usage.cacheCreationTokens;\n total.cacheReadTokens += usage.cacheReadTokens;\n total.outputTokens += usage.outputTokens;\n }\n }\n }\n return total;\n};\n"],"mappings":"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","names":["readdirSync","statSync","join","existsSync","homedir","join","dirs","join","existsSync","homedir","readdirSync","join","statSync","openSync","readSync","closeSync","statSync"]}
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setNickname,
|
|
18
18
|
startMcpServer,
|
|
19
19
|
unarchiveSession
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-6D2UXDV4.js";
|
|
21
21
|
|
|
22
22
|
// src/index.tsx
|
|
23
23
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -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
|
|
1959
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1958
1960
|
for (const s of sessions) {
|
|
1959
|
-
const
|
|
1960
|
-
const
|
|
1961
|
-
if (
|
|
1962
|
-
|
|
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
|
|
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,
|
|
2055
|
-
}, [
|
|
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
|
|
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) =>
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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" ? [
|
|
3022
|
-
|
|
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)
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|