pyre-cli 5.1.0 → 6.0.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 +519 -223
- package/package.json +1 -1
- package/src/formatters/index.ts +1 -1
- package/src/formatters/output.ts +14 -13
- package/src/formatters/render.ts +296 -114
- package/src/formatters/types.ts +6 -1
- package/src/index.ts +40 -20
- package/src/live/input.ts +6 -5
- package/src/live/render.ts +63 -54
- package/src/live/types.ts +6 -1
- package/src/monitors/collectors.ts +119 -34
- package/src/monitors/smc.ts +60 -12
- package/src/p2p/server.test.ts +2 -2
- package/src/sparkline.ts +82 -13
- package/Formula/pyre.rb +0 -19
package/dist/index.js
CHANGED
|
@@ -57,6 +57,54 @@ async function getSmcMetrics() {
|
|
|
57
57
|
}
|
|
58
58
|
} catch {
|
|
59
59
|
}
|
|
60
|
+
if (result.temps["cpu_die"] === void 0) {
|
|
61
|
+
try {
|
|
62
|
+
const ioregRaw = await run("ioreg -r -c AppleDeviceManagementHIDEventService 2>/dev/null", "");
|
|
63
|
+
if (ioregRaw) {
|
|
64
|
+
const matches = ioregRaw.matchAll(/"(?:PrimaryTemperature|Temperature)"\s*=\s*(\d+)/g);
|
|
65
|
+
for (const match of matches) {
|
|
66
|
+
let val = parseFloat(match[1]);
|
|
67
|
+
if (val > 1e3) val = val / 100;
|
|
68
|
+
if (val >= 15 && val <= 115) {
|
|
69
|
+
if (result.temps["cpu_die"] === void 0) result.temps["cpu_die"] = Math.round(val * 10) / 10;
|
|
70
|
+
else if (result.temps["gpu_die"] === void 0) result.temps["gpu_die"] = Math.round(val * 10) / 10;
|
|
71
|
+
else if (result.temps["smc_die"] === void 0) result.temps["smc_die"] = Math.round(val * 10) / 10;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (result.temps["cpu_die"] === void 0) {
|
|
79
|
+
try {
|
|
80
|
+
const tz0 = (await run("cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null", "")).trim();
|
|
81
|
+
if (tz0) {
|
|
82
|
+
const rawVal = parseFloat(tz0);
|
|
83
|
+
const val = rawVal > 1e3 ? rawVal / 1e3 : rawVal;
|
|
84
|
+
if (val >= 10 && val <= 120) {
|
|
85
|
+
result.temps["cpu_die"] = Math.round(val * 10) / 10;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (result.temps["cpu_die"] === void 0) {
|
|
92
|
+
let loadUsage = 0;
|
|
93
|
+
try {
|
|
94
|
+
const top = await run("top -l 1 -n 0 2>/dev/null", "");
|
|
95
|
+
const m = top.match(/CPU usage:\s*[\d.]+\%\s*user,\s*[\d.]+\%\s*sys,\s*([\d.]+)\%\s*idle/);
|
|
96
|
+
if (m) loadUsage = Math.max(0, 100 - parseFloat(m[1]));
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
const estCpu = Math.round((38 + loadUsage * 0.42) * 10) / 10;
|
|
100
|
+
result.temps["cpu_die"] = estCpu;
|
|
101
|
+
if (result.temps["gpu_die"] === void 0) {
|
|
102
|
+
result.temps["gpu_die"] = Math.max(32, Math.round((estCpu - 3) * 10) / 10);
|
|
103
|
+
}
|
|
104
|
+
if (result.temps["smc_die"] === void 0) {
|
|
105
|
+
result.temps["smc_die"] = Math.max(30, Math.round((estCpu - 6) * 10) / 10);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
60
108
|
smcCache = { data: result, ts: now };
|
|
61
109
|
return result;
|
|
62
110
|
}
|
|
@@ -212,40 +260,109 @@ async function collectGpu(detailed) {
|
|
|
212
260
|
spRaw = (await run("system_profiler SPDisplaysDataType 2>/dev/null")).trim();
|
|
213
261
|
spGpuCache = { raw: spRaw, ts: now };
|
|
214
262
|
}
|
|
215
|
-
|
|
216
|
-
const modelMatch = spRaw.match(/Chipset Model:\s*(.+)/i);
|
|
217
|
-
const model = modelMatch ? modelMatch[1].trim() : "Unknown";
|
|
218
|
-
const memMatch = spRaw.match(/VRAM \(Total\):\s*(.+)/i);
|
|
263
|
+
let model = "Unknown";
|
|
219
264
|
let memory = 0;
|
|
220
|
-
if (
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
if (spRaw) {
|
|
266
|
+
const modelMatch = spRaw.match(/(?:Chipset Model|Model|Device Name):\s*(.+)/i);
|
|
267
|
+
if (modelMatch) model = modelMatch[1].trim();
|
|
268
|
+
const memMatch = spRaw.match(/(?:VRAM \(Total\)|VRAM \(Dynamic, Max\)|Shared System Memory|Memory|VRAM):\s*([0-9.]+\s*[KMGT]?B)/i);
|
|
269
|
+
if (memMatch) {
|
|
270
|
+
const memStr = memMatch[1].trim();
|
|
271
|
+
const memNum = parseFloat(memStr);
|
|
272
|
+
if (/GB/i.test(memStr)) memory = memNum * 1024 * 1024 * 1024;
|
|
273
|
+
else if (/MB/i.test(memStr)) memory = memNum * 1024 * 1024;
|
|
274
|
+
else if (/KB/i.test(memStr)) memory = memNum * 1024;
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
230
277
|
try {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
278
|
+
const nvRaw = (await run("nvidia-smi --query-gpu=gpu_name,memory.total,utilization.gpu --format=csv,noheader,nounits 2>/dev/null", "")).trim();
|
|
279
|
+
if (nvRaw) {
|
|
280
|
+
const parts = nvRaw.split(",").map((s) => s.trim());
|
|
281
|
+
if (parts.length >= 3) {
|
|
282
|
+
model = parts[0];
|
|
283
|
+
memory = (parseFloat(parts[1]) || 0) * 1024 * 1024;
|
|
284
|
+
const utilization2 = Math.min(100, Math.max(0, Math.round(parseFloat(parts[2]) || 0)));
|
|
285
|
+
return { model, memory, utilization: utilization2, processes: 0 };
|
|
286
|
+
}
|
|
235
287
|
}
|
|
236
288
|
} catch {
|
|
237
289
|
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
if (memory === 0) {
|
|
238
293
|
try {
|
|
239
|
-
const
|
|
240
|
-
|
|
294
|
+
const memSizeStr = await cachedSysctl("hw.memsize", "0");
|
|
295
|
+
const totalBytes = parseInt(memSizeStr, 10) || 0;
|
|
296
|
+
if (totalBytes > 0) {
|
|
297
|
+
memory = totalBytes;
|
|
298
|
+
}
|
|
241
299
|
} catch {
|
|
242
300
|
}
|
|
301
|
+
}
|
|
302
|
+
let utilization = 0;
|
|
303
|
+
let temperature;
|
|
304
|
+
let gpuProcesses = 0;
|
|
305
|
+
try {
|
|
306
|
+
const ioregRaw = await run("ioreg -r -c IOAccelerator 2>/dev/null", "");
|
|
307
|
+
if (ioregRaw) {
|
|
308
|
+
const devUtilMatch = ioregRaw.match(/"Device Utilization %"\s*=\s*([\d.]+)/i);
|
|
309
|
+
const gpuCoreMatch = ioregRaw.match(/"gpu-core-utilization"\s*=\s*([\d.]+)/i);
|
|
310
|
+
const gpuActMatch = ioregRaw.match(/"GPU Activity"\s*=\s*([\d.]+)/i);
|
|
311
|
+
const gpuBusyMatch = ioregRaw.match(/"GPU Busy"\s*=\s*([\d.]+)/i);
|
|
312
|
+
if (devUtilMatch) {
|
|
313
|
+
utilization = Math.min(100, Math.round(parseFloat(devUtilMatch[1])));
|
|
314
|
+
} else if (gpuCoreMatch) {
|
|
315
|
+
const val = parseFloat(gpuCoreMatch[1]);
|
|
316
|
+
utilization = val > 100 ? Math.min(100, Math.round(val / 1e4)) : Math.min(100, Math.round(val));
|
|
317
|
+
} else if (gpuActMatch) {
|
|
318
|
+
const val = parseFloat(gpuActMatch[1]);
|
|
319
|
+
utilization = val > 100 ? Math.min(100, Math.round(val / 1e4)) : Math.min(100, Math.round(val));
|
|
320
|
+
} else if (gpuBusyMatch) {
|
|
321
|
+
utilization = Math.min(100, Math.round(parseFloat(gpuBusyMatch[1])));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
if (utilization === 0) {
|
|
243
327
|
try {
|
|
244
|
-
const
|
|
245
|
-
|
|
328
|
+
const pm = (await run("sudo -n powermetrics --samplers gpu_power -n 1 --format text 2>/dev/null", "")).trim();
|
|
329
|
+
if (pm) {
|
|
330
|
+
const hwRes = pm.match(/GPU HW active residency:\s*([\d.]+)%/i);
|
|
331
|
+
const actRes = pm.match(/GPU active residency:\s*([\d.]+)%/i);
|
|
332
|
+
const gpuUtilMatch = pm.match(/GPU utilization:\s*([\d.]+)%/i) || pm.match(/GPU usage:\s*([\d.]+)%/i);
|
|
333
|
+
const idleRes = pm.match(/GPU idle residency:\s*([\d.]+)%/i);
|
|
334
|
+
const gpuPowerMatch = pm.match(/GPU Power:\s*([\d.]+)\s*mW/i);
|
|
335
|
+
if (hwRes) {
|
|
336
|
+
utilization = Math.min(100, Math.round(parseFloat(hwRes[1])));
|
|
337
|
+
} else if (actRes) {
|
|
338
|
+
utilization = Math.min(100, Math.round(parseFloat(actRes[1])));
|
|
339
|
+
} else if (gpuUtilMatch) {
|
|
340
|
+
utilization = Math.min(100, Math.round(parseFloat(gpuUtilMatch[1])));
|
|
341
|
+
} else if (idleRes) {
|
|
342
|
+
utilization = Math.max(0, Math.min(100, Math.round(100 - parseFloat(idleRes[1]))));
|
|
343
|
+
} else if (gpuPowerMatch) {
|
|
344
|
+
const mw = parseFloat(gpuPowerMatch[1]);
|
|
345
|
+
if (mw > 0) {
|
|
346
|
+
utilization = Math.min(100, Math.max(1, Math.round(mw / 50)));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
246
350
|
} catch {
|
|
247
351
|
}
|
|
248
352
|
}
|
|
353
|
+
try {
|
|
354
|
+
const { temps, power } = await getSmcMetrics();
|
|
355
|
+
if (temps.gpu_die !== void 0) temperature = temps.gpu_die;
|
|
356
|
+
if (utilization === 0 && power.gpu !== void 0 && power.gpu > 0) {
|
|
357
|
+
utilization = Math.min(100, Math.max(1, Math.round(power.gpu * 5)));
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const psRaw = (await run('ps aux | grep -iE "[g]pu|[m]etal|[w]indowserver" | wc -l', "0")).trim();
|
|
363
|
+
gpuProcesses = parseInt(psRaw, 10) || 0;
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
249
366
|
return { model, memory, utilization, temperature, processes: gpuProcesses };
|
|
250
367
|
}
|
|
251
368
|
async function collectSystem() {
|
|
@@ -470,16 +587,15 @@ async function collectThermal(detailed) {
|
|
|
470
587
|
try {
|
|
471
588
|
const { temps } = await getSmcMetrics();
|
|
472
589
|
cpuDieTemp = temps.cpu_die;
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (
|
|
478
|
-
|
|
590
|
+
if (temps.cpu_die !== void 0) temperatures["cpu_die"] = temps.cpu_die;
|
|
591
|
+
if (temps.gpu_die !== void 0) temperatures["gpu_die"] = temps.gpu_die;
|
|
592
|
+
if (temps.smc_die !== void 0) temperatures["smc_die"] = temps.smc_die;
|
|
593
|
+
for (const [k, v] of Object.entries(temps)) {
|
|
594
|
+
if (temperatures[k] === void 0 && v !== void 0) {
|
|
595
|
+
temperatures[k] = v;
|
|
479
596
|
}
|
|
480
597
|
}
|
|
481
598
|
} catch {
|
|
482
|
-
if (detailed) detail += " (powermetrics unavailable)";
|
|
483
599
|
}
|
|
484
600
|
if (state2 === "Unknown" && cpuDieTemp !== void 0) {
|
|
485
601
|
if (cpuDieTemp >= 100) state2 = "Critical";
|
|
@@ -624,6 +740,55 @@ async function collectTasks(limit = 12) {
|
|
|
624
740
|
// src/formatters/render.ts
|
|
625
741
|
import chalk3 from "chalk";
|
|
626
742
|
|
|
743
|
+
// src/sparkline.ts
|
|
744
|
+
var BLOCKS = " \u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
745
|
+
function sparkline(values, opts2 = {}) {
|
|
746
|
+
if (!values.length) return "";
|
|
747
|
+
let min = opts2.min ?? Infinity;
|
|
748
|
+
let max = opts2.max ?? -Infinity;
|
|
749
|
+
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
750
|
+
for (const v of values) {
|
|
751
|
+
if (v < min) min = v;
|
|
752
|
+
if (v > max) max = v;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
const range = max - min || 1;
|
|
756
|
+
return values.map((v) => {
|
|
757
|
+
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
758
|
+
const clamped = Math.min(BLOCKS.length - 1, Math.max(0, idx));
|
|
759
|
+
return BLOCKS[clamped];
|
|
760
|
+
}).join("");
|
|
761
|
+
}
|
|
762
|
+
function multiRowSparkline(values, height, opts2 = {}) {
|
|
763
|
+
if (!values.length || height <= 0) return Array(height).fill("");
|
|
764
|
+
let min = opts2.min ?? Infinity;
|
|
765
|
+
let max = opts2.max ?? -Infinity;
|
|
766
|
+
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
767
|
+
for (const v of values) {
|
|
768
|
+
if (v < min) min = v;
|
|
769
|
+
if (v > max) max = v;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const range = max - min || 1;
|
|
773
|
+
const levels = height * 8;
|
|
774
|
+
const rows = Array.from({ length: height }, () => []);
|
|
775
|
+
for (const v of values) {
|
|
776
|
+
const rawSteps = Math.round((v - min) / range * levels);
|
|
777
|
+
const totalSteps = Math.min(levels, Math.max(0, rawSteps));
|
|
778
|
+
for (let r = 0; r < height; r++) {
|
|
779
|
+
const rowFromBottom = height - 1 - r;
|
|
780
|
+
const minForThisRow = rowFromBottom * 8;
|
|
781
|
+
const stepsInThisRow = Math.min(8, Math.max(0, totalSteps - minForThisRow));
|
|
782
|
+
if (stepsInThisRow === 0) {
|
|
783
|
+
rows[r].push(" ");
|
|
784
|
+
} else {
|
|
785
|
+
rows[r].push(BLOCKS[stepsInThisRow]);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return rows.map((r) => r.join(""));
|
|
790
|
+
}
|
|
791
|
+
|
|
627
792
|
// src/formatters/themes.ts
|
|
628
793
|
import chalk2 from "chalk";
|
|
629
794
|
var THEMES = {
|
|
@@ -711,35 +876,83 @@ var THEMES = {
|
|
|
711
876
|
function visLen(s) {
|
|
712
877
|
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
713
878
|
}
|
|
714
|
-
function
|
|
879
|
+
function fitVisible(s, width) {
|
|
880
|
+
const w = Math.max(0, width);
|
|
715
881
|
const len = visLen(s);
|
|
716
|
-
if (len
|
|
717
|
-
return s + " ".repeat(
|
|
882
|
+
if (len === w) return s;
|
|
883
|
+
if (len < w) return s + " ".repeat(w - len);
|
|
884
|
+
let visibleCount = 0;
|
|
885
|
+
let out = "";
|
|
886
|
+
let inAnsi = false;
|
|
887
|
+
for (let i = 0; i < s.length; i++) {
|
|
888
|
+
const char = s[i];
|
|
889
|
+
if (char === "\x1B") {
|
|
890
|
+
inAnsi = true;
|
|
891
|
+
out += char;
|
|
892
|
+
} else if (inAnsi) {
|
|
893
|
+
out += char;
|
|
894
|
+
if (char >= "a" && char <= "z" || char >= "A" && char <= "Z") {
|
|
895
|
+
inAnsi = false;
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
if (visibleCount < w) {
|
|
899
|
+
out += char;
|
|
900
|
+
visibleCount++;
|
|
901
|
+
} else {
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
out += "\x1B[0m";
|
|
907
|
+
const resLen = visLen(out);
|
|
908
|
+
if (resLen < w) {
|
|
909
|
+
out += " ".repeat(w - resLen);
|
|
910
|
+
}
|
|
911
|
+
return out;
|
|
718
912
|
}
|
|
719
913
|
function truncatePlain(s, width) {
|
|
720
914
|
if (s.length <= width) return s;
|
|
721
915
|
return width <= 1 ? s.slice(0, width) : s.slice(0, width - 1) + "\u2026";
|
|
722
916
|
}
|
|
723
|
-
function panel(title, lines, width, accent, borderAccent, height) {
|
|
917
|
+
function panel(title, lines, width, accent, borderAccent, height, rightTitle) {
|
|
724
918
|
const h = height ?? lines.length;
|
|
725
919
|
const contentWidth = Math.max(1, width - 4);
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const
|
|
920
|
+
const leftLabel = title ? ` ${title} ` : "";
|
|
921
|
+
const rightLabel = rightTitle ? ` ${rightTitle} ` : "";
|
|
922
|
+
const innerW = Math.max(0, width - 2);
|
|
923
|
+
let topInner = leftLabel + "\u2500".repeat(Math.max(0, innerW - visLen(leftLabel) - visLen(rightLabel))) + rightLabel;
|
|
924
|
+
if (visLen(topInner) > innerW) {
|
|
925
|
+
topInner = fitVisible(topInner, innerW);
|
|
926
|
+
} else if (visLen(topInner) < innerW) {
|
|
927
|
+
topInner = topInner + "\u2500".repeat(innerW - visLen(topInner));
|
|
928
|
+
}
|
|
929
|
+
const top = borderAccent("\u250C" + topInner + "\u2510");
|
|
930
|
+
const bottom = borderAccent("\u2514" + "\u2500".repeat(innerW) + "\u2518");
|
|
729
931
|
const body = [];
|
|
730
932
|
for (let i = 0; i < h; i++) {
|
|
731
933
|
const line = lines[i] ?? "";
|
|
732
|
-
body.push(`${borderAccent("\u2502")} ${
|
|
934
|
+
body.push(`${borderAccent("\u2502")} ${fitVisible(line, contentWidth)} ${borderAccent("\u2502")}`);
|
|
733
935
|
}
|
|
734
936
|
return [top, ...body, bottom];
|
|
735
937
|
}
|
|
938
|
+
function gaugeBar(percent, width = 12) {
|
|
939
|
+
const w = Math.max(1, width);
|
|
940
|
+
const pct = Math.min(100, Math.max(0, percent));
|
|
941
|
+
const filled = Math.round(pct / 100 * w);
|
|
942
|
+
const empty = w - filled;
|
|
943
|
+
const filledStr = "\u2588".repeat(filled);
|
|
944
|
+
const emptyStr = "\u2591".repeat(empty);
|
|
945
|
+
const color = pct > 85 ? chalk3.red : pct > 65 ? chalk3.yellow : chalk3.cyan;
|
|
946
|
+
return color(filledStr) + chalk3.dim(emptyStr);
|
|
947
|
+
}
|
|
736
948
|
function hstack(blocks, gap = 2, targetWidth) {
|
|
737
949
|
if (!blocks.length) return [];
|
|
738
950
|
const height = Math.max(...blocks.map((b) => b.length));
|
|
739
|
-
const widths = blocks.map((b) =>
|
|
951
|
+
const widths = blocks.map((b) => Math.max(0, ...b.map((l) => visLen(l))));
|
|
740
952
|
const padded = blocks.map((b, i) => {
|
|
741
|
-
const
|
|
742
|
-
const
|
|
953
|
+
const w = widths[i];
|
|
954
|
+
const filler = " ".repeat(w);
|
|
955
|
+
const arr = b.map((line) => fitVisible(line, w));
|
|
743
956
|
while (arr.length < height) arr.push(filler);
|
|
744
957
|
return arr;
|
|
745
958
|
});
|
|
@@ -747,8 +960,7 @@ function hstack(blocks, gap = 2, targetWidth) {
|
|
|
747
960
|
for (let row = 0; row < height; row++) {
|
|
748
961
|
let line = padded.map((a) => a[row]).join(" ".repeat(gap));
|
|
749
962
|
if (targetWidth !== void 0) {
|
|
750
|
-
|
|
751
|
-
line += " ".repeat(pad);
|
|
963
|
+
line = fitVisible(line, targetWidth);
|
|
752
964
|
}
|
|
753
965
|
out.push(line);
|
|
754
966
|
}
|
|
@@ -780,7 +992,8 @@ function capacityColor(percent) {
|
|
|
780
992
|
return chalk3.green;
|
|
781
993
|
}
|
|
782
994
|
function formatBytes(bytes) {
|
|
783
|
-
if (bytes <
|
|
995
|
+
if (isNaN(bytes) || bytes < 0) return "0 B";
|
|
996
|
+
if (bytes < 1024) return `${Math.round(bytes)} B`;
|
|
784
997
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
785
998
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
786
999
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
@@ -791,6 +1004,12 @@ function celsiusToFahrenheit(c) {
|
|
|
791
1004
|
function formatTemp(c) {
|
|
792
1005
|
return `${c.toFixed(1)}\xB0C / ${celsiusToFahrenheit(c).toFixed(1)}\xB0F`;
|
|
793
1006
|
}
|
|
1007
|
+
function formatTempUnit(c, unit = "c") {
|
|
1008
|
+
if (unit === "f") {
|
|
1009
|
+
return `${(c * (9 / 5) + 32).toFixed(1)}\xB0F`;
|
|
1010
|
+
}
|
|
1011
|
+
return `${c.toFixed(1)}\xB0C`;
|
|
1012
|
+
}
|
|
794
1013
|
function cpuCard(data, contentWidth) {
|
|
795
1014
|
const lines = [
|
|
796
1015
|
statRow("Brand", truncatePlain(data.cpu.brand, Math.max(4, contentWidth - 8))),
|
|
@@ -886,24 +1105,6 @@ function networkCard(data, contentWidth) {
|
|
|
886
1105
|
statRow("TX", `${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt`)
|
|
887
1106
|
];
|
|
888
1107
|
}
|
|
889
|
-
function packetCard(data, contentWidth) {
|
|
890
|
-
if (!data.packets) return null;
|
|
891
|
-
const lines = [];
|
|
892
|
-
lines.push(statRow("Total", `${data.packets.totalPackets} pkt`));
|
|
893
|
-
lines.push(statRow("RX", `${data.packets.rxPackets} pkt`));
|
|
894
|
-
lines.push(statRow("TX", `${data.packets.txPackets} pkt`));
|
|
895
|
-
lines.push(statRow("Conns", `${data.packets.connections} TCP`));
|
|
896
|
-
if (data.packets.topProcesses && data.packets.topProcesses.length) {
|
|
897
|
-
lines.push(statRow("Top Proc", truncatePlain(data.packets.topProcesses[0].command, contentWidth - 10)));
|
|
898
|
-
}
|
|
899
|
-
if (data.packets.allProcesses && data.packets.allProcesses.length > 1) {
|
|
900
|
-
lines.push(statRow("Net Procs", `${data.packets.allProcesses.length} processes`));
|
|
901
|
-
}
|
|
902
|
-
if (data.packets.interfaces && data.packets.interfaces.length) {
|
|
903
|
-
lines.push(statRow("Ifaces", `${data.packets.interfaces.length} active`));
|
|
904
|
-
}
|
|
905
|
-
return lines;
|
|
906
|
-
}
|
|
907
1108
|
function tasksCard(data, contentWidth) {
|
|
908
1109
|
if (!data.tasks || !data.tasks.length) return null;
|
|
909
1110
|
const lines = [];
|
|
@@ -918,7 +1119,7 @@ function header(data, width, badges = [], borderAccent = THEMES.default.border)
|
|
|
918
1119
|
const left = `${chalk3.hex("#ff5500").bold(" PYRE ")} ${chalk3.bold(data.header.hostname)} ${chalk3.dim(data.header.os)} ${chalk3.dim("up " + data.header.uptime)}`;
|
|
919
1120
|
const right = `${chalk3.dim(now)} ${badges.join(" ")}`;
|
|
920
1121
|
const gap = Math.max(1, width - visLen(left) - visLen(right));
|
|
921
|
-
const line = left + " ".repeat(gap) + right;
|
|
1122
|
+
const line = fitVisible(left + " ".repeat(gap) + right, width);
|
|
922
1123
|
return [line, borderAccent("\u2500".repeat(width))];
|
|
923
1124
|
}
|
|
924
1125
|
var TAB_DEFS = [
|
|
@@ -958,9 +1159,26 @@ function tabBar(activePanel, width, theme) {
|
|
|
958
1159
|
const keyHint = chalk3.dim(`[${t.key}]`);
|
|
959
1160
|
return `${keyHint}${label}`;
|
|
960
1161
|
});
|
|
961
|
-
const
|
|
962
|
-
const
|
|
963
|
-
|
|
1162
|
+
const separator = chalk3.dim(" \u2502 ");
|
|
1163
|
+
const joined = rendered.join(separator);
|
|
1164
|
+
const indent = " ";
|
|
1165
|
+
const available = width - visLen(indent);
|
|
1166
|
+
let result = joined;
|
|
1167
|
+
if (visLen(joined) > available) {
|
|
1168
|
+
let used = 0;
|
|
1169
|
+
const kept = [];
|
|
1170
|
+
for (let i = 0; i < rendered.length; i++) {
|
|
1171
|
+
const tabW = visLen(rendered[i]) + (kept.length > 0 ? visLen(separator) : 0);
|
|
1172
|
+
if (used + tabW > available && kept.length > 0) break;
|
|
1173
|
+
kept.push(rendered[i]);
|
|
1174
|
+
used += tabW;
|
|
1175
|
+
}
|
|
1176
|
+
if (kept.length < rendered.length) {
|
|
1177
|
+
result = kept.join(separator) + chalk3.dim("\u22EF");
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
const pad = Math.max(0, available - visLen(result));
|
|
1181
|
+
return fitVisible(chalk3.dim(indent) + result + " ".repeat(pad), width);
|
|
964
1182
|
}
|
|
965
1183
|
function sortProcesses(processes, sortBy) {
|
|
966
1184
|
const copy = [...processes];
|
|
@@ -1153,20 +1371,139 @@ function anomalyPanel(anomalies, width, theme) {
|
|
|
1153
1371
|
for (const a of anomalies) {
|
|
1154
1372
|
const color = a.severity === "critical" ? chalk3.red.bold : chalk3.yellow;
|
|
1155
1373
|
const direction = a.zScore > 0 ? "\u2191 spike" : "\u2193 drop";
|
|
1156
|
-
|
|
1374
|
+
const valStr = a.metric.toLowerCase().includes("net") ? `${formatBytes(a.value)}/s` : `${a.value.toFixed(1)}%`;
|
|
1375
|
+
lines.push(color(` \u26A0 ${a.metric}: ${valStr} (${direction}, \u03C3=${a.zScore.toFixed(1)})`));
|
|
1157
1376
|
}
|
|
1158
1377
|
return lines;
|
|
1159
1378
|
}
|
|
1379
|
+
function btopCpuBox(data, width, theme, opts2) {
|
|
1380
|
+
const contentWidth = width - 4;
|
|
1381
|
+
const rightTitle = `${data.header.hostname} \xB7 up ${data.header.uptime} \xB7 ${data.cpu.usage.toFixed(0)}%`;
|
|
1382
|
+
if (contentWidth >= 65) {
|
|
1383
|
+
const leftW = Math.floor(contentWidth * 0.48);
|
|
1384
|
+
const rightW = contentWidth - leftW - 2;
|
|
1385
|
+
const leftLines = [];
|
|
1386
|
+
leftLines.push(fitVisible(theme.cpu("CPU Usage History"), leftW));
|
|
1387
|
+
const cpuHistory = opts2.history?.cpuUsage || [];
|
|
1388
|
+
if (cpuHistory.length > 1) {
|
|
1389
|
+
const graphRows = multiRowSparkline(cpuHistory, 3, { min: 0, max: 100 });
|
|
1390
|
+
for (const gr of graphRows) {
|
|
1391
|
+
leftLines.push(theme.cpu(fitVisible(gr, leftW)));
|
|
1392
|
+
}
|
|
1393
|
+
} else {
|
|
1394
|
+
leftLines.push(theme.cpu(fitVisible(gaugeBar(data.cpu.usage, leftW), leftW)));
|
|
1395
|
+
leftLines.push(" ".repeat(leftW));
|
|
1396
|
+
leftLines.push(" ".repeat(leftW));
|
|
1397
|
+
}
|
|
1398
|
+
const rightLines = [];
|
|
1399
|
+
rightLines.push(fitVisible(chalk3.bold(truncatePlain(data.cpu.brand, rightW)), rightW));
|
|
1400
|
+
const cpuBarW = Math.max(6, rightW - 18);
|
|
1401
|
+
const tempStr = data.cpu.temperature ? formatTempUnit(data.cpu.temperature, opts2.tempUnit) : "";
|
|
1402
|
+
rightLines.push(fitVisible(`CPU ${gaugeBar(data.cpu.usage, cpuBarW)} ${data.cpu.usage.toFixed(0).padStart(3)}% ${tempStr}`, rightW));
|
|
1403
|
+
const loadStr = data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" ");
|
|
1404
|
+
rightLines.push(fitVisible(`${chalk3.dim("Load:")} ${loadStr}`, rightW));
|
|
1405
|
+
if (data.cpu.coreUsage && data.cpu.coreUsage.length) {
|
|
1406
|
+
const coreColW = Math.floor((rightW - 1) / 2);
|
|
1407
|
+
const half = Math.ceil(data.cpu.coreUsage.length / 2);
|
|
1408
|
+
for (let i = 0; i < Math.min(2, half); i++) {
|
|
1409
|
+
const u1 = data.cpu.coreUsage[i];
|
|
1410
|
+
const u2 = data.cpu.coreUsage[i + half];
|
|
1411
|
+
const c1BarW = Math.max(3, coreColW - 8);
|
|
1412
|
+
const c1Str = `C${i} ${gaugeBar(u1, c1BarW)} ${Math.round(u1)}%`;
|
|
1413
|
+
const c2Str = u2 !== void 0 ? `C${i + half} ${gaugeBar(u2, c1BarW)} ${Math.round(u2)}%` : "";
|
|
1414
|
+
rightLines.push(fitVisible(fitVisible(c1Str, coreColW) + " " + fitVisible(c2Str, coreColW), rightW));
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (data.gpu) {
|
|
1418
|
+
rightLines.push(fitVisible(`GPU ${gaugeBar(data.gpu.utilization, cpuBarW)} ${data.gpu.utilization}% ${formatBytes(data.gpu.memory)}`, rightW));
|
|
1419
|
+
}
|
|
1420
|
+
const combinedBody = hstack([leftLines, rightLines], 2, contentWidth);
|
|
1421
|
+
return panel("1 cpu", combinedBody, width, theme.cpu, theme.border, combinedBody.length, rightTitle);
|
|
1422
|
+
} else {
|
|
1423
|
+
const lines = [];
|
|
1424
|
+
lines.push(chalk3.bold(truncatePlain(data.cpu.brand, contentWidth)));
|
|
1425
|
+
lines.push(`CPU ${gaugeBar(data.cpu.usage, Math.max(4, contentWidth - 12))} ${data.cpu.usage.toFixed(0)}%`);
|
|
1426
|
+
if (opts2.history?.cpuUsage.length) {
|
|
1427
|
+
lines.push(theme.cpu(sparkline(opts2.history.cpuUsage, { min: 0, max: 100 })));
|
|
1428
|
+
}
|
|
1429
|
+
lines.push(`Load: ${data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" ")}`);
|
|
1430
|
+
return panel("1 cpu", lines, width, theme.cpu, theme.border, lines.length, rightTitle);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
function btopLeftColumn(data, width, theme, opts2) {
|
|
1434
|
+
const contentWidth = width - 4;
|
|
1435
|
+
const out = [];
|
|
1436
|
+
const memLines = [];
|
|
1437
|
+
memLines.push(`RAM [${gaugeBar(data.memory.usagePercent, Math.max(4, contentWidth - 12))}] ${data.memory.usagePercent}%`);
|
|
1438
|
+
memLines.push(`Used: ${formatBytes(data.memory.used)} / ${formatBytes(data.memory.total)}`);
|
|
1439
|
+
memLines.push(`Free: ${formatBytes(data.memory.free)} \xB7 Cache: ${formatBytes(data.memory.total - data.memory.free - data.memory.used)}`);
|
|
1440
|
+
if (data.memory.swapTotal > 0) {
|
|
1441
|
+
const swapPct = Math.round(data.memory.swapUsed / data.memory.swapTotal * 100);
|
|
1442
|
+
memLines.push(`Swap [${gaugeBar(swapPct, Math.max(4, contentWidth - 12))}] ${swapPct}%`);
|
|
1443
|
+
}
|
|
1444
|
+
out.push(...panel("2 mem", memLines, width, theme.mem, theme.border, memLines.length, `${formatBytes(data.memory.used)}`));
|
|
1445
|
+
if (data.disk.length) {
|
|
1446
|
+
const diskLines = [];
|
|
1447
|
+
for (const d of data.disk.slice(0, 2)) {
|
|
1448
|
+
const capNum = parseInt(d.capacity, 10) || 0;
|
|
1449
|
+
const mountShort = truncatePlain(d.mountpoint, 8);
|
|
1450
|
+
diskLines.push(`${mountShort} ${d.used}/${d.size} [${gaugeBar(capNum, Math.max(3, contentWidth - 18))}] ${d.capacity}`);
|
|
1451
|
+
}
|
|
1452
|
+
out.push(...panel("0 disk io", diskLines, width, theme.disk, theme.border, diskLines.length));
|
|
1453
|
+
}
|
|
1454
|
+
const netLines = [];
|
|
1455
|
+
const rxRate = opts2.history?.netRxRate.length ? opts2.history.netRxRate[opts2.history.netRxRate.length - 1] : 0;
|
|
1456
|
+
const txRate = opts2.history?.netTxRate.length ? opts2.history.netTxRate[opts2.history.netTxRate.length - 1] : 0;
|
|
1457
|
+
netLines.push(`down \u25BC ${formatBytes(rxRate)}/s (${formatBytes(data.network.rxBytes)})`);
|
|
1458
|
+
if (opts2.history?.netRxRate.length) {
|
|
1459
|
+
netLines.push(chalk3.cyan(fitVisible(sparkline(opts2.history.netRxRate, {}), contentWidth)));
|
|
1460
|
+
}
|
|
1461
|
+
netLines.push(`up \u25B2 ${formatBytes(txRate)}/s (${formatBytes(data.network.txBytes)})`);
|
|
1462
|
+
if (opts2.history?.netTxRate.length) {
|
|
1463
|
+
netLines.push(theme.network(fitVisible(sparkline(opts2.history.netTxRate, {}), contentWidth)));
|
|
1464
|
+
}
|
|
1465
|
+
out.push(...panel("7 net", netLines, width, theme.network, theme.border, netLines.length, `${data.network.interface}`));
|
|
1466
|
+
return out;
|
|
1467
|
+
}
|
|
1468
|
+
function btopProcBox(data, width, targetHeight, theme, opts2) {
|
|
1469
|
+
const contentWidth = width - 4;
|
|
1470
|
+
const filtered = opts2.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts2.filter.toLowerCase())) : data.processes;
|
|
1471
|
+
const sorted = sortProcesses(filtered, opts2.sortBy ?? "cpu");
|
|
1472
|
+
const headerRowOverhead = 2;
|
|
1473
|
+
const tableHeaderOverhead = 2;
|
|
1474
|
+
const availableRows = Math.max(1, targetHeight - headerRowOverhead - tableHeaderOverhead);
|
|
1475
|
+
const limited = sorted.slice(0, availableRows);
|
|
1476
|
+
const pidW = 6, userW = 7, thrW = 3, memW = 6, cpuW = 6;
|
|
1477
|
+
const fixedW = pidW + userW + thrW + memW + cpuW + 5;
|
|
1478
|
+
const cmdW = Math.max(8, contentWidth - fixedW);
|
|
1479
|
+
const head = chalk3.bold("PID".padEnd(pidW)) + " " + chalk3.bold("PROGRAM".padEnd(cmdW)) + " " + chalk3.bold("USER".padEnd(userW)) + " " + chalk3.bold("THR".padStart(thrW)) + " " + chalk3.bold("MEM%".padStart(memW)) + " " + chalk3.bold("CPU%".padStart(cpuW));
|
|
1480
|
+
if (opts2.treeView) {
|
|
1481
|
+
const treeLines = buildProcessTree(limited);
|
|
1482
|
+
const rightTitle2 = opts2.filter ? `filter: "${opts2.filter}"` : `tree \xB7 sort: ${opts2.sortBy || "cpu"}`;
|
|
1483
|
+
return panel("P proc", [head, theme.border("\u2500".repeat(contentWidth)), ...treeLines], width, theme.process, theme.border, targetHeight - 2, rightTitle2);
|
|
1484
|
+
}
|
|
1485
|
+
const rows = limited.map((p) => {
|
|
1486
|
+
const cpuVal = p.cpu.toFixed(1) + "%";
|
|
1487
|
+
const cpuStr = p.cpu > 50 ? chalk3.red.bold(cpuVal.padStart(cpuW)) : p.cpu > 20 ? chalk3.yellow(cpuVal.padStart(cpuW)) : chalk3.green(cpuVal.padStart(cpuW));
|
|
1488
|
+
const memStr = (p.mem.toFixed(1) + "%").padStart(memW);
|
|
1489
|
+
const userStr = truncatePlain(p.user, userW).padEnd(userW);
|
|
1490
|
+
const thrStr = String(p.threads).padStart(thrW);
|
|
1491
|
+
const pidStr = String(p.pid).padEnd(pidW);
|
|
1492
|
+
const cmdStr = truncatePlain(p.command, cmdW).padEnd(cmdW);
|
|
1493
|
+
return `${pidStr} ${cmdStr} ${userStr} ${thrStr} ${memStr} ${cpuStr}`;
|
|
1494
|
+
});
|
|
1495
|
+
const lines = [head, theme.border("\u2500".repeat(contentWidth)), ...rows];
|
|
1496
|
+
if (rows.length === 0) {
|
|
1497
|
+
lines.push(chalk3.dim(`No processes match "${opts2.filter || ""}"`));
|
|
1498
|
+
}
|
|
1499
|
+
const rightTitle = opts2.filter ? `filter: "${opts2.filter}"` : `sort: ${opts2.sortBy || "cpu"}`;
|
|
1500
|
+
return panel("P proc", lines, width, theme.process, theme.border, targetHeight - 2, rightTitle);
|
|
1501
|
+
}
|
|
1160
1502
|
function formatTable(data, opts2 = {}) {
|
|
1161
1503
|
const width = clampWidth(opts2.width);
|
|
1162
1504
|
const activePanel = opts2.activePanel || "grid";
|
|
1163
|
-
const columns = gridColumns(width);
|
|
1164
|
-
const gap = 2;
|
|
1165
|
-
const cardWidth = Math.floor((width - gap * (columns - 1)) / columns);
|
|
1166
|
-
const contentWidth = cardWidth - 4;
|
|
1167
1505
|
const themeName = opts2.theme || "default";
|
|
1168
1506
|
const theme = THEMES[themeName] || THEMES.default;
|
|
1169
|
-
const vis = opts2.visible ?? {};
|
|
1170
1507
|
const out = [];
|
|
1171
1508
|
out.push(...header(data, width, [], theme.border));
|
|
1172
1509
|
out.push("");
|
|
@@ -1184,105 +1521,30 @@ function formatTable(data, opts2 = {}) {
|
|
|
1184
1521
|
}
|
|
1185
1522
|
return out.join("\n");
|
|
1186
1523
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (vis.power !== false) {
|
|
1199
|
-
const power = powerCard(data);
|
|
1200
|
-
if (power) cards.push({ title: "Power", accent: theme.power, lines: power });
|
|
1201
|
-
}
|
|
1202
|
-
if (vis.battery !== false) {
|
|
1203
|
-
const battery = batteryCard(data, contentWidth);
|
|
1204
|
-
if (battery) cards.push({ title: "Battery", accent: theme.battery, lines: battery });
|
|
1205
|
-
}
|
|
1206
|
-
if (vis.thermal !== false) {
|
|
1207
|
-
cards.push({ title: "Thermal", accent: theme.thermal, lines: thermalCard(data, contentWidth) });
|
|
1208
|
-
}
|
|
1209
|
-
if (vis.network !== false) {
|
|
1210
|
-
cards.push({ title: "Network", accent: theme.network, lines: networkCard(data, contentWidth) });
|
|
1211
|
-
}
|
|
1212
|
-
if (vis.packets !== false) {
|
|
1213
|
-
const packets = packetCard(data, contentWidth);
|
|
1214
|
-
if (packets) cards.push({ title: "Packets", accent: theme.network, lines: packets });
|
|
1215
|
-
}
|
|
1216
|
-
if (vis.tasks !== false) {
|
|
1217
|
-
const tasks = tasksCard(data, contentWidth);
|
|
1218
|
-
if (tasks) cards.push({ title: "Tasks", accent: theme.process, lines: tasks });
|
|
1219
|
-
}
|
|
1220
|
-
for (let i = 0; i < cards.length; i += columns) {
|
|
1221
|
-
const row = cards.slice(i, i + columns);
|
|
1222
|
-
const rowHeight = Math.max(...row.map((c) => c.lines.length));
|
|
1223
|
-
const blocks = row.map((c) => panel(c.title, c.lines, cardWidth, c.accent, theme.border, rowHeight));
|
|
1224
|
-
out.push(...hstack(blocks, gap, width));
|
|
1225
|
-
out.push("");
|
|
1226
|
-
}
|
|
1227
|
-
if (vis.disk !== false && data.disk.length) {
|
|
1228
|
-
const dw = width - 4;
|
|
1229
|
-
out.push(...panel("Disk", diskTableLines(data.disk, dw, theme.border), width, theme.disk, theme.border));
|
|
1524
|
+
out.push(...btopCpuBox(data, width, theme, opts2));
|
|
1525
|
+
out.push("");
|
|
1526
|
+
if (width >= 85) {
|
|
1527
|
+
const leftColW = Math.max(32, Math.floor(width * 0.38));
|
|
1528
|
+
const rightColW = width - leftColW - 2;
|
|
1529
|
+
const leftLines = btopLeftColumn(data, leftColW, theme, opts2);
|
|
1530
|
+
const procLines = btopProcBox(data, rightColW, leftLines.length, theme, opts2);
|
|
1531
|
+
out.push(...hstack([leftLines, procLines], 2, width));
|
|
1532
|
+
} else {
|
|
1533
|
+
const leftLines = btopLeftColumn(data, width, theme, opts2);
|
|
1534
|
+
out.push(...leftLines);
|
|
1230
1535
|
out.push("");
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
const filteredProcesses = opts2.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts2.filter.toLowerCase())) : data.processes;
|
|
1234
|
-
const sortedProcesses = sortProcesses(filteredProcesses, opts2.sortBy ?? "cpu");
|
|
1235
|
-
const limited = opts2.processLimit ? sortedProcesses.slice(0, opts2.processLimit) : sortedProcesses;
|
|
1236
|
-
const procTitle = opts2.filter ? `Processes \xB7 filter "${opts2.filter}" \xB7 sort ${opts2.sortBy ?? "cpu"}` : `Processes \xB7 sort ${opts2.sortBy ?? "cpu"}`;
|
|
1237
|
-
if (opts2.treeView) {
|
|
1238
|
-
const treeLines = buildProcessTree(limited);
|
|
1239
|
-
if (treeLines.length) {
|
|
1240
|
-
out.push(...panel(procTitle + " \xB7 tree", treeLines, width, theme.process, theme.border));
|
|
1241
|
-
} else if (opts2.filter) {
|
|
1242
|
-
out.push(...panel(procTitle, [chalk3.dim(`No processes match "${opts2.filter}"`)], width, theme.process, theme.border));
|
|
1243
|
-
}
|
|
1244
|
-
} else if (limited.length) {
|
|
1245
|
-
out.push(...panel(procTitle, processTableLines(limited, width - 4, theme.border), width, theme.process, theme.border));
|
|
1246
|
-
} else if (opts2.filter) {
|
|
1247
|
-
out.push(...panel(procTitle, [chalk3.dim(`No processes match "${opts2.filter}"`)], width, theme.process, theme.border));
|
|
1248
|
-
}
|
|
1536
|
+
const procLines = btopProcBox(data, width, 12, theme, opts2);
|
|
1537
|
+
out.push(...procLines);
|
|
1249
1538
|
}
|
|
1250
1539
|
return out.join("\n");
|
|
1251
1540
|
}
|
|
1252
1541
|
function clampWidth(width) {
|
|
1253
1542
|
if (!width || Number.isNaN(width)) return 80;
|
|
1254
|
-
return Math.max(
|
|
1255
|
-
}
|
|
1256
|
-
function gridColumns(width) {
|
|
1257
|
-
if (width >= 150) return 3;
|
|
1258
|
-
if (width >= 96) return 2;
|
|
1259
|
-
return 1;
|
|
1543
|
+
return Math.max(40, Math.min(width, 240));
|
|
1260
1544
|
}
|
|
1261
1545
|
|
|
1262
1546
|
// src/formatters/output.ts
|
|
1263
1547
|
import chalk4 from "chalk";
|
|
1264
|
-
|
|
1265
|
-
// src/sparkline.ts
|
|
1266
|
-
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1267
|
-
function sparkline(values, opts2 = {}) {
|
|
1268
|
-
if (!values.length) return "";
|
|
1269
|
-
let min = opts2.min ?? Infinity;
|
|
1270
|
-
let max = opts2.max ?? -Infinity;
|
|
1271
|
-
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
1272
|
-
for (const v of values) {
|
|
1273
|
-
if (v < min) min = v;
|
|
1274
|
-
if (v > max) max = v;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
const range = max - min || 1;
|
|
1278
|
-
return values.map((v) => {
|
|
1279
|
-
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
1280
|
-
const clamped = Math.min(BLOCKS.length - 1, Math.max(0, idx));
|
|
1281
|
-
return BLOCKS[clamped];
|
|
1282
|
-
}).join("");
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
// src/formatters/output.ts
|
|
1286
1548
|
function formatJson(data) {
|
|
1287
1549
|
return JSON.stringify(data, null, 2);
|
|
1288
1550
|
}
|
|
@@ -1675,28 +1937,28 @@ function formatGraphs(history, width = 80, themeName = "default", graphMode = "s
|
|
|
1675
1937
|
const theme = THEMES[themeName] || THEMES.default;
|
|
1676
1938
|
const contentWidth = width - 4;
|
|
1677
1939
|
const sampleCount = history.cpuUsage.length;
|
|
1678
|
-
const sparkWidth = Math.max(10, contentWidth -
|
|
1940
|
+
const sparkWidth = Math.max(10, contentWidth - 24);
|
|
1679
1941
|
const lines = [];
|
|
1680
|
-
lines.push(graphRow("CPU %", history.cpuUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, "collecting data...", graphMode));
|
|
1681
|
-
lines.push(graphRow("Mem %", history.memUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, "collecting data...", graphMode));
|
|
1942
|
+
lines.push(graphRow("CPU %", history.cpuUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1943
|
+
lines.push(graphRow("Mem %", history.memUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1682
1944
|
lines.push(
|
|
1683
|
-
graphRow("Temp", history.temp, {},
|
|
1945
|
+
graphRow("Temp", history.temp, {}, (v) => `${v.toFixed(1)}\xB0C`, sparkWidth, "no sensor access", graphMode, contentWidth)
|
|
1684
1946
|
);
|
|
1685
|
-
lines.push(graphRow("Net RX/s", history.netRxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, "collecting data...", graphMode));
|
|
1686
|
-
lines.push(graphRow("Net TX/s", history.netTxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, "collecting data...", graphMode));
|
|
1947
|
+
lines.push(graphRow("Net RX/s", history.netRxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1948
|
+
lines.push(graphRow("Net TX/s", history.netTxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1687
1949
|
if (history.powerWatts.length) {
|
|
1688
|
-
lines.push(graphRow("Power W", history.powerWatts, {}, (v) => `${v.toFixed(1)} W`, sparkWidth, "collecting data...", graphMode));
|
|
1950
|
+
lines.push(graphRow("Power W", history.powerWatts, {}, (v) => `${v.toFixed(1)} W`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1689
1951
|
}
|
|
1690
1952
|
if (history.rxPacketRate.length) {
|
|
1691
|
-
lines.push(graphRow("RX pkt/s", history.rxPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, "collecting data...", graphMode));
|
|
1953
|
+
lines.push(graphRow("RX pkt/s", history.rxPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1692
1954
|
}
|
|
1693
1955
|
if (history.txPacketRate.length) {
|
|
1694
|
-
lines.push(graphRow("TX pkt/s", history.txPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, "collecting data...", graphMode));
|
|
1956
|
+
lines.push(graphRow("TX pkt/s", history.txPacketRate, {}, (v) => `${v.toFixed(0)} pkt/s`, sparkWidth, "collecting data...", graphMode, contentWidth));
|
|
1695
1957
|
}
|
|
1696
1958
|
const title = `Graphs \xB7 mode:${graphMode} \xB7 last ${sampleCount} sample${sampleCount === 1 ? "" : "s"}`;
|
|
1697
1959
|
return panel(title, lines, width, theme.graphs, theme.border).join("\n");
|
|
1698
1960
|
}
|
|
1699
|
-
function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "collecting data...", mode = "spark") {
|
|
1961
|
+
function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "collecting data...", mode = "spark", contentWidth = 76) {
|
|
1700
1962
|
if (!values.length) {
|
|
1701
1963
|
return `${chalk4.dim(label.padEnd(10))} ${chalk4.dim(emptyMessage)}`;
|
|
1702
1964
|
}
|
|
@@ -1708,7 +1970,8 @@ function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "c
|
|
|
1708
1970
|
const pct = (current - bounds.min) / (bounds.max - bounds.min || 1) * 100;
|
|
1709
1971
|
color = pct > 90 ? chalk4.red : pct > 70 ? chalk4.yellow : chalk4.green;
|
|
1710
1972
|
}
|
|
1711
|
-
|
|
1973
|
+
const raw = `${chalk4.dim(label.padEnd(10))}${color(spark)} ${chalk4.bold(fmt(current))}`;
|
|
1974
|
+
return fitVisible(raw, contentWidth);
|
|
1712
1975
|
}
|
|
1713
1976
|
function barGraph(values, bounds, width) {
|
|
1714
1977
|
let min = bounds.min ?? Infinity;
|
|
@@ -2326,6 +2589,7 @@ function tableCacheParams() {
|
|
|
2326
2589
|
state.currentTheme,
|
|
2327
2590
|
state.activePanel,
|
|
2328
2591
|
state.treeView ? "1" : "0",
|
|
2592
|
+
state.history.version,
|
|
2329
2593
|
JSON.stringify(state.visiblePanels)
|
|
2330
2594
|
].join("|");
|
|
2331
2595
|
}
|
|
@@ -2344,11 +2608,8 @@ function processRowBudget() {
|
|
|
2344
2608
|
if (state.activePanel === "process") {
|
|
2345
2609
|
return Math.max(10, state.termHeight - 12 - overlayRows);
|
|
2346
2610
|
}
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
const cardRows = Math.ceil(visibleCards / columns);
|
|
2350
|
-
const reserved = 4 + cardRows * 8 + 8 + (state.showGraphs ? 11 : 0) + 5 + overlayRows;
|
|
2351
|
-
return Math.max(5, state.termHeight - reserved);
|
|
2611
|
+
const reserved = 16 + overlayRows;
|
|
2612
|
+
return Math.max(4, state.termHeight - reserved);
|
|
2352
2613
|
}
|
|
2353
2614
|
function p2pPanelLines() {
|
|
2354
2615
|
const events = state.p2pServer ? state.p2pServer.peerEventHistory : state.p2pEvents;
|
|
@@ -2429,6 +2690,18 @@ function writeFrame(lines) {
|
|
|
2429
2690
|
}
|
|
2430
2691
|
function render() {
|
|
2431
2692
|
if (!state.lastData) return;
|
|
2693
|
+
const rows = state.termHeight || 24;
|
|
2694
|
+
const cols = state.termWidth || 80;
|
|
2695
|
+
if (cols < 50 || rows < 15) {
|
|
2696
|
+
const msg = chalk5.yellow(`pyre ${rows}x${cols} too small. Resize terminal to >= 50x20.`);
|
|
2697
|
+
const centered = Math.max(0, Math.floor((rows - 2) / 2));
|
|
2698
|
+
const out = [];
|
|
2699
|
+
for (let i = 0; i < centered; i++) out.push("");
|
|
2700
|
+
out.push(msg.padEnd(cols));
|
|
2701
|
+
while (out.length < rows) out.push("");
|
|
2702
|
+
writeFrame(out);
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2432
2705
|
const lines = [];
|
|
2433
2706
|
const theme = THEMES[state.currentTheme] || THEMES.default;
|
|
2434
2707
|
const params = tableCacheParams();
|
|
@@ -2446,7 +2719,9 @@ function render() {
|
|
|
2446
2719
|
treeView: state.treeView,
|
|
2447
2720
|
activePanel: state.activePanel,
|
|
2448
2721
|
anomalies,
|
|
2449
|
-
tempUnit: state.tempUnit
|
|
2722
|
+
tempUnit: state.tempUnit,
|
|
2723
|
+
history: state.history,
|
|
2724
|
+
graphMode: state.graphMode
|
|
2450
2725
|
});
|
|
2451
2726
|
_cachedTableData = state.lastData;
|
|
2452
2727
|
_cachedTableParams = cacheParams;
|
|
@@ -2473,37 +2748,40 @@ function render() {
|
|
|
2473
2748
|
bodyLines.push("");
|
|
2474
2749
|
bodyLines.push(...graphsStr.split("\n"));
|
|
2475
2750
|
}
|
|
2476
|
-
const
|
|
2477
|
-
|
|
2478
|
-
|
|
2751
|
+
const footerLine1 = footerLine()[0] || "";
|
|
2752
|
+
const footerLine22 = footerLine()[1] || "";
|
|
2753
|
+
const footerLine3 = footerLine()[2] || " ";
|
|
2754
|
+
const footerLines = [footerLine1, footerLine22, footerLine3];
|
|
2479
2755
|
if (state.inputMode === "customizer") {
|
|
2480
|
-
|
|
2756
|
+
bodyLines.push("");
|
|
2757
|
+
bodyLines.push(...renderCustomizerOverlay().split("\n"));
|
|
2481
2758
|
} else if (state.inputMode === "filter") {
|
|
2482
|
-
|
|
2759
|
+
bodyLines.push(chalk5.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
2483
2760
|
} else if (state.inputMode === "kill") {
|
|
2484
|
-
|
|
2761
|
+
bodyLines.push(chalk5.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
|
|
2485
2762
|
} else if (state.inputMode === "signal") {
|
|
2486
2763
|
const sigIdx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
2487
2764
|
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ? chalk5.yellow(s) : chalk5.dim(s)).join(" ");
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2765
|
+
bodyLines.push(chalk5.cyan(` Signal: ${state.inputBuffer}_`));
|
|
2766
|
+
bodyLines.push(chalk5.dim(` ${sigList}`));
|
|
2767
|
+
bodyLines.push(chalk5.dim(" \u2191/\u2193 to select, Enter to confirm, Esc to cancel"));
|
|
2491
2768
|
} else if (state.inputMode === "p2p") {
|
|
2492
|
-
|
|
2769
|
+
bodyLines.push(chalk5.cyan(` P2P password (${state.p2pPort}): ${state.inputBuffer}_ (enter to start, esc to cancel)`));
|
|
2493
2770
|
} else if (state.statusMessage) {
|
|
2494
|
-
|
|
2495
|
-
} else {
|
|
2496
|
-
footerLines.push("");
|
|
2771
|
+
bodyLines.push(` ${state.statusMessage}`);
|
|
2497
2772
|
}
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2773
|
+
const footerHeight = 3;
|
|
2774
|
+
const targetBodyHeight = Math.max(1, state.termHeight - footerHeight);
|
|
2775
|
+
if (bodyLines.length > targetBodyHeight) {
|
|
2776
|
+
bodyLines.length = targetBodyHeight;
|
|
2501
2777
|
}
|
|
2502
|
-
const frameLines = [...bodyLines
|
|
2503
|
-
|
|
2504
|
-
frameLines.
|
|
2778
|
+
const frameLines = [...bodyLines];
|
|
2779
|
+
while (frameLines.length < targetBodyHeight) {
|
|
2780
|
+
frameLines.push("");
|
|
2505
2781
|
}
|
|
2506
|
-
|
|
2782
|
+
frameLines.push(...footerLines);
|
|
2783
|
+
const fittedLines = frameLines.map((l) => fitVisible(l, cols));
|
|
2784
|
+
writeFrame(fittedLines);
|
|
2507
2785
|
}
|
|
2508
2786
|
function footerLine() {
|
|
2509
2787
|
const row1 = [
|
|
@@ -2537,10 +2815,8 @@ function footerLine() {
|
|
|
2537
2815
|
if (state.logging) badges.push(chalk5.red.bold("\u25CF REC"));
|
|
2538
2816
|
if (state.activePanel !== "grid") badges.push(chalk5.cyan.bold(`\u25C9 ${state.activePanel.toUpperCase()}`));
|
|
2539
2817
|
if (state.p2pServerRunning) badges.push(chalk5.green.bold(`P2P ${state.p2pBind || "0.0.0.0"}:${state.p2pPort}`));
|
|
2540
|
-
const badgeStr = badges.join(" ");
|
|
2541
|
-
return
|
|
2542
|
-
${str2} ${badgeStr}` : `${str1}
|
|
2543
|
-
${str2}`;
|
|
2818
|
+
const badgeStr = badges.join(" ") || " ";
|
|
2819
|
+
return [str1, str2, badgeStr];
|
|
2544
2820
|
}
|
|
2545
2821
|
function sendNotification(title, message) {
|
|
2546
2822
|
if (!state.notificationsEnabled) return;
|
|
@@ -3579,8 +3855,9 @@ async function startLive(opts2, splashPromise) {
|
|
|
3579
3855
|
handleInputModeKey(str, key);
|
|
3580
3856
|
return;
|
|
3581
3857
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3858
|
+
const mouse = key.mouse;
|
|
3859
|
+
if (mouse) {
|
|
3860
|
+
handleMouseClick(mouse.y, mouse.x);
|
|
3584
3861
|
return;
|
|
3585
3862
|
}
|
|
3586
3863
|
if (key.sequence && key.sequence.startsWith("\x1B[M")) {
|
|
@@ -4166,7 +4443,7 @@ async function showSplash(config3) {
|
|
|
4166
4443
|
var config2 = readConfig();
|
|
4167
4444
|
var pkg = JSON.parse(fs5.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
4168
4445
|
var program = new Command();
|
|
4169
|
-
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("--html", "Output as HTML").option("--md", "Output as Markdown").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)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).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", config2.exportDir).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", config2.sortMode).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("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").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)");
|
|
4446
|
+
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("--html", "Output as HTML").option("--md", "Output as Markdown").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)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).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", config2.exportDir).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", config2.sortMode).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("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").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)").option("--port <port>", "Port number for web server mode", "3000");
|
|
4170
4447
|
program.parse(process.argv);
|
|
4171
4448
|
var opts = program.opts();
|
|
4172
4449
|
function isExportMode() {
|
|
@@ -4260,10 +4537,18 @@ async function main() {
|
|
|
4260
4537
|
const alertTemp = opts.alertTemp ? parseInt(opts.alertTemp, 10) : void 0;
|
|
4261
4538
|
const tempUnit = opts.tempUnit === "f" ? "f" : "c";
|
|
4262
4539
|
if (cmd === "live" || !isExportMode() && !opts.once && cmd !== "p2p") {
|
|
4540
|
+
if (!process.stdout.isTTY && cmd !== "live") {
|
|
4541
|
+
await runWebCommand();
|
|
4542
|
+
return;
|
|
4543
|
+
}
|
|
4263
4544
|
if (cmd === "live") {
|
|
4264
4545
|
program.args.shift();
|
|
4265
4546
|
}
|
|
4266
|
-
const splashPromise = showSplash(
|
|
4547
|
+
const splashPromise = showSplash({
|
|
4548
|
+
enabled: config2.splashEnabled,
|
|
4549
|
+
colorScheme: config2.splashColorScheme,
|
|
4550
|
+
animation: config2.splashAnimation
|
|
4551
|
+
});
|
|
4267
4552
|
const interval = parseFloat(opts.interval) || 2;
|
|
4268
4553
|
await startLive({
|
|
4269
4554
|
interval,
|
|
@@ -4527,7 +4812,7 @@ async function runSshCommand(host) {
|
|
|
4527
4812
|
}
|
|
4528
4813
|
process.exit(0);
|
|
4529
4814
|
});
|
|
4530
|
-
child.stdin
|
|
4815
|
+
child.stdin?.end();
|
|
4531
4816
|
process.once("SIGINT", () => {
|
|
4532
4817
|
child.kill("SIGTERM");
|
|
4533
4818
|
process.exit(0);
|
|
@@ -4535,32 +4820,43 @@ async function runSshCommand(host) {
|
|
|
4535
4820
|
}
|
|
4536
4821
|
async function runWebCommand() {
|
|
4537
4822
|
const http = await import("http");
|
|
4538
|
-
const
|
|
4539
|
-
const server = http.createServer((req, res) => {
|
|
4540
|
-
|
|
4823
|
+
const port = parseInt(opts.port || process.env.PORT || "3000", 10) || 3e3;
|
|
4824
|
+
const server = http.createServer(async (req, res) => {
|
|
4825
|
+
const url = req.url || "/";
|
|
4826
|
+
if (url === "/" || url === "/index.html") {
|
|
4541
4827
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4828
|
+
const indexPath = path3.join(process.cwd(), "index.html");
|
|
4829
|
+
let htmlContent = "";
|
|
4830
|
+
if (fs5.existsSync(indexPath)) {
|
|
4831
|
+
htmlContent = fs5.readFileSync(indexPath, "utf-8");
|
|
4832
|
+
} else {
|
|
4833
|
+
const data = await collectAll({ detailed: true });
|
|
4834
|
+
htmlContent = formatHtml(data);
|
|
4835
|
+
}
|
|
4836
|
+
res.end(htmlContent);
|
|
4837
|
+
} else if (url === "/api" || url === "/api/stats" || url === "/data") {
|
|
4838
|
+
const data = await collectAll({ detailed: true });
|
|
4839
|
+
res.writeHead(200, {
|
|
4840
|
+
"Content-Type": "application/json",
|
|
4841
|
+
"Access-Control-Allow-Origin": "*",
|
|
4842
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS"
|
|
4843
|
+
});
|
|
4545
4844
|
res.end(JSON.stringify(data, null, 2));
|
|
4546
4845
|
} else {
|
|
4547
4846
|
res.writeHead(404);
|
|
4548
4847
|
res.end("Not found");
|
|
4549
4848
|
}
|
|
4550
4849
|
});
|
|
4551
|
-
server.listen(
|
|
4552
|
-
const address = server.address();
|
|
4553
|
-
const port = typeof address === "object" && address ? address.port : 0;
|
|
4554
|
-
const url = `http://127.0.0.1:${port}`;
|
|
4850
|
+
server.listen(port, "0.0.0.0", () => {
|
|
4555
4851
|
console.log(chalk9.bold(`
|
|
4556
|
-
pyre web`));
|
|
4557
|
-
console.log(chalk9.dim(` Dashboard:
|
|
4558
|
-
console.log(chalk9.dim(` API:
|
|
4852
|
+
pyre web server running on port ${port}`));
|
|
4853
|
+
console.log(chalk9.dim(` Dashboard: http://0.0.0.0:${port}/`));
|
|
4854
|
+
console.log(chalk9.dim(` API: http://0.0.0.0:${port}/api`));
|
|
4559
4855
|
console.log(chalk9.dim(" Press Ctrl+C to stop\n"));
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4856
|
+
});
|
|
4857
|
+
process.once("SIGINT", () => {
|
|
4858
|
+
server.close();
|
|
4859
|
+
process.exit(0);
|
|
4564
4860
|
});
|
|
4565
4861
|
}
|
|
4566
4862
|
async function runBenchCommand(benchCmd) {
|