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/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-6D2UXDV4.js";
22
+ } from "./chunk-3LEBAMTZ.js";
21
23
 
22
24
  // src/index.tsx
23
- import { readFileSync as readFileSync4 } from "fs";
24
- import { join as join4, dirname as dirname4 } from "path";
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 { execSync, exec } from "child_process";
352
- import { readFileSync } from "fs";
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(readFileSync(pkgPath, "utf-8"));
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
- const current = getPackageVersion();
367
- try {
368
- const latest = execSync("npm view agenttop version", { encoding: "utf-8", timeout: 5e3 }).trim();
369
- return {
370
- current,
371
- latest,
372
- available: latest !== current && compareVersions(latest, current) > 0
373
- };
374
- } catch {
375
- return { current, latest: current, available: false };
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 SIDEBAR_WIDTH = 30;
494
- var INNER_WIDTH = SIDEBAR_WIDTH - 4;
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 = (sessions, expandedKeys) => {
2350
+ var buildGroups = (sessions2, expandedKeys) => {
1947
2351
  const byKey = /* @__PURE__ */ new Map();
1948
- for (const s of sessions) {
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 [sessions, setSessions] = useState8([]);
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 = discoverSessions(allUsers);
1997
- const nicknames = getNicknames();
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 = sessions.length > 0 ? ACTIVE_POLL_MS : IDLE_POLL_MS;
2032
- const interval = setInterval(refresh, pollMs);
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, sessions.length > 0]);
2035
- const groups = useMemo2(() => buildGroups(sessions, expandedKeys), [sessions, expandedKeys]);
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 sessions = session === null ? [] : Array.isArray(session) ? session : [session];
2095
- const sessionKey = sessions.map((s) => s.sessionId).sort().join(",");
2096
- const sessionIdSet = new Set(sessions.map((s) => s.sessionId));
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 (sessions.length === 0) return;
2100
- const existingCalls = [];
2101
- const tempWatcher = new Watcher(() => {
2102
- }, allUsers);
2103
- const allFiles = sessions.flatMap((s) => s.outputFiles);
2104
- const seen = /* @__PURE__ */ new Set();
2105
- for (const file of allFiles) {
2106
- if (seen.has(file)) continue;
2107
- seen.add(file);
2108
- existingCalls.push(...tempWatcher.readExisting(file));
2109
- }
2110
- existingCalls.sort((a, b) => a.timestamp - b.timestamp);
2111
- setEvents(existingCalls.slice(-MAX_EVENTS));
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
- try {
2526
- const i = checkForUpdate();
2527
- if (i.available) setUpdateInfo(i);
2528
- } catch {
2529
- }
2530
- const iv = setInterval(() => {
2531
- try {
2532
- const i = checkForUpdate();
2533
- if (i.available) setUpdateInfo(i);
2534
- } catch {
2535
- }
2536
- }, checkInterval);
2537
- return () => clearInterval(iv);
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 as readFileSync2, writeFileSync, copyFileSync, mkdirSync as mkdirSync2, chmodSync } from "fs";
2547
- import { join as join2, dirname as dirname3 } from "path";
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 = join2(homedir(), ".claude", "settings.json");
2984
+ var SETTINGS_PATH = join3(homedir(), ".claude", "settings.json");
2552
2985
  var getHookSource = () => {
2553
2986
  const thisFile = fileURLToPath2(import.meta.url);
2554
- const srcHooksDir = join2(dirname3(thisFile), "..", "src", "hooks");
2555
- const distHooksDir = join2(dirname3(thisFile), "hooks");
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 = join2(dir, HOOK_FILENAME);
2990
+ const path = join3(dir, HOOK_FILENAME);
2558
2991
  if (existsSync(path)) return path;
2559
2992
  }
2560
- const npmGlobalPath = join2(dirname3(thisFile), "..", "hooks", HOOK_FILENAME);
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 = join2(homedir(), ".claude", "hooks");
2998
+ const claudeHooksDir = join3(homedir(), ".claude", "hooks");
2566
2999
  mkdirSync2(claudeHooksDir, { recursive: true });
2567
- return join2(claudeHooksDir, HOOK_FILENAME);
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(readFileSync2(SETTINGS_PATH, "utf-8"));
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 readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
2642
- import { join as join3 } from "path";
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 = join3(homedir2(), ".claude", "settings.json");
3078
+ const settingsPath = join4(homedir2(), ".claude", "settings.json");
2646
3079
  let settings = {};
2647
3080
  try {
2648
- settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
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(join3(homedir2(), ".claude"), { recursive: true });
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: sessions.length, alertCount: alerts.length, version, updateInfo }),
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: sessions.length
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 sessions = discoverSessions(options.allUsers);
3611
+ const sessions2 = discoverSessions(options.allUsers);
3165
3612
  if (isJson) {
3166
- write(JSON.stringify({ type: "sessions", data: sessions }));
3613
+ write(JSON.stringify({ type: "sessions", data: sessions2 }));
3167
3614
  } else {
3168
- for (const s of sessions) {
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 = join4(dirname4(thisFile), "..", "package.json");
3225
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
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";