pyre-cli 3.3.0 → 3.5.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 +395 -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 +2 -0
- package/src/live/input.ts +80 -57
- package/src/live/live.md +282 -0
- package/src/live/render.ts +182 -68
- package/src/live/state.ts +22 -1
- package/src/live/types.ts +1 -0
- 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/p2p.md +284 -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;
|
|
@@ -2348,8 +2533,20 @@ function deriveHMACKey2(password) {
|
|
|
2348
2533
|
}
|
|
2349
2534
|
|
|
2350
2535
|
// src/live/input.ts
|
|
2536
|
+
function isUpKey(key, str) {
|
|
2537
|
+
return key.name === "up" || str === "k" || key.sequence === "\x1B[A" || key.sequence === "\x1BOA";
|
|
2538
|
+
}
|
|
2539
|
+
function isDownKey(key, str) {
|
|
2540
|
+
return key.name === "down" || str === "j" || key.sequence === "\x1B[B" || key.sequence === "\x1BOB";
|
|
2541
|
+
}
|
|
2542
|
+
function isEnterKey(key, str) {
|
|
2543
|
+
return key.name === "return" || key.name === "enter" || key.sequence === "\r" || key.sequence === "\n" || str === "\r" || str === "\n";
|
|
2544
|
+
}
|
|
2545
|
+
function isEscKey(key, str) {
|
|
2546
|
+
return key.name === "escape" || key.name === "esc" || key.sequence === "\x1B" || str === "\x1B";
|
|
2547
|
+
}
|
|
2351
2548
|
function handleInputModeKey(str, key) {
|
|
2352
|
-
if (key
|
|
2549
|
+
if (isEscKey(key, str)) {
|
|
2353
2550
|
state.inputMode = null;
|
|
2354
2551
|
state.inputBuffer = "";
|
|
2355
2552
|
render();
|
|
@@ -2357,38 +2554,55 @@ function handleInputModeKey(str, key) {
|
|
|
2357
2554
|
}
|
|
2358
2555
|
if (state.inputMode === "customizer") {
|
|
2359
2556
|
const themesList = Object.keys(THEMES);
|
|
2360
|
-
if (key
|
|
2557
|
+
if (isUpKey(key, str)) {
|
|
2361
2558
|
state.customizerIndex = (state.customizerIndex - 1 + state.CUSTOMIZER_OPTIONS.length) % state.CUSTOMIZER_OPTIONS.length;
|
|
2362
|
-
} else if (key
|
|
2559
|
+
} else if (isDownKey(key, str)) {
|
|
2363
2560
|
state.customizerIndex = (state.customizerIndex + 1) % state.CUSTOMIZER_OPTIONS.length;
|
|
2364
|
-
} else if (key
|
|
2561
|
+
} else if (isEnterKey(key, str) || str === " ") {
|
|
2365
2562
|
const selected = state.CUSTOMIZER_OPTIONS[state.customizerIndex];
|
|
2366
2563
|
if (selected === "Theme") {
|
|
2367
2564
|
const nextIdx = (themesList.indexOf(state.currentTheme) + 1) % themesList.length;
|
|
2368
2565
|
state.currentTheme = themesList[nextIdx];
|
|
2369
|
-
} else if (selected === "Toggle Tree View") {
|
|
2370
|
-
state.treeView = !state.treeView;
|
|
2371
2566
|
} else if (selected === "Graph Mode") {
|
|
2372
2567
|
state.graphMode = state.graphMode === "spark" ? "bar" : "spark";
|
|
2373
2568
|
} else {
|
|
2374
|
-
const
|
|
2375
|
-
|
|
2569
|
+
const toggleKey = getToggleKey(selected);
|
|
2570
|
+
if (toggleKey) {
|
|
2571
|
+
if (toggleKey === "tree") {
|
|
2572
|
+
state.treeView = !state.treeView;
|
|
2573
|
+
state.visiblePanels.tree = state.treeView;
|
|
2574
|
+
} else {
|
|
2575
|
+
const isVisible = state.visiblePanels[toggleKey] !== false;
|
|
2576
|
+
state.visiblePanels[toggleKey] = !isVisible;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2376
2579
|
}
|
|
2580
|
+
invalidateTableCache();
|
|
2581
|
+
invalidateFrame();
|
|
2377
2582
|
}
|
|
2378
2583
|
render();
|
|
2379
2584
|
return;
|
|
2380
2585
|
}
|
|
2381
2586
|
if (state.inputMode === "signal") {
|
|
2382
|
-
if (key
|
|
2587
|
+
if (isUpKey(key, str)) {
|
|
2383
2588
|
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
2384
2589
|
state.inputBuffer = state.SIGNAL_OPTIONS[(idx - 1 + state.SIGNAL_OPTIONS.length) % state.SIGNAL_OPTIONS.length];
|
|
2385
|
-
|
|
2590
|
+
state.selectedSignal = state.inputBuffer;
|
|
2591
|
+
} else if (isDownKey(key, str)) {
|
|
2386
2592
|
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
2387
2593
|
state.inputBuffer = state.SIGNAL_OPTIONS[(idx + 1) % state.SIGNAL_OPTIONS.length];
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
state.
|
|
2391
|
-
state.
|
|
2594
|
+
state.selectedSignal = state.inputBuffer;
|
|
2595
|
+
} else if (isEnterKey(key, str) || str === " ") {
|
|
2596
|
+
state.selectedSignal = state.inputBuffer.trim();
|
|
2597
|
+
if (state.targetPid) {
|
|
2598
|
+
killProcess(state.targetPid, state.selectedSignal);
|
|
2599
|
+
state.inputMode = null;
|
|
2600
|
+
state.inputBuffer = "";
|
|
2601
|
+
} else {
|
|
2602
|
+
state.inputMode = "kill";
|
|
2603
|
+
state.inputBuffer = "";
|
|
2604
|
+
setStatus(`Signal ${state.selectedSignal} selected. Enter target PID to kill:`, 5e3);
|
|
2605
|
+
}
|
|
2392
2606
|
render();
|
|
2393
2607
|
return;
|
|
2394
2608
|
}
|
|
@@ -2396,13 +2610,13 @@ function handleInputModeKey(str, key) {
|
|
|
2396
2610
|
return;
|
|
2397
2611
|
}
|
|
2398
2612
|
if (state.inputMode === "p2p") {
|
|
2399
|
-
if (key
|
|
2613
|
+
if (isEscKey(key, str)) {
|
|
2400
2614
|
state.inputMode = null;
|
|
2401
2615
|
state.inputBuffer = "";
|
|
2402
2616
|
render();
|
|
2403
2617
|
return;
|
|
2404
2618
|
}
|
|
2405
|
-
if (key
|
|
2619
|
+
if (isEnterKey(key, str)) {
|
|
2406
2620
|
const password = state.inputBuffer.trim();
|
|
2407
2621
|
if (password) {
|
|
2408
2622
|
state.p2pPassword = password;
|
|
@@ -2424,12 +2638,14 @@ function handleInputModeKey(str, key) {
|
|
|
2424
2638
|
}
|
|
2425
2639
|
return;
|
|
2426
2640
|
}
|
|
2427
|
-
if (key
|
|
2641
|
+
if (isEnterKey(key, str)) {
|
|
2428
2642
|
if (state.inputMode === "filter") {
|
|
2429
2643
|
state.processFilter = state.inputBuffer.trim();
|
|
2430
2644
|
setStatus(state.processFilter ? `Filtering: "${state.processFilter}"` : "Filter cleared");
|
|
2431
2645
|
} else if (state.inputMode === "kill") {
|
|
2432
|
-
|
|
2646
|
+
const pidStr = state.inputBuffer.trim();
|
|
2647
|
+
state.targetPid = pidStr;
|
|
2648
|
+
killProcess(pidStr, state.selectedSignal);
|
|
2433
2649
|
}
|
|
2434
2650
|
state.inputMode = null;
|
|
2435
2651
|
state.inputBuffer = "";
|
|
@@ -2509,6 +2725,8 @@ function onResize() {
|
|
|
2509
2725
|
state.termWidth = process.stdout.columns || 80;
|
|
2510
2726
|
state.termHeight = process.stdout.rows || 24;
|
|
2511
2727
|
state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
|
|
2728
|
+
invalidateFrame();
|
|
2729
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
2512
2730
|
render();
|
|
2513
2731
|
}
|
|
2514
2732
|
function restartTicker() {
|
|
@@ -2517,8 +2735,9 @@ function restartTicker() {
|
|
|
2517
2735
|
}
|
|
2518
2736
|
async function doWarmup() {
|
|
2519
2737
|
try {
|
|
2520
|
-
const data = await collectAll({ detailed: state.detailed
|
|
2738
|
+
const data = await collectAll({ detailed: state.detailed });
|
|
2521
2739
|
state.lastData = data;
|
|
2740
|
+
invalidateTableCache();
|
|
2522
2741
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
2523
2742
|
state.history.push({
|
|
2524
2743
|
cpuUsage: data.cpu.usage,
|
|
@@ -2541,8 +2760,9 @@ async function doWarmup() {
|
|
|
2541
2760
|
async function tick() {
|
|
2542
2761
|
if (state.paused) return;
|
|
2543
2762
|
try {
|
|
2544
|
-
const data = await collectAll({ detailed: state.detailed
|
|
2763
|
+
const data = await collectAll({ detailed: state.detailed });
|
|
2545
2764
|
state.lastData = data;
|
|
2765
|
+
invalidateTableCache();
|
|
2546
2766
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
2547
2767
|
state.history.push({
|
|
2548
2768
|
cpuUsage: data.cpu.usage,
|
|
@@ -2568,6 +2788,7 @@ async function startLive(opts2, splashPromise) {
|
|
|
2568
2788
|
state.running = true;
|
|
2569
2789
|
state.paused = false;
|
|
2570
2790
|
state.detailed = !!opts2.detailed;
|
|
2791
|
+
if (opts2.theme) state.currentTheme = opts2.theme;
|
|
2571
2792
|
state.interval = opts2.interval;
|
|
2572
2793
|
if (opts2.exportDir) state.exportDir = opts2.exportDir;
|
|
2573
2794
|
state.termWidth = process.stdout.columns || 80;
|
|
@@ -2593,6 +2814,10 @@ async function startLive(opts2, splashPromise) {
|
|
|
2593
2814
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
2594
2815
|
process.stdin.resume();
|
|
2595
2816
|
process.stdout.on("resize", onResize);
|
|
2817
|
+
if (state.uiIntervalHandle) clearInterval(state.uiIntervalHandle);
|
|
2818
|
+
state.uiIntervalHandle = setInterval(() => {
|
|
2819
|
+
if (state.running && !state.paused) render();
|
|
2820
|
+
}, 250);
|
|
2596
2821
|
state.keypressHandler = (str, key) => {
|
|
2597
2822
|
if (!key) return;
|
|
2598
2823
|
if (key.ctrl && key.name === "c") {
|
|
@@ -2683,28 +2908,12 @@ async function startLive(opts2, splashPromise) {
|
|
|
2683
2908
|
case "q":
|
|
2684
2909
|
stopLive();
|
|
2685
2910
|
break;
|
|
2686
|
-
case "left":
|
|
2687
|
-
|
|
2688
|
-
if (currentIdx > 0) {
|
|
2689
|
-
state.activePanel = state.PANEL_TABS[currentIdx - 1].id;
|
|
2690
|
-
} else if (currentIdx === -1) {
|
|
2691
|
-
state.activePanel = state.PANEL_TABS[state.PANEL_TABS.length - 1].id;
|
|
2692
|
-
}
|
|
2693
|
-
setStatus(`Panel: ${state.activePanel}`);
|
|
2694
|
-
render();
|
|
2911
|
+
case "left":
|
|
2912
|
+
cycleTab(-1);
|
|
2695
2913
|
break;
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
const currentIdx = state.PANEL_TABS.findIndex((t) => t.id === state.activePanel);
|
|
2699
|
-
if (currentIdx < state.PANEL_TABS.length - 1) {
|
|
2700
|
-
state.activePanel = state.PANEL_TABS[currentIdx + 1].id;
|
|
2701
|
-
} else if (currentIdx === -1) {
|
|
2702
|
-
state.activePanel = state.PANEL_TABS[0].id;
|
|
2703
|
-
}
|
|
2704
|
-
setStatus(`Panel: ${state.activePanel}`);
|
|
2705
|
-
render();
|
|
2914
|
+
case "right":
|
|
2915
|
+
cycleTab(1);
|
|
2706
2916
|
break;
|
|
2707
|
-
}
|
|
2708
2917
|
case "c":
|
|
2709
2918
|
state.inputMode = "customizer";
|
|
2710
2919
|
state.customizerIndex = 0;
|
|
@@ -2766,12 +2975,6 @@ async function startLive(opts2, splashPromise) {
|
|
|
2766
2975
|
case "tab":
|
|
2767
2976
|
cycleTab(1);
|
|
2768
2977
|
break;
|
|
2769
|
-
case "left":
|
|
2770
|
-
cycleTab(-1);
|
|
2771
|
-
break;
|
|
2772
|
-
case "right":
|
|
2773
|
-
cycleTab(1);
|
|
2774
|
-
break;
|
|
2775
2978
|
default:
|
|
2776
2979
|
if (str && str.length === 1) {
|
|
2777
2980
|
const tabId = tabKeyToId(str);
|
|
@@ -2811,8 +3014,8 @@ function handleSGRMouse(seq) {
|
|
|
2811
3014
|
const match = seq.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
2812
3015
|
if (!match) return;
|
|
2813
3016
|
const button = parseInt(match[1]);
|
|
2814
|
-
const cx = parseInt(match[2]);
|
|
2815
|
-
const cy = parseInt(match[3]);
|
|
3017
|
+
const cx = parseInt(match[2]) - 1;
|
|
3018
|
+
const cy = parseInt(match[3]) - 1;
|
|
2816
3019
|
if (button === 0 || button === 1) {
|
|
2817
3020
|
handleMouseClick(cy, cx);
|
|
2818
3021
|
}
|
|
@@ -2832,6 +3035,10 @@ function stopLive() {
|
|
|
2832
3035
|
clearInterval(state.intervalHandle);
|
|
2833
3036
|
state.intervalHandle = null;
|
|
2834
3037
|
}
|
|
3038
|
+
if (state.uiIntervalHandle) {
|
|
3039
|
+
clearInterval(state.uiIntervalHandle);
|
|
3040
|
+
state.uiIntervalHandle = null;
|
|
3041
|
+
}
|
|
2835
3042
|
if (state.statusTimer) {
|
|
2836
3043
|
clearTimeout(state.statusTimer);
|
|
2837
3044
|
state.statusTimer = null;
|
|
@@ -2977,7 +3184,7 @@ async function showSplash() {
|
|
|
2977
3184
|
// src/index.ts
|
|
2978
3185
|
var pkg = JSON.parse(fs4.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
2979
3186
|
var program = new Command();
|
|
2980
|
-
program.name("pyre").version(pkg.version).description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--interval <seconds>", "Refresh interval for live mode", "2").option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", "./pyre-exports").option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", "cpu").option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)", "0.0.0.0").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)");
|
|
3187
|
+
program.name("pyre").version(pkg.version).description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--theme <name>", "Default theme for live mode (default, dracula, cyberpunk, monochrome, nord, gruvbox)", "default").option("--interval <seconds>", "Refresh interval for live mode", "2").option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", "./pyre-exports").option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", "cpu").option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)", "0.0.0.0").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)");
|
|
2981
3188
|
program.parse(process.argv);
|
|
2982
3189
|
var opts = program.opts();
|
|
2983
3190
|
function isExportMode() {
|
|
@@ -2997,6 +3204,7 @@ async function main() {
|
|
|
2997
3204
|
await startLive({
|
|
2998
3205
|
interval,
|
|
2999
3206
|
detailed: opts.detailed,
|
|
3207
|
+
theme: opts.theme,
|
|
3000
3208
|
exportDir: opts.exportDir,
|
|
3001
3209
|
autoLog: opts.log
|
|
3002
3210
|
}, splashPromise);
|