agenttop 0.9.3 → 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 +582 -135
- 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,18 +626,6 @@ 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 getDisplayName = (s) => {
|
|
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
629
|
const displayName = truncate(getDisplayName(session), INNER_WIDTH - 4);
|
|
624
630
|
return /* @__PURE__ */ jsxs2(
|
|
625
631
|
Box2,
|
|
@@ -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,6 +1940,400 @@ 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";
|
|
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);
|
|
1972
|
+
}
|
|
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
|
+
}
|
|
1980
|
+
}
|
|
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
|
|
1933
2337
|
var ACTIVE_POLL_MS = 1e4;
|
|
1934
2338
|
var IDLE_POLL_MS = 3e4;
|
|
1935
2339
|
var getGroupKey = (session) => {
|
|
@@ -1943,9 +2347,9 @@ var getGroupKey = (session) => {
|
|
|
1943
2347
|
}
|
|
1944
2348
|
return session.slug;
|
|
1945
2349
|
};
|
|
1946
|
-
var buildGroups = (
|
|
2350
|
+
var buildGroups = (sessions2, expandedKeys) => {
|
|
1947
2351
|
const byKey = /* @__PURE__ */ new Map();
|
|
1948
|
-
for (const s of
|
|
2352
|
+
for (const s of sessions2) {
|
|
1949
2353
|
const key = getGroupKey(s);
|
|
1950
2354
|
const existing = byKey.get(key);
|
|
1951
2355
|
if (existing) existing.push(s);
|
|
@@ -1987,52 +2391,64 @@ var buildVisibleItems = (groups) => {
|
|
|
1987
2391
|
}
|
|
1988
2392
|
return items;
|
|
1989
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
|
+
};
|
|
1990
2426
|
var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
|
|
1991
|
-
const [
|
|
2427
|
+
const [sessions2, setSessions] = useState8([]);
|
|
1992
2428
|
const [selectedIndex, setSelectedIndex] = useState8(0);
|
|
1993
2429
|
const [expandedKeys, setExpandedKeys] = useState8(/* @__PURE__ */ new Set());
|
|
1994
2430
|
const usageOverrides = useRef3(/* @__PURE__ */ new Map());
|
|
1995
2431
|
const refresh = useCallback2(() => {
|
|
1996
|
-
const found =
|
|
1997
|
-
const
|
|
1998
|
-
const enriched = found.map((s) => {
|
|
1999
|
-
const override = usageOverrides.current.get(s.sessionId);
|
|
2000
|
-
return {
|
|
2001
|
-
...s,
|
|
2002
|
-
nickname: nicknames[s.sessionId],
|
|
2003
|
-
usage: override ? {
|
|
2004
|
-
inputTokens: s.usage.inputTokens + override.inputTokens,
|
|
2005
|
-
cacheCreationTokens: s.usage.cacheCreationTokens + override.cacheCreationTokens,
|
|
2006
|
-
cacheReadTokens: s.usage.cacheReadTokens + override.cacheReadTokens,
|
|
2007
|
-
outputTokens: s.usage.outputTokens + override.outputTokens
|
|
2008
|
-
} : s.usage
|
|
2009
|
-
};
|
|
2010
|
-
});
|
|
2011
|
-
let filtered = enriched;
|
|
2012
|
-
if (archivedIds && archivedIds.size > 0) {
|
|
2013
|
-
if (viewingArchive) {
|
|
2014
|
-
filtered = filtered.filter((s) => archivedIds.has(s.sessionId));
|
|
2015
|
-
} else {
|
|
2016
|
-
filtered = filtered.filter((s) => !archivedIds.has(s.sessionId));
|
|
2017
|
-
}
|
|
2018
|
-
} else if (viewingArchive) {
|
|
2019
|
-
filtered = [];
|
|
2020
|
-
}
|
|
2021
|
-
if (filter) {
|
|
2022
|
-
const lower = filter.toLowerCase();
|
|
2023
|
-
filtered = filtered.filter(
|
|
2024
|
-
(s) => s.slug.toLowerCase().includes(lower) || s.nickname?.toLowerCase().includes(lower) || s.project.toLowerCase().includes(lower) || s.model.toLowerCase().includes(lower)
|
|
2025
|
-
);
|
|
2026
|
-
}
|
|
2432
|
+
const found = getCachedSessions();
|
|
2433
|
+
const filtered = enrichAndFilter(found, usageOverrides.current, filter, archivedIds, viewingArchive);
|
|
2027
2434
|
setSessions(filtered);
|
|
2435
|
+
triggerRefresh(allUsers);
|
|
2028
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]);
|
|
2029
2445
|
useEffect5(() => {
|
|
2030
2446
|
refresh();
|
|
2031
|
-
const pollMs =
|
|
2032
|
-
const interval = setInterval(
|
|
2447
|
+
const pollMs = sessions2.length > 0 ? ACTIVE_POLL_MS : IDLE_POLL_MS;
|
|
2448
|
+
const interval = setInterval(() => triggerRefresh(allUsers), pollMs);
|
|
2033
2449
|
return () => clearInterval(interval);
|
|
2034
|
-
}, [refresh,
|
|
2035
|
-
const groups = useMemo2(() => buildGroups(
|
|
2450
|
+
}, [refresh, sessions2.length > 0]);
|
|
2451
|
+
const groups = useMemo2(() => buildGroups(sessions2, expandedKeys), [sessions2, expandedKeys]);
|
|
2036
2452
|
const visibleItems = useMemo2(() => buildVisibleItems(groups), [groups]);
|
|
2037
2453
|
const itemCountRef = useRef3(visibleItems.length);
|
|
2038
2454
|
itemCountRef.current = visibleItems.length;
|
|
@@ -2070,7 +2486,7 @@ var useSessions = (allUsers, filter, archivedIds, viewingArchive) => {
|
|
|
2070
2486
|
}
|
|
2071
2487
|
}, []);
|
|
2072
2488
|
return {
|
|
2073
|
-
sessions,
|
|
2489
|
+
sessions: sessions2,
|
|
2074
2490
|
groups,
|
|
2075
2491
|
visibleItems,
|
|
2076
2492
|
selectedSession,
|
|
@@ -2091,24 +2507,32 @@ var MAX_EVENTS = 200;
|
|
|
2091
2507
|
var useActivityStream = (session, allUsers) => {
|
|
2092
2508
|
const [events, setEvents] = useState9([]);
|
|
2093
2509
|
const watcherRef = useRef4(null);
|
|
2094
|
-
const
|
|
2095
|
-
const sessionKey =
|
|
2096
|
-
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));
|
|
2097
2513
|
useEffect6(() => {
|
|
2098
2514
|
setEvents([]);
|
|
2099
|
-
if (
|
|
2100
|
-
|
|
2101
|
-
const
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
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();
|
|
2112
2536
|
const handler = (calls) => {
|
|
2113
2537
|
const matched = calls.filter((c) => sessionIdSet.has(c.sessionId));
|
|
2114
2538
|
if (matched.length === 0) return;
|
|
@@ -2118,6 +2542,7 @@ var useActivityStream = (session, allUsers) => {
|
|
|
2118
2542
|
watcherRef.current = watcher;
|
|
2119
2543
|
watcher.start();
|
|
2120
2544
|
return () => {
|
|
2545
|
+
cancelled = true;
|
|
2121
2546
|
watcher.stop();
|
|
2122
2547
|
watcherRef.current = null;
|
|
2123
2548
|
};
|
|
@@ -2492,6 +2917,14 @@ var useKeyHandler = (deps) => {
|
|
|
2492
2917
|
return;
|
|
2493
2918
|
}
|
|
2494
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
|
+
}
|
|
2495
2928
|
if (matchKey(d.kb.navDown, input, key) || key.downArrow) d.selectNext();
|
|
2496
2929
|
if (matchKey(d.kb.navUp, input, key) || key.upArrow) d.selectPrev();
|
|
2497
2930
|
}
|
|
@@ -2522,19 +2955,19 @@ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
|
|
|
2522
2955
|
const [updateInfo, setUpdateInfo] = useState12(null);
|
|
2523
2956
|
useEffect8(() => {
|
|
2524
2957
|
if (disabled || !checkOnLaunch) return;
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
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
|
+
};
|
|
2538
2971
|
}, []);
|
|
2539
2972
|
return updateInfo;
|
|
2540
2973
|
};
|
|
@@ -2543,35 +2976,35 @@ var useUpdateChecker = (disabled, checkOnLaunch, checkInterval) => {
|
|
|
2543
2976
|
import { useState as useState13, useCallback as useCallback4 } from "react";
|
|
2544
2977
|
|
|
2545
2978
|
// src/hooks/installer.ts
|
|
2546
|
-
import { existsSync, readFileSync
|
|
2547
|
-
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";
|
|
2548
2981
|
import { homedir } from "os";
|
|
2549
2982
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2550
2983
|
var HOOK_FILENAME = "agenttop-guard.py";
|
|
2551
|
-
var SETTINGS_PATH =
|
|
2984
|
+
var SETTINGS_PATH = join3(homedir(), ".claude", "settings.json");
|
|
2552
2985
|
var getHookSource = () => {
|
|
2553
2986
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
2554
|
-
const srcHooksDir =
|
|
2555
|
-
const distHooksDir =
|
|
2987
|
+
const srcHooksDir = join3(dirname3(thisFile), "..", "src", "hooks");
|
|
2988
|
+
const distHooksDir = join3(dirname3(thisFile), "hooks");
|
|
2556
2989
|
for (const dir of [distHooksDir, srcHooksDir]) {
|
|
2557
|
-
const path =
|
|
2990
|
+
const path = join3(dir, HOOK_FILENAME);
|
|
2558
2991
|
if (existsSync(path)) return path;
|
|
2559
2992
|
}
|
|
2560
|
-
const npmGlobalPath =
|
|
2993
|
+
const npmGlobalPath = join3(dirname3(thisFile), "..", "hooks", HOOK_FILENAME);
|
|
2561
2994
|
if (existsSync(npmGlobalPath)) return npmGlobalPath;
|
|
2562
2995
|
throw new Error(`cannot find ${HOOK_FILENAME} \u2014 is agenttop installed correctly?`);
|
|
2563
2996
|
};
|
|
2564
2997
|
var getHookTarget = () => {
|
|
2565
|
-
const claudeHooksDir =
|
|
2998
|
+
const claudeHooksDir = join3(homedir(), ".claude", "hooks");
|
|
2566
2999
|
mkdirSync2(claudeHooksDir, { recursive: true });
|
|
2567
|
-
return
|
|
3000
|
+
return join3(claudeHooksDir, HOOK_FILENAME);
|
|
2568
3001
|
};
|
|
2569
3002
|
var readSettings = () => {
|
|
2570
3003
|
if (!existsSync(SETTINGS_PATH)) {
|
|
2571
3004
|
return {};
|
|
2572
3005
|
}
|
|
2573
3006
|
try {
|
|
2574
|
-
return JSON.parse(
|
|
3007
|
+
return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2575
3008
|
} catch {
|
|
2576
3009
|
return {};
|
|
2577
3010
|
}
|
|
@@ -2638,20 +3071,20 @@ var uninstallHooks = () => {
|
|
|
2638
3071
|
};
|
|
2639
3072
|
|
|
2640
3073
|
// src/install-mcp.ts
|
|
2641
|
-
import { readFileSync as
|
|
2642
|
-
import { join as
|
|
3074
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3075
|
+
import { join as join4 } from "path";
|
|
2643
3076
|
import { homedir as homedir2 } from "os";
|
|
2644
3077
|
var installMcpConfig = () => {
|
|
2645
|
-
const settingsPath =
|
|
3078
|
+
const settingsPath = join4(homedir2(), ".claude", "settings.json");
|
|
2646
3079
|
let settings = {};
|
|
2647
3080
|
try {
|
|
2648
|
-
settings = JSON.parse(
|
|
3081
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
2649
3082
|
} catch {
|
|
2650
3083
|
}
|
|
2651
3084
|
const mcpServers = settings.mcpServers ?? {};
|
|
2652
3085
|
mcpServers.agenttop = { command: "agenttop", args: ["--mcp"] };
|
|
2653
3086
|
settings.mcpServers = mcpServers;
|
|
2654
|
-
mkdirSync3(
|
|
3087
|
+
mkdirSync3(join4(homedir2(), ".claude"), { recursive: true });
|
|
2655
3088
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
2656
3089
|
process.stdout.write("agenttop MCP server registered in Claude Code settings\n");
|
|
2657
3090
|
process.stdout.write(` settings: ${settingsPath}
|
|
@@ -2852,7 +3285,19 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
2852
3285
|
null
|
|
2853
3286
|
);
|
|
2854
3287
|
const [archivedIds, setArchivedIds] = useState15(() => new Set(Object.keys(getArchived())));
|
|
3288
|
+
const [sidebarWidth, setSidebarWidth] = useState15(() => initialConfig.sidebarWidth ?? 30);
|
|
2855
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
|
+
}, []);
|
|
2856
3301
|
const updateInfo = useUpdateChecker(
|
|
2857
3302
|
options.noUpdates,
|
|
2858
3303
|
setup.liveConfig.updates.checkOnLaunch,
|
|
@@ -2862,7 +3307,7 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
2862
3307
|
applyTheme(resolveTheme(setup.liveConfig.theme, setup.liveConfig.customThemes));
|
|
2863
3308
|
}, [setup.liveConfig.theme, setup.liveConfig.customThemes]);
|
|
2864
3309
|
const {
|
|
2865
|
-
sessions,
|
|
3310
|
+
sessions: sessions2,
|
|
2866
3311
|
visibleItems,
|
|
2867
3312
|
selectedSession,
|
|
2868
3313
|
selectedGroup,
|
|
@@ -2985,6 +3430,7 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
2985
3430
|
setActivityScroll,
|
|
2986
3431
|
setConfirmAction,
|
|
2987
3432
|
setUpdateStatus,
|
|
3433
|
+
setSidebarWidth: persistSidebarWidth,
|
|
2988
3434
|
nicknameInput,
|
|
2989
3435
|
filterInput,
|
|
2990
3436
|
onNickname: (id) => {
|
|
@@ -3110,7 +3556,7 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
3110
3556
|
}
|
|
3111
3557
|
);
|
|
3112
3558
|
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: termHeight, children: [
|
|
3113
|
-
/* @__PURE__ */ jsx15(StatusBar, { sessionCount:
|
|
3559
|
+
/* @__PURE__ */ jsx15(StatusBar, { sessionCount: sessions2.length, alertCount: alerts.length, version, updateInfo }),
|
|
3114
3560
|
/* @__PURE__ */ jsxs15(Box15, { flexGrow: 1, height: mainHeight, children: [
|
|
3115
3561
|
/* @__PURE__ */ jsx15(
|
|
3116
3562
|
SessionList,
|
|
@@ -3121,7 +3567,8 @@ var App = ({ options, config: initialConfig, version, firstRun }) => {
|
|
|
3121
3567
|
height: mainHeight,
|
|
3122
3568
|
filter: filter || void 0,
|
|
3123
3569
|
viewingArchive,
|
|
3124
|
-
totalSessions:
|
|
3570
|
+
totalSessions: sessions2.length,
|
|
3571
|
+
sidebarWidth
|
|
3125
3572
|
}
|
|
3126
3573
|
),
|
|
3127
3574
|
rightPanel
|
|
@@ -3161,11 +3608,11 @@ var formatTokens3 = (n) => {
|
|
|
3161
3608
|
};
|
|
3162
3609
|
var runStreamMode = (options, isJson) => {
|
|
3163
3610
|
const engine = options.noSecurity ? null : new SecurityEngine(options.alertLevel);
|
|
3164
|
-
const
|
|
3611
|
+
const sessions2 = discoverSessions(options.allUsers);
|
|
3165
3612
|
if (isJson) {
|
|
3166
|
-
write(JSON.stringify({ type: "sessions", data:
|
|
3613
|
+
write(JSON.stringify({ type: "sessions", data: sessions2 }));
|
|
3167
3614
|
} else {
|
|
3168
|
-
for (const s of
|
|
3615
|
+
for (const s of sessions2) {
|
|
3169
3616
|
write(
|
|
3170
3617
|
`SESSION ${s.slug} | ${s.model} | ${s.cwd} | CPU ${s.cpu}% | ${s.memMB}MB | ${formatTokens3(s.usage.inputTokens)} in / ${formatTokens3(s.usage.outputTokens)} out`
|
|
3171
3618
|
);
|
|
@@ -3221,8 +3668,8 @@ process.title = "agenttop";
|
|
|
3221
3668
|
var getVersion = () => {
|
|
3222
3669
|
try {
|
|
3223
3670
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
3224
|
-
const pkgPath =
|
|
3225
|
-
const pkg = JSON.parse(
|
|
3671
|
+
const pkgPath = join5(dirname4(thisFile), "..", "package.json");
|
|
3672
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
3226
3673
|
return pkg.version || "0.0.0";
|
|
3227
3674
|
} catch {
|
|
3228
3675
|
return "0.0.0";
|