pyre-cli 9.5.0 → 9.5.2
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 +15 -2
- package/dist/blender-7UT5J7IP.js +7 -0
- package/dist/{checkCmd-ULNENOPZ.js → checkCmd-T7O4KXXA.js} +4 -3
- package/dist/chunk-25I533OF.js +133 -0
- package/dist/{chunk-MOAEGMFP.js → chunk-3TE7MJLS.js} +168 -36
- package/dist/{chunk-RVGKEJXT.js → chunk-CXL4GYGQ.js} +3 -0
- package/dist/{chunk-PV4MXTAJ.js → chunk-N4WJVF5M.js} +289 -23
- package/dist/{collectors-VYUH3UWY.js → collectors-JB4OGUEF.js} +4 -1
- package/dist/{config-4ZLF4HTS.js → config-KS5ZZ3VN.js} +1 -1
- package/dist/{doctor-L6FZYA7F.js → doctor-UV7TWIHX.js} +1 -1
- package/dist/index.js +419 -156
- package/dist/{pipeCmd-BWNMUV2X.js → pipeCmd-TYLJHQ5B.js} +3 -2
- package/dist/{wizard-GVIAHIVV.js → wizard-Z3RGA3VB.js} +1 -1
- package/package.json +1 -1
- package/src/anomalies.ts +114 -60
- package/src/formatters/index.ts +1 -1
- package/src/formatters/output.ts +100 -13
- package/src/formatters/render.ts +240 -28
- package/src/formatters/themes.ts +8 -0
- package/src/formatters/types.ts +4 -1
- package/src/index.ts +53 -26
- package/src/live/input.ts +62 -4
- package/src/live/render.ts +31 -0
- package/src/live/state.ts +65 -52
- package/src/live/types.ts +2 -2
- package/src/monitors/blender.ts +144 -0
- package/src/monitors/collectors.ts +287 -40
- package/src/monitors/dns_cache.ts +48 -0
- package/src/monitors/index.ts +3 -2
- package/src/monitors/smc.ts +27 -0
- package/src/monitors/types.ts +97 -14
- package/src/state/config.ts +6 -0
- /package/dist/{chunk-SG26Y3WF.js → chunk-IM3IQMMT.js} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pyre
|
|
2
2
|
|
|
3
|
-
Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, live dashboard, packet monitor, process management, export, alerts, P2P live data streaming, web dashboard, SSH monitoring,
|
|
3
|
+
Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, live dashboard, packet monitor, process management, export, alerts, P2P live data streaming, web dashboard, SSH monitoring, micro-benchmarking, and Blender render tracking.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|

