agenttop 0.9.2 → 0.9.4
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-6D2UXDV4.js → chunk-3LEBAMTZ.js} +75 -26
- package/dist/chunk-3LEBAMTZ.js.map +1 -0
- package/dist/index.js +585 -153
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6D2UXDV4.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
discoverSessions,
|
|
9
9
|
getArchived,
|
|
10
10
|
getNicknames,
|
|
11
|
+
getProjectsDirs,
|
|
12
|
+
getTaskDirs,
|
|
11
13
|
isFirstRun,
|
|
12
14
|
loadConfig,
|
|
13
15
|
purgeExpiredArchives,
|
|
@@ -17,11 +19,11 @@ import {
|
|
|
17
19
|
setNickname,
|
|
18
20
|
startMcpServer,
|
|
19
21
|
unarchiveSession
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-3LEBAMTZ.js";
|
|
21
23
|
|
|
22
24
|
// src/index.tsx
|
|
23
|
-
import { readFileSync as
|
|
24
|
-
import { join as
|
|
25
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
26
|
+
import { join as join5, dirname as dirname4 } from "path";
|
|
25
27
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
26
28
|
import React16 from "react";
|
|
27
29
|
import { render } from "ink";
|
|
@@ -348,33 +350,36 @@ var deriveSeverityColors = (c) => ({
|
|
|
348
350
|
});
|
|
349
351
|
|
|
350
352
|
// src/updates.ts
|
|
351
|
-
import {
|
|
352
|
-
import {
|
|
353
|
+
import { execFile, exec } from "child_process";
|
|
354
|
+
import { readFile } from "fs/promises";
|
|
353
355
|
import { join, dirname } from "path";
|
|
354
356
|
import { fileURLToPath } from "url";
|
|
355
|
-
var getPackageVersion = () => {
|
|
357
|
+
var getPackageVersion = async () => {
|
|
356
358
|
try {
|
|
357
359
|
const thisFile = fileURLToPath(import.meta.url);
|
|
358
360
|
const pkgPath = join(dirname(thisFile), "..", "package.json");
|
|
359
|
-
const pkg = JSON.parse(
|
|
361
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
360
362
|
return pkg.version || "0.0.0";
|
|
361
363
|
} catch {
|
|
362
364
|
return "0.0.0";
|
|
363
365
|
}
|
|
364
366
|
};
|
|
365
|
-
var checkForUpdate = () => {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
};
|
|
367
|
+
var checkForUpdate = () => new Promise((resolve) => {
|
|
368
|
+
getPackageVersion().then((current) => {
|
|
369
|
+
execFile("npm", ["view", "agenttop", "version"], { encoding: "utf-8", timeout: 5e3 }, (err, stdout) => {
|
|
370
|
+
if (err || !stdout) {
|
|
371
|
+
resolve({ current, latest: current, available: false });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const latest = stdout.trim();
|
|
375
|
+
resolve({
|
|
376
|
+
current,
|
|
377
|
+
latest,
|
|
378
|
+
available: latest !== current && compareVersions(latest, current) > 0
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
378
383
|
var installUpdate = () => {
|
|
379
384
|
return new Promise((resolve, reject) => {
|
|
380
385
|
exec("npm install -g agenttop@latest", { timeout: 6e4 }, (err, stdout) => {
|
|
@@ -490,11 +495,24 @@ var formatTokens = (n) => {
|
|
|
490
495
|
return String(n);
|
|
491
496
|
};
|
|
492
497
|
var truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
493
|
-
var
|
|
494
|
-
|
|
498
|
+
var getDisplayName = (s) => {
|
|
499
|
+
if (s.nickname) return s.nickname;
|
|
500
|
+
if (s.cwd) {
|
|
501
|
+
const parts = s.cwd.replace(/\/+$/, "").split("/");
|
|
502
|
+
return parts[parts.length - 1] || s.slug;
|
|
503
|
+
}
|
|
504
|
+
if (s.project) {
|
|
505
|
+
const parts = s.project.replace(/\/+$/, "").split("/");
|
|
506
|
+
return parts[parts.length - 1] || s.slug;
|
|
507
|
+
}
|
|
508
|
+
return s.slug;
|
|
509
|
+
};
|
|
510
|
+
var DEFAULT_SIDEBAR_WIDTH = 30;
|
|
495
511
|
var LINES_PER_ITEM = 3;
|
|
496
512
|
var SessionList = React2.memo(
|
|
497
|
-
({ visibleItems, selectedIndex, focused, height, filter, viewingArchive, totalSessions }) => {
|
|
513
|
+
({ visibleItems, selectedIndex, focused, height, filter, viewingArchive, totalSessions, sidebarWidth }) => {
|
|
514
|
+
const SIDEBAR_WIDTH = sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH;
|
|
515
|
+
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
498
516
|
const availableRows = height - 2;
|
|
499
517
|
const maxVisible = Math.max(1, Math.floor((availableRows + 1) / LINES_PER_ITEM));
|
|
500
518
|
const halfView = Math.floor(maxVisible / 2);
|
|
@@ -546,14 +564,14 @@ var SessionList = React2.memo(
|
|
|
546
564
|
paddingX: 1,
|
|
547
565
|
backgroundColor: isSelected ? colors.selected : void 0,
|
|
548
566
|
children: [
|
|
549
|
-
/* @__PURE__ */ jsxs2(Text2, { color: nameColor2, bold: isSelected, wrap: "truncate", children: [
|
|
567
|
+
/* @__PURE__ */ jsxs2(Text2, { color: nameColor2, bold: isSelected, underline: isSelected, wrap: "truncate", children: [
|
|
550
568
|
arrow,
|
|
551
569
|
" ",
|
|
552
570
|
/* @__PURE__ */ jsx2(Text2, { color: dotColor2, children: statusDot2 }),
|
|
553
571
|
" ",
|
|
554
572
|
label2
|
|
555
573
|
] }),
|
|
556
|
-
/* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
|
|
574
|
+
/* @__PURE__ */ jsxs2(Text2, { color: isSelected ? colors.text : colors.muted, wrap: "truncate", children: [
|
|
557
575
|
" ",
|
|
558
576
|
model2,
|
|
559
577
|
" ",
|
|
@@ -576,7 +594,7 @@ var SessionList = React2.memo(
|
|
|
576
594
|
const model = formatModel(session.model);
|
|
577
595
|
if (item.type === "session") {
|
|
578
596
|
const nameColor2 = isSelected ? colors.bright : isActive ? colors.secondary : colors.muted;
|
|
579
|
-
const displayName2 = truncate(session.slug, INNER_WIDTH - 6);
|
|
597
|
+
const displayName2 = truncate(session.nickname || session.slug, INNER_WIDTH - 6);
|
|
580
598
|
return /* @__PURE__ */ jsxs2(
|
|
581
599
|
Box2,
|
|
582
600
|
{
|
|
@@ -584,14 +602,14 @@ var SessionList = React2.memo(
|
|
|
584
602
|
paddingX: 1,
|
|
585
603
|
backgroundColor: isSelected ? colors.selected : void 0,
|
|
586
604
|
children: [
|
|
587
|
-
/* @__PURE__ */ jsxs2(Text2, { color: nameColor2, bold: isSelected, wrap: "truncate", children: [
|
|
605
|
+
/* @__PURE__ */ jsxs2(Text2, { color: nameColor2, bold: isSelected, underline: isSelected, wrap: "truncate", children: [
|
|
588
606
|
" ",
|
|
589
607
|
" ",
|
|
590
608
|
/* @__PURE__ */ jsx2(Text2, { color: dotColor, children: statusDot }),
|
|
591
609
|
" ",
|
|
592
610
|
displayName2
|
|
593
611
|
] }),
|
|
594
|
-
/* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
|
|
612
|
+
/* @__PURE__ */ jsxs2(Text2, { color: isSelected ? colors.text : colors.muted, wrap: "truncate", children: [
|
|
595
613
|
" ",
|
|
596
614
|
model,
|
|
597
615
|
" ",
|
|
@@ -608,19 +626,7 @@ var SessionList = React2.memo(
|
|
|
608
626
|
}
|
|
609
627
|
const indicator = isSelected ? "\u25B8" : " ";
|
|
610
628
|
const nameColor = isSelected ? colors.bright : isActive ? colors.secondary : colors.text;
|
|
611
|
-
const
|
|
612
|
-
if (s.nickname) return s.nickname;
|
|
613
|
-
if (s.cwd) {
|
|
614
|
-
const parts = s.cwd.replace(/\/+$/, "").split("/");
|
|
615
|
-
return parts[parts.length - 1] || s.slug;
|
|
616
|
-
}
|
|
617
|
-
if (s.project) {
|
|
618
|
-
const parts = s.project.replace(/\/+$/, "").split("/");
|
|
619
|
-
return parts[parts.length - 1] || s.slug;
|
|
620
|
-
}
|
|
621
|
-
return s.slug;
|
|
622
|
-
};
|
|
623
|
-
const displayName = truncate(getDisplayName2(session), INNER_WIDTH - 4);
|
|
629
|
+
const displayName = truncate(getDisplayName(session), INNER_WIDTH - 4);
|
|
624
630
|
return /* @__PURE__ */ jsxs2(
|
|
625
631
|
Box2,
|
|
626
632
|
{
|
|
@@ -628,14 +634,14 @@ var SessionList = React2.memo(
|
|
|
628
634
|
paddingX: 1,
|
|
629
635
|
backgroundColor: isSelected ? colors.selected : void 0,
|
|
630
636
|
children: [
|
|
631
|
-
/* @__PURE__ */ jsxs2(Text2, { color: nameColor, bold: isSelected, wrap: "truncate", children: [
|
|
637
|
+
/* @__PURE__ */ jsxs2(Text2, { color: nameColor, bold: isSelected, underline: isSelected, wrap: "truncate", children: [
|
|
632
638
|
indicator,
|
|
633
639
|
" ",
|
|
634
640
|
/* @__PURE__ */ jsx2(Text2, { color: dotColor, children: statusDot }),
|
|
635
641
|
" ",
|
|
636
642
|
displayName
|
|
637
643
|
] }),
|
|
638
|
-
/* @__PURE__ */ jsxs2(Text2, { color: colors.muted, wrap: "truncate", children: [
|
|
644
|
+
/* @__PURE__ */ jsxs2(Text2, { color: isSelected ? colors.text : colors.muted, wrap: "truncate", children: [
|
|
639
645
|
" ",
|
|
640
646
|
model,
|
|
641
647
|
" ",
|
|
@@ -1099,7 +1105,9 @@ var KEYBIND_LABELS = {
|
|
|
1099
1105
|
pinLeft: "Pin to left panel",
|
|
1100
1106
|
pinRight: "Pin to right panel",
|
|
1101
1107
|
swapPanels: "Swap panels",
|
|
1102
|
-
closePanel: "Close panel"
|
|
1108
|
+
closePanel: "Close panel",
|
|
1109
|
+
sidebarNarrower: "Sidebar narrower",
|
|
1110
|
+
sidebarWider: "Sidebar wider"
|
|
1103
1111
|
};
|
|
1104
1112
|
var RULE_LABELS = {
|
|
1105
1113
|
network: "Network detection",
|
|
@@ -1129,7 +1137,9 @@ var DEFAULT_KEYBINDINGS = {
|
|
|
1129
1137
|
pinLeft: "1",
|
|
1130
1138
|
pinRight: "2",
|
|
1131
1139
|
swapPanels: "S",
|
|
1132
|
-
closePanel: "X"
|
|
1140
|
+
closePanel: "X",
|
|
1141
|
+
sidebarNarrower: "<",
|
|
1142
|
+
sidebarWider: ">"
|
|
1133
1143
|
};
|
|
1134
1144
|
var SEVERITY_OPTIONS = ["info", "warn", "high", "critical"];
|
|
1135
1145
|
var ARCHIVE_EXPIRY_OPTIONS = [0, 7, 14, 30, 60, 90];
|
|
@@ -1930,20 +1940,402 @@ var SplitPanel = React14.memo(
|
|
|
1930
1940
|
|
|
1931
1941
|
// src/ui/hooks/useSessions.ts
|
|
1932
1942
|
import { useState as useState8, useEffect as useEffect5, useCallback as useCallback2, useRef as useRef3, useMemo as useMemo2 } from "react";
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1943
|
+
|
|
1944
|
+
// src/discovery/sessionsAsync.ts
|
|
1945
|
+
import { readdir, stat as stat2 } from "fs/promises";
|
|
1946
|
+
import { join as join2, basename } from "path";
|
|
1947
|
+
|
|
1948
|
+
// src/discovery/cache.ts
|
|
1949
|
+
var sessions = [];
|
|
1950
|
+
var refreshInFlight = false;
|
|
1951
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1952
|
+
var getCachedSessions = () => sessions;
|
|
1953
|
+
var setCachedSessions = (next) => {
|
|
1954
|
+
sessions = next;
|
|
1955
|
+
for (const fn of listeners) fn(next);
|
|
1956
|
+
};
|
|
1957
|
+
var subscribe = (fn) => {
|
|
1958
|
+
listeners.add(fn);
|
|
1959
|
+
return () => {
|
|
1960
|
+
listeners.delete(fn);
|
|
1961
|
+
};
|
|
1962
|
+
};
|
|
1963
|
+
var isRefreshInFlight = () => refreshInFlight;
|
|
1964
|
+
var setRefreshInFlight = (v) => {
|
|
1965
|
+
refreshInFlight = v;
|
|
1966
|
+
};
|
|
1967
|
+
var MAX_CACHE_ENTRIES = 500;
|
|
1968
|
+
var fileMetaCache = /* @__PURE__ */ new Map();
|
|
1969
|
+
var pruneFileMetaCache = (validPaths) => {
|
|
1970
|
+
for (const key of fileMetaCache.keys()) {
|
|
1971
|
+
if (!validPaths.has(key)) fileMetaCache.delete(key);
|
|
1940
1972
|
}
|
|
1941
|
-
if (
|
|
1942
|
-
const
|
|
1943
|
-
|
|
1973
|
+
if (fileMetaCache.size > MAX_CACHE_ENTRIES) {
|
|
1974
|
+
const excess = fileMetaCache.size - MAX_CACHE_ENTRIES;
|
|
1975
|
+
const iter = fileMetaCache.keys();
|
|
1976
|
+
for (let i = 0; i < excess; i++) {
|
|
1977
|
+
const next = iter.next();
|
|
1978
|
+
if (!next.done) fileMetaCache.delete(next.value);
|
|
1979
|
+
}
|
|
1944
1980
|
}
|
|
1945
|
-
return session.slug;
|
|
1946
1981
|
};
|
|
1982
|
+
|
|
1983
|
+
// src/discovery/asyncHelpers.ts
|
|
1984
|
+
import { execFile as execFile2 } from "child_process";
|
|
1985
|
+
import { open, stat, readlink } from "fs/promises";
|
|
1986
|
+
var normalisePath = (p) => p.replace(/\/+$/, "");
|
|
1987
|
+
var getClaudeProcessesAsync = async () => {
|
|
1988
|
+
const stdout = await new Promise((resolve) => {
|
|
1989
|
+
execFile2("ps", ["aux"], { encoding: "utf-8", timeout: 5e3, maxBuffer: 4 * 1024 * 1024 }, (err, out) => {
|
|
1990
|
+
resolve(err || !out ? "" : out);
|
|
1991
|
+
});
|
|
1992
|
+
});
|
|
1993
|
+
if (!stdout) return [];
|
|
1994
|
+
const procs = [];
|
|
1995
|
+
for (const line of stdout.split("\n")) {
|
|
1996
|
+
if (!line.includes("/claude") || line.includes("grep") || line.includes("agenttop")) continue;
|
|
1997
|
+
const parts = line.trim().split(/\s+/);
|
|
1998
|
+
const pid = parseInt(parts[1], 10);
|
|
1999
|
+
if (isNaN(pid)) continue;
|
|
2000
|
+
const command = parts.slice(10).join(" ");
|
|
2001
|
+
if (command.startsWith("sudo")) continue;
|
|
2002
|
+
procs.push({
|
|
2003
|
+
pid,
|
|
2004
|
+
cpu: parseFloat(parts[2]) || 0,
|
|
2005
|
+
mem: parseFloat(parts[3]) || 0,
|
|
2006
|
+
memKB: parseInt(parts[5], 10) || 0,
|
|
2007
|
+
startTime: parts[8] || "",
|
|
2008
|
+
command,
|
|
2009
|
+
cwd: ""
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
await Promise.all(
|
|
2013
|
+
procs.map(async (p) => {
|
|
2014
|
+
try {
|
|
2015
|
+
p.cwd = await readlink(`/proc/${p.pid}/cwd`);
|
|
2016
|
+
} catch {
|
|
2017
|
+
}
|
|
2018
|
+
})
|
|
2019
|
+
);
|
|
2020
|
+
return procs;
|
|
2021
|
+
};
|
|
2022
|
+
var readFirstLinesAsync = async (filePath, bytes) => {
|
|
2023
|
+
let fh;
|
|
2024
|
+
try {
|
|
2025
|
+
fh = await open(filePath, "r");
|
|
2026
|
+
const buf = Buffer.alloc(bytes);
|
|
2027
|
+
const { bytesRead } = await fh.read(buf, 0, bytes, 0);
|
|
2028
|
+
return buf.subarray(0, bytesRead).toString("utf-8").split("\n").filter(Boolean);
|
|
2029
|
+
} catch {
|
|
2030
|
+
return [];
|
|
2031
|
+
} finally {
|
|
2032
|
+
await fh?.close();
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
var extractSessionMetaCached = async (filePath) => {
|
|
2036
|
+
let fstat;
|
|
2037
|
+
try {
|
|
2038
|
+
fstat = await stat(filePath);
|
|
2039
|
+
if (!fstat.isFile() || fstat.size === 0) return null;
|
|
2040
|
+
} catch {
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
const cached = fileMetaCache.get(filePath);
|
|
2044
|
+
if (cached && cached.mtimeMs === fstat.mtimeMs && cached.size === fstat.size) {
|
|
2045
|
+
return {
|
|
2046
|
+
sessionId: cached.sessionId,
|
|
2047
|
+
cwd: cached.cwd,
|
|
2048
|
+
version: cached.version,
|
|
2049
|
+
gitBranch: cached.gitBranch,
|
|
2050
|
+
model: cached.model,
|
|
2051
|
+
usage: cached.usage
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
const lines = await readFirstLinesAsync(filePath, 65536);
|
|
2055
|
+
let sessionId = "";
|
|
2056
|
+
let cwd = "";
|
|
2057
|
+
let version = "";
|
|
2058
|
+
let gitBranch = "";
|
|
2059
|
+
let model = "";
|
|
2060
|
+
const usage = { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
|
|
2061
|
+
for (const line of lines) {
|
|
2062
|
+
try {
|
|
2063
|
+
const evt = JSON.parse(line);
|
|
2064
|
+
if (!sessionId && evt.sessionId) sessionId = String(evt.sessionId);
|
|
2065
|
+
if (!cwd && evt.cwd) cwd = String(evt.cwd);
|
|
2066
|
+
if (!version && evt.version) version = String(evt.version);
|
|
2067
|
+
if (!gitBranch && evt.gitBranch) gitBranch = String(evt.gitBranch);
|
|
2068
|
+
if (evt.type === "assistant") {
|
|
2069
|
+
if (!model && evt.message?.model) model = String(evt.message.model);
|
|
2070
|
+
const u = evt.message?.usage;
|
|
2071
|
+
if (u) {
|
|
2072
|
+
usage.inputTokens += u.input_tokens ?? 0;
|
|
2073
|
+
usage.cacheCreationTokens += u.cache_creation_input_tokens ?? 0;
|
|
2074
|
+
usage.cacheReadTokens += u.cache_read_input_tokens ?? 0;
|
|
2075
|
+
usage.outputTokens += u.output_tokens ?? 0;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
} catch {
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
if (!sessionId) return null;
|
|
2083
|
+
fileMetaCache.set(filePath, {
|
|
2084
|
+
mtimeMs: fstat.mtimeMs,
|
|
2085
|
+
size: fstat.size,
|
|
2086
|
+
sessionId,
|
|
2087
|
+
cwd,
|
|
2088
|
+
version,
|
|
2089
|
+
gitBranch,
|
|
2090
|
+
model,
|
|
2091
|
+
usage
|
|
2092
|
+
});
|
|
2093
|
+
return { sessionId, cwd, version, gitBranch, model, usage };
|
|
2094
|
+
};
|
|
2095
|
+
var readFirstEventAsync = async (filePath) => {
|
|
2096
|
+
const lines = await readFirstLinesAsync(filePath, 16384);
|
|
2097
|
+
if (lines.length === 0) return null;
|
|
2098
|
+
try {
|
|
2099
|
+
return JSON.parse(lines[0]);
|
|
2100
|
+
} catch {
|
|
2101
|
+
return null;
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
var findModelAndUsageAsync = async (filePath) => {
|
|
2105
|
+
let fstat;
|
|
2106
|
+
try {
|
|
2107
|
+
fstat = await stat(filePath);
|
|
2108
|
+
} catch {
|
|
2109
|
+
return { model: "", usage: { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 } };
|
|
2110
|
+
}
|
|
2111
|
+
const cached = fileMetaCache.get(filePath);
|
|
2112
|
+
if (cached && cached.mtimeMs === fstat.mtimeMs && cached.size === fstat.size) {
|
|
2113
|
+
return { model: cached.model, usage: cached.usage };
|
|
2114
|
+
}
|
|
2115
|
+
const usage = { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
|
|
2116
|
+
let model = "";
|
|
2117
|
+
const lines = await readFirstLinesAsync(filePath, 65536);
|
|
2118
|
+
for (const line of lines) {
|
|
2119
|
+
try {
|
|
2120
|
+
const evt = JSON.parse(line);
|
|
2121
|
+
if (evt.type === "assistant") {
|
|
2122
|
+
if (!model && evt.message?.model) model = String(evt.message.model);
|
|
2123
|
+
const u = evt.message?.usage;
|
|
2124
|
+
if (u) {
|
|
2125
|
+
usage.inputTokens += u.input_tokens ?? 0;
|
|
2126
|
+
usage.cacheCreationTokens += u.cache_creation_input_tokens ?? 0;
|
|
2127
|
+
usage.cacheReadTokens += u.cache_read_input_tokens ?? 0;
|
|
2128
|
+
usage.outputTokens += u.output_tokens ?? 0;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
} catch {
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
return { model, usage };
|
|
2136
|
+
};
|
|
2137
|
+
|
|
2138
|
+
// src/discovery/sessionsAsync.ts
|
|
2139
|
+
var discoverFromProjectsAsync = async (allUsers, processes, sessionMap, seenFiles) => {
|
|
2140
|
+
const projectsDirs = getProjectsDirs(allUsers);
|
|
2141
|
+
for (const projectsDir of projectsDirs) {
|
|
2142
|
+
let projectNames;
|
|
2143
|
+
try {
|
|
2144
|
+
projectNames = await readdir(projectsDir);
|
|
2145
|
+
} catch {
|
|
2146
|
+
continue;
|
|
2147
|
+
}
|
|
2148
|
+
for (const projectName of projectNames) {
|
|
2149
|
+
const projectPath = join2(projectsDir, projectName);
|
|
2150
|
+
try {
|
|
2151
|
+
const s = await stat2(projectPath);
|
|
2152
|
+
if (!s.isDirectory()) continue;
|
|
2153
|
+
} catch {
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
let files;
|
|
2157
|
+
try {
|
|
2158
|
+
files = (await readdir(projectPath)).filter((f) => f.endsWith(".jsonl"));
|
|
2159
|
+
} catch {
|
|
2160
|
+
continue;
|
|
2161
|
+
}
|
|
2162
|
+
for (const file of files) {
|
|
2163
|
+
const filePath = join2(projectPath, file);
|
|
2164
|
+
seenFiles.add(filePath);
|
|
2165
|
+
let fstat;
|
|
2166
|
+
try {
|
|
2167
|
+
fstat = await stat2(filePath);
|
|
2168
|
+
if (!fstat.isFile() || fstat.size === 0) continue;
|
|
2169
|
+
} catch {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
const meta = await extractSessionMetaCached(filePath);
|
|
2173
|
+
if (!meta) continue;
|
|
2174
|
+
if (sessionMap.has(meta.sessionId)) continue;
|
|
2175
|
+
const normCwd = normalisePath(meta.cwd);
|
|
2176
|
+
const matchingProcess = processes.find((p) => p.cwd && normalisePath(p.cwd) === normCwd);
|
|
2177
|
+
const session = {
|
|
2178
|
+
sessionId: meta.sessionId,
|
|
2179
|
+
slug: meta.sessionId.slice(0, 12),
|
|
2180
|
+
project: projectName.replace(/-/g, "/"),
|
|
2181
|
+
cwd: meta.cwd,
|
|
2182
|
+
model: meta.model || "unknown",
|
|
2183
|
+
version: meta.version,
|
|
2184
|
+
gitBranch: meta.gitBranch,
|
|
2185
|
+
pid: matchingProcess?.pid ?? null,
|
|
2186
|
+
cpu: matchingProcess?.cpu ?? 0,
|
|
2187
|
+
mem: matchingProcess?.mem ?? 0,
|
|
2188
|
+
memMB: matchingProcess ? Math.round(matchingProcess.memKB / 1024) : 0,
|
|
2189
|
+
agentCount: 1,
|
|
2190
|
+
agentIds: [basename(file, ".jsonl")],
|
|
2191
|
+
outputFiles: [filePath],
|
|
2192
|
+
startTime: fstat.birthtimeMs || fstat.ctimeMs,
|
|
2193
|
+
lastActivity: fstat.mtimeMs,
|
|
2194
|
+
usage: meta.usage
|
|
2195
|
+
};
|
|
2196
|
+
sessionMap.set(meta.sessionId, session);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
var discoverFromTmpAsync = async (allUsers, processes, sessionMap, seenFiles) => {
|
|
2202
|
+
const taskDirs = getTaskDirs(allUsers);
|
|
2203
|
+
for (const taskDir of taskDirs) {
|
|
2204
|
+
let projectDirs;
|
|
2205
|
+
try {
|
|
2206
|
+
projectDirs = await readdir(taskDir);
|
|
2207
|
+
} catch {
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
for (const projectName of projectDirs) {
|
|
2211
|
+
const projectPath = join2(taskDir, projectName);
|
|
2212
|
+
try {
|
|
2213
|
+
const s = await stat2(projectPath);
|
|
2214
|
+
if (!s.isDirectory()) continue;
|
|
2215
|
+
} catch {
|
|
2216
|
+
continue;
|
|
2217
|
+
}
|
|
2218
|
+
const tasksPaths = [];
|
|
2219
|
+
const tasksDir = join2(projectPath, "tasks");
|
|
2220
|
+
try {
|
|
2221
|
+
await readdir(tasksDir);
|
|
2222
|
+
tasksPaths.push(tasksDir);
|
|
2223
|
+
} catch {
|
|
2224
|
+
try {
|
|
2225
|
+
for (const sub of await readdir(projectPath)) {
|
|
2226
|
+
const subTasks = join2(projectPath, sub, "tasks");
|
|
2227
|
+
try {
|
|
2228
|
+
await readdir(subTasks);
|
|
2229
|
+
tasksPaths.push(subTasks);
|
|
2230
|
+
} catch {
|
|
2231
|
+
continue;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
} catch {
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
for (const tDir of tasksPaths) {
|
|
2239
|
+
let outputFiles;
|
|
2240
|
+
try {
|
|
2241
|
+
outputFiles = (await readdir(tDir)).filter((f) => f.endsWith(".output")).map((f) => join2(tDir, f));
|
|
2242
|
+
} catch {
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
if (outputFiles.length === 0) continue;
|
|
2246
|
+
const agentIds = [];
|
|
2247
|
+
let sessionId = "";
|
|
2248
|
+
let slug = "";
|
|
2249
|
+
let cwd = "";
|
|
2250
|
+
let model = "";
|
|
2251
|
+
let version = "";
|
|
2252
|
+
let gitBranch = "";
|
|
2253
|
+
let startTime = Infinity;
|
|
2254
|
+
let lastActivity = 0;
|
|
2255
|
+
const totalUsage = {
|
|
2256
|
+
inputTokens: 0,
|
|
2257
|
+
cacheCreationTokens: 0,
|
|
2258
|
+
cacheReadTokens: 0,
|
|
2259
|
+
outputTokens: 0
|
|
2260
|
+
};
|
|
2261
|
+
for (const outputFile of outputFiles) {
|
|
2262
|
+
seenFiles.add(outputFile);
|
|
2263
|
+
const agentId = basename(outputFile, ".output");
|
|
2264
|
+
agentIds.push(agentId);
|
|
2265
|
+
const firstEvent = await readFirstEventAsync(outputFile);
|
|
2266
|
+
if (firstEvent) {
|
|
2267
|
+
if (!sessionId) sessionId = String(firstEvent.sessionId || "");
|
|
2268
|
+
if (!slug) slug = String(firstEvent.slug || "");
|
|
2269
|
+
if (!cwd) cwd = String(firstEvent.cwd || "");
|
|
2270
|
+
if (!version) version = String(firstEvent.version || "");
|
|
2271
|
+
if (!gitBranch) gitBranch = String(firstEvent.gitBranch || "");
|
|
2272
|
+
}
|
|
2273
|
+
try {
|
|
2274
|
+
const fstat = await stat2(outputFile);
|
|
2275
|
+
const created = fstat.birthtimeMs || fstat.ctimeMs;
|
|
2276
|
+
if (created < startTime) startTime = created;
|
|
2277
|
+
if (fstat.mtimeMs > lastActivity) lastActivity = fstat.mtimeMs;
|
|
2278
|
+
} catch {
|
|
2279
|
+
}
|
|
2280
|
+
const result = await findModelAndUsageAsync(outputFile);
|
|
2281
|
+
if (!model && result.model) model = result.model;
|
|
2282
|
+
totalUsage.inputTokens += result.usage.inputTokens;
|
|
2283
|
+
totalUsage.cacheCreationTokens += result.usage.cacheCreationTokens;
|
|
2284
|
+
totalUsage.cacheReadTokens += result.usage.cacheReadTokens;
|
|
2285
|
+
totalUsage.outputTokens += result.usage.outputTokens;
|
|
2286
|
+
}
|
|
2287
|
+
if (!sessionId && !slug) continue;
|
|
2288
|
+
if (sessionMap.has(sessionId || projectName)) continue;
|
|
2289
|
+
const normCwd = normalisePath(cwd);
|
|
2290
|
+
const matchingProcess = processes.find((p) => p.cwd && normalisePath(p.cwd) === normCwd);
|
|
2291
|
+
const session = {
|
|
2292
|
+
sessionId,
|
|
2293
|
+
slug: slug || sessionId.slice(0, 12),
|
|
2294
|
+
project: projectName.replace(/-/g, "/"),
|
|
2295
|
+
cwd,
|
|
2296
|
+
model: model || "unknown",
|
|
2297
|
+
version,
|
|
2298
|
+
gitBranch,
|
|
2299
|
+
pid: matchingProcess?.pid ?? null,
|
|
2300
|
+
cpu: matchingProcess?.cpu ?? 0,
|
|
2301
|
+
mem: matchingProcess?.mem ?? 0,
|
|
2302
|
+
memMB: matchingProcess ? Math.round(matchingProcess.memKB / 1024) : 0,
|
|
2303
|
+
agentCount: agentIds.length,
|
|
2304
|
+
agentIds,
|
|
2305
|
+
outputFiles,
|
|
2306
|
+
startTime: startTime === Infinity ? Date.now() : startTime,
|
|
2307
|
+
lastActivity,
|
|
2308
|
+
usage: totalUsage
|
|
2309
|
+
};
|
|
2310
|
+
sessionMap.set(sessionId || projectName, session);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
var discoverSessionsAsync = async (allUsers) => {
|
|
2316
|
+
const processes = await getClaudeProcessesAsync();
|
|
2317
|
+
const sessionMap = /* @__PURE__ */ new Map();
|
|
2318
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
2319
|
+
await discoverFromProjectsAsync(allUsers, processes, sessionMap, seenFiles);
|
|
2320
|
+
await discoverFromTmpAsync(allUsers, processes, sessionMap, seenFiles);
|
|
2321
|
+
pruneFileMetaCache(seenFiles);
|
|
2322
|
+
return Array.from(sessionMap.values()).sort((a, b) => {
|
|
2323
|
+
const aActive = a.pid !== null ? 1 : 0;
|
|
2324
|
+
const bActive = b.pid !== null ? 1 : 0;
|
|
2325
|
+
if (aActive !== bActive) return bActive - aActive;
|
|
2326
|
+
return b.lastActivity - a.lastActivity;
|
|
2327
|
+
});
|
|
2328
|
+
};
|
|
2329
|
+
var triggerRefresh = (allUsers) => {
|
|
2330
|
+
if (isRefreshInFlight()) return;
|
|
2331
|
+
setRefreshInFlight(true);
|
|
2332
|
+
discoverSessionsAsync(allUsers).then((result) => setCachedSessions(result)).catch(() => {
|
|
2333
|
+
}).finally(() => setRefreshInFlight(false));
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
// src/ui/hooks/useSessions.ts
|
|
2337
|
+
var ACTIVE_POLL_MS = 1e4;
|
|
2338
|
+
var IDLE_POLL_MS = 3e4;
|
|
1947
2339
|
var getGroupKey = (session) => {
|
|
1948
2340
|
if (session.cwd) {
|
|
1949
2341
|
const parts = session.cwd.replace(/\/+$/, "").split("/");
|
|
@@ -1955,19 +2347,16 @@ var getGroupKey = (session) => {
|
|
|
1955
2347
|
}
|
|
1956
2348
|
return session.slug;
|
|
1957
2349
|
};
|
|
1958
|
-
var buildGroups = (
|
|
2350
|
+
var buildGroups = (sessions2, expandedKeys) => {
|
|
1959
2351
|
const byKey = /* @__PURE__ */ new Map();
|
|
1960
|
-
for (const s of
|
|
2352
|
+
for (const s of sessions2) {
|
|
1961
2353
|
const key = getGroupKey(s);
|
|
1962
2354
|
const existing = byKey.get(key);
|
|
1963
|
-
if (existing)
|
|
1964
|
-
|
|
1965
|
-
} else {
|
|
1966
|
-
byKey.set(key, { displayName: getDisplayName(s), sessions: [s] });
|
|
1967
|
-
}
|
|
2355
|
+
if (existing) existing.push(s);
|
|
2356
|
+
else byKey.set(key, [s]);
|
|
1968
2357
|
}
|
|
1969
2358
|
const groups = [];
|
|
1970
|
-
for (const [key,
|
|
2359
|
+
for (const [key, list] of byKey) {
|
|
1971
2360
|
list.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
1972
2361
|
const totalIn = list.reduce((sum, s) => sum + s.usage.inputTokens + s.usage.cacheReadTokens, 0);
|
|
1973
2362
|
const totalOut = list.reduce((sum, s) => sum + s.usage.outputTokens, 0);
|
|
@@ -2002,52 +2391,64 @@ var buildVisibleItems = (groups) => {
|
|
|
2002
2391
|
}
|
|
2003
2392
|
return items;
|
|
2004
2393
|
};
|
|
2394
|
+
var enrichAndFilter = (found, usageOverrides, filter, archivedIds, viewingArchive) => {
|
|
2395
|
+
const nicknames = getNicknames();
|
|
2396
|
+
let enriched = found.map((s) => {
|
|
2397
|
+
const override = usageOverrides.get(s.sessionId);
|
|
2398
|
+
return {
|
|
2399
|
+
...s,
|
|
2400
|
+
nickname: nicknames[s.sessionId],
|
|
2401
|
+
usage: override ? {
|
|
2402
|
+
inputTokens: s.usage.inputTokens + override.inputTokens,
|
|
2403
|
+
cacheCreationTokens: s.usage.cacheCreationTokens + override.cacheCreationTokens,
|
|
2404
|
+
cacheReadTokens: s.usage.cacheReadTokens + override.cacheReadTokens,
|
|
2405
|
+
outputTokens: s.usage.outputTokens + override.outputTokens
|
|
2406
|
+
} : s.usage
|
|
2407
|
+
};
|
|
2408
|
+
});
|
|
2409
|
+
if (archivedIds && archivedIds.size > 0) {
|
|
2410
|
+
if (viewingArchive) {
|
|
2411
|
+
enriched = enriched.filter((s) => archivedIds.has(s.sessionId));
|
|
2412
|
+
} else {
|
|
2413
|
+
enriched = enriched.filter((s) => !archivedIds.has(s.sessionId));
|
|
2414
|
+
}
|
|
2415
|
+
} else if (viewingArchive) {
|
|
2416
|
+
enriched = [];
|
|
2417
|
+
}
|
|
2418
|
+
if (filter) {
|
|
2419
|
+
const lower = filter.toLowerCase();
|
|
2420
|
+
enriched = enriched.filter(
|
|
2421
|
+
(s) => s.slug.toLowerCase().includes(lower) || s.nickname?.toLowerCase().includes(lower) || s.project.toLowerCase().includes(lower) || s.model.toLowerCase().includes(lower)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
return enriched;
|
|
2425
|
+
};
|
|
2005
2426
|
var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
|
|
2006
|
-
const [
|
|
2427
|
+
const [sessions2, setSessions] = useState8([]);
|
|
2007
2428
|
const [selectedIndex, setSelectedIndex] = useState8(0);
|
|
2008
2429
|
const [expandedKeys, setExpandedKeys] = useState8(/* @__PURE__ */ new Set());
|
|
2009
2430
|
const usageOverrides = useRef3(/* @__PURE__ */ new Map());
|
|
2010
2431
|
const refresh = useCallback2(() => {
|
|
2011
|
-
const found =
|
|
2012
|
-
const
|
|
2013
|
-
const enriched = found.map((s) => {
|
|
2014
|
-
const override = usageOverrides.current.get(s.sessionId);
|
|
2015
|
-
return {
|
|
2016
|
-
...s,
|
|
2017
|
-
nickname: nicknames[s.sessionId],
|
|
2018
|
-
usage: override ? {
|
|
2019
|
-
inputTokens: s.usage.inputTokens + override.inputTokens,
|
|
2020
|
-
cacheCreationTokens: s.usage.cacheCreationTokens + override.cacheCreationTokens,
|
|
2021
|
-
cacheReadTokens: s.usage.cacheReadTokens + override.cacheReadTokens,
|
|
2022
|
-
outputTokens: s.usage.outputTokens + override.outputTokens
|
|
2023
|
-
} : s.usage
|
|
2024
|
-
};
|
|
2025
|
-
});
|
|
2026
|
-
let filtered = enriched;
|
|
2027
|
-
if (archivedIds && archivedIds.size > 0) {
|
|
2028
|
-
if (viewingArchive) {
|
|
2029
|
-
filtered = filtered.filter((s) => archivedIds.has(s.sessionId));
|
|
2030
|
-
} else {
|
|
2031
|
-
filtered = filtered.filter((s) => !archivedIds.has(s.sessionId));
|
|
2032
|
-
}
|
|
2033
|
-
} else if (viewingArchive) {
|
|
2034
|
-
filtered = [];
|
|
2035
|
-
}
|
|
2036
|
-
if (filter) {
|
|
2037
|
-
const lower = filter.toLowerCase();
|
|
2038
|
-
filtered = filtered.filter(
|
|
2039
|
-
(s) => s.slug.toLowerCase().includes(lower) || s.nickname?.toLowerCase().includes(lower) || s.project.toLowerCase().includes(lower) || s.model.toLowerCase().includes(lower)
|
|
2040
|
-
);
|
|
2041
|
-
}
|
|
2432
|
+
const found = getCachedSessions();
|
|
2433
|
+
const filtered = enrichAndFilter(found, usageOverrides.current, filter, archivedIds, viewingArchive);
|
|
2042
2434
|
setSessions(filtered);
|
|
2435
|
+
triggerRefresh(allUsers);
|
|
2043
2436
|
}, [allUsers, filter, archivedIds, viewingArchive]);
|
|
2437
|
+
useEffect5(() => {
|
|
2438
|
+
const unsubscribe = subscribe(() => {
|
|
2439
|
+
const found = getCachedSessions();
|
|
2440
|
+
const filtered = enrichAndFilter(found, usageOverrides.current, filter, archivedIds, viewingArchive);
|
|
2441
|
+
setSessions(filtered);
|
|
2442
|
+
});
|
|
2443
|
+
return unsubscribe;
|
|
2444
|
+
}, [filter, archivedIds, viewingArchive]);
|
|
2044
2445
|
useEffect5(() => {
|
|
2045
2446
|
refresh();
|
|
2046
|
-
const pollMs =
|
|
2047
|
-
const interval = setInterval(
|
|
2447
|
+
const pollMs = sessions2.length > 0 ? ACTIVE_POLL_MS : IDLE_POLL_MS;
|
|
2448
|
+
const interval = setInterval(() => triggerRefresh(allUsers), pollMs);
|
|
2048
2449
|
return () => clearInterval(interval);
|
|
2049
|
-
}, [refresh,
|
|
2050
|
-
const groups = useMemo2(() => buildGroups(
|
|
2450
|
+
}, [refresh, sessions2.length > 0]);
|
|
2451
|
+
const groups = useMemo2(() => buildGroups(sessions2, expandedKeys), [sessions2, expandedKeys]);
|
|
2051
2452
|
const visibleItems = useMemo2(() => buildVisibleItems(groups), [groups]);
|
|
2052
2453
|
const itemCountRef = useRef3(visibleItems.length);
|
|
2053
2454
|
itemCountRef.current = visibleItems.length;
|
|
@@ -2085,7 +2486,7 @@ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
|
|
|
2085
2486
|
}
|
|
2086
2487
|
}, []);
|
|
2087
2488
|
return {
|
|
2088
|
-
sessions,
|
|
2489
|
+
sessions: sessions2,
|
|
2089
2490
|
groups,
|
|
2090
2491
|
visibleItems,
|
|
2091
2492
|
selectedSession,
|
|
@@ -2106,24 +2507,32 @@ var MAX_EVENTS = 200;
|
|
|
2106
2507
|
var useActivityStream = (session, allUsers) => {
|
|
2107
2508
|
const [events, setEvents] = useState9([]);
|
|
2108
2509
|
const watcherRef = useRef4(null);
|
|
2109
|
-
const
|
|
2110
|
-
const sessionKey =
|
|
2111
|
-
const sessionIdSet = new Set(
|
|
2510
|
+
const sessions2 = session === null ? [] : Array.isArray(session) ? session : [session];
|
|
2511
|
+
const sessionKey = sessions2.map((s) => s.sessionId).sort().join(",");
|
|
2512
|
+
const sessionIdSet = new Set(sessions2.map((s) => s.sessionId));
|
|
2112
2513
|
useEffect6(() => {
|
|
2113
2514
|
setEvents([]);
|
|
2114
|
-
if (
|
|
2115
|
-
|
|
2116
|
-
const
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2515
|
+
if (sessions2.length === 0) return;
|
|
2516
|
+
let cancelled = false;
|
|
2517
|
+
const loadExisting = async () => {
|
|
2518
|
+
const tempWatcher = new Watcher(() => {
|
|
2519
|
+
}, allUsers);
|
|
2520
|
+
const allFiles = sessions2.flatMap((s) => s.outputFiles);
|
|
2521
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2522
|
+
const existingCalls = [];
|
|
2523
|
+
for (const file of allFiles) {
|
|
2524
|
+
if (cancelled) return;
|
|
2525
|
+
if (seen.has(file)) continue;
|
|
2526
|
+
seen.add(file);
|
|
2527
|
+
if (cancelled) return;
|
|
2528
|
+
const calls = await tempWatcher.readExistingAsync(file);
|
|
2529
|
+
existingCalls.push(...calls);
|
|
2530
|
+
}
|
|
2531
|
+
if (cancelled) return;
|
|
2532
|
+
existingCalls.sort((a, b) => a.timestamp - b.timestamp);
|
|
2533
|
+
setEvents(existingCalls.slice(-MAX_EVENTS));
|
|
2534
|
+
};
|
|
2535
|
+
loadExisting();
|
|
2127
2536
|
const handler = (calls) => {
|
|
2128
2537
|
const matched = calls.filter((c) => sessionIdSet.has(c.sessionId));
|
|
2129
2538
|
if (matched.length === 0) return;
|
|
@@ -2133,6 +2542,7 @@ var useActivityStream = (session, allUsers) => {
|
|
|
2133
2542
|
watcherRef.current = watcher;
|
|
2134
2543
|
watcher.start();
|
|
2135
2544
|
return () => {
|
|
2545
|
+
cancelled = true;
|
|
2136
2546
|
watcher.stop();
|
|
2137
2547
|
watcherRef.current = null;
|
|
2138
2548
|
};
|
|
@@ -2507,6 +2917,14 @@ var useKeyHandler = (deps) => {
|
|
|
2507
2917
|
return;
|
|
2508
2918
|
}
|
|
2509
2919
|
if (d.activePanel === "sessions") {
|
|
2920
|
+
if (matchKey(d.kb.sidebarWider, input, key)) {
|
|
2921
|
+
d.setSidebarWidth((w) => Math.min(w + 5, 60));
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
if (matchKey(d.kb.sidebarNarrower, input, key)) {
|
|
2925
|
+
d.setSidebarWidth((w) => Math.max(w - 5, 20));
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2510
2928
|
if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.selectNext();
|
|
2511
2929
|
if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.selectPrev();
|
|
2512
2930
|
}
|
|
@@ -2537,19 +2955,19 @@ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
|
|
|
2537
2955
|
const [updateInfo, setUpdateInfo] = useState12(null);
|
|
2538
2956
|
useEffect8(() => {
|
|
2539
2957
|
if (disabled || !checkOnLaunch) return;
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2958
|
+
let cancelled = false;
|
|
2959
|
+
const check = () => {
|
|
2960
|
+
checkForUpdate().then((i) => {
|
|
2961
|
+
if (!cancelled && i.available) setUpdateInfo(i);
|
|
2962
|
+
}).catch(() => {
|
|
2963
|
+
});
|
|
2964
|
+
};
|
|
2965
|
+
check();
|
|
2966
|
+
const iv = setInterval(check, checkInterval);
|
|
2967
|
+
return () => {
|
|
2968
|
+
cancelled = true;
|
|
2969
|
+
clearInterval(iv);
|
|
2970
|
+
};
|
|
2553
2971
|
}, []);
|
|
2554
2972
|
return updateInfo;
|
|
2555
2973
|
};
|
|
@@ -2558,35 +2976,35 @@ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
|
|
|
2558
2976
|
import { useState as useState13, useCallback as useCallback4 } from "react";
|
|
2559
2977
|
|
|
2560
2978
|
// src/hooks/installer.ts
|
|
2561
|
-
import { existsSync, readFileSync
|
|
2562
|
-
import { join as
|
|
2979
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync as mkdirSync2, chmodSync } from "fs";
|
|
2980
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
2563
2981
|
import { homedir } from "os";
|
|
2564
2982
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2565
2983
|
var HOOK_FILENAME = "agenttop-guard.py";
|
|
2566
|
-
var SETTINGS_PATH =
|
|
2984
|
+
var SETTINGS_PATH = join3(homedir(), ".claude", "settings.json");
|
|
2567
2985
|
var getHookSource = () => {
|
|
2568
2986
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
2569
|
-
const srcHooksDir =
|
|
2570
|
-
const distHooksDir =
|
|
2987
|
+
const srcHooksDir = join3(dirname3(thisFile), "..", "src", "hooks");
|
|
2988
|
+
const distHooksDir = join3(dirname3(thisFile), "hooks");
|
|
2571
2989
|
for (const dir of [distHooksDir, srcHooksDir]) {
|
|
2572
|
-
const path =
|
|
2990
|
+
const path = join3(dir, HOOK_FILENAME);
|
|
2573
2991
|
if (existsSync(path)) return path;
|
|
2574
2992
|
}
|
|
2575
|
-
const npmGlobalPath =
|
|
2993
|
+
const npmGlobalPath = join3(dirname3(thisFile), "..", "hooks", HOOK_FILENAME);
|
|
2576
2994
|
if (existsSync(npmGlobalPath)) return npmGlobalPath;
|
|
2577
2995
|
throw new Error(`cannot find ${HOOK_FILENAME} \u2014 is agenttop installed correctly?`);
|
|
2578
2996
|
};
|
|
2579
2997
|
var getHookTarget = () => {
|
|
2580
|
-
const claudeHooksDir =
|
|
2998
|
+
const claudeHooksDir = join3(homedir(), ".claude", "hooks");
|
|
2581
2999
|
mkdirSync2(claudeHooksDir, { recursive: true });
|
|
2582
|
-
return
|
|
3000
|
+
return join3(claudeHooksDir, HOOK_FILENAME);
|
|
2583
3001
|
};
|
|
2584
3002
|
var readSettings = () => {
|
|
2585
3003
|
if (!existsSync(SETTINGS_PATH)) {
|
|
2586
3004
|
return {};
|
|
2587
3005
|
}
|
|
2588
3006
|
try {
|
|
2589
|
-
return JSON.parse(
|
|
3007
|
+
return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2590
3008
|
} catch {
|
|
2591
3009
|
return {};
|
|
2592
3010
|
}
|
|
@@ -2653,20 +3071,20 @@ var uninstallHooks = () => {
|
|
|
2653
3071
|
};
|
|
2654
3072
|
|
|
2655
3073
|
// src/install-mcp.ts
|
|
2656
|
-
import { readFileSync as
|
|
2657
|
-
import { join as
|
|
3074
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3075
|
+
import { join as join4 } from "path";
|
|
2658
3076
|
import { homedir as homedir2 } from "os";
|
|
2659
3077
|
var installMcpConfig = () => {
|
|
2660
|
-
const settingsPath =
|
|
3078
|
+
const settingsPath = join4(homedir2(), ".claude", "settings.json");
|
|
2661
3079
|
let settings = {};
|
|
2662
3080
|
try {
|
|
2663
|
-
settings = JSON.parse(
|
|
3081
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
2664
3082
|
} catch {
|
|
2665
3083
|
}
|
|
2666
3084
|
const mcpServers = settings.mcpServers ?? {};
|
|
2667
3085
|
mcpServers.agenttop = { command: "agenttop", args: ["--mcp"] };
|
|
2668
3086
|
settings.mcpServers = mcpServers;
|
|
2669
|
-
mkdirSync3(
|
|
3087
|
+
mkdirSync3(join4(homedir2(), ".claude"), { recursive: true });
|
|
2670
3088
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
2671
3089
|
process.stdout.write("agenttop MCP server registered in Claude Code settings\n");
|
|
2672
3090
|
process.stdout.write(` settings: ${settingsPath}
|
|
@@ -2867,7 +3285,19 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
2867
3285
|
null
|
|
2868
3286
|
);
|
|
2869
3287
|
const [archivedIds, setArchivedIds] = useState15(() => new Set(Object.keys(getArchived())));
|
|
3288
|
+
const [sidebarWidth, setSidebarWidth] = useState15(() => initialConfig.sidebarWidth ?? 30);
|
|
2870
3289
|
const refreshArchived = useCallback6(() => setArchivedIds(new Set(Object.keys(getArchived()))), []);
|
|
3290
|
+
const persistSidebarWidth = useCallback6((v) => {
|
|
3291
|
+
setSidebarWidth((prev) => {
|
|
3292
|
+
const next = typeof v === "function" ? v(prev) : v;
|
|
3293
|
+
if (next !== prev) {
|
|
3294
|
+
const cfg = loadConfig();
|
|
3295
|
+
cfg.sidebarWidth = next;
|
|
3296
|
+
saveConfig(cfg);
|
|
3297
|
+
}
|
|
3298
|
+
return next;
|
|
3299
|
+
});
|
|
3300
|
+
}, []);
|
|
2871
3301
|
const updateInfo = useUpdateChecker(
|
|
2872
3302
|
options.noUpdates,
|
|
2873
3303
|
setup.liveConfig.updates.checkOnLaunch,
|
|
@@ -2877,7 +3307,7 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
2877
3307
|
applyTheme(resolveTheme(setup.liveConfig.theme, setup.liveConfig.customThemes));
|
|
2878
3308
|
}, [setup.liveConfig.theme, setup.liveConfig.customThemes]);
|
|
2879
3309
|
const {
|
|
2880
|
-
sessions,
|
|
3310
|
+
sessions: sessions2,
|
|
2881
3311
|
visibleItems,
|
|
2882
3312
|
selectedSession,
|
|
2883
3313
|
selectedGroup,
|
|
@@ -3000,6 +3430,7 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
3000
3430
|
setActivityScroll,
|
|
3001
3431
|
setConfirmAction,
|
|
3002
3432
|
setUpdateStatus,
|
|
3433
|
+
setSidebarWidth: persistSidebarWidth,
|
|
3003
3434
|
nicknameInput,
|
|
3004
3435
|
filterInput,
|
|
3005
3436
|
onNickname: (id) => {
|
|
@@ -3125,7 +3556,7 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
3125
3556
|
}
|
|
3126
3557
|
);
|
|
3127
3558
|
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: termHeight, children: [
|
|
3128
|
-
/* @__PURE__ */ jsx15(StatusBar, { sessionCount:
|
|
3559
|
+
/* @__PURE__ */ jsx15(StatusBar, { sessionCount: sessions2.length, alertCount: alerts.length, version, updateInfo }),
|
|
3129
3560
|
/* @__PURE__ */ jsxs15(Box15, { flexGrow: 1, height: mainHeight, children: [
|
|
3130
3561
|
/* @__PURE__ */ jsx15(
|
|
3131
3562
|
SessionList,
|
|
@@ -3136,7 +3567,8 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
3136
3567
|
height: mainHeight,
|
|
3137
3568
|
filter: filter || void 0,
|
|
3138
3569
|
viewingArchive,
|
|
3139
|
-
totalSessions:
|
|
3570
|
+
totalSessions: sessions2.length,
|
|
3571
|
+
sidebarWidth
|
|
3140
3572
|
}
|
|
3141
3573
|
),
|
|
3142
3574
|
rightPanel
|
|
@@ -3176,11 +3608,11 @@ var formatTokens3 = (n) => {
|
|
|
3176
3608
|
};
|
|
3177
3609
|
var runStreamMode = (options, isJson) => {
|
|
3178
3610
|
const engine = options.noSecurity ? null : new SecurityEngine(options.alertLevel);
|
|
3179
|
-
const
|
|
3611
|
+
const sessions2 = discoverSessions(options.allUsers);
|
|
3180
3612
|
if (isJson) {
|
|
3181
|
-
write(JSON.stringify({ type: "sessions", data:
|
|
3613
|
+
write(JSON.stringify({ type: "sessions", data: sessions2 }));
|
|
3182
3614
|
} else {
|
|
3183
|
-
for (const s of
|
|
3615
|
+
for (const s of sessions2) {
|
|
3184
3616
|
write(
|
|
3185
3617
|
`SESSION ${s.slug} | ${s.model} | ${s.cwd} | CPU ${s.cpu}% | ${s.memMB}MB | ${formatTokens3(s.usage.inputTokens)} in / ${formatTokens3(s.usage.outputTokens)} out`
|
|
3186
3618
|
);
|
|
@@ -3236,8 +3668,8 @@ process.title = "agenttop";
|
|
|
3236
3668
|
var getVersion = () => {
|
|
3237
3669
|
try {
|
|
3238
3670
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
3239
|
-
const pkgPath =
|
|
3240
|
-
const pkg = JSON.parse(
|
|
3671
|
+
const pkgPath = join5(dirname4(thisFile), "..", "package.json");
|
|
3672
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
3241
3673
|
return pkg.version || "0.0.0";
|
|
3242
3674
|
} catch {
|
|
3243
3675
|
return "0.0.0";
|