pyre-cli 1.3.0 → 1.4.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/README.md +209 -16
- package/dist/index.js +735 -332
- package/package.json +5 -2
- package/parity.md +542 -0
- package/src/formatters/index.ts +17 -0
- package/src/formatters/output.ts +137 -0
- package/src/formatters/render.ts +468 -0
- package/src/formatters/themes.ts +115 -0
- package/src/formatters/types.ts +58 -0
- package/src/history.ts +115 -0
- package/src/index.ts +81 -0
- package/src/live/export.ts +73 -0
- package/src/live/index.ts +16 -0
- package/src/live/input.ts +311 -0
- package/src/live/render.ts +142 -0
- package/src/live/state.ts +83 -0
- package/src/live/types.ts +23 -0
- package/src/monitors/collectors.ts +484 -0
- package/src/monitors/index.ts +14 -0
- package/src/monitors/run.ts +18 -0
- package/src/monitors/smc.ts +107 -0
- package/src/monitors/types.ts +107 -0
- package/src/pyre_img +0 -0
- package/src/sparkline.ts +34 -0
- package/src/splash.ts +102 -0
- package/tsconfig.json +19 -0
package/dist/index.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import fs2 from "fs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import
|
|
6
|
+
import chalk7 from "chalk";
|
|
7
7
|
|
|
8
|
-
// src/monitors.ts
|
|
8
|
+
// src/monitors/collectors.ts
|
|
9
9
|
import chalk from "chalk";
|
|
10
|
+
|
|
11
|
+
// src/monitors/run.ts
|
|
10
12
|
async function run(cmd, fallback = "") {
|
|
11
13
|
try {
|
|
12
14
|
const { execSync } = await import("child_process");
|
|
@@ -15,6 +17,8 @@ async function run(cmd, fallback = "") {
|
|
|
15
17
|
return fallback;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
// src/monitors/smc.ts
|
|
18
22
|
var smcCache = null;
|
|
19
23
|
async function getSmcMetrics() {
|
|
20
24
|
const now = Date.now();
|
|
@@ -53,9 +57,12 @@ async function getSmcMetrics() {
|
|
|
53
57
|
function parseSuffix(s) {
|
|
54
58
|
return s.toUpperCase() === "K" ? 1024 : s.toUpperCase() === "M" ? 1024 * 1024 : s.toUpperCase() === "G" ? 1024 * 1024 * 1024 : 1;
|
|
55
59
|
}
|
|
60
|
+
|
|
61
|
+
// src/monitors/collectors.ts
|
|
56
62
|
async function collectAll(opts2 = {}) {
|
|
57
|
-
const [cpu, memory, disk, battery, thermal, network, processes, system, power] = await Promise.all([
|
|
63
|
+
const [cpu, gpu, memory, disk, battery, thermal, network, processes, system, power] = await Promise.all([
|
|
58
64
|
collectCpu(),
|
|
65
|
+
collectGpu(opts2.detailed),
|
|
59
66
|
collectMemory(),
|
|
60
67
|
collectDisk(),
|
|
61
68
|
collectBattery(),
|
|
@@ -73,6 +80,7 @@ async function collectAll(opts2 = {}) {
|
|
|
73
80
|
uptime: system.uptime
|
|
74
81
|
},
|
|
75
82
|
cpu,
|
|
83
|
+
gpu,
|
|
76
84
|
memory,
|
|
77
85
|
disk,
|
|
78
86
|
battery,
|
|
@@ -92,6 +100,48 @@ async function collectPower() {
|
|
|
92
100
|
return null;
|
|
93
101
|
}
|
|
94
102
|
}
|
|
103
|
+
async function collectGpu(detailed) {
|
|
104
|
+
try {
|
|
105
|
+
const spRaw = (await run("system_profiler SPDisplaysDataType 2>/dev/null")).trim();
|
|
106
|
+
if (!spRaw) return null;
|
|
107
|
+
const modelMatch = spRaw.match(/Chipset Model:\s*(.+)/i);
|
|
108
|
+
const model = modelMatch ? modelMatch[1].trim() : "Unknown";
|
|
109
|
+
const memMatch = spRaw.match(/VRAM \(Total\):\s*(.+)/i);
|
|
110
|
+
let memory = 0;
|
|
111
|
+
if (memMatch) {
|
|
112
|
+
const memStr = memMatch[1].trim();
|
|
113
|
+
const memNum = parseFloat(memStr);
|
|
114
|
+
if (memStr.includes("GB")) memory = memNum * 1024 * 1024 * 1024;
|
|
115
|
+
else if (memStr.includes("MB")) memory = memNum * 1024 * 1024;
|
|
116
|
+
}
|
|
117
|
+
let utilization = 0;
|
|
118
|
+
let temperature;
|
|
119
|
+
let gpuProcesses = 0;
|
|
120
|
+
if (detailed) {
|
|
121
|
+
try {
|
|
122
|
+
const pm = (await run("sudo -n powermetrics --samplers gpu_power -n 1 --format text 2>/dev/null", "")).trim();
|
|
123
|
+
const gpuPowerMatch = pm.match(/GPU Power:\s*([\d.]+)\s*mW/i);
|
|
124
|
+
if (gpuPowerMatch) {
|
|
125
|
+
utilization = Math.min(100, Math.round(parseFloat(gpuPowerMatch[1]) / 10));
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const { temps } = await getSmcMetrics();
|
|
131
|
+
if (temps.gpu_die !== void 0) temperature = temps.gpu_die;
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const psRaw = (await run('ps aux | grep -i "[g]pu" | wc -l')).trim();
|
|
136
|
+
gpuProcesses = parseInt(psRaw) || 0;
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { model, memory, utilization, temperature, processes: gpuProcesses };
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
95
145
|
async function collectSystem() {
|
|
96
146
|
const hostname = (await run("hostname", "Mac")).trim();
|
|
97
147
|
const osRaw = (await run("sw_vers -productVersion", "Unknown")).trim();
|
|
@@ -113,7 +163,9 @@ async function collectCpu() {
|
|
|
113
163
|
try {
|
|
114
164
|
const { freq } = await getSmcMetrics();
|
|
115
165
|
const values = Object.values(freq);
|
|
116
|
-
if (values.length)
|
|
166
|
+
if (values.length) {
|
|
167
|
+
frequency = Math.round(Math.max(...values) / 1e6);
|
|
168
|
+
}
|
|
117
169
|
} catch {
|
|
118
170
|
}
|
|
119
171
|
}
|
|
@@ -203,7 +255,7 @@ async function collectBattery() {
|
|
|
203
255
|
const timeMatch = raw.match(/:\s*([\d:]+)\s+remaining/i);
|
|
204
256
|
const powerPlugged = raw.includes("AC Power") || raw.includes("attached to charger");
|
|
205
257
|
const level = levelMatch ? parseInt(levelMatch[1]) : 0;
|
|
206
|
-
const
|
|
258
|
+
const state2 = powerPlugged ? "charged" : stateMatch ? stateMatch[1].toLowerCase() : "discharging";
|
|
207
259
|
const timeRemaining = timeMatch ? timeMatch[1] : powerPlugged ? "\u221E" : "calculating";
|
|
208
260
|
let cycles;
|
|
209
261
|
let condition;
|
|
@@ -235,7 +287,7 @@ async function collectBattery() {
|
|
|
235
287
|
const health = healthParts.length ? healthParts.join(", ") : "unknown";
|
|
236
288
|
return {
|
|
237
289
|
level,
|
|
238
|
-
state,
|
|
290
|
+
state: state2,
|
|
239
291
|
timeRemaining,
|
|
240
292
|
health,
|
|
241
293
|
powerSource: powerPlugged ? "AC" : "Battery",
|
|
@@ -247,36 +299,36 @@ async function collectBattery() {
|
|
|
247
299
|
return null;
|
|
248
300
|
}
|
|
249
301
|
}
|
|
250
|
-
function pressureFromState(
|
|
251
|
-
const s =
|
|
302
|
+
function pressureFromState(state2) {
|
|
303
|
+
const s = state2.toLowerCase();
|
|
252
304
|
if (s.includes("critical")) return 3;
|
|
253
305
|
if (s.includes("serious") || s.includes("heavy")) return 2;
|
|
254
306
|
if (s.includes("fair") || s.includes("moderate")) return 1;
|
|
255
307
|
return 0;
|
|
256
308
|
}
|
|
257
|
-
async function collectThermal(
|
|
309
|
+
async function collectThermal(detailed) {
|
|
258
310
|
try {
|
|
259
311
|
const therm = (await run("pmset -g therm", "")).trim();
|
|
260
|
-
let
|
|
312
|
+
let state2 = "Unknown";
|
|
261
313
|
let detail = therm || "No pmset thermal data";
|
|
262
314
|
const stateMatch = therm.match(/Thermal state:\s*(.+)/i);
|
|
263
315
|
if (stateMatch) {
|
|
264
316
|
const raw = stateMatch[1].trim();
|
|
265
|
-
|
|
266
|
-
detail =
|
|
317
|
+
state2 = /0|no warning|normal/i.test(raw) ? "Nominal" : raw;
|
|
318
|
+
detail = state2;
|
|
267
319
|
} else {
|
|
268
320
|
const limitMatch = therm.match(
|
|
269
321
|
/CPU_(?:Speed|Scheduler)_Limit\s*(?:=|:)\s*([\d.]+)/i
|
|
270
322
|
);
|
|
271
323
|
if (limitMatch) {
|
|
272
324
|
const limit = parseFloat(limitMatch[1]);
|
|
273
|
-
if (limit >= 100)
|
|
274
|
-
else if (limit >= 85)
|
|
275
|
-
else if (limit >= 50)
|
|
276
|
-
else
|
|
277
|
-
detail = `${
|
|
325
|
+
if (limit >= 100) state2 = "Nominal";
|
|
326
|
+
else if (limit >= 85) state2 = "Fair";
|
|
327
|
+
else if (limit >= 50) state2 = "Serious";
|
|
328
|
+
else state2 = "Critical";
|
|
329
|
+
detail = `${state2} (CPU limit ${limit}%)`;
|
|
278
330
|
} else if (/no thermal warning level has been recorded/i.test(therm)) {
|
|
279
|
-
|
|
331
|
+
state2 = "Nominal";
|
|
280
332
|
detail = "Nominal (no thermal warning recorded)";
|
|
281
333
|
}
|
|
282
334
|
}
|
|
@@ -285,7 +337,7 @@ async function collectThermal(detailed2) {
|
|
|
285
337
|
try {
|
|
286
338
|
const { temps } = await getSmcMetrics();
|
|
287
339
|
cpuDieTemp = temps.cpu_die;
|
|
288
|
-
if (
|
|
340
|
+
if (detailed) {
|
|
289
341
|
if (temps.cpu_die !== void 0) temperatures["cpu_die"] = temps.cpu_die;
|
|
290
342
|
if (temps.gpu_die !== void 0) temperatures["gpu_die"] = temps.gpu_die;
|
|
291
343
|
if (temps.smc_die !== void 0) temperatures["smc_die"] = temps.smc_die;
|
|
@@ -294,19 +346,19 @@ async function collectThermal(detailed2) {
|
|
|
294
346
|
}
|
|
295
347
|
}
|
|
296
348
|
} catch {
|
|
297
|
-
if (
|
|
349
|
+
if (detailed) detail += " (powermetrics unavailable)";
|
|
298
350
|
}
|
|
299
|
-
if (
|
|
300
|
-
if (cpuDieTemp >= 100)
|
|
301
|
-
else if (cpuDieTemp >= 90)
|
|
302
|
-
else if (cpuDieTemp >= 80)
|
|
303
|
-
else
|
|
304
|
-
detail = `${
|
|
351
|
+
if (state2 === "Unknown" && cpuDieTemp !== void 0) {
|
|
352
|
+
if (cpuDieTemp >= 100) state2 = "Critical";
|
|
353
|
+
else if (cpuDieTemp >= 90) state2 = "Serious";
|
|
354
|
+
else if (cpuDieTemp >= 80) state2 = "Fair";
|
|
355
|
+
else state2 = "Nominal";
|
|
356
|
+
detail = `${state2} (estimated from ${cpuDieTemp}\xB0C)`;
|
|
305
357
|
}
|
|
306
358
|
return {
|
|
307
|
-
state,
|
|
359
|
+
state: state2,
|
|
308
360
|
detail,
|
|
309
|
-
pressureLevel: pressureFromState(
|
|
361
|
+
pressureLevel: pressureFromState(state2),
|
|
310
362
|
temperatures: Object.keys(temperatures).length ? temperatures : void 0
|
|
311
363
|
};
|
|
312
364
|
} catch {
|
|
@@ -348,41 +400,30 @@ async function collectNetwork() {
|
|
|
348
400
|
}
|
|
349
401
|
async function collectProcesses(limit = 10) {
|
|
350
402
|
try {
|
|
351
|
-
const raw = (await run(`ps -Ao pid,user,pcpu,pmem,comm -r | head -n ${limit + 1}`)).trim();
|
|
403
|
+
const raw = (await run(`ps -Ao pid,ppid,user,pcpu,pmem,state,threads,time,comm -r | head -n ${limit + 1}`)).trim();
|
|
352
404
|
const lines = raw.split("\n").slice(1);
|
|
353
405
|
return lines.map((line) => {
|
|
354
|
-
const parts = line.match(/\s*(\d+)\s+(\S+)\s+([\d.]+)\s+([\d.]+)\s+(.+)/);
|
|
406
|
+
const parts = line.match(/\s*(\d+)\s+(\d+)\s+(\S+)\s+([\d.]+)\s+([\d.]+)\s+(\S+)\s+(\d+)\s+([\d:]+)\s+(.+)/);
|
|
355
407
|
if (!parts) return null;
|
|
356
|
-
|
|
408
|
+
const runtimeSec = parts[8].split(":").reduce((acc, val, idx) => acc + parseInt(val) * Math.pow(60, 2 - idx), 0);
|
|
409
|
+
return { pid: parseInt(parts[1]), ppid: parseInt(parts[2]), user: parts[3], cpu: parseFloat(parts[4]), mem: parseFloat(parts[5]), state: parts[6], threads: parseInt(parts[7]), runtime: runtimeSec, command: parts[9] };
|
|
357
410
|
}).filter((p) => p !== null && p.pid > 0);
|
|
358
411
|
} catch {
|
|
359
412
|
return [];
|
|
360
413
|
}
|
|
361
414
|
}
|
|
362
415
|
|
|
363
|
-
// src/formatters.ts
|
|
364
|
-
import
|
|
365
|
-
|
|
366
|
-
// src/sparkline.ts
|
|
367
|
-
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
368
|
-
function sparkline(values, opts2 = {}) {
|
|
369
|
-
if (!values.length) return "";
|
|
370
|
-
const min = opts2.min ?? Math.min(...values);
|
|
371
|
-
const max = opts2.max ?? Math.max(...values);
|
|
372
|
-
const range = max - min || 1;
|
|
373
|
-
return values.map((v) => {
|
|
374
|
-
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
375
|
-
const clamped = Math.min(BLOCKS.length - 1, Math.max(0, idx));
|
|
376
|
-
return BLOCKS[clamped];
|
|
377
|
-
}).join("");
|
|
378
|
-
}
|
|
416
|
+
// src/formatters/render.ts
|
|
417
|
+
import chalk3 from "chalk";
|
|
379
418
|
|
|
380
|
-
// src/formatters.ts
|
|
419
|
+
// src/formatters/themes.ts
|
|
420
|
+
import chalk2 from "chalk";
|
|
381
421
|
var THEMES = {
|
|
382
422
|
default: {
|
|
383
423
|
border: chalk2.hex("#4b5563"),
|
|
384
424
|
cpu: chalk2.hex("#22d3ee"),
|
|
385
425
|
mem: chalk2.hex("#a78bfa"),
|
|
426
|
+
gpu: chalk2.hex("#f472b6"),
|
|
386
427
|
power: chalk2.hex("#fbbf24"),
|
|
387
428
|
battery: chalk2.hex("#34d399"),
|
|
388
429
|
thermal: chalk2.hex("#fb923c"),
|
|
@@ -395,6 +436,7 @@ var THEMES = {
|
|
|
395
436
|
border: chalk2.hex("#6272a4"),
|
|
396
437
|
cpu: chalk2.hex("#8be9fd"),
|
|
397
438
|
mem: chalk2.hex("#bd93f9"),
|
|
439
|
+
gpu: chalk2.hex("#ff5555"),
|
|
398
440
|
power: chalk2.hex("#f1fa8c"),
|
|
399
441
|
battery: chalk2.hex("#50fa7b"),
|
|
400
442
|
thermal: chalk2.hex("#ffb86c"),
|
|
@@ -407,6 +449,7 @@ var THEMES = {
|
|
|
407
449
|
border: chalk2.hex("#ff0055"),
|
|
408
450
|
cpu: chalk2.hex("#00ffcc"),
|
|
409
451
|
mem: chalk2.hex("#ff00ff"),
|
|
452
|
+
gpu: chalk2.hex("#ffff00"),
|
|
410
453
|
power: chalk2.hex("#ffff00"),
|
|
411
454
|
battery: chalk2.hex("#00ff00"),
|
|
412
455
|
thermal: chalk2.hex("#ff6600"),
|
|
@@ -419,6 +462,7 @@ var THEMES = {
|
|
|
419
462
|
border: chalk2.gray,
|
|
420
463
|
cpu: chalk2.white.bold,
|
|
421
464
|
mem: chalk2.white.bold,
|
|
465
|
+
gpu: chalk2.white.bold,
|
|
422
466
|
power: chalk2.white.bold,
|
|
423
467
|
battery: chalk2.white.bold,
|
|
424
468
|
thermal: chalk2.white.bold,
|
|
@@ -426,17 +470,36 @@ var THEMES = {
|
|
|
426
470
|
disk: chalk2.white.bold,
|
|
427
471
|
graphs: chalk2.white.bold,
|
|
428
472
|
process: chalk2.white.bold
|
|
473
|
+
},
|
|
474
|
+
nord: {
|
|
475
|
+
border: chalk2.hex("#4c566a"),
|
|
476
|
+
cpu: chalk2.hex("#88c0d0"),
|
|
477
|
+
mem: chalk2.hex("#b48ead"),
|
|
478
|
+
gpu: chalk2.hex("#bf616a"),
|
|
479
|
+
power: chalk2.hex("#ebcb8b"),
|
|
480
|
+
battery: chalk2.hex("#a3be8c"),
|
|
481
|
+
thermal: chalk2.hex("#d08770"),
|
|
482
|
+
network: chalk2.hex("#81a1c1"),
|
|
483
|
+
disk: chalk2.hex("#b48ead"),
|
|
484
|
+
graphs: chalk2.hex("#88c0d0"),
|
|
485
|
+
process: chalk2.hex("#eceff4")
|
|
486
|
+
},
|
|
487
|
+
gruvbox: {
|
|
488
|
+
border: chalk2.hex("#928374"),
|
|
489
|
+
cpu: chalk2.hex("#fb4934"),
|
|
490
|
+
mem: chalk2.hex("#b8bb26"),
|
|
491
|
+
gpu: chalk2.hex("#d3869b"),
|
|
492
|
+
power: chalk2.hex("#fabd2f"),
|
|
493
|
+
battery: chalk2.hex("#b8bb26"),
|
|
494
|
+
thermal: chalk2.hex("#fb4934"),
|
|
495
|
+
network: chalk2.hex("#83a598"),
|
|
496
|
+
disk: chalk2.hex("#d3869b"),
|
|
497
|
+
graphs: chalk2.hex("#fb4934"),
|
|
498
|
+
process: chalk2.hex("#ebdbb2")
|
|
429
499
|
}
|
|
430
500
|
};
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return Math.max(60, Math.min(width, 240));
|
|
434
|
-
}
|
|
435
|
-
function gridColumns(width) {
|
|
436
|
-
if (width >= 150) return 3;
|
|
437
|
-
if (width >= 96) return 2;
|
|
438
|
-
return 1;
|
|
439
|
-
}
|
|
501
|
+
|
|
502
|
+
// src/formatters/render.ts
|
|
440
503
|
function visLen(s) {
|
|
441
504
|
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
442
505
|
}
|
|
@@ -453,9 +516,8 @@ function panel(title, lines, width, accent, borderAccent, height) {
|
|
|
453
516
|
const h = height ?? lines.length;
|
|
454
517
|
const contentWidth = Math.max(1, width - 4);
|
|
455
518
|
const label = ` ${title} `;
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
const bottom = borderAccent("\u2570" + "\u2500".repeat(width - 2) + "\u256F");
|
|
519
|
+
const top = borderAccent("\u250C" + label + "\u2500".repeat(width - label.length - 2) + "\u2510");
|
|
520
|
+
const bottom = borderAccent("\u2514" + "\u2500".repeat(width - 2) + "\u2518");
|
|
459
521
|
const body = [];
|
|
460
522
|
for (let i = 0; i < h; i++) {
|
|
461
523
|
const line = lines[i] ?? "";
|
|
@@ -481,31 +543,28 @@ function hstack(blocks, gap = 2) {
|
|
|
481
543
|
}
|
|
482
544
|
function statRow(label, value, width = 9) {
|
|
483
545
|
const w = Math.max(width, label.length + 1);
|
|
484
|
-
return `${
|
|
485
|
-
}
|
|
486
|
-
function bar(percent, width = 20) {
|
|
487
|
-
const filled = Math.round(Math.min(100, Math.max(0, percent)) / 100 * width);
|
|
488
|
-
const empty = width - filled;
|
|
489
|
-
const barStr = "\u2588".repeat(Math.max(0, filled)) + "\u2591".repeat(Math.max(0, empty));
|
|
490
|
-
const color = percent > 90 ? chalk2.red : percent > 70 ? chalk2.yellow : chalk2.green;
|
|
491
|
-
return color(barStr);
|
|
546
|
+
return `${chalk3.dim(label.padEnd(w))}${value}`;
|
|
492
547
|
}
|
|
493
548
|
function gaugeRow(label, percent, contentWidth) {
|
|
494
549
|
const labelW = 9;
|
|
495
550
|
const pctText = `${Math.round(percent)}%`;
|
|
496
551
|
const barW = Math.max(6, contentWidth - labelW - pctText.length - 1);
|
|
497
|
-
|
|
552
|
+
const filled = Math.round(Math.min(100, Math.max(0, percent)) / 100 * barW);
|
|
553
|
+
const empty = barW - filled;
|
|
554
|
+
const barStr = "\u2588".repeat(Math.max(0, filled)) + "\u2591".repeat(Math.max(0, empty));
|
|
555
|
+
const color = percent > 90 ? chalk3.red : percent > 70 ? chalk3.yellow : chalk3.green;
|
|
556
|
+
return `${chalk3.dim(label.padEnd(labelW))}${color(barStr)} ${pctText}`;
|
|
498
557
|
}
|
|
499
558
|
function thermalColor(pressureLevel) {
|
|
500
|
-
if (pressureLevel >= 3) return
|
|
501
|
-
if (pressureLevel === 2) return
|
|
502
|
-
if (pressureLevel === 1) return
|
|
503
|
-
return
|
|
559
|
+
if (pressureLevel >= 3) return chalk3.red;
|
|
560
|
+
if (pressureLevel === 2) return chalk3.yellow;
|
|
561
|
+
if (pressureLevel === 1) return chalk3.yellowBright;
|
|
562
|
+
return chalk3.green;
|
|
504
563
|
}
|
|
505
564
|
function capacityColor(percent) {
|
|
506
|
-
if (percent < 80) return
|
|
507
|
-
if (percent < 90) return
|
|
508
|
-
return
|
|
565
|
+
if (percent < 80) return chalk3.red;
|
|
566
|
+
if (percent < 90) return chalk3.yellow;
|
|
567
|
+
return chalk3.green;
|
|
509
568
|
}
|
|
510
569
|
function formatBytes(bytes) {
|
|
511
570
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -525,7 +584,7 @@ function cpuCard(data, contentWidth) {
|
|
|
525
584
|
statRow("Cores", `${data.cpu.physicalCores}/${data.cpu.cores} phys/log`),
|
|
526
585
|
statRow("Freq", `${data.cpu.frequency} MHz`),
|
|
527
586
|
gaugeRow("Usage", data.cpu.usage, contentWidth),
|
|
528
|
-
statRow("Load", data.cpu.loadAvg.map((l) => l.toFixed(2)).join("
|
|
587
|
+
statRow("Load", data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" "))
|
|
529
588
|
];
|
|
530
589
|
if (data.cpu.temperature) lines.push(statRow("Temp", formatTemp(data.cpu.temperature)));
|
|
531
590
|
return lines;
|
|
@@ -534,11 +593,22 @@ function memCard(data, contentWidth) {
|
|
|
534
593
|
return [
|
|
535
594
|
statRow("Total", formatBytes(data.memory.total)),
|
|
536
595
|
statRow("Used", formatBytes(data.memory.used)),
|
|
596
|
+
statRow("Cached", formatBytes(data.memory.total - data.memory.free - data.memory.used)),
|
|
537
597
|
statRow("Free", formatBytes(data.memory.free)),
|
|
538
598
|
gaugeRow("Usage", data.memory.usagePercent, contentWidth),
|
|
539
599
|
statRow("Swap", `${formatBytes(data.memory.swapUsed)} / ${formatBytes(data.memory.swapTotal)}`)
|
|
540
600
|
];
|
|
541
601
|
}
|
|
602
|
+
function gpuCard(data, contentWidth) {
|
|
603
|
+
if (!data.gpu) return null;
|
|
604
|
+
const lines = [];
|
|
605
|
+
lines.push(statRow("Model", truncatePlain(data.gpu.model, Math.max(4, contentWidth - 8))));
|
|
606
|
+
lines.push(statRow("Memory", formatBytes(data.gpu.memory)));
|
|
607
|
+
lines.push(statRow("Util", `${data.gpu.utilization}%`));
|
|
608
|
+
if (data.gpu.temperature) lines.push(statRow("Temp", formatTemp(data.gpu.temperature)));
|
|
609
|
+
lines.push(statRow("Procs", String(data.gpu.processes)));
|
|
610
|
+
return lines;
|
|
611
|
+
}
|
|
542
612
|
function powerCard(data) {
|
|
543
613
|
if (!data.power) return null;
|
|
544
614
|
const lines = [];
|
|
@@ -553,7 +623,8 @@ function batteryCard(data, contentWidth) {
|
|
|
553
623
|
const lines = [
|
|
554
624
|
statRow("Level", `${data.battery.level}% (${data.battery.state})`),
|
|
555
625
|
statRow("Time", data.battery.timeRemaining),
|
|
556
|
-
statRow("
|
|
626
|
+
statRow("Cycles", String(data.battery.cycles ?? "N/A")),
|
|
627
|
+
statRow("Condition", data.battery.condition ?? "N/A")
|
|
557
628
|
];
|
|
558
629
|
if (data.battery.maxCapacityPercent !== void 0) {
|
|
559
630
|
lines.push(statRow("Capacity", capacityColor(data.battery.maxCapacityPercent)(`${data.battery.maxCapacityPercent}%`)));
|
|
@@ -576,42 +647,99 @@ function networkCard(data, contentWidth) {
|
|
|
576
647
|
const valW = Math.max(6, contentWidth - 8);
|
|
577
648
|
return [
|
|
578
649
|
statRow("Iface", truncatePlain(`${data.network.interface} (${data.network.ip})`, valW)),
|
|
579
|
-
statRow("RX", `${formatBytes(data.network.rxBytes)}
|
|
580
|
-
statRow("TX", `${formatBytes(data.network.txBytes)}
|
|
650
|
+
statRow("RX", `${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt`),
|
|
651
|
+
statRow("TX", `${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt`)
|
|
581
652
|
];
|
|
582
653
|
}
|
|
583
654
|
function header(data, width, badges = [], borderAccent = THEMES.default.border) {
|
|
584
|
-
const
|
|
585
|
-
const
|
|
655
|
+
const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
656
|
+
const left = `${chalk3.hex("#ff5500").bold(" PYRE ")} ${chalk3.bold(data.header.hostname)} ${chalk3.dim(data.header.os)} ${chalk3.dim("up " + data.header.uptime)}`;
|
|
657
|
+
const right = `${chalk3.dim(now)} ${badges.join(" ")}`;
|
|
586
658
|
const gap = Math.max(1, width - visLen(left) - visLen(right));
|
|
587
659
|
const line = left + " ".repeat(gap) + right;
|
|
588
660
|
return [line, borderAccent("\u2500".repeat(width))];
|
|
589
661
|
}
|
|
590
662
|
function sortProcesses(processes, sortBy) {
|
|
591
663
|
const copy = [...processes];
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
664
|
+
switch (sortBy) {
|
|
665
|
+
case "pid":
|
|
666
|
+
return copy.sort((a, b) => a.pid - b.pid);
|
|
667
|
+
case "mem":
|
|
668
|
+
return copy.sort((a, b) => b.mem - a.mem);
|
|
669
|
+
case "user":
|
|
670
|
+
return copy.sort((a, b) => a.user.localeCompare(b.user));
|
|
671
|
+
case "command":
|
|
672
|
+
return copy.sort((a, b) => a.command.localeCompare(b.command));
|
|
673
|
+
case "state":
|
|
674
|
+
return copy.sort((a, b) => a.state.localeCompare(b.state));
|
|
675
|
+
case "threads":
|
|
676
|
+
return copy.sort((a, b) => b.threads - a.threads);
|
|
677
|
+
case "runtime":
|
|
678
|
+
return copy.sort((a, b) => b.runtime - a.runtime);
|
|
679
|
+
default:
|
|
680
|
+
return copy.sort((a, b) => b.cpu - a.cpu);
|
|
681
|
+
}
|
|
595
682
|
}
|
|
596
683
|
function pctColor(v) {
|
|
597
|
-
return v > 80 ?
|
|
684
|
+
return v > 80 ? chalk3.red : v > 50 ? chalk3.yellow : chalk3.dim;
|
|
598
685
|
}
|
|
599
686
|
function processTableLines(processes, contentWidth, borderAccent = THEMES.default.border) {
|
|
600
|
-
const pidW =
|
|
601
|
-
const cmdW = Math.max(10, contentWidth - pidW - userW - cpuW - memW -
|
|
602
|
-
const head =
|
|
687
|
+
const pidW = 8, ppidW = 8, userW = 12, cpuW = 8, memW = 8, stateW = 8, thrW = 6, rtW = 10;
|
|
688
|
+
const cmdW = Math.max(10, contentWidth - pidW - ppidW - userW - cpuW - memW - stateW - thrW - rtW - 8);
|
|
689
|
+
const head = chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" PID ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" PPID ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" USER ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" CPU% ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" MEM% ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" STATE ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" THREADS") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" RUNTIME ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" COMMAND".padEnd(cmdW + 1));
|
|
603
690
|
const rows = processes.map((p) => {
|
|
604
691
|
const cpuStr = pctColor(p.cpu)(`${p.cpu.toFixed(1)}`.padEnd(cpuW));
|
|
605
692
|
const memStr = pctColor(p.mem)(`${p.mem.toFixed(1)}`.padEnd(memW));
|
|
606
|
-
|
|
693
|
+
const stateStr = chalk3.dim(p.state.padEnd(stateW));
|
|
694
|
+
const thrStr = String(p.threads).padEnd(thrW);
|
|
695
|
+
const rtStr = formatRuntime(p.runtime).padEnd(rtW);
|
|
696
|
+
return String(p.pid).padEnd(pidW) + String(p.ppid).padEnd(ppidW) + truncatePlain(p.user, userW - 1).padEnd(userW) + cpuStr + memStr + stateStr + thrStr + rtStr + truncatePlain(p.command, cmdW);
|
|
607
697
|
});
|
|
608
698
|
return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
|
|
609
699
|
}
|
|
700
|
+
function formatRuntime(seconds) {
|
|
701
|
+
if (seconds < 60) return `${seconds}s`;
|
|
702
|
+
const mins = Math.floor(seconds / 60);
|
|
703
|
+
if (mins < 60) return `${mins}m ${seconds % 60}s`;
|
|
704
|
+
const hrs = Math.floor(mins / 60);
|
|
705
|
+
return `${hrs}h ${mins % 60}m`;
|
|
706
|
+
}
|
|
707
|
+
function buildProcessTree(processes) {
|
|
708
|
+
const map = /* @__PURE__ */ new Map();
|
|
709
|
+
const roots = [];
|
|
710
|
+
for (const p of processes) map.set(p.pid, p);
|
|
711
|
+
for (const p of processes) {
|
|
712
|
+
if (p.ppid === 0 || !map.has(p.ppid)) roots.push(p);
|
|
713
|
+
}
|
|
714
|
+
const lines = [];
|
|
715
|
+
const visited = /* @__PURE__ */ new Set();
|
|
716
|
+
function renderNode(proc, prefix, isLast) {
|
|
717
|
+
if (visited.has(proc.pid)) return;
|
|
718
|
+
visited.add(proc.pid);
|
|
719
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
720
|
+
const pidStr = String(proc.pid).padEnd(8);
|
|
721
|
+
const cpuStr = pctColor(proc.cpu)(`${proc.cpu.toFixed(1)}%`.padEnd(8));
|
|
722
|
+
const memStr = pctColor(proc.mem)(`${proc.mem.toFixed(1)}%`.padEnd(8));
|
|
723
|
+
const stateStr = chalk3.dim(proc.state.padEnd(8));
|
|
724
|
+
const thrStr = String(proc.threads).padEnd(6);
|
|
725
|
+
const cmd = truncatePlain(proc.command, Math.max(10, 100 - pidStr.length - cpuStr.length - memStr.length - stateStr.length - thrStr.length - 10));
|
|
726
|
+
lines.push(`${prefix}${connector}${pidStr}${cpuStr}${memStr}${stateStr}${thrStr} ${cmd}`);
|
|
727
|
+
const children = processes.filter((p) => p.ppid === proc.pid && !visited.has(p.pid));
|
|
728
|
+
children.sort((a, b) => b.cpu - a.cpu);
|
|
729
|
+
children.forEach((child, i) => {
|
|
730
|
+
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
731
|
+
renderNode(child, nextPrefix, i === children.length - 1);
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
roots.sort((a, b) => b.cpu - a.cpu);
|
|
735
|
+
roots.forEach((root, i) => renderNode(root, "", i === roots.length - 1));
|
|
736
|
+
return lines;
|
|
737
|
+
}
|
|
610
738
|
function diskTableLines(disks, contentWidth, borderAccent = THEMES.default.border) {
|
|
611
|
-
const fsW =
|
|
739
|
+
const fsW = 20, sizeW = 10, usedW = 10, availW = 10, capW = 8;
|
|
612
740
|
const mountW = Math.max(8, contentWidth - fsW - sizeW - usedW - availW - capW - 5);
|
|
613
|
-
const head =
|
|
614
|
-
const rows = disks.
|
|
741
|
+
const head = chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" FILESYSTEM ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" SIZE ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" USED ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" AVAIL ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" CAP ") + chalk3.bold.hex("#f8f8f2").bgHex("#44475a")(" MOUNT".padEnd(mountW + 1));
|
|
742
|
+
const rows = disks.map((d) => {
|
|
615
743
|
const capNum = parseInt(d.capacity, 10) || 0;
|
|
616
744
|
const capStr = pctColor(capNum)(d.capacity.padEnd(capW));
|
|
617
745
|
return truncatePlain(d.filesystem, fsW - 1).padEnd(fsW) + d.size.padEnd(sizeW) + d.used.padEnd(usedW) + d.available.padEnd(availW) + capStr + truncatePlain(d.mountpoint, mountW);
|
|
@@ -637,6 +765,10 @@ function formatTable(data, opts2 = {}) {
|
|
|
637
765
|
if (vis.mem !== false) {
|
|
638
766
|
cards.push({ title: "Memory", accent: theme.mem, lines: memCard(data, contentWidth) });
|
|
639
767
|
}
|
|
768
|
+
if (vis.gpu !== false) {
|
|
769
|
+
const gpu = gpuCard(data, contentWidth);
|
|
770
|
+
if (gpu) cards.push({ title: "GPU", accent: theme.gpu, lines: gpu });
|
|
771
|
+
}
|
|
640
772
|
if (vis.power !== false) {
|
|
641
773
|
const power = powerCard(data);
|
|
642
774
|
if (power) cards.push({ title: "Power", accent: theme.power, lines: power });
|
|
@@ -668,14 +800,49 @@ function formatTable(data, opts2 = {}) {
|
|
|
668
800
|
const sortedProcesses = sortProcesses(filteredProcesses, opts2.sortBy ?? "cpu");
|
|
669
801
|
const limited = opts2.processLimit ? sortedProcesses.slice(0, opts2.processLimit) : sortedProcesses;
|
|
670
802
|
const procTitle = opts2.filter ? `Processes \xB7 filter "${opts2.filter}" \xB7 sort ${opts2.sortBy ?? "cpu"}` : `Processes \xB7 sort ${opts2.sortBy ?? "cpu"}`;
|
|
671
|
-
if (
|
|
803
|
+
if (opts2.treeView) {
|
|
804
|
+
const treeLines = buildProcessTree(limited);
|
|
805
|
+
if (treeLines.length) {
|
|
806
|
+
out.push(...panel(procTitle + " \xB7 tree", treeLines, width, theme.process, theme.border));
|
|
807
|
+
} else if (opts2.filter) {
|
|
808
|
+
out.push(...panel(procTitle, [chalk3.dim(`No processes match "${opts2.filter}"`)], width, theme.process, theme.border));
|
|
809
|
+
}
|
|
810
|
+
} else if (limited.length) {
|
|
672
811
|
out.push(...panel(procTitle, processTableLines(limited, width - 4, theme.border), width, theme.process, theme.border));
|
|
673
812
|
} else if (opts2.filter) {
|
|
674
|
-
out.push(...panel(procTitle, [
|
|
813
|
+
out.push(...panel(procTitle, [chalk3.dim(`No processes match "${opts2.filter}"`)], width, theme.process, theme.border));
|
|
675
814
|
}
|
|
676
815
|
}
|
|
677
816
|
return out.join("\n");
|
|
678
817
|
}
|
|
818
|
+
function clampWidth(width) {
|
|
819
|
+
if (!width || Number.isNaN(width)) return 80;
|
|
820
|
+
return Math.max(60, Math.min(width, 240));
|
|
821
|
+
}
|
|
822
|
+
function gridColumns(width) {
|
|
823
|
+
if (width >= 150) return 3;
|
|
824
|
+
if (width >= 96) return 2;
|
|
825
|
+
return 1;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/formatters/output.ts
|
|
829
|
+
import chalk4 from "chalk";
|
|
830
|
+
|
|
831
|
+
// src/sparkline.ts
|
|
832
|
+
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
833
|
+
function sparkline(values, opts2 = {}) {
|
|
834
|
+
if (!values.length) return "";
|
|
835
|
+
const min = opts2.min ?? Math.min(...values);
|
|
836
|
+
const max = opts2.max ?? Math.max(...values);
|
|
837
|
+
const range = max - min || 1;
|
|
838
|
+
return values.map((v) => {
|
|
839
|
+
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
840
|
+
const clamped = Math.min(BLOCKS.length - 1, Math.max(0, idx));
|
|
841
|
+
return BLOCKS[clamped];
|
|
842
|
+
}).join("");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/formatters/output.ts
|
|
679
846
|
function formatJson(data) {
|
|
680
847
|
return JSON.stringify(data, null, 2);
|
|
681
848
|
}
|
|
@@ -697,7 +864,7 @@ function formatCsv(data) {
|
|
|
697
864
|
[
|
|
698
865
|
"thermal",
|
|
699
866
|
"cpuTempF",
|
|
700
|
-
data.thermal.temperatures?.cpu_die
|
|
867
|
+
data.thermal.temperatures?.cpu_die != null ? String(celsiusToFahrenheit(data.thermal.temperatures.cpu_die)) : ""
|
|
701
868
|
],
|
|
702
869
|
["network", "rxBytes", String(data.network.rxBytes)],
|
|
703
870
|
["network", "txBytes", String(data.network.txBytes)],
|
|
@@ -713,62 +880,109 @@ function formatCsv(data) {
|
|
|
713
880
|
["system", "uptime", data.header.uptime],
|
|
714
881
|
["system", "timestamp", data.timestamp]
|
|
715
882
|
];
|
|
883
|
+
if (data.gpu) {
|
|
884
|
+
rows.push(["gpu", "model", data.gpu.model]);
|
|
885
|
+
rows.push(["gpu", "memory", String(data.gpu.memory)]);
|
|
886
|
+
rows.push(["gpu", "utilization", String(data.gpu.utilization)]);
|
|
887
|
+
if (data.gpu.temperature) rows.push(["gpu", "tempC", String(data.gpu.temperature)]);
|
|
888
|
+
rows.push(["gpu", "processes", String(data.gpu.processes)]);
|
|
889
|
+
}
|
|
890
|
+
for (const p of data.processes) {
|
|
891
|
+
rows.push(["process", `pid=${p.pid}`, `${p.pid}`]);
|
|
892
|
+
rows.push(["process", `ppid=${p.pid}`, String(p.ppid)]);
|
|
893
|
+
rows.push(["process", `user=${p.pid}`, p.user]);
|
|
894
|
+
rows.push(["process", `cpu=${p.pid}`, String(p.cpu)]);
|
|
895
|
+
rows.push(["process", `mem=${p.pid}`, String(p.mem)]);
|
|
896
|
+
rows.push(["process", `state=${p.pid}`, p.state]);
|
|
897
|
+
rows.push(["process", `threads=${p.pid}`, String(p.threads)]);
|
|
898
|
+
rows.push(["process", `runtime=${p.pid}`, String(p.runtime)]);
|
|
899
|
+
rows.push(["process", `command=${p.pid}`, p.command]);
|
|
900
|
+
}
|
|
716
901
|
return rows.map((r) => r.map((item) => `"${String(item).replace(/"/g, '""')}"`).join(",")).join("\n");
|
|
717
902
|
}
|
|
718
903
|
function formatTsv(data) {
|
|
719
904
|
return formatCsv(data).split("\n").map((r) => r.split(",").join(" ")).join("\n");
|
|
720
905
|
}
|
|
721
|
-
function formatGraphs(
|
|
906
|
+
function formatGraphs(history, width = 80, themeName = "default", graphMode = "spark") {
|
|
722
907
|
const theme = THEMES[themeName] || THEMES.default;
|
|
723
908
|
const contentWidth = width - 4;
|
|
724
|
-
const sampleCount =
|
|
909
|
+
const sampleCount = history.cpuUsage.length;
|
|
725
910
|
const sparkWidth = Math.max(10, contentWidth - 28);
|
|
726
911
|
const lines = [];
|
|
727
|
-
lines.push(graphRow("CPU %",
|
|
728
|
-
lines.push(graphRow("Mem %",
|
|
912
|
+
lines.push(graphRow("CPU %", history.cpuUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, graphMode));
|
|
913
|
+
lines.push(graphRow("Mem %", history.memUsage, { min: 0, max: 100 }, (v) => `${v.toFixed(0)}%`, sparkWidth, graphMode));
|
|
729
914
|
lines.push(
|
|
730
|
-
graphRow("Temp",
|
|
915
|
+
graphRow("Temp", history.temp, {}, formatTemp, sparkWidth, "no sensor access (needs sudo powermetrics)", graphMode)
|
|
731
916
|
);
|
|
732
|
-
lines.push(graphRow("Net RX/s",
|
|
733
|
-
lines.push(graphRow("Net TX/s",
|
|
734
|
-
const title = `Graphs \xB7 last ${sampleCount} sample${sampleCount === 1 ? "" : "s"}`;
|
|
917
|
+
lines.push(graphRow("Net RX/s", history.netRxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, graphMode));
|
|
918
|
+
lines.push(graphRow("Net TX/s", history.netTxRate, {}, (v) => `${formatBytes(v)}/s`, sparkWidth, graphMode));
|
|
919
|
+
const title = `Graphs \xB7 mode:${graphMode} \xB7 last ${sampleCount} sample${sampleCount === 1 ? "" : "s"}`;
|
|
735
920
|
return panel(title, lines, width, theme.graphs, theme.border).join("\n");
|
|
736
921
|
}
|
|
737
|
-
function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "collecting data...") {
|
|
922
|
+
function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "collecting data...", mode = "spark") {
|
|
738
923
|
if (!values.length) {
|
|
739
|
-
return `${
|
|
924
|
+
return `${chalk4.dim(label.padEnd(10))} ${chalk4.dim(emptyMessage)}`;
|
|
740
925
|
}
|
|
741
926
|
const visible = values.slice(-sparkWidth);
|
|
742
|
-
const spark = sparkline(visible, bounds);
|
|
927
|
+
const spark = mode === "spark" ? sparkline(visible, bounds) : barGraph(visible, bounds, sparkWidth);
|
|
743
928
|
const current = values[values.length - 1];
|
|
744
|
-
let color =
|
|
929
|
+
let color = chalk4.cyan;
|
|
745
930
|
if (bounds.min !== void 0 && bounds.max !== void 0) {
|
|
746
931
|
const pct = (current - bounds.min) / (bounds.max - bounds.min || 1) * 100;
|
|
747
|
-
color = pct > 90 ?
|
|
932
|
+
color = pct > 90 ? chalk4.red : pct > 70 ? chalk4.yellow : chalk4.green;
|
|
748
933
|
}
|
|
749
|
-
return `${
|
|
934
|
+
return `${chalk4.dim(label.padEnd(10))}${color(spark)} ${chalk4.bold(fmt(current))}`;
|
|
935
|
+
}
|
|
936
|
+
function barGraph(values, bounds, width) {
|
|
937
|
+
const min = bounds.min ?? Math.min(...values);
|
|
938
|
+
const max = bounds.max ?? Math.max(...values);
|
|
939
|
+
const range = max - min || 1;
|
|
940
|
+
return values.map((v) => {
|
|
941
|
+
const pct = (v - min) / range;
|
|
942
|
+
if (pct > 0.8) return "\u2588";
|
|
943
|
+
if (pct > 0.6) return "\u2587";
|
|
944
|
+
if (pct > 0.4) return "\u2586";
|
|
945
|
+
if (pct > 0.2) return "\u2584";
|
|
946
|
+
return " ";
|
|
947
|
+
}).join("");
|
|
750
948
|
}
|
|
751
949
|
|
|
752
|
-
// src/live.ts
|
|
753
|
-
import fs from "fs";
|
|
754
|
-
import path from "path";
|
|
950
|
+
// src/live/input.ts
|
|
755
951
|
import readline from "readline";
|
|
756
|
-
import chalk3 from "chalk";
|
|
757
952
|
|
|
758
953
|
// src/history.ts
|
|
759
954
|
var History = class {
|
|
955
|
+
/** Maximum number of samples retained in the rolling window. */
|
|
760
956
|
maxLen;
|
|
957
|
+
/** Rolling window of CPU usage percentages (0–100). */
|
|
761
958
|
cpuUsage = [];
|
|
959
|
+
/** Rolling window of memory usage percentages (0–100). */
|
|
762
960
|
memUsage = [];
|
|
961
|
+
/** Rolling window of CPU die temperatures in °C. */
|
|
763
962
|
temp = [];
|
|
963
|
+
/** Rolling window of network receive rates in bytes/sec. */
|
|
764
964
|
netRxRate = [];
|
|
965
|
+
/** Rolling window of network transmit rates in bytes/sec. */
|
|
765
966
|
netTxRate = [];
|
|
967
|
+
/** Rolling window of GPU utilization percentages (0–100). */
|
|
968
|
+
gpuUtil = [];
|
|
766
969
|
lastRxBytes = 0;
|
|
767
970
|
lastTxBytes = 0;
|
|
768
971
|
lastTs = 0;
|
|
972
|
+
/**
|
|
973
|
+
* Create a new History instance.
|
|
974
|
+
* @param maxLen - Maximum number of samples to retain. Defaults to 40.
|
|
975
|
+
*/
|
|
769
976
|
constructor(maxLen = 40) {
|
|
770
977
|
this.maxLen = maxLen;
|
|
771
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Push a new sample into the rolling window.
|
|
981
|
+
*
|
|
982
|
+
* Network rates are computed as bytes/sec from the cumulative
|
|
983
|
+
* counters. The first sample always reports 0 for rates
|
|
984
|
+
* because there is no prior sample to compute a delta from.
|
|
985
|
+
*/
|
|
772
986
|
push(sample) {
|
|
773
987
|
const now = Date.now();
|
|
774
988
|
let rxRate = 0;
|
|
@@ -786,21 +1000,28 @@ var History = class {
|
|
|
786
1000
|
if (sample.temp !== null) this.pushBounded(this.temp, sample.temp);
|
|
787
1001
|
this.pushBounded(this.netRxRate, rxRate);
|
|
788
1002
|
this.pushBounded(this.netTxRate, txRate);
|
|
1003
|
+
if (sample.gpuUtil !== void 0) this.pushBounded(this.gpuUtil, sample.gpuUtil);
|
|
789
1004
|
}
|
|
1005
|
+
/** Clear all rolling windows and reset cumulative byte counters. */
|
|
790
1006
|
reset() {
|
|
791
1007
|
this.cpuUsage = [];
|
|
792
1008
|
this.memUsage = [];
|
|
793
1009
|
this.temp = [];
|
|
794
1010
|
this.netRxRate = [];
|
|
795
1011
|
this.netTxRate = [];
|
|
1012
|
+
this.gpuUtil = [];
|
|
796
1013
|
this.lastRxBytes = 0;
|
|
797
1014
|
this.lastTxBytes = 0;
|
|
798
1015
|
this.lastTs = 0;
|
|
799
1016
|
}
|
|
800
|
-
/**
|
|
1017
|
+
/**
|
|
1018
|
+
* Grow or shrink the rolling window (e.g. on terminal resize),
|
|
1019
|
+
* trimming from the oldest samples.
|
|
1020
|
+
* @param n - New maximum length; clamped to at least 1.
|
|
1021
|
+
*/
|
|
801
1022
|
setMaxLen(n) {
|
|
802
1023
|
this.maxLen = Math.max(1, n);
|
|
803
|
-
for (const arr of [this.cpuUsage, this.memUsage, this.temp, this.netRxRate, this.netTxRate]) {
|
|
1024
|
+
for (const arr of [this.cpuUsage, this.memUsage, this.temp, this.netRxRate, this.netTxRate, this.gpuUtil]) {
|
|
804
1025
|
while (arr.length > this.maxLen) arr.shift();
|
|
805
1026
|
}
|
|
806
1027
|
}
|
|
@@ -810,62 +1031,73 @@ var History = class {
|
|
|
810
1031
|
}
|
|
811
1032
|
};
|
|
812
1033
|
|
|
813
|
-
// src/live.ts
|
|
814
|
-
var
|
|
815
|
-
var
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1034
|
+
// src/live/state.ts
|
|
1035
|
+
var SIGNAL_OPTIONS = ["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGSTOP", "SIGCONT"];
|
|
1036
|
+
var state = {
|
|
1037
|
+
intervalHandle: null,
|
|
1038
|
+
running: false,
|
|
1039
|
+
paused: false,
|
|
1040
|
+
detailed: false,
|
|
1041
|
+
interval: 2,
|
|
1042
|
+
showGraphs: true,
|
|
1043
|
+
graphMode: "spark",
|
|
1044
|
+
exportFormat: "json",
|
|
1045
|
+
exportDir: "./pyre-exports",
|
|
1046
|
+
currentTheme: "default",
|
|
1047
|
+
visiblePanels: {
|
|
1048
|
+
cpu: true,
|
|
1049
|
+
mem: true,
|
|
1050
|
+
power: true,
|
|
1051
|
+
battery: true,
|
|
1052
|
+
thermal: true,
|
|
1053
|
+
network: true,
|
|
1054
|
+
disk: true,
|
|
1055
|
+
process: true
|
|
1056
|
+
},
|
|
1057
|
+
logging: false,
|
|
1058
|
+
logStream: null,
|
|
1059
|
+
statusMessage: "",
|
|
1060
|
+
statusTimer: null,
|
|
1061
|
+
lastData: null,
|
|
1062
|
+
history: new History(40),
|
|
1063
|
+
keypressHandler: null,
|
|
1064
|
+
termWidth: process.stdout.columns || 80,
|
|
1065
|
+
termHeight: process.stdout.rows || 24,
|
|
1066
|
+
sortMode: "cpu",
|
|
1067
|
+
processFilter: "",
|
|
1068
|
+
inputMode: null,
|
|
1069
|
+
inputBuffer: "",
|
|
1070
|
+
treeView: false,
|
|
1071
|
+
SIGNAL_OPTIONS,
|
|
1072
|
+
customizerIndex: 0,
|
|
1073
|
+
CUSTOMIZER_OPTIONS: [
|
|
1074
|
+
"Theme",
|
|
1075
|
+
"Graph Mode",
|
|
1076
|
+
"Toggle CPU",
|
|
1077
|
+
"Toggle Memory",
|
|
1078
|
+
"Toggle Power",
|
|
1079
|
+
"Toggle Battery",
|
|
1080
|
+
"Toggle Thermal",
|
|
1081
|
+
"Toggle Network",
|
|
1082
|
+
"Toggle Disk",
|
|
1083
|
+
"Toggle Processes",
|
|
1084
|
+
"Toggle Tree View"
|
|
1085
|
+
],
|
|
1086
|
+
CPU_ALERT_PCT: 90,
|
|
1087
|
+
TEMP_ALERT_C: 95,
|
|
1088
|
+
alerted: false
|
|
832
1089
|
};
|
|
833
|
-
var logging = false;
|
|
834
|
-
var logStream = null;
|
|
835
|
-
var statusMessage = "";
|
|
836
|
-
var statusTimer = null;
|
|
837
|
-
var lastData = null;
|
|
838
|
-
var history = new History(40);
|
|
839
|
-
var keypressHandler = null;
|
|
840
|
-
var termWidth = process.stdout.columns || 80;
|
|
841
|
-
var termHeight = process.stdout.rows || 24;
|
|
842
|
-
var sortMode = "cpu";
|
|
843
|
-
var processFilter = "";
|
|
844
|
-
var inputMode = null;
|
|
845
|
-
var inputBuffer = "";
|
|
846
|
-
var customizerIndex = 0;
|
|
847
|
-
var CUSTOMIZER_OPTIONS = [
|
|
848
|
-
"Theme",
|
|
849
|
-
"Toggle CPU",
|
|
850
|
-
"Toggle Memory",
|
|
851
|
-
"Toggle Power",
|
|
852
|
-
"Toggle Battery",
|
|
853
|
-
"Toggle Thermal",
|
|
854
|
-
"Toggle Network",
|
|
855
|
-
"Toggle Disk",
|
|
856
|
-
"Toggle Processes"
|
|
857
|
-
];
|
|
858
|
-
var CPU_ALERT_PCT = 90;
|
|
859
|
-
var TEMP_ALERT_C = 95;
|
|
860
|
-
var alerted = false;
|
|
861
1090
|
function setStatus(msg, ms = 3e3) {
|
|
862
|
-
statusMessage = msg;
|
|
863
|
-
if (statusTimer) clearTimeout(statusTimer);
|
|
864
|
-
statusTimer = setTimeout(() => {
|
|
865
|
-
statusMessage = "";
|
|
866
|
-
render();
|
|
1091
|
+
state.statusMessage = msg;
|
|
1092
|
+
if (state.statusTimer) clearTimeout(state.statusTimer);
|
|
1093
|
+
state.statusTimer = setTimeout(() => {
|
|
1094
|
+
state.statusMessage = "";
|
|
867
1095
|
}, ms);
|
|
868
1096
|
}
|
|
1097
|
+
|
|
1098
|
+
// src/live/export.ts
|
|
1099
|
+
import fs from "fs";
|
|
1100
|
+
import path from "path";
|
|
869
1101
|
function ensureDir(dir) {
|
|
870
1102
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
871
1103
|
}
|
|
@@ -873,14 +1105,14 @@ function timestampForFile() {
|
|
|
873
1105
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
874
1106
|
}
|
|
875
1107
|
function exportSnapshot() {
|
|
876
|
-
if (!lastData) {
|
|
1108
|
+
if (!state.lastData) {
|
|
877
1109
|
setStatus("No data yet \u2014 try again in a moment");
|
|
878
1110
|
return;
|
|
879
1111
|
}
|
|
880
1112
|
try {
|
|
881
|
-
ensureDir(exportDir);
|
|
882
|
-
const file = path.join(exportDir, `pyre-${timestampForFile()}.${exportFormat}`);
|
|
883
|
-
const content = exportFormat === "json" ? formatJson(lastData) : exportFormat === "csv" ? formatCsv(lastData) : formatTsv(lastData);
|
|
1113
|
+
ensureDir(state.exportDir);
|
|
1114
|
+
const file = path.join(state.exportDir, `pyre-${timestampForFile()}.${state.exportFormat}`);
|
|
1115
|
+
const content = state.exportFormat === "json" ? formatJson(state.lastData) : state.exportFormat === "csv" ? formatCsv(state.lastData) : formatTsv(state.lastData);
|
|
884
1116
|
fs.writeFileSync(file, content);
|
|
885
1117
|
setStatus(`Exported snapshot -> ${file}`);
|
|
886
1118
|
} catch (err) {
|
|
@@ -889,86 +1121,101 @@ function exportSnapshot() {
|
|
|
889
1121
|
}
|
|
890
1122
|
function startLogging() {
|
|
891
1123
|
try {
|
|
892
|
-
ensureDir(exportDir);
|
|
893
|
-
const file = path.join(exportDir, `pyre-log-${timestampForFile()}.csv`);
|
|
894
|
-
logStream = fs.createWriteStream(file, { flags: "a" });
|
|
895
|
-
logStream.write("timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,thermal_state\n");
|
|
896
|
-
logging = true;
|
|
1124
|
+
ensureDir(state.exportDir);
|
|
1125
|
+
const file = path.join(state.exportDir, `pyre-log-${timestampForFile()}.csv`);
|
|
1126
|
+
state.logStream = fs.createWriteStream(file, { flags: "a" });
|
|
1127
|
+
state.logStream.write("timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,thermal_state\n");
|
|
1128
|
+
state.logging = true;
|
|
897
1129
|
setStatus(`Logging every tick -> ${file}`);
|
|
898
1130
|
} catch (err) {
|
|
899
1131
|
setStatus(`Logging failed to start: ${err.message}`);
|
|
900
1132
|
}
|
|
901
1133
|
}
|
|
902
1134
|
function stopLogging() {
|
|
903
|
-
logging = false;
|
|
904
|
-
logStream?.end();
|
|
905
|
-
logStream = null;
|
|
1135
|
+
state.logging = false;
|
|
1136
|
+
state.logStream?.end();
|
|
1137
|
+
state.logStream = null;
|
|
906
1138
|
setStatus("Logging stopped");
|
|
907
1139
|
}
|
|
908
1140
|
function toggleLogging() {
|
|
909
|
-
if (logging) stopLogging();
|
|
1141
|
+
if (state.logging) stopLogging();
|
|
910
1142
|
else startLogging();
|
|
911
1143
|
}
|
|
912
1144
|
function writeLogRow(data) {
|
|
913
|
-
if (!logging || !logStream) return;
|
|
1145
|
+
if (!state.logging || !state.logStream) return;
|
|
914
1146
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? "";
|
|
915
|
-
logStream.write(
|
|
1147
|
+
state.logStream.write(
|
|
916
1148
|
`${data.timestamp},${data.cpu.usage},${data.memory.usagePercent},${temp},${data.network.rxBytes},${data.network.txBytes},${data.thermal.state}
|
|
917
1149
|
`
|
|
918
1150
|
);
|
|
919
1151
|
}
|
|
1152
|
+
|
|
1153
|
+
// src/live/render.ts
|
|
1154
|
+
import chalk5 from "chalk";
|
|
920
1155
|
function processRowBudget() {
|
|
921
|
-
const columns = gridColumns(termWidth);
|
|
1156
|
+
const columns = gridColumns(state.termWidth);
|
|
922
1157
|
const cardRows = Math.ceil(6 / columns);
|
|
923
|
-
const reserved = 2 + cardRows * 8 + 11 + (showGraphs ? 9 : 0) + 4;
|
|
924
|
-
return Math.max(3, Math.min(40, termHeight - reserved));
|
|
1158
|
+
const reserved = 2 + cardRows * 8 + 11 + (state.showGraphs ? 9 : 0) + 4;
|
|
1159
|
+
return Math.max(3, Math.min(40, state.termHeight - reserved));
|
|
925
1160
|
}
|
|
926
1161
|
function renderCustomizerOverlay() {
|
|
927
1162
|
const themesList = Object.keys(THEMES);
|
|
928
1163
|
const lines = [
|
|
929
|
-
|
|
1164
|
+
chalk5.bgCyan.black.bold(" UI CUSTOMIZER (Use \u2191/\u2193 or J/K to navigate, Enter/Space to toggle, Esc to exit) ")
|
|
930
1165
|
];
|
|
931
|
-
CUSTOMIZER_OPTIONS.forEach((opt, idx) => {
|
|
932
|
-
const isSelected = idx === customizerIndex;
|
|
933
|
-
const prefix = isSelected ?
|
|
1166
|
+
state.CUSTOMIZER_OPTIONS.forEach((opt, idx) => {
|
|
1167
|
+
const isSelected = idx === state.customizerIndex;
|
|
1168
|
+
const prefix = isSelected ? chalk5.yellow("\u25B6 ") : " ";
|
|
934
1169
|
if (opt === "Theme") {
|
|
935
|
-
lines.push(`${prefix}${opt}: ${
|
|
1170
|
+
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.currentTheme)} [${themesList.join(", ")}]`);
|
|
1171
|
+
} else if (opt === "Graph Mode") {
|
|
1172
|
+
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.graphMode)} [spark, bar]`);
|
|
1173
|
+
} else if (opt === "Toggle Tree View") {
|
|
1174
|
+
const status = state.treeView ? chalk5.green("[VISIBLE]") : chalk5.red("[HIDDEN]");
|
|
1175
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
936
1176
|
} else {
|
|
937
1177
|
const key = opt.replace("Toggle ", "").toLowerCase();
|
|
938
|
-
const isVisible = visiblePanels[key] !== false;
|
|
939
|
-
const status = isVisible ?
|
|
1178
|
+
const isVisible = state.visiblePanels[key] !== false;
|
|
1179
|
+
const status = isVisible ? chalk5.green("[VISIBLE]") : chalk5.red("[HIDDEN]");
|
|
940
1180
|
lines.push(`${prefix}${opt}: ${status}`);
|
|
941
1181
|
}
|
|
942
1182
|
});
|
|
943
1183
|
return lines.map((l) => ` ${l}`).join("\n");
|
|
944
1184
|
}
|
|
945
1185
|
function render() {
|
|
946
|
-
if (!lastData) return;
|
|
1186
|
+
if (!state.lastData) return;
|
|
947
1187
|
const lines = [];
|
|
948
1188
|
lines.push(
|
|
949
|
-
formatTable(lastData, {
|
|
950
|
-
width: termWidth,
|
|
951
|
-
sortBy: sortMode,
|
|
952
|
-
filter: processFilter || void 0,
|
|
1189
|
+
formatTable(state.lastData, {
|
|
1190
|
+
width: state.termWidth,
|
|
1191
|
+
sortBy: state.sortMode,
|
|
1192
|
+
filter: state.processFilter || void 0,
|
|
953
1193
|
processLimit: processRowBudget(),
|
|
954
|
-
theme: currentTheme,
|
|
955
|
-
visible: visiblePanels
|
|
1194
|
+
theme: state.currentTheme,
|
|
1195
|
+
visible: state.visiblePanels,
|
|
1196
|
+
treeView: state.treeView
|
|
956
1197
|
})
|
|
957
1198
|
);
|
|
958
|
-
if (showGraphs) {
|
|
1199
|
+
if (state.showGraphs) {
|
|
959
1200
|
lines.push("");
|
|
960
|
-
lines.push(formatGraphs(history, termWidth, currentTheme));
|
|
1201
|
+
lines.push(formatGraphs(state.history, state.termWidth, state.currentTheme, state.graphMode));
|
|
961
1202
|
}
|
|
962
1203
|
lines.push("");
|
|
963
1204
|
lines.push(footerLine());
|
|
964
|
-
if (inputMode === "customizer") {
|
|
1205
|
+
if (state.inputMode === "customizer") {
|
|
965
1206
|
lines.push(renderCustomizerOverlay());
|
|
966
|
-
} else if (inputMode === "filter") {
|
|
967
|
-
lines.push(
|
|
968
|
-
} else if (inputMode === "kill") {
|
|
969
|
-
lines.push(
|
|
970
|
-
} else if (
|
|
971
|
-
|
|
1207
|
+
} else if (state.inputMode === "filter") {
|
|
1208
|
+
lines.push(chalk5.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
1209
|
+
} else if (state.inputMode === "kill") {
|
|
1210
|
+
lines.push(chalk5.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
|
|
1211
|
+
} else if (state.inputMode === "signal") {
|
|
1212
|
+
const sigIdx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
1213
|
+
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ? chalk5.yellow(s) : chalk5.dim(s)).join(" ");
|
|
1214
|
+
lines.push(chalk5.cyan(` Signal: ${state.inputBuffer}_`));
|
|
1215
|
+
lines.push(chalk5.dim(` ${sigList}`));
|
|
1216
|
+
lines.push(chalk5.dim(" \u2191/\u2193 to select, Enter to confirm, Esc to cancel"));
|
|
1217
|
+
} else if (state.statusMessage) {
|
|
1218
|
+
lines.push(` ${state.statusMessage}`);
|
|
972
1219
|
}
|
|
973
1220
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
974
1221
|
process.stdout.write(lines.join("\n"));
|
|
@@ -977,137 +1224,164 @@ function footerLine() {
|
|
|
977
1224
|
const keys = [
|
|
978
1225
|
["q", "quit"],
|
|
979
1226
|
["c", "customize UI"],
|
|
980
|
-
["p", paused ? "resume" : "pause"],
|
|
981
|
-
["g", showGraphs ? "hide graphs" : "show graphs"],
|
|
982
|
-
["
|
|
983
|
-
["
|
|
1227
|
+
["p", state.paused ? "resume" : "pause"],
|
|
1228
|
+
["g", state.showGraphs ? "hide graphs" : "show graphs"],
|
|
1229
|
+
["b", `graph:${state.graphMode}`],
|
|
1230
|
+
["d", state.detailed ? "basic" : "detailed"],
|
|
1231
|
+
["s", `sort:${state.sortMode}`],
|
|
984
1232
|
["/", "filter"],
|
|
985
1233
|
["k", "kill"],
|
|
1234
|
+
["S", "signal"],
|
|
1235
|
+
["t", state.treeView ? "flat" : "tree"],
|
|
986
1236
|
["e", "export"],
|
|
987
|
-
["l", logging ? "stop log" : "log"],
|
|
988
|
-
["f", exportFormat],
|
|
989
|
-
["+/-", `${interval}s`]
|
|
1237
|
+
["l", state.logging ? "stop log" : "log"],
|
|
1238
|
+
["f", state.exportFormat],
|
|
1239
|
+
["+/-", `${state.interval}s`]
|
|
990
1240
|
];
|
|
991
|
-
const keyStr = keys.map(([k, label]) => `${
|
|
1241
|
+
const keyStr = keys.map(([k, label]) => `${chalk5.hex("#50fa7b").bold(k)} ${chalk5.dim(label)}`).join(chalk5.dim(" | "));
|
|
992
1242
|
const badges = [];
|
|
993
|
-
if (paused) badges.push(
|
|
994
|
-
if (logging) badges.push(
|
|
1243
|
+
if (state.paused) badges.push(chalk5.yellow.bold("\u23F8 PAUSED"));
|
|
1244
|
+
if (state.logging) badges.push(chalk5.red.bold("\u25CF REC"));
|
|
995
1245
|
const badgeStr = badges.join(" ");
|
|
996
1246
|
return badgeStr ? `${keyStr} ${badgeStr}` : keyStr;
|
|
997
1247
|
}
|
|
998
1248
|
function checkAlerts(data) {
|
|
999
1249
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
1000
|
-
const hot = data.cpu.usage >= CPU_ALERT_PCT || temp !== null && temp >= TEMP_ALERT_C;
|
|
1001
|
-
if (hot && !alerted) {
|
|
1002
|
-
alerted = true;
|
|
1250
|
+
const hot = data.cpu.usage >= state.CPU_ALERT_PCT || temp !== null && temp >= state.TEMP_ALERT_C;
|
|
1251
|
+
if (hot && !state.alerted) {
|
|
1252
|
+
state.alerted = true;
|
|
1003
1253
|
process.stdout.write("\x07");
|
|
1004
|
-
const reason = data.cpu.usage >= CPU_ALERT_PCT ? `CPU at ${data.cpu.usage}%` : `Temp at ${temp}\xB0C`;
|
|
1005
|
-
setStatus(
|
|
1254
|
+
const reason = data.cpu.usage >= state.CPU_ALERT_PCT ? `CPU at ${data.cpu.usage}%` : `Temp at ${temp}\xB0C`;
|
|
1255
|
+
setStatus(chalk5.red(`\u26A0 Alert: ${reason}`), 5e3);
|
|
1006
1256
|
} else if (!hot) {
|
|
1007
|
-
alerted = false;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
async function tick() {
|
|
1011
|
-
if (paused) return;
|
|
1012
|
-
try {
|
|
1013
|
-
const data = await collectAll({ detailed, processLimit: processRowBudget() });
|
|
1014
|
-
lastData = data;
|
|
1015
|
-
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
1016
|
-
history.push({
|
|
1017
|
-
cpuUsage: data.cpu.usage,
|
|
1018
|
-
memUsage: data.memory.usagePercent,
|
|
1019
|
-
temp,
|
|
1020
|
-
rxBytes: data.network.rxBytes,
|
|
1021
|
-
txBytes: data.network.txBytes
|
|
1022
|
-
});
|
|
1023
|
-
writeLogRow(data);
|
|
1024
|
-
checkAlerts(data);
|
|
1025
|
-
render();
|
|
1026
|
-
} catch {
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
function killProcess(pidStr) {
|
|
1030
|
-
const pid = parseInt(pidStr, 10);
|
|
1031
|
-
if (!pid || pid <= 0) {
|
|
1032
|
-
setStatus(`Invalid PID: ${pidStr}`);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
try {
|
|
1036
|
-
process.kill(pid, "SIGTERM");
|
|
1037
|
-
setStatus(`Sent SIGTERM to PID ${pid}`);
|
|
1038
|
-
} catch (err) {
|
|
1039
|
-
setStatus(`Failed to kill ${pid}: ${err.message}`);
|
|
1257
|
+
state.alerted = false;
|
|
1040
1258
|
}
|
|
1041
1259
|
}
|
|
1260
|
+
|
|
1261
|
+
// src/live/input.ts
|
|
1042
1262
|
function handleInputModeKey(str, key) {
|
|
1043
1263
|
if (key.name === "escape") {
|
|
1044
|
-
inputMode = null;
|
|
1045
|
-
inputBuffer = "";
|
|
1264
|
+
state.inputMode = null;
|
|
1265
|
+
state.inputBuffer = "";
|
|
1046
1266
|
render();
|
|
1047
1267
|
return;
|
|
1048
1268
|
}
|
|
1049
|
-
if (inputMode === "customizer") {
|
|
1269
|
+
if (state.inputMode === "customizer") {
|
|
1050
1270
|
const themesList = Object.keys(THEMES);
|
|
1051
1271
|
if (key.name === "up" || str === "k") {
|
|
1052
|
-
customizerIndex = (customizerIndex - 1 + CUSTOMIZER_OPTIONS.length) % CUSTOMIZER_OPTIONS.length;
|
|
1272
|
+
state.customizerIndex = (state.customizerIndex - 1 + state.CUSTOMIZER_OPTIONS.length) % state.CUSTOMIZER_OPTIONS.length;
|
|
1053
1273
|
} else if (key.name === "down" || str === "j") {
|
|
1054
|
-
customizerIndex = (customizerIndex + 1) % CUSTOMIZER_OPTIONS.length;
|
|
1274
|
+
state.customizerIndex = (state.customizerIndex + 1) % state.CUSTOMIZER_OPTIONS.length;
|
|
1055
1275
|
} else if (key.name === "return" || str === " ") {
|
|
1056
|
-
const selected = CUSTOMIZER_OPTIONS[customizerIndex];
|
|
1276
|
+
const selected = state.CUSTOMIZER_OPTIONS[state.customizerIndex];
|
|
1057
1277
|
if (selected === "Theme") {
|
|
1058
|
-
const nextIdx = (themesList.indexOf(currentTheme) + 1) % themesList.length;
|
|
1059
|
-
currentTheme = themesList[nextIdx];
|
|
1278
|
+
const nextIdx = (themesList.indexOf(state.currentTheme) + 1) % themesList.length;
|
|
1279
|
+
state.currentTheme = themesList[nextIdx];
|
|
1280
|
+
} else if (selected === "Toggle Tree View") {
|
|
1281
|
+
state.treeView = !state.treeView;
|
|
1282
|
+
} else if (selected === "Graph Mode") {
|
|
1283
|
+
state.graphMode = state.graphMode === "spark" ? "bar" : "spark";
|
|
1060
1284
|
} else {
|
|
1061
1285
|
const itemKey = selected.replace("Toggle ", "").toLowerCase();
|
|
1062
|
-
visiblePanels[itemKey] = !visiblePanels[itemKey];
|
|
1286
|
+
state.visiblePanels[itemKey] = !state.visiblePanels[itemKey];
|
|
1063
1287
|
}
|
|
1064
1288
|
}
|
|
1065
1289
|
render();
|
|
1066
1290
|
return;
|
|
1067
1291
|
}
|
|
1292
|
+
if (state.inputMode === "signal") {
|
|
1293
|
+
if (key.name === "up" || str === "k") {
|
|
1294
|
+
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
1295
|
+
state.inputBuffer = state.SIGNAL_OPTIONS[(idx - 1 + state.SIGNAL_OPTIONS.length) % state.SIGNAL_OPTIONS.length];
|
|
1296
|
+
} else if (key.name === "down" || str === "j") {
|
|
1297
|
+
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
1298
|
+
state.inputBuffer = state.SIGNAL_OPTIONS[(idx + 1) % state.SIGNAL_OPTIONS.length];
|
|
1299
|
+
} else if (key.name === "return" || str === " ") {
|
|
1300
|
+
killProcess(state.inputBuffer.trim(), state.inputBuffer.trim());
|
|
1301
|
+
state.inputMode = null;
|
|
1302
|
+
state.inputBuffer = "";
|
|
1303
|
+
render();
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
render();
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1068
1309
|
if (key.name === "return") {
|
|
1069
|
-
if (inputMode === "filter") {
|
|
1070
|
-
processFilter = inputBuffer.trim();
|
|
1071
|
-
setStatus(processFilter ? `Filtering: "${processFilter}"` : "Filter cleared");
|
|
1072
|
-
} else if (inputMode === "kill") {
|
|
1073
|
-
killProcess(inputBuffer.trim());
|
|
1310
|
+
if (state.inputMode === "filter") {
|
|
1311
|
+
state.processFilter = state.inputBuffer.trim();
|
|
1312
|
+
setStatus(state.processFilter ? `Filtering: "${state.processFilter}"` : "Filter cleared");
|
|
1313
|
+
} else if (state.inputMode === "kill") {
|
|
1314
|
+
killProcess(state.inputBuffer.trim());
|
|
1074
1315
|
}
|
|
1075
|
-
inputMode = null;
|
|
1076
|
-
inputBuffer = "";
|
|
1316
|
+
state.inputMode = null;
|
|
1317
|
+
state.inputBuffer = "";
|
|
1077
1318
|
render();
|
|
1078
1319
|
return;
|
|
1079
1320
|
}
|
|
1080
1321
|
if (key.name === "backspace") {
|
|
1081
|
-
inputBuffer = inputBuffer.slice(0, -1);
|
|
1322
|
+
state.inputBuffer = state.inputBuffer.slice(0, -1);
|
|
1082
1323
|
render();
|
|
1083
1324
|
return;
|
|
1084
1325
|
}
|
|
1085
1326
|
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
1086
|
-
inputBuffer += str;
|
|
1327
|
+
state.inputBuffer += str;
|
|
1087
1328
|
render();
|
|
1088
1329
|
}
|
|
1089
1330
|
}
|
|
1331
|
+
function killProcess(pidStr, signal = "SIGTERM") {
|
|
1332
|
+
const pid = parseInt(pidStr, 10);
|
|
1333
|
+
if (!pid || pid <= 0) {
|
|
1334
|
+
setStatus(`Invalid PID: ${pidStr}`);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
try {
|
|
1338
|
+
process.kill(pid, signal);
|
|
1339
|
+
setStatus(`Sent ${signal} to PID ${pid}`);
|
|
1340
|
+
} catch (err) {
|
|
1341
|
+
setStatus(`Failed to send ${signal} to ${pid}: ${err.message}`);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1090
1344
|
function onResize() {
|
|
1091
|
-
termWidth = process.stdout.columns || 80;
|
|
1092
|
-
termHeight = process.stdout.rows || 24;
|
|
1093
|
-
history.setMaxLen(Math.max(20, Math.min(200, termWidth - 30)));
|
|
1345
|
+
state.termWidth = process.stdout.columns || 80;
|
|
1346
|
+
state.termHeight = process.stdout.rows || 24;
|
|
1347
|
+
state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
|
|
1094
1348
|
render();
|
|
1095
1349
|
}
|
|
1096
1350
|
function restartTicker() {
|
|
1097
|
-
if (intervalHandle) clearInterval(intervalHandle);
|
|
1098
|
-
intervalHandle = setInterval(tick, interval * 1e3);
|
|
1351
|
+
if (state.intervalHandle) clearInterval(state.intervalHandle);
|
|
1352
|
+
state.intervalHandle = setInterval(tick, state.interval * 1e3);
|
|
1353
|
+
}
|
|
1354
|
+
async function tick() {
|
|
1355
|
+
if (state.paused) return;
|
|
1356
|
+
try {
|
|
1357
|
+
const data = await collectAll({ detailed: state.detailed, processLimit: processRowBudget() });
|
|
1358
|
+
state.lastData = data;
|
|
1359
|
+
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
1360
|
+
state.history.push({
|
|
1361
|
+
cpuUsage: data.cpu.usage,
|
|
1362
|
+
memUsage: data.memory.usagePercent,
|
|
1363
|
+
temp,
|
|
1364
|
+
rxBytes: data.network.rxBytes,
|
|
1365
|
+
txBytes: data.network.txBytes,
|
|
1366
|
+
gpuUtil: data.gpu?.utilization
|
|
1367
|
+
});
|
|
1368
|
+
writeLogRow(data);
|
|
1369
|
+
checkAlerts(data);
|
|
1370
|
+
render();
|
|
1371
|
+
} catch {
|
|
1372
|
+
}
|
|
1099
1373
|
}
|
|
1100
1374
|
async function startLive(opts2) {
|
|
1101
|
-
if (running) return;
|
|
1102
|
-
running = true;
|
|
1103
|
-
paused = false;
|
|
1104
|
-
detailed = !!opts2.detailed;
|
|
1105
|
-
interval = opts2.interval;
|
|
1106
|
-
if (opts2.exportDir) exportDir = opts2.exportDir;
|
|
1107
|
-
termWidth = process.stdout.columns || 80;
|
|
1108
|
-
termHeight = process.stdout.rows || 24;
|
|
1109
|
-
history.reset();
|
|
1110
|
-
history.setMaxLen(Math.max(20, Math.min(200, termWidth - 30)));
|
|
1375
|
+
if (state.running) return;
|
|
1376
|
+
state.running = true;
|
|
1377
|
+
state.paused = false;
|
|
1378
|
+
state.detailed = !!opts2.detailed;
|
|
1379
|
+
state.interval = opts2.interval;
|
|
1380
|
+
if (opts2.exportDir) state.exportDir = opts2.exportDir;
|
|
1381
|
+
state.termWidth = process.stdout.columns || 80;
|
|
1382
|
+
state.termHeight = process.stdout.rows || 24;
|
|
1383
|
+
state.history.reset();
|
|
1384
|
+
state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
|
|
1111
1385
|
process.stdout.write("\x1B[?1049h");
|
|
1112
1386
|
process.stdout.write("\x1B[?25l");
|
|
1113
1387
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
@@ -1119,30 +1393,30 @@ async function startLive(opts2) {
|
|
|
1119
1393
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
1120
1394
|
process.stdin.resume();
|
|
1121
1395
|
process.stdout.on("resize", onResize);
|
|
1122
|
-
keypressHandler = (str, key) => {
|
|
1396
|
+
state.keypressHandler = (str, key) => {
|
|
1123
1397
|
if (!key) return;
|
|
1124
1398
|
if (key.ctrl && key.name === "c") {
|
|
1125
1399
|
stopLive();
|
|
1126
1400
|
return;
|
|
1127
1401
|
}
|
|
1128
|
-
if (inputMode) {
|
|
1402
|
+
if (state.inputMode) {
|
|
1129
1403
|
handleInputModeKey(str, key);
|
|
1130
1404
|
return;
|
|
1131
1405
|
}
|
|
1132
1406
|
switch (key.sequence) {
|
|
1133
1407
|
case "+":
|
|
1134
|
-
interval += 1;
|
|
1408
|
+
state.interval += 1;
|
|
1135
1409
|
restartTicker();
|
|
1136
|
-
setStatus(`Interval set to ${interval}s`);
|
|
1410
|
+
setStatus(`Interval set to ${state.interval}s`);
|
|
1137
1411
|
return;
|
|
1138
1412
|
case "-":
|
|
1139
|
-
interval = Math.max(1, interval - 1);
|
|
1413
|
+
state.interval = Math.max(1, state.interval - 1);
|
|
1140
1414
|
restartTicker();
|
|
1141
|
-
setStatus(`Interval set to ${interval}s`);
|
|
1415
|
+
setStatus(`Interval set to ${state.interval}s`);
|
|
1142
1416
|
return;
|
|
1143
1417
|
case "/":
|
|
1144
|
-
inputMode = "filter";
|
|
1145
|
-
inputBuffer = processFilter;
|
|
1418
|
+
state.inputMode = "filter";
|
|
1419
|
+
state.inputBuffer = state.processFilter;
|
|
1146
1420
|
render();
|
|
1147
1421
|
return;
|
|
1148
1422
|
}
|
|
@@ -1151,31 +1425,48 @@ async function startLive(opts2) {
|
|
|
1151
1425
|
stopLive();
|
|
1152
1426
|
break;
|
|
1153
1427
|
case "c":
|
|
1154
|
-
inputMode = "customizer";
|
|
1155
|
-
customizerIndex = 0;
|
|
1428
|
+
state.inputMode = "customizer";
|
|
1429
|
+
state.customizerIndex = 0;
|
|
1156
1430
|
render();
|
|
1157
1431
|
break;
|
|
1158
1432
|
case "p":
|
|
1159
|
-
paused = !paused;
|
|
1160
|
-
setStatus(paused ? "Paused" : "Resumed");
|
|
1433
|
+
state.paused = !state.paused;
|
|
1434
|
+
setStatus(state.paused ? "Paused" : "Resumed");
|
|
1161
1435
|
render();
|
|
1162
1436
|
break;
|
|
1163
1437
|
case "g":
|
|
1164
|
-
showGraphs = !showGraphs;
|
|
1438
|
+
state.showGraphs = !state.showGraphs;
|
|
1439
|
+
render();
|
|
1440
|
+
break;
|
|
1441
|
+
case "b":
|
|
1442
|
+
state.graphMode = state.graphMode === "spark" ? "bar" : "spark";
|
|
1443
|
+
setStatus(`Graph mode: ${state.graphMode}`);
|
|
1165
1444
|
render();
|
|
1166
1445
|
break;
|
|
1167
1446
|
case "d":
|
|
1168
|
-
detailed = !detailed;
|
|
1169
|
-
setStatus(`Detailed sensor mode: ${detailed ? "on" : "off"}`);
|
|
1447
|
+
state.detailed = !state.detailed;
|
|
1448
|
+
setStatus(`Detailed sensor mode: ${state.detailed ? "on" : "off"}`);
|
|
1170
1449
|
break;
|
|
1171
1450
|
case "s":
|
|
1172
|
-
|
|
1173
|
-
|
|
1451
|
+
const sortCycle = ["cpu", "mem", "pid", "user", "command", "state", "threads", "runtime"];
|
|
1452
|
+
const curIdx = sortCycle.indexOf(state.sortMode);
|
|
1453
|
+
state.sortMode = sortCycle[(curIdx + 1) % sortCycle.length];
|
|
1454
|
+
setStatus(`Sorting by ${state.sortMode}`);
|
|
1174
1455
|
render();
|
|
1175
1456
|
break;
|
|
1176
1457
|
case "k":
|
|
1177
|
-
inputMode = "kill";
|
|
1178
|
-
inputBuffer = "";
|
|
1458
|
+
state.inputMode = "kill";
|
|
1459
|
+
state.inputBuffer = "";
|
|
1460
|
+
render();
|
|
1461
|
+
break;
|
|
1462
|
+
case "S":
|
|
1463
|
+
state.inputMode = "signal";
|
|
1464
|
+
state.inputBuffer = "SIGTERM";
|
|
1465
|
+
render();
|
|
1466
|
+
break;
|
|
1467
|
+
case "t":
|
|
1468
|
+
state.treeView = !state.treeView;
|
|
1469
|
+
setStatus(state.treeView ? "Tree view enabled" : "Flat view enabled");
|
|
1179
1470
|
render();
|
|
1180
1471
|
break;
|
|
1181
1472
|
case "e":
|
|
@@ -1187,35 +1478,35 @@ async function startLive(opts2) {
|
|
|
1187
1478
|
render();
|
|
1188
1479
|
break;
|
|
1189
1480
|
case "f":
|
|
1190
|
-
exportFormat = exportFormat === "json" ? "csv" : exportFormat === "csv" ? "tsv" : "json";
|
|
1191
|
-
setStatus(`Export format: ${exportFormat}`);
|
|
1481
|
+
state.exportFormat = state.exportFormat === "json" ? "csv" : state.exportFormat === "csv" ? "tsv" : "json";
|
|
1482
|
+
setStatus(`Export format: ${state.exportFormat}`);
|
|
1192
1483
|
render();
|
|
1193
1484
|
break;
|
|
1194
1485
|
}
|
|
1195
1486
|
};
|
|
1196
|
-
process.stdin.on("keypress", keypressHandler);
|
|
1487
|
+
process.stdin.on("keypress", state.keypressHandler);
|
|
1197
1488
|
process.once("SIGINT", () => stopLive());
|
|
1198
1489
|
}
|
|
1199
1490
|
function stopLive() {
|
|
1200
|
-
if (intervalHandle) {
|
|
1201
|
-
clearInterval(intervalHandle);
|
|
1202
|
-
intervalHandle = null;
|
|
1491
|
+
if (state.intervalHandle) {
|
|
1492
|
+
clearInterval(state.intervalHandle);
|
|
1493
|
+
state.intervalHandle = null;
|
|
1203
1494
|
}
|
|
1204
|
-
if (statusTimer) {
|
|
1205
|
-
clearTimeout(statusTimer);
|
|
1206
|
-
statusTimer = null;
|
|
1495
|
+
if (state.statusTimer) {
|
|
1496
|
+
clearTimeout(state.statusTimer);
|
|
1497
|
+
state.statusTimer = null;
|
|
1207
1498
|
}
|
|
1208
|
-
if (logStream) {
|
|
1209
|
-
logStream.end();
|
|
1210
|
-
logStream = null;
|
|
1499
|
+
if (state.logStream) {
|
|
1500
|
+
state.logStream.end();
|
|
1501
|
+
state.logStream = null;
|
|
1211
1502
|
}
|
|
1212
|
-
if (keypressHandler) {
|
|
1213
|
-
process.stdin.removeListener("keypress", keypressHandler);
|
|
1214
|
-
keypressHandler = null;
|
|
1503
|
+
if (state.keypressHandler) {
|
|
1504
|
+
process.stdin.removeListener("keypress", state.keypressHandler);
|
|
1505
|
+
state.keypressHandler = null;
|
|
1215
1506
|
}
|
|
1216
1507
|
process.stdout.removeListener("resize", onResize);
|
|
1217
|
-
running = false;
|
|
1218
|
-
logging = false;
|
|
1508
|
+
state.running = false;
|
|
1509
|
+
state.logging = false;
|
|
1219
1510
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1220
1511
|
process.stdin.pause();
|
|
1221
1512
|
process.stdout.write("\x1B[?25h");
|
|
@@ -1224,9 +1515,120 @@ function stopLive() {
|
|
|
1224
1515
|
process.exit(0);
|
|
1225
1516
|
}
|
|
1226
1517
|
|
|
1518
|
+
// src/splash.ts
|
|
1519
|
+
import chalk6 from "chalk";
|
|
1520
|
+
var MAXV = 36;
|
|
1521
|
+
var CHAR_RAMP = [" ", ".", "`", ",", ":", ";", "+", "*", "?", "%", "#", "@"];
|
|
1522
|
+
var palette = [
|
|
1523
|
+
"#0a0505",
|
|
1524
|
+
"#1a0806",
|
|
1525
|
+
"#2a0b06",
|
|
1526
|
+
"#3f1004",
|
|
1527
|
+
"#591404",
|
|
1528
|
+
"#791804",
|
|
1529
|
+
"#9e1c04",
|
|
1530
|
+
"#c92004",
|
|
1531
|
+
"#d63004",
|
|
1532
|
+
"#e34004",
|
|
1533
|
+
"#f05004",
|
|
1534
|
+
"#fd6004",
|
|
1535
|
+
"#ff7004",
|
|
1536
|
+
"#ff8004",
|
|
1537
|
+
"#ff9004",
|
|
1538
|
+
"#ffa004",
|
|
1539
|
+
"#ffb004",
|
|
1540
|
+
"#ffc004",
|
|
1541
|
+
"#ffd004",
|
|
1542
|
+
"#ffe004",
|
|
1543
|
+
"#fff004",
|
|
1544
|
+
"#fff820",
|
|
1545
|
+
"#ffff40",
|
|
1546
|
+
"#ffff60",
|
|
1547
|
+
"#ffff80",
|
|
1548
|
+
"#ffffa0",
|
|
1549
|
+
"#ffffc0",
|
|
1550
|
+
"#ffffe0",
|
|
1551
|
+
"#ffffff",
|
|
1552
|
+
"#ffffff",
|
|
1553
|
+
"#ffffff",
|
|
1554
|
+
"#ffffff",
|
|
1555
|
+
"#ffffff",
|
|
1556
|
+
"#ffffff",
|
|
1557
|
+
"#ffffff",
|
|
1558
|
+
"#ffffff"
|
|
1559
|
+
];
|
|
1560
|
+
function getRgb(hex) {
|
|
1561
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
1562
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
1563
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
1564
|
+
return { r, g, b };
|
|
1565
|
+
}
|
|
1566
|
+
var rgbPalette = palette.map(getRgb);
|
|
1567
|
+
function getChar(val) {
|
|
1568
|
+
const idx = Math.min(CHAR_RAMP.length - 1, Math.floor(val / (MAXV + 1) * CHAR_RAMP.length));
|
|
1569
|
+
return CHAR_RAMP[idx];
|
|
1570
|
+
}
|
|
1571
|
+
async function showSplash() {
|
|
1572
|
+
const cols = 60;
|
|
1573
|
+
const rows = 15;
|
|
1574
|
+
let fire = new Uint8Array(cols * rows);
|
|
1575
|
+
process.stdout.write("\x1B[2J");
|
|
1576
|
+
const termRows = process.stdout.rows || 24;
|
|
1577
|
+
const termCols = process.stdout.columns || 80;
|
|
1578
|
+
const startRow = Math.max(0, Math.floor((termRows - rows) / 2));
|
|
1579
|
+
const startCol = Math.max(0, Math.floor((termCols - cols) / 2));
|
|
1580
|
+
return new Promise((resolve) => {
|
|
1581
|
+
const interval = setInterval(() => {
|
|
1582
|
+
for (let x = 0; x < cols; x++) {
|
|
1583
|
+
fire[(rows - 1) * cols + x] = MAXV - Math.floor(Math.random() * 4);
|
|
1584
|
+
}
|
|
1585
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
1586
|
+
for (let x = 0; x < cols; x++) {
|
|
1587
|
+
const src = y * cols + x;
|
|
1588
|
+
const val = fire[src];
|
|
1589
|
+
if (val === 0) {
|
|
1590
|
+
fire[(y - 1) * cols + x] = 0;
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
const decay = Math.floor(Math.random() * 3);
|
|
1594
|
+
const drift = Math.floor(Math.random() * 3) - 1;
|
|
1595
|
+
let nx = x + drift;
|
|
1596
|
+
if (nx < 0) nx = 0;
|
|
1597
|
+
else if (nx >= cols) nx = cols - 1;
|
|
1598
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
let output = "";
|
|
1602
|
+
for (let i = 0; i < startRow; i++) output += "\n";
|
|
1603
|
+
for (let y = 0; y < rows; y++) {
|
|
1604
|
+
output += " ".repeat(startCol);
|
|
1605
|
+
for (let x = 0; x < cols; x++) {
|
|
1606
|
+
const v = fire[y * cols + x];
|
|
1607
|
+
const color = rgbPalette[Math.min(v, rgbPalette.length - 1)];
|
|
1608
|
+
output += chalk6.rgb(color.r, color.g, color.b)(getChar(v));
|
|
1609
|
+
}
|
|
1610
|
+
output += "\n";
|
|
1611
|
+
}
|
|
1612
|
+
output += " ".repeat(Math.max(0, startCol + (cols - 45) / 2)) + chalk6.bold.white("\u{1F525} pyre \u2014 system monitor (warming up sensors...)\n");
|
|
1613
|
+
output += " ".repeat(Math.max(0, startCol + (cols - 25) / 2)) + chalk6.dim("[Press Enter to continue]");
|
|
1614
|
+
process.stdout.write(`\x1B[H${output}`);
|
|
1615
|
+
}, 65);
|
|
1616
|
+
const onEnter = () => {
|
|
1617
|
+
process.stdin.removeListener("data", onEnter);
|
|
1618
|
+
clearInterval(interval);
|
|
1619
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
1620
|
+
resolve();
|
|
1621
|
+
};
|
|
1622
|
+
process.stdin.setRawMode(true);
|
|
1623
|
+
process.stdin.resume();
|
|
1624
|
+
process.stdin.on("data", onEnter);
|
|
1625
|
+
setTimeout(onEnter, 5e3);
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1227
1629
|
// src/index.ts
|
|
1228
1630
|
var program = new Command();
|
|
1229
|
-
program.name("pyre").version("
|
|
1631
|
+
program.name("pyre").version("2.0.0").description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export").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");
|
|
1230
1632
|
program.parse(process.argv);
|
|
1231
1633
|
var opts = program.opts();
|
|
1232
1634
|
function isExportMode() {
|
|
@@ -1238,9 +1640,10 @@ function stripAnsi(s) {
|
|
|
1238
1640
|
async function main() {
|
|
1239
1641
|
const requestedLive = program.args[0] === "live" || !isExportMode() && !opts.once;
|
|
1240
1642
|
if (requestedLive) {
|
|
1241
|
-
|
|
1643
|
+
await showSplash();
|
|
1644
|
+
const interval = parseFloat(opts.interval) || 2;
|
|
1242
1645
|
await startLive({
|
|
1243
|
-
interval
|
|
1646
|
+
interval,
|
|
1244
1647
|
detailed: opts.detailed,
|
|
1245
1648
|
exportDir: opts.exportDir,
|
|
1246
1649
|
autoLog: opts.log
|
|
@@ -1252,16 +1655,16 @@ async function main() {
|
|
|
1252
1655
|
if (opts.json) output = formatJson(data);
|
|
1253
1656
|
else if (opts.csv) output = formatCsv(data);
|
|
1254
1657
|
else if (opts.tsv) output = formatTsv(data);
|
|
1255
|
-
else output = formatTable(data, { width: process.stdout.columns || 80 });
|
|
1658
|
+
else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree });
|
|
1256
1659
|
console.log(output);
|
|
1257
1660
|
if (opts.out) {
|
|
1258
1661
|
fs2.writeFileSync(opts.out, stripAnsi(output));
|
|
1259
|
-
console.log(
|
|
1662
|
+
console.log(chalk7.dim(`
|
|
1260
1663
|
Saved to ${opts.out}`));
|
|
1261
1664
|
}
|
|
1262
1665
|
}
|
|
1263
1666
|
main().catch((err) => {
|
|
1264
|
-
console.error(
|
|
1667
|
+
console.error(chalk7.red(`Error: ${err.message}`));
|
|
1265
1668
|
stopLive();
|
|
1266
1669
|
process.exit(1);
|
|
1267
1670
|
});
|