|
|
@@ -21,6 +21,7 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, l
|
|
|
21
21
|
- **Disk I/O throughput & space** — read/write speed per volume and mounted volume space usage
|
|
22
22
|
- **Battery & power health** — level, power source, condition, charge cycles, max capacity, health trends, estimated time to empty, discharge rate, power draw in watts
|
|
23
23
|
- **Packet monitor** — network packet counts, packet rates, active TCP connections, top network processes
|
|
24
|
+
- **Advanced network monitoring** — real-time bandwidth rates (RX/TX bytes/sec), protocol breakdown (TCP/UDP), connection state analysis (ESTABLISHED, LISTENING, TIME_WAIT, CLOSE_WAIT), listening port detection, top remote hosts, per-process network I/O tracking, and GlassWire-style modern TUI panel with sparkline graphs
|
|
24
25
|
- **Task list** — running tasks/applications sorted by CPU with PID, user, memory, state, and runtime
|
|
25
26
|
- **Thermal state** — CPU temperature via `pmset` and `powermetrics` (sudo for detailed readings)
|
|
26
27
|
- **Network** — RX/TX bytes and per-second rates
|
|
@@ -28,6 +29,7 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, l
|
|
|
28
29
|
- **Interactive live dashboard** — full-screen TUI with keyboard-driven controls, mouse support, configurable panel layout, and tab-based panel navigation
|
|
29
30
|
- **Snapshot export** — JSON, CSV, TSV, HTML, or Markdown output formats
|
|
30
31
|
- **Continuous CSV logging & Anomaly Digest** — automatic per-tick logging and statistical spike analysis (`pyre anomalies`)
|
|
32
|
+
- **Blender render tracking** — automatically detects running Blender background renders, tracks elapsed time, frame progress, completion percentage, render engine, and blend file; fully loggable via CSV/JSON/Markdown exports
|
|
31
33
|
- **Visual themes & custom themes** — six built-in color themes plus user-defined JSON theme support in `~/.config/pyre/themes/`
|
|
32
34
|
- **Persistent configuration & Profiles** — save themes and thresholds in `~/.config/pyre/config.json`, plus atomic profile management (`pyre profile`)
|
|
33
35
|
- **Alert system & webhooks** — configurable threshold alerts with terminal bell, desktop notifications, webhook POST payload execution (`--webhook-url`), and custom shell command execution (`--alert-cmd`)
|
|
@@ -132,6 +134,7 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
132
134
|
| `pyre doctor` | Run system diagnostics (permissions, Gatekeeper, SIP, XProtect, P2P network) |
|
|
133
135
|
| `pyre extensions` | System Extensions inspector (`systemextensionsctl list`) |
|
|
134
136
|
| `pyre brew` | Homebrew health panel (installed formulae, outdated count, Cellar disk size, doctor summary) |
|
|
137
|
+
| `pyre blender` | Track active Blender background renders: PID, blend file, engine, frame progress, elapsed time, and completion status |
|
|
135
138
|
| `pyre update` | Check npm registry for latest `pyre-cli` release |
|
|
136
139
|
| `pyre profile <save\|load\|list> [name]` | Atomic configuration profile management (`~/.config/pyre/profiles/`) |
|
|
137
140
|
| `pyre config <show\|reset>` | View or reset persistent configuration file (`~/.config/pyre/config.json`) |
|
|
@@ -314,6 +317,8 @@ Run with `pyre live`.
|
|
|
314
317
|
| `←` / `→` / `Tab` | Cycle to previous / next panel tab |
|
|
315
318
|
| `1`–`9`, `0` | Jump to panel by number (cpu, mem, gpu, power, battery, thermal, network, packets, tasks, disk) |
|
|
316
319
|
| `P` | Toggle process panel |
|
|
320
|
+
| `C` | Toggle containers panel |
|
|
321
|
+
| `B` | Toggle Blender render tracker panel |
|
|
317
322
|
| `Esc` | Close customizer or clear filter/kill input |
|
|
318
323
|
|
|
319
324
|
### UI Customizer
|
|
@@ -321,7 +326,7 @@ Run with `pyre live`.
|
|
|
321
326
|
Press `c` to open the customizer overlay. From there:
|
|
322
327
|
|
|
323
328
|
- Cycle through six built-in themes (Default, Dracula, Cyberpunk, Monochrome, Nord, Gruvbox) — `↑`/`↓` or `j`/`k` to navigate, `Enter`/`Space` to select
|
|
324
|
-
- Toggle individual panels (CPU, Memory, GPU, Power, Battery, Thermal, Network, Packets, Tasks, Disk, Processes) on or off
|
|
329
|
+
- Toggle individual panels (CPU, Memory, GPU, Power, Battery, Thermal, Network, Packets, Tasks, Disk, Processes, Containers, Blender) on or off
|
|
325
330
|
- Switch graph mode between sparkline and bar charts
|
|
326
331
|
- Toggle process tree view
|
|
327
332
|
- Toggle temperature unit between Celsius and Fahrenheit
|
|
@@ -440,6 +445,14 @@ timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_pa
|
|
|
440
445
|
- **Node.js 18+**
|
|
441
446
|
- **Optional:** `sudo` for `powermetrics` detailed temperatures in `--detailed` mode
|
|
442
447
|
|
|
448
|
+
To enable password‑less access for `powermetrics` (required for accurate sensor readings), run:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
echo "$(whoami) ALL=(root) NOPASSWD: /usr/bin/powermetrics" | sudo tee /etc/sudoers.d/pyre-powermetrics
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
This adds a single sudoers entry granting `pyre` the ability to execute `powermetrics` without a password prompt. Remove it later with `sudo rm /etc/sudoers.d/pyre-powermetrics`.
|
|
455
|
+
|
|
443
456
|
## Project Structure
|
|
444
457
|
|
|
445
458
|
```
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatBytes
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-3TE7MJLS.js";
|
|
4
|
+
import "./chunk-IM3IQMMT.js";
|
|
5
5
|
import {
|
|
6
6
|
collectAll
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-N4WJVF5M.js";
|
|
8
|
+
import "./chunk-25I533OF.js";
|
|
8
9
|
import "./chunk-FH5WDJBF.js";
|
|
9
10
|
|
|
10
11
|
// src/checkCmd.ts
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
run
|
|
3
|
+
} from "./chunk-FH5WDJBF.js";
|
|
4
|
+
|
|
5
|
+
// src/monitors/blender.ts
|
|
6
|
+
var RENDER_LOG_DIR = "/tmp/blender_render_logs";
|
|
7
|
+
function blendFileFromArgs(args) {
|
|
8
|
+
const nonFlag = args.filter((a) => !a.startsWith("-"));
|
|
9
|
+
for (const a of nonFlag) {
|
|
10
|
+
if (a.endsWith(".blend") || a.includes(".blend")) {
|
|
11
|
+
return a.split("/").pop() || a;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return "unknown.blend";
|
|
15
|
+
}
|
|
16
|
+
function engineFromArgs(args) {
|
|
17
|
+
const idx = args.indexOf("-E");
|
|
18
|
+
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
|
|
19
|
+
const flag = args.find((a) => a.startsWith("-E="));
|
|
20
|
+
if (flag) return flag.slice(3);
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
function outputPathFromArgs(args) {
|
|
24
|
+
const oIdx = args.indexOf("-o");
|
|
25
|
+
if (oIdx !== -1 && args[oIdx + 1]) {
|
|
26
|
+
let p = args[oIdx + 1];
|
|
27
|
+
if (p.endsWith("/")) p = p.slice(0, -1);
|
|
28
|
+
return p.split("/").slice(0, -1).join("/") || "/tmp";
|
|
29
|
+
}
|
|
30
|
+
const fFlag = args.find((a) => a.startsWith("-o="));
|
|
31
|
+
if (fFlag) {
|
|
32
|
+
const p = fFlag.slice(3);
|
|
33
|
+
return p.endsWith("/") ? p.slice(0, -1).split("/").slice(0, -1).join("/") || "/tmp" : p.split("/").slice(0, -1).join("/") || "/tmp";
|
|
34
|
+
}
|
|
35
|
+
return "/tmp";
|
|
36
|
+
}
|
|
37
|
+
function frameRangeFromArgs(args) {
|
|
38
|
+
const fIdx = args.indexOf("-f");
|
|
39
|
+
if (fIdx !== -1 && args[fIdx + 1]) {
|
|
40
|
+
const raw = args[fIdx + 1];
|
|
41
|
+
if (raw.includes("..")) {
|
|
42
|
+
const [s, e] = raw.split("..").map(Number);
|
|
43
|
+
if (!isNaN(s) && !isNaN(e)) return { start: s, end: e };
|
|
44
|
+
}
|
|
45
|
+
const n = parseInt(raw, 10);
|
|
46
|
+
if (!isNaN(n)) return { start: n, end: n };
|
|
47
|
+
}
|
|
48
|
+
const sFlag = args.find((a) => a.startsWith("-s="));
|
|
49
|
+
const eFlag = args.find((a) => a.startsWith("-e="));
|
|
50
|
+
const start = sFlag ? parseInt(sFlag.slice(3), 10) : 1;
|
|
51
|
+
const end = eFlag ? parseInt(eFlag.slice(3), 10) : 250;
|
|
52
|
+
return { start: isNaN(start) ? 1 : start, end: isNaN(end) ? 250 : end };
|
|
53
|
+
}
|
|
54
|
+
async function parseRenderLog(blendName) {
|
|
55
|
+
const baseName = blendName.replace(/\.blend$/i, "");
|
|
56
|
+
try {
|
|
57
|
+
const entries = (await run(`ls -t "${RENDER_LOG_DIR}" 2>/dev/null | grep "^${baseName}_" | head -5`, "")).trim().split("\n").filter(Boolean);
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const logPath = `${RENDER_LOG_DIR}/${entry}`;
|
|
60
|
+
try {
|
|
61
|
+
const content = await run(`cat "${logPath}" 2>/dev/null`, "", 500);
|
|
62
|
+
if (!content) continue;
|
|
63
|
+
const lines = content.split("\n");
|
|
64
|
+
const rendered = lines.filter((l) => /Fra:(\d+)/i.test(l)).length;
|
|
65
|
+
const totalMatch = content.match(/Fra:\s*\d+\s*\/\s*(\d+)/);
|
|
66
|
+
const totalFrames = totalMatch ? parseInt(totalMatch[1], 10) : 0;
|
|
67
|
+
const startMatch = content.match(/^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/m);
|
|
68
|
+
const startTime = startMatch ? new Date(startMatch[1]).getTime() : Date.now() - 6e4;
|
|
69
|
+
const sampleMatch = content.match(/Sample\s*(\d+)/i);
|
|
70
|
+
if (rendered > 0) {
|
|
71
|
+
return { framesRendered: rendered, totalFrames: totalFrames || rendered, startTime, samples: sampleMatch ? parseInt(sampleMatch[1], 10) : void 0 };
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
async function collectBlenderRenders() {
|
|
81
|
+
const renders = [];
|
|
82
|
+
try {
|
|
83
|
+
const psRaw = await run("ps -eo pid,pcpu,pmem,state,command 2>/dev/null | grep -iE '[b]lender'", "", 2e3);
|
|
84
|
+
if (!psRaw) return renders;
|
|
85
|
+
const lines = psRaw.split("\n");
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
const parts = line.trim().match(/\s*(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\S+)\s+(.+)/);
|
|
88
|
+
if (!parts) continue;
|
|
89
|
+
const pid = parseInt(parts[1], 10);
|
|
90
|
+
const cpuPct = parseFloat(parts[2]);
|
|
91
|
+
const command = parts[5];
|
|
92
|
+
const args = command.split(/\s+/);
|
|
93
|
+
const isRender = /-b|--background|-f\s/.test(command) || /render/i.test(command);
|
|
94
|
+
if (!isRender && !command.includes("blender")) continue;
|
|
95
|
+
const blendFile = blendFileFromArgs(args);
|
|
96
|
+
const engine = engineFromArgs(args);
|
|
97
|
+
const outputPath = outputPathFromArgs(args);
|
|
98
|
+
const { start: frameStart, end: frameEnd } = frameRangeFromArgs(args);
|
|
99
|
+
const totalFrames = Math.max(1, frameEnd - frameStart + 1);
|
|
100
|
+
const logData = await parseRenderLog(blendFile);
|
|
101
|
+
const startTime = logData?.startTime ?? Date.now() - 6e4;
|
|
102
|
+
const elapsedSec = Math.max(0, Math.round((Date.now() - startTime) / 1e3));
|
|
103
|
+
let currentFrame = logData?.framesRendered ?? 0;
|
|
104
|
+
if (currentFrame === 0 && cpuPct > 0) currentFrame = 1;
|
|
105
|
+
let status = "unknown";
|
|
106
|
+
if (cpuPct > 0 && currentFrame > 0 && currentFrame < totalFrames) status = "rendering";
|
|
107
|
+
else if (cpuPct > 0 && currentFrame >= totalFrames) status = "finalizing";
|
|
108
|
+
else if (currentFrame === 0 && cpuPct > 0) status = "loading";
|
|
109
|
+
else if (cpuPct === 0) status = "idle";
|
|
110
|
+
const completionPercent = totalFrames > 0 ? Math.min(100, Math.round(currentFrame / totalFrames * 100)) : 0;
|
|
111
|
+
renders.push({
|
|
112
|
+
pid,
|
|
113
|
+
blendFile,
|
|
114
|
+
renderEngine: engine,
|
|
115
|
+
frameStart,
|
|
116
|
+
frameEnd,
|
|
117
|
+
currentFrame,
|
|
118
|
+
totalFrames,
|
|
119
|
+
elapsedSec,
|
|
120
|
+
completionPercent,
|
|
121
|
+
status,
|
|
122
|
+
outputPath,
|
|
123
|
+
sampleCount: logData?.samples
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
return renders;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
collectBlenderRenders
|
|
133
|
+
};
|
|
@@ -106,7 +106,8 @@ var BUILTIN_THEMES = {
|
|
|
106
106
|
network: chalk.hex("#60a5fa"),
|
|
107
107
|
disk: chalk.hex("#f472b6"),
|
|
108
108
|
graphs: chalk.hex("#22d3ee"),
|
|
109
|
-
process: chalk.hex("#e5e7eb")
|
|
109
|
+
process: chalk.hex("#e5e7eb"),
|
|
110
|
+
blender: chalk.hex("#f97316")
|
|
110
111
|
},
|
|
111
112
|
dracula: {
|
|
112
113
|
border: chalk.hex("#6272a4"),
|
|
@@ -119,7 +120,8 @@ var BUILTIN_THEMES = {
|
|
|
119
120
|
network: chalk.hex("#ff79c6"),
|
|
120
121
|
disk: chalk.hex("#ff5555"),
|
|
121
122
|
graphs: chalk.hex("#8be9fd"),
|
|
122
|
-
process: chalk.hex("#f8f8f2")
|
|
123
|
+
process: chalk.hex("#f8f8f2"),
|
|
124
|
+
blender: chalk.hex("#ffb86c")
|
|
123
125
|
},
|
|
124
126
|
cyberpunk: {
|
|
125
127
|
border: chalk.hex("#ff0055"),
|
|
@@ -132,7 +134,8 @@ var BUILTIN_THEMES = {
|
|
|
132
134
|
network: chalk.hex("#00ffff"),
|
|
133
135
|
disk: chalk.hex("#ff007f"),
|
|
134
136
|
graphs: chalk.hex("#00ffcc"),
|
|
135
|
-
process: chalk.hex("#ffffff")
|
|
137
|
+
process: chalk.hex("#ffffff"),
|
|
138
|
+
blender: chalk.hex("#ff6600")
|
|
136
139
|
},
|
|
137
140
|
monochrome: {
|
|
138
141
|
border: chalk.gray,
|
|
@@ -145,7 +148,8 @@ var BUILTIN_THEMES = {
|
|
|
145
148
|
network: chalk.white.bold,
|
|
146
149
|
disk: chalk.white.bold,
|
|
147
150
|
graphs: chalk.white.bold,
|
|
148
|
-
process: chalk.white.bold
|
|
151
|
+
process: chalk.white.bold,
|
|
152
|
+
blender: chalk.white.bold
|
|
149
153
|
},
|
|
150
154
|
nord: {
|
|
151
155
|
border: chalk.hex("#4c566a"),
|
|
@@ -158,7 +162,8 @@ var BUILTIN_THEMES = {
|
|
|
158
162
|
network: chalk.hex("#81a1c1"),
|
|
159
163
|
disk: chalk.hex("#b48ead"),
|
|
160
164
|
graphs: chalk.hex("#88c0d0"),
|
|
161
|
-
process: chalk.hex("#eceff4")
|
|
165
|
+
process: chalk.hex("#eceff4"),
|
|
166
|
+
blender: chalk.hex("#d08770")
|
|
162
167
|
},
|
|
163
168
|
gruvbox: {
|
|
164
169
|
border: chalk.hex("#928374"),
|
|
@@ -171,7 +176,8 @@ var BUILTIN_THEMES = {
|
|
|
171
176
|
network: chalk.hex("#83a598"),
|
|
172
177
|
disk: chalk.hex("#d3869b"),
|
|
173
178
|
graphs: chalk.hex("#fb4934"),
|
|
174
|
-
process: chalk.hex("#ebdbb2")
|
|
179
|
+
process: chalk.hex("#ebdbb2"),
|
|
180
|
+
blender: chalk.hex("#fe8019")
|
|
175
181
|
}
|
|
176
182
|
};
|
|
177
183
|
function loadCustomThemes() {
|
|
@@ -198,7 +204,8 @@ function loadCustomThemes() {
|
|
|
198
204
|
network: buildColor(data.network),
|
|
199
205
|
disk: buildColor(data.disk),
|
|
200
206
|
graphs: buildColor(data.graphs),
|
|
201
|
-
process: buildColor(data.process)
|
|
207
|
+
process: buildColor(data.process),
|
|
208
|
+
blender: buildColor(data.blender)
|
|
202
209
|
};
|
|
203
210
|
} catch {
|
|
204
211
|
}
|
|
@@ -458,11 +465,83 @@ function thermalCard(data, contentWidth) {
|
|
|
458
465
|
}
|
|
459
466
|
function networkCard(data, contentWidth) {
|
|
460
467
|
const valW = Math.max(6, contentWidth - 8);
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
468
|
+
const lines = [];
|
|
469
|
+
lines.push(statRow("Iface", truncatePlain(`${data.network.interface} (${data.network.ip})`, valW)));
|
|
470
|
+
const rxTotal = formatBytes(data.network.rxBytes);
|
|
471
|
+
const txTotal = formatBytes(data.network.txBytes);
|
|
472
|
+
const rxRateStr = formatBytes(data.network.rxRate);
|
|
473
|
+
const txRateStr = formatBytes(data.network.txRate);
|
|
474
|
+
lines.push(statRow("RX", `${rxTotal} \xB7 ${rxRateStr}/s`));
|
|
475
|
+
lines.push(statRow("TX", `${txTotal} \xB7 ${txRateStr}/s`));
|
|
476
|
+
const totalBytes = data.network.rxBytes + data.network.txBytes;
|
|
477
|
+
if (totalBytes > 0) {
|
|
478
|
+
const rxPct = Math.round(data.network.rxBytes / totalBytes * 100);
|
|
479
|
+
const barW = Math.max(4, contentWidth - 20);
|
|
480
|
+
const rxBar = gaugeBar(rxPct, barW);
|
|
481
|
+
lines.push(statRow("RX/TX", `${rxBar} ${rxPct}%`));
|
|
482
|
+
}
|
|
483
|
+
if (data.network.protocols) {
|
|
484
|
+
const p = data.network.protocols;
|
|
485
|
+
const totalConns = (p.tcp.connections || 0) + (p.udp.connections || 0);
|
|
486
|
+
if (totalConns > 0) {
|
|
487
|
+
const tcpPct = Math.round(p.tcp.connections / totalConns * 100);
|
|
488
|
+
lines.push(statRow("TCP", `${p.tcp.connections} conns \xB7 ${gaugeBar(tcpPct, Math.max(4, contentWidth - 20))} ${tcpPct}%`));
|
|
489
|
+
lines.push(statRow("UDP", `${p.udp.connections} conns`));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (data.network.connectionStates) {
|
|
493
|
+
const cs = data.network.connectionStates;
|
|
494
|
+
const total = cs.established + cs.listening + cs.timeWait + cs.closeWait + cs.other;
|
|
495
|
+
if (total > 0) {
|
|
496
|
+
const estPct = Math.round(cs.established / total * 100);
|
|
497
|
+
lines.push(statRow("Active", `${cs.established} ESTABLISHED \xB7 ${estPct}%`));
|
|
498
|
+
lines.push(statRow("Listen", `${cs.listening} LISTEN`));
|
|
499
|
+
if (cs.timeWait > 0) lines.push(statRow("TimeWait", `${cs.timeWait}`));
|
|
500
|
+
if (cs.closeWait > 0) lines.push(statRow("CloseWait", `${cs.closeWait}`));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (data.network.listeningPorts && data.network.listeningPorts.length > 0) {
|
|
504
|
+
const ports = data.network.listeningPorts.slice(0, 8).join(", ");
|
|
505
|
+
lines.push(statRow("Ports", truncatePlain(ports, valW)));
|
|
506
|
+
}
|
|
507
|
+
if (data.network.topRemoteHosts && data.network.topRemoteHosts.length > 0) {
|
|
508
|
+
for (const host of data.network.topRemoteHosts.slice(0, 3)) {
|
|
509
|
+
lines.push(statRow("Host", truncatePlain(`${host.host} (${host.connectionCount})`, valW)));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return lines;
|
|
513
|
+
}
|
|
514
|
+
function networkDetailCard(data, width, theme) {
|
|
515
|
+
const contentWidth = width - 4;
|
|
516
|
+
const lines = networkCard(data, contentWidth);
|
|
517
|
+
if (data.packets && data.packets.allProcesses && data.packets.allProcesses.length > 0) {
|
|
518
|
+
lines.push("");
|
|
519
|
+
lines.push(chalk2.bold("PROCESSES & BANDWIDTH USAGE:"));
|
|
520
|
+
lines.push(chalk2.dim("\u2500".repeat(contentWidth)));
|
|
521
|
+
const pWidth = 8;
|
|
522
|
+
const rxWidth = 12;
|
|
523
|
+
const txWidth = 12;
|
|
524
|
+
const totalWidth = 12;
|
|
525
|
+
const cmdWidth = Math.max(15, contentWidth - (pWidth + rxWidth + txWidth + totalWidth + 4 * 2));
|
|
526
|
+
lines.push(
|
|
527
|
+
chalk2.bold("PID".padEnd(pWidth)) + " " + chalk2.bold("COMMAND".padEnd(cmdWidth)) + " " + chalk2.bold("RX".padStart(rxWidth)) + " " + chalk2.bold("TX".padStart(txWidth)) + " " + chalk2.bold("TOTAL".padStart(totalWidth))
|
|
528
|
+
);
|
|
529
|
+
const sorted = [...data.packets.allProcesses].sort((a, b) => b.rxBytes + b.txBytes - (a.rxBytes + a.txBytes));
|
|
530
|
+
for (const proc of sorted) {
|
|
531
|
+
if (proc.rxBytes + proc.txBytes === 0) continue;
|
|
532
|
+
const rxStr = formatBytes(proc.rxBytes).padStart(rxWidth);
|
|
533
|
+
const txStr = formatBytes(proc.txBytes).padStart(txWidth);
|
|
534
|
+
const totalStr = formatBytes(proc.rxBytes + proc.txBytes).padStart(totalWidth);
|
|
535
|
+
const cmdStr = truncatePlain(proc.command, cmdWidth).padEnd(cmdWidth);
|
|
536
|
+
lines.push(
|
|
537
|
+
chalk2.cyan(proc.pid.toString().padEnd(pWidth)) + " " + cmdStr + " " + chalk2.yellow(rxStr) + " " + chalk2.yellow(txStr) + " " + chalk2.bold(totalStr)
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
lines.push("");
|
|
542
|
+
lines.push(chalk2.dim("No active network process data available."));
|
|
543
|
+
}
|
|
544
|
+
return lines;
|
|
466
545
|
}
|
|
467
546
|
function tasksCard(data, contentWidth) {
|
|
468
547
|
if (!data.tasks || !data.tasks.length) return null;
|
|
@@ -488,13 +567,14 @@ var TAB_DEFS = [
|
|
|
488
567
|
{ id: "power", label: "Power", key: "4" },
|
|
489
568
|
{ id: "battery", label: "Battery", key: "5" },
|
|
490
569
|
{ id: "thermal", label: "Thermal", key: "6" },
|
|
491
|
-
{ id: "
|
|
492
|
-
{ id: "
|
|
493
|
-
{ id: "
|
|
494
|
-
{ id: "
|
|
495
|
-
{ id: "process", label: "Process", key: "P" },
|
|
570
|
+
{ id: "packets", label: "Conns", key: "7" },
|
|
571
|
+
{ id: "tasks", label: "Tasks", key: "8" },
|
|
572
|
+
{ id: "disk", label: "Disk", key: "9" },
|
|
573
|
+
{ id: "process", label: "Process", key: "0" },
|
|
496
574
|
{ id: "p2p", label: "P2P", key: "R" },
|
|
497
|
-
{ id: "anomalies", label: "Anomalies", key: "A" }
|
|
575
|
+
{ id: "anomalies", label: "Anomalies", key: "A" },
|
|
576
|
+
{ id: "containers", label: "Containers", key: "C" },
|
|
577
|
+
{ id: "blender", label: "Blender", key: "B" }
|
|
498
578
|
];
|
|
499
579
|
var TAB_BAR_INDENT = 2;
|
|
500
580
|
var TAB_SEPARATOR_WIDTH = 3;
|
|
@@ -645,6 +725,31 @@ function diskTableLines(disks, contentWidth, borderAccent = THEMES.default.borde
|
|
|
645
725
|
});
|
|
646
726
|
return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
|
|
647
727
|
}
|
|
728
|
+
function containersTableLines(containers, contentWidth, borderAccent = THEMES.default.border) {
|
|
729
|
+
const idW = 14, cpuW = 8, memW = 8, memUsgW = 20, netW = 20, blockW = 20, pidsW = 6;
|
|
730
|
+
const nameW = Math.max(10, contentWidth - idW - cpuW - memW - memUsgW - netW - blockW - pidsW - 7);
|
|
731
|
+
const head = chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" CONTAINER ID ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" CPU% ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" MEM% ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" MEM USAGE ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" NET I/O ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" BLOCK I/O ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" PIDS ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" NAME".padEnd(nameW + 1));
|
|
732
|
+
const rows = containers.map((c) => {
|
|
733
|
+
const cpuStr = pctColor(c.cpuPercent)(`${c.cpuPercent.toFixed(1)}`.padEnd(cpuW));
|
|
734
|
+
const memStr = pctColor(c.memPercent)(`${c.memPercent.toFixed(1)}`.padEnd(memW));
|
|
735
|
+
return truncatePlain(c.id, idW - 1).padEnd(idW) + cpuStr + memStr + c.memUsage.padEnd(memUsgW) + c.netIO.padEnd(netW) + c.blockIO.padEnd(blockW) + String(c.pids).padEnd(pidsW) + truncatePlain(c.name, nameW);
|
|
736
|
+
});
|
|
737
|
+
return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
|
|
738
|
+
}
|
|
739
|
+
function blenderCard(renders, contentWidth, theme) {
|
|
740
|
+
if (!renders.length) return ["No active Blender renders detected"];
|
|
741
|
+
const lines = [];
|
|
742
|
+
for (const r of renders.slice(0, 8)) {
|
|
743
|
+
const statusColor = r.status === "rendering" ? chalk2.green : r.status === "finalizing" ? chalk2.yellow : r.status === "loading" ? chalk2.cyan : chalk2.dim;
|
|
744
|
+
const pctStr = `${r.completionPercent}%`;
|
|
745
|
+
const barW = Math.max(4, contentWidth - 28);
|
|
746
|
+
const bar = gaugeBar(r.completionPercent, barW);
|
|
747
|
+
lines.push(`${theme.blender(`PID ${r.pid}`)} ${truncatePlain(r.blendFile, 18)} ${r.renderEngine}`);
|
|
748
|
+
lines.push(` Fra ${String(r.currentFrame).padStart(4)}/${r.totalFrames} ${bar} ${pctStr}`);
|
|
749
|
+
lines.push(` ${r.elapsedSec}s elapsed \xB7 ${statusColor(r.status)}${r.sampleCount !== void 0 ? ` \xB7 ${r.sampleCount} spp` : ""}`);
|
|
750
|
+
}
|
|
751
|
+
return lines;
|
|
752
|
+
}
|
|
648
753
|
function detailPanelTitle(panel2, trackedPid) {
|
|
649
754
|
const map = {
|
|
650
755
|
cpu: "CPU",
|
|
@@ -657,7 +762,9 @@ function detailPanelTitle(panel2, trackedPid) {
|
|
|
657
762
|
packets: "Packets",
|
|
658
763
|
tasks: "Tasks",
|
|
659
764
|
disk: "Disk",
|
|
660
|
-
process: trackedPid !== void 0 && trackedPid !== null ? `Processes (Following PID ${trackedPid})` : "Processes"
|
|
765
|
+
process: trackedPid !== void 0 && trackedPid !== null ? `Processes (Following PID ${trackedPid})` : "Processes",
|
|
766
|
+
containers: "Containers",
|
|
767
|
+
blender: "Blender Renders"
|
|
661
768
|
};
|
|
662
769
|
return map[panel2] || panel2;
|
|
663
770
|
}
|
|
@@ -689,24 +796,42 @@ function activeDetailLines(data, activePanel, width, opts = {}) {
|
|
|
689
796
|
case "thermal":
|
|
690
797
|
return thermalCard(data, contentWidth);
|
|
691
798
|
case "network":
|
|
692
|
-
return
|
|
799
|
+
return networkDetailCard(data, width, theme);
|
|
693
800
|
case "packets": {
|
|
694
|
-
if (!data.packets
|
|
695
|
-
|
|
696
|
-
lines.push(statRow("Total", `${data.packets.totalPackets} pkt`));
|
|
697
|
-
lines.push(statRow("RX", `${data.packets.rxPackets} pkt`));
|
|
698
|
-
lines.push(statRow("TX", `${data.packets.txPackets} pkt`));
|
|
699
|
-
lines.push(statRow("Conns", `${data.packets.connections} TCP`));
|
|
700
|
-
if (data.packets.interfaces && data.packets.interfaces.length) {
|
|
701
|
-
lines.push(statRow("Ifaces", `${data.packets.interfaces.length} active`));
|
|
702
|
-
for (const iface of data.packets.interfaces.slice(0, 8)) {
|
|
703
|
-
lines.push(statRow(` ${iface.iface}`, `RX ${formatBytes(iface.rxBytes)} / TX ${formatBytes(iface.txBytes)}`));
|
|
704
|
-
}
|
|
801
|
+
if (!data.packets || !data.packets.allProcesses || !data.packets.allProcesses.length) {
|
|
802
|
+
return ["No advanced network tracking data available."];
|
|
705
803
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
804
|
+
const lines = [];
|
|
805
|
+
const pWidth = 8;
|
|
806
|
+
const cmdWidth = 15;
|
|
807
|
+
const protoWidth = 5;
|
|
808
|
+
const stateWidth = 11;
|
|
809
|
+
const rxWidth = 10;
|
|
810
|
+
const txWidth = 10;
|
|
811
|
+
const remaining = Math.max(20, width - (pWidth + cmdWidth + protoWidth + stateWidth + rxWidth + txWidth + 7 * 2));
|
|
812
|
+
const localWidth = Math.floor(remaining * 0.4);
|
|
813
|
+
const remoteWidth = remaining - localWidth;
|
|
814
|
+
const header2 = chalk2.bold("PID".padEnd(pWidth)) + " " + chalk2.bold("COMMAND".padEnd(cmdWidth)) + " " + chalk2.bold("PROTO".padEnd(protoWidth)) + " " + chalk2.bold("LOCAL".padEnd(localWidth)) + " " + chalk2.bold("REMOTE".padEnd(remoteWidth)) + " " + chalk2.bold("STATE".padEnd(stateWidth)) + " " + chalk2.bold("RX".padStart(rxWidth)) + " " + chalk2.bold("TX".padStart(txWidth));
|
|
815
|
+
lines.push(header2);
|
|
816
|
+
lines.push(chalk2.dim("\u2500".repeat(width)));
|
|
817
|
+
for (const proc of data.packets.allProcesses) {
|
|
818
|
+
if (!proc.connections || proc.connections.length === 0) continue;
|
|
819
|
+
const summaryCmd = truncatePlain(proc.command, cmdWidth + protoWidth + localWidth + remoteWidth + stateWidth + 8);
|
|
820
|
+
const summaryRx = formatBytes(proc.rxBytes).padStart(rxWidth);
|
|
821
|
+
const summaryTx = formatBytes(proc.txBytes).padStart(txWidth);
|
|
822
|
+
lines.push(
|
|
823
|
+
chalk2.cyan(proc.pid.toString().padEnd(pWidth)) + " " + chalk2.cyan(summaryCmd.padEnd(width - pWidth - rxWidth - txWidth - 6)) + " " + chalk2.yellow(summaryRx) + " " + chalk2.yellow(summaryTx)
|
|
824
|
+
);
|
|
825
|
+
for (const conn of proc.connections) {
|
|
826
|
+
const cProto = chalk2.dim(truncatePlain(conn.protocol, protoWidth).padEnd(protoWidth));
|
|
827
|
+
const cLocal = chalk2.dim(truncatePlain(conn.local, localWidth).padEnd(localWidth));
|
|
828
|
+
const cRemote = truncatePlain(conn.remote, remoteWidth).padEnd(remoteWidth);
|
|
829
|
+
const cStateStr = conn.state === "Established" ? chalk2.green("ESTABLISHED".padEnd(stateWidth)) : chalk2.dim(truncatePlain(conn.state, stateWidth).padEnd(stateWidth));
|
|
830
|
+
const cRx = chalk2.dim(formatBytes(conn.rxBytes).padStart(rxWidth));
|
|
831
|
+
const cTx = chalk2.dim(formatBytes(conn.txBytes).padStart(txWidth));
|
|
832
|
+
lines.push(
|
|
833
|
+
" ".repeat(pWidth) + " " + " ".repeat(cmdWidth) + " " + cProto + " " + cLocal + " " + cRemote + " " + cStateStr + " " + cRx + " " + cTx
|
|
834
|
+
);
|
|
710
835
|
}
|
|
711
836
|
}
|
|
712
837
|
return lines;
|
|
@@ -769,6 +894,13 @@ function activeDetailLines(data, activePanel, width, opts = {}) {
|
|
|
769
894
|
}
|
|
770
895
|
return lines;
|
|
771
896
|
}
|
|
897
|
+
case "containers": {
|
|
898
|
+
return data.containers && data.containers.length ? containersTableLines(data.containers, width - 4, theme.border) : ["No container data available"];
|
|
899
|
+
}
|
|
900
|
+
case "blender": {
|
|
901
|
+
const renders = data.blenderRenders && data.blenderRenders.length ? data.blenderRenders : [];
|
|
902
|
+
return blenderCard(renders, contentWidth, theme);
|
|
903
|
+
}
|
|
772
904
|
default:
|
|
773
905
|
return null;
|
|
774
906
|
}
|
|
@@ -992,7 +1124,7 @@ function formatTable(data, opts = {}) {
|
|
|
992
1124
|
lines.push(gaugeRow("GPU", data.gpu.utilization, contentWidth));
|
|
993
1125
|
}
|
|
994
1126
|
const tempC = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die;
|
|
995
|
-
if (tempC
|
|
1127
|
+
if (tempC != null) {
|
|
996
1128
|
const tempStr = formatTempUnit(tempC, opts.tempUnit);
|
|
997
1129
|
lines.push(statRow("Temp", thermalColor(data.thermal.pressureLevel)(tempStr)));
|
|
998
1130
|
}
|
|
@@ -29,6 +29,7 @@ var DEFAULT_CONFIG = {
|
|
|
29
29
|
splashAnimation: "classic",
|
|
30
30
|
webhookUrl: "",
|
|
31
31
|
alertCmd: "",
|
|
32
|
+
dockerModeConfirmed: false,
|
|
32
33
|
visiblePanels: {
|
|
33
34
|
cpu: true,
|
|
34
35
|
mem: true,
|
|
@@ -41,6 +42,8 @@ var DEFAULT_CONFIG = {
|
|
|
41
42
|
tasks: true,
|
|
42
43
|
disk: true,
|
|
43
44
|
process: true,
|
|
45
|
+
containers: true,
|
|
46
|
+
blender: true,
|
|
44
47
|
tree: false
|
|
45
48
|
},
|
|
46
49
|
panelLayout: ["mem", "disk", "net"]
|