pyre-cli 3.4.0 → 3.6.0
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 +414 -187
- package/package.json +1 -1
- package/src/formatters/formatters.md +164 -0
- package/src/formatters/output.ts +15 -9
- package/src/formatters/render.ts +68 -36
- package/src/history.ts +6 -0
- package/src/index.ts +20 -1
- package/src/live/input.ts +79 -57
- package/src/live/live.md +282 -0
- package/src/live/render.ts +182 -68
- package/src/live/state.ts +22 -1
- package/src/monitors/collectors.ts +126 -97
- package/src/monitors/monitors.md +165 -0
- package/src/monitors/run.ts +7 -3
- package/src/monitors/smc.ts +1 -1
- package/src/p2p/client.ts +2 -0
- package/src/p2p/p2p.md +284 -0
- package/src/p2p/server.test.ts +543 -0
- package/src/rest.md +129 -0
- package/src/sparkline.ts +8 -2
- package/src/ux.ts +62 -0
package/dist/index.js
CHANGED
|
@@ -10,10 +10,12 @@ import chalk9 from "chalk";
|
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
|
|
12
12
|
// src/monitors/run.ts
|
|
13
|
-
|
|
13
|
+
import { exec } from "child_process";
|
|
14
|
+
import { promisify } from "util";
|
|
15
|
+
var execAsync = promisify(exec);
|
|
16
|
+
async function run(cmd, fallback = "", timeout = 5e3) {
|
|
14
17
|
try {
|
|
15
|
-
|
|
16
|
-
return execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }).trim();
|
|
18
|
+
return (await execAsync(cmd, { encoding: "utf8", timeout })).stdout.trim();
|
|
17
19
|
} catch {
|
|
18
20
|
return fallback;
|
|
19
21
|
}
|
|
@@ -23,7 +25,7 @@ async function run(cmd, fallback = "") {
|
|
|
23
25
|
var smcCache = null;
|
|
24
26
|
async function getSmcMetrics() {
|
|
25
27
|
const now = Date.now();
|
|
26
|
-
if (smcCache && now - smcCache.ts <
|
|
28
|
+
if (smcCache && now - smcCache.ts < 4e3) return smcCache.data;
|
|
27
29
|
const result = { temps: {}, power: {}, freq: {} };
|
|
28
30
|
try {
|
|
29
31
|
const pm = (await run(
|
|
@@ -60,6 +62,46 @@ function parseSuffix(s) {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
// src/monitors/collectors.ts
|
|
65
|
+
var SP_TTL_MS = 1e4;
|
|
66
|
+
var NETSTAT_TTL_MS = 1e3;
|
|
67
|
+
var ROUTE_TTL_MS = 5e3;
|
|
68
|
+
var SYSCTL_TTL_MS = 6e4;
|
|
69
|
+
var spGpuCache = null;
|
|
70
|
+
var spBatteryCache = null;
|
|
71
|
+
var netstatIbCache = null;
|
|
72
|
+
var routeCache = null;
|
|
73
|
+
var sysctlCache = /* @__PURE__ */ new Map();
|
|
74
|
+
async function cachedSysctl(key, fallback) {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const hit = sysctlCache.get(key);
|
|
77
|
+
if (hit && now - hit.ts < SYSCTL_TTL_MS) return hit.value;
|
|
78
|
+
const value = await run(`sysctl -n ${key}`, fallback);
|
|
79
|
+
sysctlCache.set(key, { value, ts: now });
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
async function cachedNetstatIb() {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
if (netstatIbCache && now - netstatIbCache.ts < NETSTAT_TTL_MS) return netstatIbCache.raw;
|
|
85
|
+
const raw = await run("netstat -ib");
|
|
86
|
+
netstatIbCache = { raw, ts: now };
|
|
87
|
+
return raw;
|
|
88
|
+
}
|
|
89
|
+
async function cachedRouteIface() {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
if (routeCache && now - routeCache.ts < ROUTE_TTL_MS) return routeCache;
|
|
92
|
+
const primary = (await run("route -n get default")).trim();
|
|
93
|
+
const ifaceMatch = primary.match(/interface:\s+(\S+)/);
|
|
94
|
+
const iface = ifaceMatch?.[1] || "en0";
|
|
95
|
+
let ip = "0.0.0.0";
|
|
96
|
+
try {
|
|
97
|
+
const ifconfig = (await run(`ifconfig ${iface}`)).trim();
|
|
98
|
+
const ipMatch = ifconfig.match(/inet\s+([\d.]+)\s+netmask/);
|
|
99
|
+
if (ipMatch) ip = ipMatch[1];
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
routeCache = { iface, ip, ts: now };
|
|
103
|
+
return routeCache;
|
|
104
|
+
}
|
|
63
105
|
var BATTERY_WINDOW_MS = 15 * 60 * 1e3;
|
|
64
106
|
var MIN_ESTIMATE_WINDOW_MS = 30 * 1e3;
|
|
65
107
|
var prevBattery = { samples: [] };
|
|
@@ -158,46 +200,49 @@ async function collectPower() {
|
|
|
158
200
|
return null;
|
|
159
201
|
}
|
|
160
202
|
async function collectGpu(detailed) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
} catch {
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
const psRaw = (await run('ps aux | grep -i "[g]pu" | wc -l')).trim();
|
|
193
|
-
gpuProcesses = parseInt(psRaw) || 0;
|
|
194
|
-
} catch {
|
|
203
|
+
let spRaw = "";
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
if (spGpuCache && now - spGpuCache.ts < SP_TTL_MS) {
|
|
206
|
+
spRaw = spGpuCache.raw;
|
|
207
|
+
} else {
|
|
208
|
+
spRaw = (await run("system_profiler SPDisplaysDataType 2>/dev/null")).trim();
|
|
209
|
+
spGpuCache = { raw: spRaw, ts: now };
|
|
210
|
+
}
|
|
211
|
+
if (!spRaw) return null;
|
|
212
|
+
const modelMatch = spRaw.match(/Chipset Model:\s*(.+)/i);
|
|
213
|
+
const model = modelMatch ? modelMatch[1].trim() : "Unknown";
|
|
214
|
+
const memMatch = spRaw.match(/VRAM \(Total\):\s*(.+)/i);
|
|
215
|
+
let memory = 0;
|
|
216
|
+
if (memMatch) {
|
|
217
|
+
const memStr = memMatch[1].trim();
|
|
218
|
+
const memNum = parseFloat(memStr);
|
|
219
|
+
if (memStr.includes("GB")) memory = memNum * 1024 * 1024 * 1024;
|
|
220
|
+
else if (memStr.includes("MB")) memory = memNum * 1024 * 1024;
|
|
221
|
+
}
|
|
222
|
+
let utilization = 0;
|
|
223
|
+
let temperature;
|
|
224
|
+
let gpuProcesses = 0;
|
|
225
|
+
if (detailed) {
|
|
226
|
+
try {
|
|
227
|
+
const pm = (await run("sudo -n powermetrics --samplers gpu_power -n 1 --format text 2>/dev/null", "")).trim();
|
|
228
|
+
const gpuPowerMatch = pm.match(/GPU Power:\s*([\d.]+)\s*mW/i);
|
|
229
|
+
if (gpuPowerMatch) {
|
|
230
|
+
utilization = Math.min(100, Math.round(parseFloat(gpuPowerMatch[1]) / 10));
|
|
195
231
|
}
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const { temps } = await getSmcMetrics();
|
|
236
|
+
if (temps.gpu_die !== void 0) temperature = temps.gpu_die;
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const psRaw = (await run('ps aux | grep -i "[g]pu" | wc -l')).trim();
|
|
241
|
+
gpuProcesses = parseInt(psRaw) || 0;
|
|
242
|
+
} catch {
|
|
196
243
|
}
|
|
197
|
-
return { model, memory, utilization, temperature, processes: gpuProcesses };
|
|
198
|
-
} catch {
|
|
199
|
-
return null;
|
|
200
244
|
}
|
|
245
|
+
return { model, memory, utilization, temperature, processes: gpuProcesses };
|
|
201
246
|
}
|
|
202
247
|
async function collectSystem() {
|
|
203
248
|
const hostname = (await run("hostname", "Mac")).trim();
|
|
@@ -211,10 +256,10 @@ async function collectSystem() {
|
|
|
211
256
|
};
|
|
212
257
|
}
|
|
213
258
|
async function collectCpu() {
|
|
214
|
-
const brand = (await
|
|
215
|
-
const cores = parseInt(
|
|
216
|
-
const physicalCores = parseInt(
|
|
217
|
-
const frequencyHz = parseInt(
|
|
259
|
+
const brand = (await cachedSysctl("machdep.cpu.brand_string", "Unknown CPU")).trim();
|
|
260
|
+
const cores = parseInt(await cachedSysctl("hw.ncpu", "1")) || 1;
|
|
261
|
+
const physicalCores = parseInt(await cachedSysctl("hw.physicalcpu", String(cores))) || cores;
|
|
262
|
+
const frequencyHz = parseInt(await cachedSysctl("hw.cpufrequency", "0"));
|
|
218
263
|
let frequency = frequencyHz > 0 ? Math.round(frequencyHz / 1e6) : 0;
|
|
219
264
|
if (frequency === 0) {
|
|
220
265
|
try {
|
|
@@ -247,8 +292,8 @@ async function collectCpu() {
|
|
|
247
292
|
return { brand, cores, physicalCores, frequency, usage, loadAvg, temperature };
|
|
248
293
|
}
|
|
249
294
|
async function collectMemory() {
|
|
250
|
-
const totalBytes = parseInt(
|
|
251
|
-
const pageSize = parseInt(
|
|
295
|
+
const totalBytes = parseInt(await cachedSysctl("hw.memsize", "0")) || 0;
|
|
296
|
+
const pageSize = parseInt(await cachedSysctl("hw.pagesize", "4096")) || 4096;
|
|
252
297
|
const vmStat = (await run("vm_stat")).trim();
|
|
253
298
|
const getPageCount = (label) => {
|
|
254
299
|
const m = vmStat.match(new RegExp(`Pages\\s+${label}:\\s+(\\d+)\\.`));
|
|
@@ -318,7 +363,14 @@ async function collectBattery() {
|
|
|
318
363
|
let condition;
|
|
319
364
|
let maxCapacityPercent;
|
|
320
365
|
try {
|
|
321
|
-
const
|
|
366
|
+
const now = Date.now();
|
|
367
|
+
let battInfo;
|
|
368
|
+
if (spBatteryCache && now - spBatteryCache.ts < SP_TTL_MS) {
|
|
369
|
+
battInfo = spBatteryCache.raw;
|
|
370
|
+
} else {
|
|
371
|
+
battInfo = (await run("system_profiler SPPowerDataType 2>/dev/null")).trim();
|
|
372
|
+
spBatteryCache = { raw: battInfo, ts: now };
|
|
373
|
+
}
|
|
322
374
|
const cycleMatch = battInfo.match(/Cycle Count:\s+(\d+)/);
|
|
323
375
|
if (cycleMatch) cycles = parseInt(cycleMatch[1]);
|
|
324
376
|
const conditionMatch = battInfo.match(/Condition:\s+(.+)/);
|
|
@@ -426,17 +478,8 @@ async function collectThermal(detailed) {
|
|
|
426
478
|
}
|
|
427
479
|
async function collectNetwork() {
|
|
428
480
|
try {
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
const iface = ifaceMatch?.[1] || "en0";
|
|
432
|
-
let ip = "0.0.0.0";
|
|
433
|
-
try {
|
|
434
|
-
const ifconfig = (await run(`ifconfig ${iface}`)).trim();
|
|
435
|
-
const ipMatch = ifconfig.match(/inet\s+([\d.]+)\s+netmask/);
|
|
436
|
-
if (ipMatch) ip = ipMatch[1];
|
|
437
|
-
} catch {
|
|
438
|
-
}
|
|
439
|
-
const netStat = (await run("netstat -ib")).trim();
|
|
481
|
+
const { iface, ip } = await cachedRouteIface();
|
|
482
|
+
const netStat = await cachedNetstatIb();
|
|
440
483
|
let rxBytes = 0;
|
|
441
484
|
let txBytes = 0;
|
|
442
485
|
let rxPackets = 0;
|
|
@@ -477,7 +520,7 @@ async function collectProcesses(limit) {
|
|
|
477
520
|
}
|
|
478
521
|
async function collectPackets() {
|
|
479
522
|
try {
|
|
480
|
-
const netStat =
|
|
523
|
+
const netStat = await cachedNetstatIb();
|
|
481
524
|
const lines = netStat.split("\n").slice(1);
|
|
482
525
|
let totalRxPackets = 0;
|
|
483
526
|
let totalTxPackets = 0;
|
|
@@ -520,15 +563,7 @@ async function collectPackets() {
|
|
|
520
563
|
}
|
|
521
564
|
return { pid: 0, command: "", rxBytes: 0, txBytes: 0 };
|
|
522
565
|
}).filter((p) => p.pid > 0);
|
|
523
|
-
|
|
524
|
-
const allLsofLines = allLsof.split("\n");
|
|
525
|
-
allProcesses = allLsofLines.map((line) => {
|
|
526
|
-
const parts = line.trim().split(/\s+/);
|
|
527
|
-
if (parts.length >= 3) {
|
|
528
|
-
return { pid: parseInt(parts[2]) || 0, command: parts[1], rxBytes: 0, txBytes: parseInt(parts[0]) || 0 };
|
|
529
|
-
}
|
|
530
|
-
return { pid: 0, command: "", rxBytes: 0, txBytes: 0 };
|
|
531
|
-
}).filter((p) => p.pid > 0);
|
|
566
|
+
allProcesses = topProcesses;
|
|
532
567
|
} catch {
|
|
533
568
|
topProcesses = [];
|
|
534
569
|
allProcesses = [];
|
|
@@ -677,7 +712,7 @@ function panel(title, lines, width, accent, borderAccent, height) {
|
|
|
677
712
|
}
|
|
678
713
|
return [top, ...body, bottom];
|
|
679
714
|
}
|
|
680
|
-
function hstack(blocks, gap = 2) {
|
|
715
|
+
function hstack(blocks, gap = 2, targetWidth) {
|
|
681
716
|
if (!blocks.length) return [];
|
|
682
717
|
const height = Math.max(...blocks.map((b) => b.length));
|
|
683
718
|
const widths = blocks.map((b) => visLen(b[0] ?? ""));
|
|
@@ -689,7 +724,12 @@ function hstack(blocks, gap = 2) {
|
|
|
689
724
|
});
|
|
690
725
|
const out = [];
|
|
691
726
|
for (let row = 0; row < height; row++) {
|
|
692
|
-
|
|
727
|
+
let line = padded.map((a) => a[row]).join(" ".repeat(gap));
|
|
728
|
+
if (targetWidth !== void 0) {
|
|
729
|
+
const pad = Math.max(0, targetWidth - visLen(line));
|
|
730
|
+
line += " ".repeat(pad);
|
|
731
|
+
}
|
|
732
|
+
out.push(line);
|
|
693
733
|
}
|
|
694
734
|
return out;
|
|
695
735
|
}
|
|
@@ -941,6 +981,15 @@ function buildProcessTree(processes) {
|
|
|
941
981
|
for (const p of processes) {
|
|
942
982
|
if (p.ppid === 0 || !map.has(p.ppid)) roots.push(p);
|
|
943
983
|
}
|
|
984
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
985
|
+
for (const p of processes) {
|
|
986
|
+
const list = childrenMap.get(p.ppid) || [];
|
|
987
|
+
list.push(p);
|
|
988
|
+
childrenMap.set(p.ppid, list);
|
|
989
|
+
}
|
|
990
|
+
for (const list of childrenMap.values()) {
|
|
991
|
+
list.sort((a, b) => b.cpu - a.cpu);
|
|
992
|
+
}
|
|
944
993
|
const lines = [];
|
|
945
994
|
const visited = /* @__PURE__ */ new Set();
|
|
946
995
|
function renderNode(proc, prefix, isLast) {
|
|
@@ -954,12 +1003,19 @@ function buildProcessTree(processes) {
|
|
|
954
1003
|
const thrStr = String(proc.threads).padEnd(6);
|
|
955
1004
|
const cmd = truncatePlain(proc.command, Math.max(10, 100 - pidStr.length - cpuStr.length - memStr.length - stateStr.length - thrStr.length - 10));
|
|
956
1005
|
lines.push(`${prefix}${connector}${pidStr}${cpuStr}${memStr}${stateStr}${thrStr} ${cmd}`);
|
|
957
|
-
const children =
|
|
958
|
-
children
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1006
|
+
const children = childrenMap.get(proc.pid);
|
|
1007
|
+
if (children) {
|
|
1008
|
+
let idx = 0;
|
|
1009
|
+
let total = 0;
|
|
1010
|
+
for (const c of children) {
|
|
1011
|
+
if (!visited.has(c.pid)) total++;
|
|
1012
|
+
}
|
|
1013
|
+
for (const child of children) {
|
|
1014
|
+
if (visited.has(child.pid)) continue;
|
|
1015
|
+
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
1016
|
+
renderNode(child, nextPrefix, ++idx === total);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
963
1019
|
}
|
|
964
1020
|
roots.sort((a, b) => b.cpu - a.cpu);
|
|
965
1021
|
roots.forEach((root, i) => renderNode(root, "", i === roots.length - 1));
|
|
@@ -992,8 +1048,10 @@ function detailPanelTitle(panel2) {
|
|
|
992
1048
|
};
|
|
993
1049
|
return map[panel2] || panel2;
|
|
994
1050
|
}
|
|
995
|
-
function activeDetailLines(data, activePanel, width) {
|
|
1051
|
+
function activeDetailLines(data, activePanel, width, opts2 = {}) {
|
|
996
1052
|
const contentWidth = width - 4;
|
|
1053
|
+
const themeName = opts2.theme || "default";
|
|
1054
|
+
const theme = THEMES[themeName] || THEMES.default;
|
|
997
1055
|
switch (activePanel) {
|
|
998
1056
|
case "cpu":
|
|
999
1057
|
return cpuCard(data, contentWidth);
|
|
@@ -1043,10 +1101,15 @@ function activeDetailLines(data, activePanel, width) {
|
|
|
1043
1101
|
case "tasks":
|
|
1044
1102
|
return data.tasks?.length ? tasksCard(data, contentWidth) : ["No task data available"];
|
|
1045
1103
|
case "disk":
|
|
1046
|
-
return data.disk.length ? diskTableLines(data.disk, width - 4,
|
|
1104
|
+
return data.disk.length ? diskTableLines(data.disk, width - 4, theme.border) : ["No disk data available"];
|
|
1047
1105
|
case "process": {
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1106
|
+
const filtered = opts2.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts2.filter.toLowerCase())) : data.processes;
|
|
1107
|
+
const sorted = sortProcesses(filtered, opts2.sortBy ?? "cpu");
|
|
1108
|
+
const limited = opts2.processLimit ? sorted.slice(0, opts2.processLimit) : sorted;
|
|
1109
|
+
if (opts2.treeView) {
|
|
1110
|
+
return buildProcessTree(limited);
|
|
1111
|
+
}
|
|
1112
|
+
return processTableLines(limited, width - 4, theme.border);
|
|
1050
1113
|
}
|
|
1051
1114
|
default:
|
|
1052
1115
|
return null;
|
|
@@ -1068,7 +1131,7 @@ function formatTable(data, opts2 = {}) {
|
|
|
1068
1131
|
out.push(tabBar(activePanel, width, theme));
|
|
1069
1132
|
out.push("");
|
|
1070
1133
|
if (activePanel !== "grid") {
|
|
1071
|
-
const lines = activeDetailLines(data, activePanel, width);
|
|
1134
|
+
const lines = activeDetailLines(data, activePanel, width, opts2);
|
|
1072
1135
|
if (lines) {
|
|
1073
1136
|
out.push(...panel(detailPanelTitle(activePanel), lines, width, theme[activePanel] ?? theme.cpu, theme.border));
|
|
1074
1137
|
}
|
|
@@ -1111,7 +1174,7 @@ function formatTable(data, opts2 = {}) {
|
|
|
1111
1174
|
const row = cards.slice(i, i + columns);
|
|
1112
1175
|
const rowHeight = Math.max(...row.map((c) => c.lines.length));
|
|
1113
1176
|
const blocks = row.map((c) => panel(c.title, c.lines, cardWidth, c.accent, theme.border, rowHeight));
|
|
1114
|
-
out.push(...hstack(blocks, gap));
|
|
1177
|
+
out.push(...hstack(blocks, gap, width));
|
|
1115
1178
|
out.push("");
|
|
1116
1179
|
}
|
|
1117
1180
|
if (vis.disk !== false && data.disk.length) {
|
|
@@ -1156,8 +1219,14 @@ import chalk4 from "chalk";
|
|
|
1156
1219
|
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1157
1220
|
function sparkline(values, opts2 = {}) {
|
|
1158
1221
|
if (!values.length) return "";
|
|
1159
|
-
|
|
1160
|
-
|
|
1222
|
+
let min = opts2.min ?? Infinity;
|
|
1223
|
+
let max = opts2.max ?? -Infinity;
|
|
1224
|
+
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
1225
|
+
for (const v of values) {
|
|
1226
|
+
if (v < min) min = v;
|
|
1227
|
+
if (v > max) max = v;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1161
1230
|
const range = max - min || 1;
|
|
1162
1231
|
return values.map((v) => {
|
|
1163
1232
|
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
@@ -1251,21 +1320,21 @@ function formatGraphs(history, width = 80, themeName = "default", graphMode = "s
|
|
|
1251
1320
|
const sampleCount = history.cpuUsage.length;
|
|
1252
1321
|
const sparkWidth = Math.max(10, contentWidth - 28);
|
|
1253
1322
|
const lines = [];
|
|
1254
|
-
lines.push(graphRow("CPU %", history.cpuUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, graphMode));
|
|
1255
|
-
lines.push(graphRow("Mem %", history.memUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, graphMode));
|
|
1323
|
+
lines.push(graphRow("CPU %", history.cpuUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, "collecting data...", graphMode));
|
|
1324
|
+
lines.push(graphRow("Mem %", history.memUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, "collecting data...", graphMode));
|
|
1256
1325
|
lines.push(
|
|
1257
1326
|
graphRow("Temp", history.temp, {}, formatTemp, sparkWidth, "no sensor access (needs sudo powermetrics)", graphMode)
|
|
1258
1327
|
);
|
|
1259
|
-
lines.push(graphRow("Net RX/s", history.netRxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, graphMode));
|
|
1260
|
-
lines.push(graphRow("Net TX/s", history.netTxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, graphMode));
|
|
1328
|
+
lines.push(graphRow("Net RX/s", history.netRxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, "collecting data...", graphMode));
|
|
1329
|
+
lines.push(graphRow("Net TX/s", history.netTxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, "collecting data...", graphMode));
|
|
1261
1330
|
if (history.powerWatts.length) {
|
|
1262
|
-
lines.push(graphRow("Power W", history.powerWatts, {}, (v) => `${v.toFixed(1)} W`, sparkWidth, graphMode));
|
|
1331
|
+
lines.push(graphRow("Power W", history.powerWatts, {}, (v) => `${v.toFixed(1)} W`, sparkWidth, "collecting data...", graphMode));
|
|
1263
1332
|
}
|
|
1264
1333
|
if (history.rxPacketRate.length) {
|
|
1265
|
-
lines.push(graphRow("RX pkt/s", history.rxPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, graphMode));
|
|
1334
|
+
lines.push(graphRow("RX pkt/s", history.rxPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, "collecting data...", graphMode));
|
|
1266
1335
|
}
|
|
1267
1336
|
if (history.txPacketRate.length) {
|
|
1268
|
-
lines.push(graphRow("TX pkt/s", history.txPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, graphMode));
|
|
1337
|
+
lines.push(graphRow("TX pkt/s", history.txPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, "collecting data...", graphMode));
|
|
1269
1338
|
}
|
|
1270
1339
|
const title = `Graphs \xB7 mode:${graphMode} \xB7 last ${sampleCount} sample${sampleCount === 1 ? "" : "s"}`;
|
|
1271
1340
|
return panel(title, lines, width, theme.graphs, theme.border).join("\n");
|
|
@@ -1285,8 +1354,14 @@ function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "c
|
|
|
1285
1354
|
return `${chalk4.dim(label.padEnd(10))}${color(spark)} ${chalk4.bold(fmt(current))}`;
|
|
1286
1355
|
}
|
|
1287
1356
|
function barGraph(values, bounds, width) {
|
|
1288
|
-
|
|
1289
|
-
|
|
1357
|
+
let min = bounds.min ?? Infinity;
|
|
1358
|
+
let max = bounds.max ?? -Infinity;
|
|
1359
|
+
if (bounds.min === void 0 || bounds.max === void 0) {
|
|
1360
|
+
for (const v of values) {
|
|
1361
|
+
if (v < min) min = v;
|
|
1362
|
+
if (v > max) max = v;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1290
1365
|
const range = max - min || 1;
|
|
1291
1366
|
return values.map((v) => {
|
|
1292
1367
|
const pct = (v - min) / range;
|
|
@@ -1305,6 +1380,8 @@ import readline from "readline";
|
|
|
1305
1380
|
var History = class {
|
|
1306
1381
|
/** Maximum number of samples retained in the rolling window. */
|
|
1307
1382
|
maxLen;
|
|
1383
|
+
/** Monotonically increasing version, bumped on every push. */
|
|
1384
|
+
version = 0;
|
|
1308
1385
|
/** Rolling window of CPU usage percentages (0–100). */
|
|
1309
1386
|
cpuUsage = [];
|
|
1310
1387
|
/** Rolling window of memory usage percentages (0–100). */
|
|
@@ -1376,6 +1453,7 @@ var History = class {
|
|
|
1376
1453
|
if (sample.rxPackets !== void 0) this.pushBounded(this.rxPacketRate, rxPktRate);
|
|
1377
1454
|
if (sample.txPackets !== void 0) this.pushBounded(this.txPacketRate, txPktRate);
|
|
1378
1455
|
if (sample.connections !== void 0) this.pushBounded(this.connections, sample.connections);
|
|
1456
|
+
this.version++;
|
|
1379
1457
|
}
|
|
1380
1458
|
/** Clear all rolling windows and reset cumulative byte counters. */
|
|
1381
1459
|
reset() {
|
|
@@ -1394,6 +1472,7 @@ var History = class {
|
|
|
1394
1472
|
this.lastRxPackets = 0;
|
|
1395
1473
|
this.lastTxPackets = 0;
|
|
1396
1474
|
this.lastTs = 0;
|
|
1475
|
+
this.version = 0;
|
|
1397
1476
|
}
|
|
1398
1477
|
/**
|
|
1399
1478
|
* Grow or shrink the rolling window (e.g. on terminal resize),
|
|
@@ -1416,6 +1495,7 @@ var History = class {
|
|
|
1416
1495
|
var SIGNAL_OPTIONS = ["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGSTOP", "SIGCONT"];
|
|
1417
1496
|
var state = {
|
|
1418
1497
|
intervalHandle: null,
|
|
1498
|
+
uiIntervalHandle: null,
|
|
1419
1499
|
running: false,
|
|
1420
1500
|
paused: false,
|
|
1421
1501
|
detailed: false,
|
|
@@ -1451,6 +1531,8 @@ var state = {
|
|
|
1451
1531
|
processFilter: "",
|
|
1452
1532
|
inputMode: null,
|
|
1453
1533
|
inputBuffer: "",
|
|
1534
|
+
targetPid: "",
|
|
1535
|
+
selectedSignal: "SIGTERM",
|
|
1454
1536
|
treeView: false,
|
|
1455
1537
|
SIGNAL_OPTIONS,
|
|
1456
1538
|
customizerIndex: 0,
|
|
@@ -1503,6 +1585,23 @@ function setStatus(msg, ms = 3e3) {
|
|
|
1503
1585
|
state.statusMessage = "";
|
|
1504
1586
|
}, ms);
|
|
1505
1587
|
}
|
|
1588
|
+
function getToggleKey(opt) {
|
|
1589
|
+
const map = {
|
|
1590
|
+
"Toggle CPU": "cpu",
|
|
1591
|
+
"Toggle Memory": "mem",
|
|
1592
|
+
"Toggle GPU": "gpu",
|
|
1593
|
+
"Toggle Power": "power",
|
|
1594
|
+
"Toggle Battery": "battery",
|
|
1595
|
+
"Toggle Thermal": "thermal",
|
|
1596
|
+
"Toggle Network": "network",
|
|
1597
|
+
"Toggle Packets": "packets",
|
|
1598
|
+
"Toggle Tasks": "tasks",
|
|
1599
|
+
"Toggle Disk": "disk",
|
|
1600
|
+
"Toggle Processes": "process",
|
|
1601
|
+
"Toggle Tree View": "tree"
|
|
1602
|
+
};
|
|
1603
|
+
return map[opt] ?? null;
|
|
1604
|
+
}
|
|
1506
1605
|
|
|
1507
1606
|
// src/live/export.ts
|
|
1508
1607
|
import fs from "fs";
|
|
@@ -1564,12 +1663,48 @@ function writeLogRow(data) {
|
|
|
1564
1663
|
|
|
1565
1664
|
// src/live/render.ts
|
|
1566
1665
|
import chalk5 from "chalk";
|
|
1666
|
+
var _cachedTable = "";
|
|
1667
|
+
var _cachedTableData = null;
|
|
1668
|
+
var _cachedTableParams = "";
|
|
1669
|
+
var _tableDataDirty = false;
|
|
1670
|
+
function invalidateTableCache() {
|
|
1671
|
+
_tableDataDirty = true;
|
|
1672
|
+
}
|
|
1673
|
+
var _cachedGraphs = "";
|
|
1674
|
+
var _cachedGraphsVersion = -1;
|
|
1675
|
+
var _cachedGraphsParams = "";
|
|
1676
|
+
function tableCacheParams() {
|
|
1677
|
+
const limit = processRowBudget();
|
|
1678
|
+
return [
|
|
1679
|
+
state.sortMode,
|
|
1680
|
+
state.processFilter || "",
|
|
1681
|
+
limit,
|
|
1682
|
+
state.currentTheme,
|
|
1683
|
+
state.activePanel,
|
|
1684
|
+
state.treeView ? "1" : "0",
|
|
1685
|
+
JSON.stringify(state.visiblePanels)
|
|
1686
|
+
].join("|");
|
|
1687
|
+
}
|
|
1688
|
+
function graphsCacheParams() {
|
|
1689
|
+
return [
|
|
1690
|
+
state.termWidth,
|
|
1691
|
+
state.currentTheme,
|
|
1692
|
+
state.graphMode
|
|
1693
|
+
].join("|");
|
|
1694
|
+
}
|
|
1567
1695
|
function processRowBudget() {
|
|
1696
|
+
let overlayRows = 0;
|
|
1697
|
+
if (state.inputMode === "customizer") overlayRows = state.CUSTOMIZER_OPTIONS.length + 3;
|
|
1698
|
+
else if (state.inputMode === "signal") overlayRows = 6;
|
|
1699
|
+
else if (state.inputMode === "filter" || state.inputMode === "kill" || state.inputMode === "p2p") overlayRows = 3;
|
|
1700
|
+
if (state.activePanel === "process") {
|
|
1701
|
+
return Math.max(10, state.termHeight - 12 - overlayRows);
|
|
1702
|
+
}
|
|
1568
1703
|
const columns = gridColumns(state.termWidth);
|
|
1569
1704
|
const visibleCards = Object.values(state.visiblePanels).filter((v) => v !== false).length;
|
|
1570
1705
|
const cardRows = Math.ceil(visibleCards / columns);
|
|
1571
|
-
const reserved =
|
|
1572
|
-
return Math.max(
|
|
1706
|
+
const reserved = 4 + cardRows * 8 + 8 + (state.showGraphs ? 11 : 0) + 5 + overlayRows;
|
|
1707
|
+
return Math.max(5, state.termHeight - reserved);
|
|
1573
1708
|
}
|
|
1574
1709
|
function p2pPanelLines() {
|
|
1575
1710
|
const events = state.p2pServer ? state.p2pServer.peerEventHistory : state.p2pEvents;
|
|
@@ -1606,94 +1741,144 @@ function renderCustomizerOverlay() {
|
|
|
1606
1741
|
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.currentTheme)} [${themesList.join(", ")}]`);
|
|
1607
1742
|
} else if (opt === "Graph Mode") {
|
|
1608
1743
|
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.graphMode)} [spark, bar]`);
|
|
1609
|
-
} else if (opt === "Toggle Tree View") {
|
|
1610
|
-
const status = state.treeView ? chalk5.green("[VISIBLE]") : chalk5.red("[HIDDEN]");
|
|
1611
|
-
lines.push(`${prefix}${opt}: ${status}`);
|
|
1612
1744
|
} else {
|
|
1613
|
-
const
|
|
1614
|
-
const isVisible = state.visiblePanels[
|
|
1745
|
+
const toggleKey = getToggleKey(opt);
|
|
1746
|
+
const isVisible = toggleKey ? toggleKey === "tree" ? state.treeView : state.visiblePanels[toggleKey] !== false : true;
|
|
1615
1747
|
const status = isVisible ? chalk5.green("[VISIBLE]") : chalk5.red("[HIDDEN]");
|
|
1616
1748
|
lines.push(`${prefix}${opt}: ${status}`);
|
|
1617
1749
|
}
|
|
1618
1750
|
});
|
|
1619
1751
|
return lines.map((l) => ` ${l}`).join("\n");
|
|
1620
1752
|
}
|
|
1753
|
+
var _prevFrameLines = [];
|
|
1754
|
+
var _frameDirty = true;
|
|
1755
|
+
function invalidateFrame() {
|
|
1756
|
+
_frameDirty = true;
|
|
1757
|
+
}
|
|
1758
|
+
function writeFrame(lines) {
|
|
1759
|
+
if (_frameDirty || _prevFrameLines.length === 0 || lines.length !== _prevFrameLines.length) {
|
|
1760
|
+
process.stdout.write("\x1B[?25l\x1B[H" + lines.map((l) => `\x1B[2K${l}`).join("\r\n") + "\x1B[0J");
|
|
1761
|
+
_prevFrameLines = lines;
|
|
1762
|
+
_frameDirty = false;
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
let out = "";
|
|
1766
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1767
|
+
if (lines[i] !== _prevFrameLines[i]) {
|
|
1768
|
+
out += `\x1B[${i + 1};1H\x1B[2K${lines[i]}`;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (out) process.stdout.write(out);
|
|
1772
|
+
_prevFrameLines = lines;
|
|
1773
|
+
}
|
|
1621
1774
|
function render() {
|
|
1622
1775
|
if (!state.lastData) return;
|
|
1623
1776
|
const lines = [];
|
|
1624
1777
|
const theme = THEMES[state.currentTheme] || THEMES.default;
|
|
1778
|
+
const params = tableCacheParams();
|
|
1779
|
+
const tableStr = _tableDataDirty || state.lastData !== _cachedTableData || params !== _cachedTableParams ? (() => {
|
|
1780
|
+
const out = formatTable(state.lastData, {
|
|
1781
|
+
width: state.termWidth,
|
|
1782
|
+
sortBy: state.sortMode,
|
|
1783
|
+
filter: state.processFilter || void 0,
|
|
1784
|
+
processLimit: processRowBudget(),
|
|
1785
|
+
theme: state.currentTheme,
|
|
1786
|
+
visible: state.visiblePanels,
|
|
1787
|
+
treeView: state.treeView,
|
|
1788
|
+
activePanel: state.activePanel
|
|
1789
|
+
});
|
|
1790
|
+
_cachedTableData = state.lastData;
|
|
1791
|
+
_cachedTableParams = params;
|
|
1792
|
+
_cachedTable = out;
|
|
1793
|
+
_tableDataDirty = false;
|
|
1794
|
+
return out;
|
|
1795
|
+
})() : _cachedTable;
|
|
1796
|
+
const bodyLines = [];
|
|
1625
1797
|
if (state.activePanel === "p2p") {
|
|
1626
1798
|
const panelLines = p2pPanelLines();
|
|
1627
|
-
|
|
1799
|
+
bodyLines.push(...panel(panelLines.title, panelLines.body, state.termWidth, theme.process, theme.border));
|
|
1628
1800
|
} else {
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
})
|
|
1640
|
-
);
|
|
1641
|
-
}
|
|
1801
|
+
bodyLines.push(...tableStr.split("\n"));
|
|
1802
|
+
}
|
|
1803
|
+
const graphsParams = graphsCacheParams();
|
|
1804
|
+
const graphsStr = state.history.version !== _cachedGraphsVersion || graphsParams !== _cachedGraphsParams ? (() => {
|
|
1805
|
+
const out = formatGraphs(state.history, state.termWidth, state.currentTheme, state.graphMode);
|
|
1806
|
+
_cachedGraphsVersion = state.history.version;
|
|
1807
|
+
_cachedGraphsParams = graphsParams;
|
|
1808
|
+
_cachedGraphs = out;
|
|
1809
|
+
return out;
|
|
1810
|
+
})() : _cachedGraphs;
|
|
1642
1811
|
if (state.showGraphs && state.activePanel !== "p2p") {
|
|
1643
|
-
|
|
1644
|
-
|
|
1812
|
+
bodyLines.push("");
|
|
1813
|
+
bodyLines.push(...graphsStr.split("\n"));
|
|
1645
1814
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1815
|
+
const footerLines = [];
|
|
1816
|
+
footerLines.push("");
|
|
1817
|
+
footerLines.push(...footerLine().split("\n"));
|
|
1648
1818
|
if (state.inputMode === "customizer") {
|
|
1649
|
-
|
|
1819
|
+
footerLines.push(...renderCustomizerOverlay().split("\n"));
|
|
1650
1820
|
} else if (state.inputMode === "filter") {
|
|
1651
|
-
|
|
1821
|
+
footerLines.push(chalk5.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
1652
1822
|
} else if (state.inputMode === "kill") {
|
|
1653
|
-
|
|
1823
|
+
footerLines.push(chalk5.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
|
|
1654
1824
|
} else if (state.inputMode === "signal") {
|
|
1655
1825
|
const sigIdx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
1656
1826
|
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ? chalk5.yellow(s) : chalk5.dim(s)).join(" ");
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1827
|
+
footerLines.push(chalk5.cyan(` Signal: ${state.inputBuffer}_`));
|
|
1828
|
+
footerLines.push(chalk5.dim(` ${sigList}`));
|
|
1829
|
+
footerLines.push(chalk5.dim(" \u2191/\u2193 to select, Enter to confirm, Esc to cancel"));
|
|
1660
1830
|
} else if (state.inputMode === "p2p") {
|
|
1661
|
-
|
|
1831
|
+
footerLines.push(chalk5.cyan(` P2P password (${state.p2pPort}): ${state.inputBuffer}_ (enter to start, esc to cancel)`));
|
|
1662
1832
|
} else if (state.statusMessage) {
|
|
1663
|
-
|
|
1833
|
+
footerLines.push(` ${state.statusMessage}`);
|
|
1834
|
+
} else {
|
|
1835
|
+
footerLines.push("");
|
|
1664
1836
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1837
|
+
const maxBodyLines = Math.max(1, state.termHeight - footerLines.length);
|
|
1838
|
+
if (bodyLines.length > maxBodyLines) {
|
|
1839
|
+
bodyLines.length = maxBodyLines;
|
|
1840
|
+
}
|
|
1841
|
+
const frameLines = [...bodyLines, ...footerLines];
|
|
1842
|
+
if (frameLines.length > state.termHeight) {
|
|
1843
|
+
frameLines.length = state.termHeight;
|
|
1844
|
+
}
|
|
1845
|
+
writeFrame(frameLines);
|
|
1667
1846
|
}
|
|
1668
1847
|
function footerLine() {
|
|
1669
|
-
const
|
|
1848
|
+
const row1 = [
|
|
1670
1849
|
["q", "quit"],
|
|
1671
1850
|
["c", "customize UI"],
|
|
1672
|
-
["
|
|
1673
|
-
["
|
|
1851
|
+
["\u2190/\u2192", "tab"],
|
|
1852
|
+
["1-9", "panels"],
|
|
1674
1853
|
["g", state.showGraphs ? "hide graphs" : "show graphs"],
|
|
1675
1854
|
["b", `graph:${state.graphMode}`],
|
|
1855
|
+
["t", state.treeView ? "flat" : "tree"],
|
|
1676
1856
|
["d", state.detailed ? "basic" : "detailed"],
|
|
1677
|
-
["
|
|
1678
|
-
|
|
1857
|
+
["p", state.paused ? "resume" : "pause"]
|
|
1858
|
+
];
|
|
1859
|
+
const row2 = [
|
|
1679
1860
|
["s", `sort:${state.sortMode}`],
|
|
1680
1861
|
["/", "filter"],
|
|
1681
1862
|
["k", "kill"],
|
|
1682
1863
|
["S", "signal"],
|
|
1683
|
-
["t", state.treeView ? "flat" : "tree"],
|
|
1684
1864
|
["e", "export"],
|
|
1685
1865
|
["l", state.logging ? "stop log" : "log"],
|
|
1686
1866
|
["f", state.exportFormat],
|
|
1687
|
-
["+/-", `${state.interval}s`]
|
|
1867
|
+
["+/-", `${state.interval}s`],
|
|
1868
|
+
["r", state.p2pServerRunning ? "stop p2p" : "start p2p"]
|
|
1688
1869
|
];
|
|
1689
|
-
const
|
|
1870
|
+
const fmt = (list) => list.map(([k, label]) => `${chalk5.hex("#50fa7b").bold(k)} ${chalk5.dim(label)}`).join(chalk5.dim(" | "));
|
|
1871
|
+
const str1 = fmt(row1);
|
|
1872
|
+
const str2 = fmt(row2);
|
|
1690
1873
|
const badges = [];
|
|
1691
1874
|
if (state.paused) badges.push(chalk5.yellow.bold("\u23F8 PAUSED"));
|
|
1692
1875
|
if (state.logging) badges.push(chalk5.red.bold("\u25CF REC"));
|
|
1693
1876
|
if (state.activePanel !== "grid") badges.push(chalk5.cyan.bold(`\u25C9 ${state.activePanel.toUpperCase()}`));
|
|
1694
1877
|
if (state.p2pServerRunning) badges.push(chalk5.green.bold(`P2P ${state.p2pBind || "0.0.0.0"}:${state.p2pPort}`));
|
|
1695
1878
|
const badgeStr = badges.join(" ");
|
|
1696
|
-
return badgeStr ? `${
|
|
1879
|
+
return badgeStr ? `${str1}
|
|
1880
|
+
${str2} ${badgeStr}` : `${str1}
|
|
1881
|
+
${str2}`;
|
|
1697
1882
|
}
|
|
1698
1883
|
function checkAlerts(data) {
|
|
1699
1884
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
@@ -2320,6 +2505,8 @@ var P2PClient = class {
|
|
|
2320
2505
|
this.status("Connection refused \u2014 is the server running?");
|
|
2321
2506
|
} else if (code === "ETIMEDOUT") {
|
|
2322
2507
|
this.status("Connection timed out");
|
|
2508
|
+
} else if (code === "EHOSTDOWN" || code === "EHOSTUNREACH") {
|
|
2509
|
+
this.status(`Host unreachable \u2014 ${err.message}`);
|
|
2323
2510
|
} else if (code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") {
|
|
2324
2511
|
this.status("TLS certificate verification failed \u2014 use --p2p-insecure to skip");
|
|
2325
2512
|
} else {
|
|
@@ -2348,8 +2535,20 @@ function deriveHMACKey2(password) {
|
|
|
2348
2535
|
}
|
|
2349
2536
|
|
|
2350
2537
|
// src/live/input.ts
|
|
2538
|
+
function isUpKey(key, str) {
|
|
2539
|
+
return key.name === "up" || str === "k" || key.sequence === "\x1B[A" || key.sequence === "\x1BOA";
|
|
2540
|
+
}
|
|
2541
|
+
function isDownKey(key, str) {
|
|
2542
|
+
return key.name === "down" || str === "j" || key.sequence === "\x1B[B" || key.sequence === "\x1BOB";
|
|
2543
|
+
}
|
|
2544
|
+
function isEnterKey(key, str) {
|
|
2545
|
+
return key.name === "return" || key.name === "enter" || key.sequence === "\r" || key.sequence === "\n" || str === "\r" || str === "\n";
|
|
2546
|
+
}
|
|
2547
|
+
function isEscKey(key, str) {
|
|
2548
|
+
return key.name === "escape" || key.name === "esc" || key.sequence === "\x1B" || str === "\x1B";
|
|
2549
|
+
}
|
|
2351
2550
|
function handleInputModeKey(str, key) {
|
|
2352
|
-
if (key
|
|
2551
|
+
if (isEscKey(key, str)) {
|
|
2353
2552
|
state.inputMode = null;
|
|
2354
2553
|
state.inputBuffer = "";
|
|
2355
2554
|
render();
|
|
@@ -2357,38 +2556,55 @@ function handleInputModeKey(str, key) {
|
|
|
2357
2556
|
}
|
|
2358
2557
|
if (state.inputMode === "customizer") {
|
|
2359
2558
|
const themesList = Object.keys(THEMES);
|
|
2360
|
-
if (key
|
|
2559
|
+
if (isUpKey(key, str)) {
|
|
2361
2560
|
state.customizerIndex = (state.customizerIndex - 1 + state.CUSTOMIZER_OPTIONS.length) % state.CUSTOMIZER_OPTIONS.length;
|
|
2362
|
-
} else if (key
|
|
2561
|
+
} else if (isDownKey(key, str)) {
|
|
2363
2562
|
state.customizerIndex = (state.customizerIndex + 1) % state.CUSTOMIZER_OPTIONS.length;
|
|
2364
|
-
} else if (key
|
|
2563
|
+
} else if (isEnterKey(key, str) || str === " ") {
|
|
2365
2564
|
const selected = state.CUSTOMIZER_OPTIONS[state.customizerIndex];
|
|
2366
2565
|
if (selected === "Theme") {
|
|
2367
2566
|
const nextIdx = (themesList.indexOf(state.currentTheme) + 1) % themesList.length;
|
|
2368
2567
|
state.currentTheme = themesList[nextIdx];
|
|
2369
|
-
} else if (selected === "Toggle Tree View") {
|
|
2370
|
-
state.treeView = !state.treeView;
|
|
2371
2568
|
} else if (selected === "Graph Mode") {
|
|
2372
2569
|
state.graphMode = state.graphMode === "spark" ? "bar" : "spark";
|
|
2373
2570
|
} else {
|
|
2374
|
-
const
|
|
2375
|
-
|
|
2571
|
+
const toggleKey = getToggleKey(selected);
|
|
2572
|
+
if (toggleKey) {
|
|
2573
|
+
if (toggleKey === "tree") {
|
|
2574
|
+
state.treeView = !state.treeView;
|
|
2575
|
+
state.visiblePanels.tree = state.treeView;
|
|
2576
|
+
} else {
|
|
2577
|
+
const isVisible = state.visiblePanels[toggleKey] !== false;
|
|
2578
|
+
state.visiblePanels[toggleKey] = !isVisible;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2376
2581
|
}
|
|
2582
|
+
invalidateTableCache();
|
|
2583
|
+
invalidateFrame();
|
|
2377
2584
|
}
|
|
2378
2585
|
render();
|
|
2379
2586
|
return;
|
|
2380
2587
|
}
|
|
2381
2588
|
if (state.inputMode === "signal") {
|
|
2382
|
-
if (key
|
|
2589
|
+
if (isUpKey(key, str)) {
|
|
2383
2590
|
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
2384
2591
|
state.inputBuffer = state.SIGNAL_OPTIONS[(idx - 1 + state.SIGNAL_OPTIONS.length) % state.SIGNAL_OPTIONS.length];
|
|
2385
|
-
|
|
2592
|
+
state.selectedSignal = state.inputBuffer;
|
|
2593
|
+
} else if (isDownKey(key, str)) {
|
|
2386
2594
|
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
2387
2595
|
state.inputBuffer = state.SIGNAL_OPTIONS[(idx + 1) % state.SIGNAL_OPTIONS.length];
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
state.
|
|
2391
|
-
state.
|
|
2596
|
+
state.selectedSignal = state.inputBuffer;
|
|
2597
|
+
} else if (isEnterKey(key, str) || str === " ") {
|
|
2598
|
+
state.selectedSignal = state.inputBuffer.trim();
|
|
2599
|
+
if (state.targetPid) {
|
|
2600
|
+
killProcess(state.targetPid, state.selectedSignal);
|
|
2601
|
+
state.inputMode = null;
|
|
2602
|
+
state.inputBuffer = "";
|
|
2603
|
+
} else {
|
|
2604
|
+
state.inputMode = "kill";
|
|
2605
|
+
state.inputBuffer = "";
|
|
2606
|
+
setStatus(`Signal ${state.selectedSignal} selected. Enter target PID to kill:`, 5e3);
|
|
2607
|
+
}
|
|
2392
2608
|
render();
|
|
2393
2609
|
return;
|
|
2394
2610
|
}
|
|
@@ -2396,13 +2612,13 @@ function handleInputModeKey(str, key) {
|
|
|
2396
2612
|
return;
|
|
2397
2613
|
}
|
|
2398
2614
|
if (state.inputMode === "p2p") {
|
|
2399
|
-
if (key
|
|
2615
|
+
if (isEscKey(key, str)) {
|
|
2400
2616
|
state.inputMode = null;
|
|
2401
2617
|
state.inputBuffer = "";
|
|
2402
2618
|
render();
|
|
2403
2619
|
return;
|
|
2404
2620
|
}
|
|
2405
|
-
if (key
|
|
2621
|
+
if (isEnterKey(key, str)) {
|
|
2406
2622
|
const password = state.inputBuffer.trim();
|
|
2407
2623
|
if (password) {
|
|
2408
2624
|
state.p2pPassword = password;
|
|
@@ -2424,12 +2640,14 @@ function handleInputModeKey(str, key) {
|
|
|
2424
2640
|
}
|
|
2425
2641
|
return;
|
|
2426
2642
|
}
|
|
2427
|
-
if (key
|
|
2643
|
+
if (isEnterKey(key, str)) {
|
|
2428
2644
|
if (state.inputMode === "filter") {
|
|
2429
2645
|
state.processFilter = state.inputBuffer.trim();
|
|
2430
2646
|
setStatus(state.processFilter ? `Filtering: "${state.processFilter}"` : "Filter cleared");
|
|
2431
2647
|
} else if (state.inputMode === "kill") {
|
|
2432
|
-
|
|
2648
|
+
const pidStr = state.inputBuffer.trim();
|
|
2649
|
+
state.targetPid = pidStr;
|
|
2650
|
+
killProcess(pidStr, state.selectedSignal);
|
|
2433
2651
|
}
|
|
2434
2652
|
state.inputMode = null;
|
|
2435
2653
|
state.inputBuffer = "";
|
|
@@ -2509,6 +2727,8 @@ function onResize() {
|
|
|
2509
2727
|
state.termWidth = process.stdout.columns || 80;
|
|
2510
2728
|
state.termHeight = process.stdout.rows || 24;
|
|
2511
2729
|
state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
|
|
2730
|
+
invalidateFrame();
|
|
2731
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
2512
2732
|
render();
|
|
2513
2733
|
}
|
|
2514
2734
|
function restartTicker() {
|
|
@@ -2517,8 +2737,9 @@ function restartTicker() {
|
|
|
2517
2737
|
}
|
|
2518
2738
|
async function doWarmup() {
|
|
2519
2739
|
try {
|
|
2520
|
-
const data = await collectAll({ detailed: state.detailed
|
|
2740
|
+
const data = await collectAll({ detailed: state.detailed });
|
|
2521
2741
|
state.lastData = data;
|
|
2742
|
+
invalidateTableCache();
|
|
2522
2743
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
2523
2744
|
state.history.push({
|
|
2524
2745
|
cpuUsage: data.cpu.usage,
|
|
@@ -2541,8 +2762,9 @@ async function doWarmup() {
|
|
|
2541
2762
|
async function tick() {
|
|
2542
2763
|
if (state.paused) return;
|
|
2543
2764
|
try {
|
|
2544
|
-
const data = await collectAll({ detailed: state.detailed
|
|
2765
|
+
const data = await collectAll({ detailed: state.detailed });
|
|
2545
2766
|
state.lastData = data;
|
|
2767
|
+
invalidateTableCache();
|
|
2546
2768
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
2547
2769
|
state.history.push({
|
|
2548
2770
|
cpuUsage: data.cpu.usage,
|
|
@@ -2594,6 +2816,10 @@ async function startLive(opts2, splashPromise) {
|
|
|
2594
2816
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
2595
2817
|
process.stdin.resume();
|
|
2596
2818
|
process.stdout.on("resize", onResize);
|
|
2819
|
+
if (state.uiIntervalHandle) clearInterval(state.uiIntervalHandle);
|
|
2820
|
+
state.uiIntervalHandle = setInterval(() => {
|
|
2821
|
+
if (state.running && !state.paused) render();
|
|
2822
|
+
}, 250);
|
|
2597
2823
|
state.keypressHandler = (str, key) => {
|
|
2598
2824
|
if (!key) return;
|
|
2599
2825
|
if (key.ctrl && key.name === "c") {
|
|
@@ -2684,28 +2910,12 @@ async function startLive(opts2, splashPromise) {
|
|
|
2684
2910
|
case "q":
|
|
2685
2911
|
stopLive();
|
|
2686
2912
|
break;
|
|
2687
|
-
case "left":
|
|
2688
|
-
|
|
2689
|
-
if (currentIdx > 0) {
|
|
2690
|
-
state.activePanel = state.PANEL_TABS[currentIdx - 1].id;
|
|
2691
|
-
} else if (currentIdx === -1) {
|
|
2692
|
-
state.activePanel = state.PANEL_TABS[state.PANEL_TABS.length - 1].id;
|
|
2693
|
-
}
|
|
2694
|
-
setStatus(`Panel: ${state.activePanel}`);
|
|
2695
|
-
render();
|
|
2913
|
+
case "left":
|
|
2914
|
+
cycleTab(-1);
|
|
2696
2915
|
break;
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
const currentIdx = state.PANEL_TABS.findIndex((t) => t.id === state.activePanel);
|
|
2700
|
-
if (currentIdx < state.PANEL_TABS.length - 1) {
|
|
2701
|
-
state.activePanel = state.PANEL_TABS[currentIdx + 1].id;
|
|
2702
|
-
} else if (currentIdx === -1) {
|
|
2703
|
-
state.activePanel = state.PANEL_TABS[0].id;
|
|
2704
|
-
}
|
|
2705
|
-
setStatus(`Panel: ${state.activePanel}`);
|
|
2706
|
-
render();
|
|
2916
|
+
case "right":
|
|
2917
|
+
cycleTab(1);
|
|
2707
2918
|
break;
|
|
2708
|
-
}
|
|
2709
2919
|
case "c":
|
|
2710
2920
|
state.inputMode = "customizer";
|
|
2711
2921
|
state.customizerIndex = 0;
|
|
@@ -2767,12 +2977,6 @@ async function startLive(opts2, splashPromise) {
|
|
|
2767
2977
|
case "tab":
|
|
2768
2978
|
cycleTab(1);
|
|
2769
2979
|
break;
|
|
2770
|
-
case "left":
|
|
2771
|
-
cycleTab(-1);
|
|
2772
|
-
break;
|
|
2773
|
-
case "right":
|
|
2774
|
-
cycleTab(1);
|
|
2775
|
-
break;
|
|
2776
2980
|
default:
|
|
2777
2981
|
if (str && str.length === 1) {
|
|
2778
2982
|
const tabId = tabKeyToId(str);
|
|
@@ -2812,8 +3016,8 @@ function handleSGRMouse(seq) {
|
|
|
2812
3016
|
const match = seq.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
2813
3017
|
if (!match) return;
|
|
2814
3018
|
const button = parseInt(match[1]);
|
|
2815
|
-
const cx = parseInt(match[2]);
|
|
2816
|
-
const cy = parseInt(match[3]);
|
|
3019
|
+
const cx = parseInt(match[2]) - 1;
|
|
3020
|
+
const cy = parseInt(match[3]) - 1;
|
|
2817
3021
|
if (button === 0 || button === 1) {
|
|
2818
3022
|
handleMouseClick(cy, cx);
|
|
2819
3023
|
}
|
|
@@ -2833,6 +3037,10 @@ function stopLive() {
|
|
|
2833
3037
|
clearInterval(state.intervalHandle);
|
|
2834
3038
|
state.intervalHandle = null;
|
|
2835
3039
|
}
|
|
3040
|
+
if (state.uiIntervalHandle) {
|
|
3041
|
+
clearInterval(state.uiIntervalHandle);
|
|
3042
|
+
state.uiIntervalHandle = null;
|
|
3043
|
+
}
|
|
2836
3044
|
if (state.statusTimer) {
|
|
2837
3045
|
clearTimeout(state.statusTimer);
|
|
2838
3046
|
state.statusTimer = null;
|
|
@@ -3155,7 +3363,26 @@ async function runP2PConnect() {
|
|
|
3155
3363
|
try {
|
|
3156
3364
|
await client.connect();
|
|
3157
3365
|
} catch (err) {
|
|
3158
|
-
|
|
3366
|
+
const code = err.code;
|
|
3367
|
+
const msg = err.message || String(err);
|
|
3368
|
+
console.log(chalk9.red(`Connection failed: ${msg}`));
|
|
3369
|
+
if (code === "ECONNREFUSED") {
|
|
3370
|
+
console.log(chalk9.yellow(" Hints:"));
|
|
3371
|
+
console.log(chalk9.dim(" 1. Start the server: pyre p2p server --p2p-host 0.0.0.0 --p2p-port " + port + " --p2p-password <password>"));
|
|
3372
|
+
console.log(chalk9.dim(" 2. Check the server IP with: hostname -I"));
|
|
3373
|
+
console.log(chalk9.dim(" 3. Open the port on the server firewall: sudo ufw allow " + port + "/tcp"));
|
|
3374
|
+
} else if (code === "ETIMEDOUT") {
|
|
3375
|
+
console.log(chalk9.yellow(" Hints:"));
|
|
3376
|
+
console.log(chalk9.dim(" 1. Verify the server IP is correct"));
|
|
3377
|
+
console.log(chalk9.dim(" 2. Check that both machines are on the same network/subnet"));
|
|
3378
|
+
console.log(chalk9.dim(" 3. Open the port on the server firewall"));
|
|
3379
|
+
} else if (code === "EHOSTDOWN" || code === "EHOSTUNREACH") {
|
|
3380
|
+
console.log(chalk9.yellow(" Hints:"));
|
|
3381
|
+
console.log(chalk9.dim(" 1. The server at " + host + " is not responding"));
|
|
3382
|
+
console.log(chalk9.dim(" 2. Start the server first: pyre p2p server --p2p-host 0.0.0.0 --p2p-port " + port + " --p2p-password <password>"));
|
|
3383
|
+
console.log(chalk9.dim(" 3. Check connectivity: ping " + host));
|
|
3384
|
+
console.log(chalk9.dim(" 4. Ensure both machines are on the same LAN (ping each other first)"));
|
|
3385
|
+
}
|
|
3159
3386
|
process.exit(1);
|
|
3160
3387
|
}
|
|
3161
3388
|
process.once("SIGINT", () => {
|