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/src/formatters/render.ts
CHANGED
|
@@ -13,6 +13,7 @@ import chalk from 'chalk';
|
|
|
13
13
|
import { sparkline, multiRowSparkline, brailleGraph, multiRowBrailleGraph } from '../sparkline.js';
|
|
14
14
|
import type { History } from '../history.js';
|
|
15
15
|
import type { StatsData, VisibleItems, TableOptions, AnomalyAlert } from './types.js';
|
|
16
|
+
import type { BlenderRenderData } from '../monitors/types.js';
|
|
16
17
|
import { THEMES, type ThemeName, type ThemeColors } from './themes.js';
|
|
17
18
|
|
|
18
19
|
|
|
@@ -333,11 +334,102 @@ function thermalCard(data: StatsData, contentWidth: number): string[] {
|
|
|
333
334
|
|
|
334
335
|
function networkCard(data: StatsData, contentWidth: number): string[] {
|
|
335
336
|
const valW = Math.max(6, contentWidth - 8);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
const lines: string[] = [];
|
|
338
|
+
lines.push(statRow('Iface', truncatePlain(`${data.network.interface} (${data.network.ip})`, valW)));
|
|
339
|
+
const rxTotal = formatBytes(data.network.rxBytes);
|
|
340
|
+
const txTotal = formatBytes(data.network.txBytes);
|
|
341
|
+
const rxRateStr = formatBytes(data.network.rxRate);
|
|
342
|
+
const txRateStr = formatBytes(data.network.txRate);
|
|
343
|
+
lines.push(statRow('RX', `${rxTotal} · ${rxRateStr}/s`));
|
|
344
|
+
lines.push(statRow('TX', `${txTotal} · ${txRateStr}/s`));
|
|
345
|
+
|
|
346
|
+
const totalBytes = data.network.rxBytes + data.network.txBytes;
|
|
347
|
+
if (totalBytes > 0) {
|
|
348
|
+
const rxPct = Math.round((data.network.rxBytes / totalBytes) * 100);
|
|
349
|
+
const barW = Math.max(4, contentWidth - 20);
|
|
350
|
+
const rxBar = gaugeBar(rxPct, barW);
|
|
351
|
+
lines.push(statRow('RX/TX', `${rxBar} ${rxPct}%`));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (data.network.protocols) {
|
|
355
|
+
const p = data.network.protocols;
|
|
356
|
+
const totalConns = (p.tcp.connections || 0) + (p.udp.connections || 0);
|
|
357
|
+
if (totalConns > 0) {
|
|
358
|
+
const tcpPct = Math.round((p.tcp.connections / totalConns) * 100);
|
|
359
|
+
lines.push(statRow('TCP', `${p.tcp.connections} conns · ${gaugeBar(tcpPct, Math.max(4, contentWidth - 20))} ${tcpPct}%`));
|
|
360
|
+
lines.push(statRow('UDP', `${p.udp.connections} conns`));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (data.network.connectionStates) {
|
|
364
|
+
const cs = data.network.connectionStates;
|
|
365
|
+
const total = cs.established + cs.listening + cs.timeWait + cs.closeWait + cs.other;
|
|
366
|
+
if (total > 0) {
|
|
367
|
+
const estPct = Math.round((cs.established / total) * 100);
|
|
368
|
+
lines.push(statRow('Active', `${cs.established} ESTABLISHED · ${estPct}%`));
|
|
369
|
+
lines.push(statRow('Listen', `${cs.listening} LISTEN`));
|
|
370
|
+
if (cs.timeWait > 0) lines.push(statRow('TimeWait', `${cs.timeWait}`));
|
|
371
|
+
if (cs.closeWait > 0) lines.push(statRow('CloseWait', `${cs.closeWait}`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (data.network.listeningPorts && data.network.listeningPorts.length > 0) {
|
|
375
|
+
const ports = data.network.listeningPorts.slice(0, 8).join(', ');
|
|
376
|
+
lines.push(statRow('Ports', truncatePlain(ports, valW)));
|
|
377
|
+
}
|
|
378
|
+
if (data.network.topRemoteHosts && data.network.topRemoteHosts.length > 0) {
|
|
379
|
+
for (const host of data.network.topRemoteHosts.slice(0, 3)) {
|
|
380
|
+
lines.push(statRow('Host', truncatePlain(`${host.host} (${host.connectionCount})`, valW)));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return lines;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function networkDetailCard(data: StatsData, width: number, theme: ThemeColors): string[] {
|
|
387
|
+
const contentWidth = width - 4;
|
|
388
|
+
const lines = networkCard(data, contentWidth);
|
|
389
|
+
|
|
390
|
+
if (data.packets && data.packets.allProcesses && data.packets.allProcesses.length > 0) {
|
|
391
|
+
lines.push('');
|
|
392
|
+
lines.push(chalk.bold('PROCESSES & BANDWIDTH USAGE:'));
|
|
393
|
+
lines.push(chalk.dim('─'.repeat(contentWidth)));
|
|
394
|
+
|
|
395
|
+
const pWidth = 8;
|
|
396
|
+
const rxWidth = 12;
|
|
397
|
+
const txWidth = 12;
|
|
398
|
+
const totalWidth = 12;
|
|
399
|
+
const cmdWidth = Math.max(15, contentWidth - (pWidth + rxWidth + txWidth + totalWidth + 4 * 2));
|
|
400
|
+
|
|
401
|
+
lines.push(
|
|
402
|
+
chalk.bold('PID'.padEnd(pWidth)) + ' ' +
|
|
403
|
+
chalk.bold('COMMAND'.padEnd(cmdWidth)) + ' ' +
|
|
404
|
+
chalk.bold('RX'.padStart(rxWidth)) + ' ' +
|
|
405
|
+
chalk.bold('TX'.padStart(txWidth)) + ' ' +
|
|
406
|
+
chalk.bold('TOTAL'.padStart(totalWidth))
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const sorted = [...data.packets.allProcesses]
|
|
410
|
+
.sort((a, b) => (b.rxBytes + b.txBytes) - (a.rxBytes + a.txBytes));
|
|
411
|
+
|
|
412
|
+
for (const proc of sorted) {
|
|
413
|
+
if (proc.rxBytes + proc.txBytes === 0) continue;
|
|
414
|
+
const rxStr = formatBytes(proc.rxBytes).padStart(rxWidth);
|
|
415
|
+
const txStr = formatBytes(proc.txBytes).padStart(txWidth);
|
|
416
|
+
const totalStr = formatBytes(proc.rxBytes + proc.txBytes).padStart(totalWidth);
|
|
417
|
+
const cmdStr = truncatePlain(proc.command, cmdWidth).padEnd(cmdWidth);
|
|
418
|
+
|
|
419
|
+
lines.push(
|
|
420
|
+
chalk.cyan(proc.pid.toString().padEnd(pWidth)) + ' ' +
|
|
421
|
+
cmdStr + ' ' +
|
|
422
|
+
chalk.yellow(rxStr) + ' ' +
|
|
423
|
+
chalk.yellow(txStr) + ' ' +
|
|
424
|
+
chalk.bold(totalStr)
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
lines.push('');
|
|
429
|
+
lines.push(chalk.dim('No active network process data available.'));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return lines;
|
|
341
433
|
}
|
|
342
434
|
|
|
343
435
|
function packetCard(data: StatsData, contentWidth: number): string[] | null {
|
|
@@ -347,8 +439,19 @@ function packetCard(data: StatsData, contentWidth: number): string[] | null {
|
|
|
347
439
|
lines.push(statRow('RX', `${data.packets.rxPackets} pkt`));
|
|
348
440
|
lines.push(statRow('TX', `${data.packets.txPackets} pkt`));
|
|
349
441
|
lines.push(statRow('Conns', `${data.packets.connections} TCP`));
|
|
442
|
+
if (data.packets.protocolStats) {
|
|
443
|
+
const ps = data.packets.protocolStats;
|
|
444
|
+
lines.push(statRow('TCP', `${ps.tcp.connections} conns`));
|
|
445
|
+
lines.push(statRow('UDP', `${ps.udp.connections} conns`));
|
|
446
|
+
}
|
|
447
|
+
if (data.packets.connectionStates) {
|
|
448
|
+
const cs = data.packets.connectionStates;
|
|
449
|
+
lines.push(statRow('ESTAB', `${cs.established}`));
|
|
450
|
+
lines.push(statRow('LISTEN', `${cs.listening}`));
|
|
451
|
+
}
|
|
350
452
|
if (data.packets.topProcesses && data.packets.topProcesses.length) {
|
|
351
|
-
|
|
453
|
+
const topProc = data.packets.topProcesses[0];
|
|
454
|
+
lines.push(statRow('Top Proc', `${truncatePlain(topProc.command, contentWidth - 25)} (${formatBytes(topProc.rxBytes + topProc.txBytes)})`));
|
|
352
455
|
}
|
|
353
456
|
if (data.packets.allProcesses && data.packets.allProcesses.length > 1) {
|
|
354
457
|
lines.push(statRow('Net Procs', `${data.packets.allProcesses.length} processes`));
|
|
@@ -392,13 +495,14 @@ export const TAB_DEFS: { id: string; label: string; key: string }[] = [
|
|
|
392
495
|
{ id: 'power', label: 'Power', key: '4' },
|
|
393
496
|
{ id: 'battery', label: 'Battery', key: '5' },
|
|
394
497
|
{ id: 'thermal', label: 'Thermal', key: '6' },
|
|
395
|
-
{ id: '
|
|
396
|
-
{ id: '
|
|
397
|
-
{ id: '
|
|
398
|
-
{ id: '
|
|
399
|
-
{ id: 'process', label: 'Process', key: 'P' },
|
|
498
|
+
{ id: 'packets', label: 'Conns', key: '7' },
|
|
499
|
+
{ id: 'tasks', label: 'Tasks', key: '8' },
|
|
500
|
+
{ id: 'disk', label: 'Disk', key: '9' },
|
|
501
|
+
{ id: 'process', label: 'Process', key: '0' },
|
|
400
502
|
{ id: 'p2p', label: 'P2P', key: 'R' },
|
|
401
503
|
{ id: 'anomalies', label: 'Anomalies', key: 'A' },
|
|
504
|
+
{ id: 'containers', label: 'Containers', key: 'C' },
|
|
505
|
+
{ id: 'blender', label: 'Blender', key: 'B' },
|
|
402
506
|
];
|
|
403
507
|
|
|
404
508
|
/** The number of leading columns of indent before the tab bar's first character (see `tabBar`). */
|
|
@@ -620,6 +724,58 @@ function diskTableLines(
|
|
|
620
724
|
return [head, borderAccent('─'.repeat(contentWidth)), ...rows];
|
|
621
725
|
}
|
|
622
726
|
|
|
727
|
+
function containersTableLines(
|
|
728
|
+
containers: { id: string; name: string; cpuPercent: number; memUsage: string; memPercent: number; netIO: string; blockIO: string; pids: number }[],
|
|
729
|
+
contentWidth: number,
|
|
730
|
+
borderAccent = THEMES.default.border
|
|
731
|
+
): string[] {
|
|
732
|
+
const idW = 14, cpuW = 8, memW = 8, memUsgW = 20, netW = 20, blockW = 20, pidsW = 6;
|
|
733
|
+
const nameW = Math.max(10, contentWidth - idW - cpuW - memW - memUsgW - netW - blockW - pidsW - 7);
|
|
734
|
+
|
|
735
|
+
const head =
|
|
736
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' CONTAINER ID ') +
|
|
737
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' CPU% ') +
|
|
738
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' MEM% ') +
|
|
739
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' MEM USAGE ') +
|
|
740
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' NET I/O ') +
|
|
741
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' BLOCK I/O ') +
|
|
742
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' PIDS ') +
|
|
743
|
+
chalk.bold.hex('#f8f8f2').bgHex('#44475a')(' NAME'.padEnd(nameW + 1));
|
|
744
|
+
|
|
745
|
+
const rows = containers.map(c => {
|
|
746
|
+
const cpuStr = pctColor(c.cpuPercent)(`${c.cpuPercent.toFixed(1)}`.padEnd(cpuW));
|
|
747
|
+
const memStr = pctColor(c.memPercent)(`${c.memPercent.toFixed(1)}`.padEnd(memW));
|
|
748
|
+
|
|
749
|
+
return (
|
|
750
|
+
truncatePlain(c.id, idW - 1).padEnd(idW) +
|
|
751
|
+
cpuStr +
|
|
752
|
+
memStr +
|
|
753
|
+
c.memUsage.padEnd(memUsgW) +
|
|
754
|
+
c.netIO.padEnd(netW) +
|
|
755
|
+
c.blockIO.padEnd(blockW) +
|
|
756
|
+
String(c.pids).padEnd(pidsW) +
|
|
757
|
+
truncatePlain(c.name, nameW)
|
|
758
|
+
);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
return [head, borderAccent('─'.repeat(contentWidth)), ...rows];
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function blenderCard(renders: BlenderRenderData[], contentWidth: number, theme: ThemeColors): string[] {
|
|
765
|
+
if (!renders.length) return ['No active Blender renders detected'];
|
|
766
|
+
const lines: string[] = [];
|
|
767
|
+
for (const r of renders.slice(0, 8)) {
|
|
768
|
+
const statusColor = r.status === 'rendering' ? chalk.green : r.status === 'finalizing' ? chalk.yellow : r.status === 'loading' ? chalk.cyan : chalk.dim;
|
|
769
|
+
const pctStr = `${r.completionPercent}%`;
|
|
770
|
+
const barW = Math.max(4, contentWidth - 28);
|
|
771
|
+
const bar = gaugeBar(r.completionPercent, barW);
|
|
772
|
+
lines.push(`${theme.blender(`PID ${r.pid}`)} ${truncatePlain(r.blendFile, 18)} ${r.renderEngine}`);
|
|
773
|
+
lines.push(` Fra ${String(r.currentFrame).padStart(4)}/${r.totalFrames} ${bar} ${pctStr}`);
|
|
774
|
+
lines.push(` ${r.elapsedSec}s elapsed · ${statusColor(r.status)}${r.sampleCount !== undefined ? ` · ${r.sampleCount} spp` : ''}`);
|
|
775
|
+
}
|
|
776
|
+
return lines;
|
|
777
|
+
}
|
|
778
|
+
|
|
623
779
|
// --- main dashboard ------------------------------------------------------------
|
|
624
780
|
|
|
625
781
|
function detailPanelTitle(panel: string, trackedPid?: number | null): string {
|
|
@@ -635,6 +791,8 @@ function detailPanelTitle(panel: string, trackedPid?: number | null): string {
|
|
|
635
791
|
tasks: 'Tasks',
|
|
636
792
|
disk: 'Disk',
|
|
637
793
|
process: trackedPid !== undefined && trackedPid !== null ? `Processes (Following PID ${trackedPid})` : 'Processes',
|
|
794
|
+
containers: 'Containers',
|
|
795
|
+
blender: 'Blender Renders',
|
|
638
796
|
};
|
|
639
797
|
return map[panel] || panel;
|
|
640
798
|
}
|
|
@@ -667,26 +825,73 @@ function activeDetailLines(data: StatsData, activePanel: string, width: number,
|
|
|
667
825
|
case 'thermal':
|
|
668
826
|
return thermalCard(data, contentWidth);
|
|
669
827
|
case 'network':
|
|
670
|
-
return
|
|
828
|
+
return networkDetailCard(data, width, theme);
|
|
671
829
|
case 'packets': {
|
|
672
|
-
if (!data.packets
|
|
673
|
-
|
|
674
|
-
lines.push(statRow('Total', `${data.packets.totalPackets} pkt`));
|
|
675
|
-
lines.push(statRow('RX', `${data.packets.rxPackets} pkt`));
|
|
676
|
-
lines.push(statRow('TX', `${data.packets.txPackets} pkt`));
|
|
677
|
-
lines.push(statRow('Conns', `${data.packets.connections} TCP`));
|
|
678
|
-
if (data.packets.interfaces && data.packets.interfaces.length) {
|
|
679
|
-
lines.push(statRow('Ifaces', `${data.packets.interfaces.length} active`));
|
|
680
|
-
for (const iface of data.packets.interfaces.slice(0, 8)) {
|
|
681
|
-
lines.push(statRow(` ${iface.iface}`, `RX ${formatBytes(iface.rxBytes)} / TX ${formatBytes(iface.txBytes)}`));
|
|
682
|
-
}
|
|
830
|
+
if (!data.packets || !data.packets.allProcesses || !data.packets.allProcesses.length) {
|
|
831
|
+
return ['No advanced network tracking data available.'];
|
|
683
832
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
833
|
+
|
|
834
|
+
const lines: string[] = [];
|
|
835
|
+
const pWidth = 8;
|
|
836
|
+
const cmdWidth = 15;
|
|
837
|
+
const protoWidth = 5;
|
|
838
|
+
const stateWidth = 11;
|
|
839
|
+
const rxWidth = 10;
|
|
840
|
+
const txWidth = 10;
|
|
841
|
+
|
|
842
|
+
const remaining = Math.max(20, width - (pWidth + cmdWidth + protoWidth + stateWidth + rxWidth + txWidth + 7 * 2));
|
|
843
|
+
const localWidth = Math.floor(remaining * 0.4);
|
|
844
|
+
const remoteWidth = remaining - localWidth;
|
|
845
|
+
|
|
846
|
+
const header =
|
|
847
|
+
chalk.bold('PID'.padEnd(pWidth)) + ' ' +
|
|
848
|
+
chalk.bold('COMMAND'.padEnd(cmdWidth)) + ' ' +
|
|
849
|
+
chalk.bold('PROTO'.padEnd(protoWidth)) + ' ' +
|
|
850
|
+
chalk.bold('LOCAL'.padEnd(localWidth)) + ' ' +
|
|
851
|
+
chalk.bold('REMOTE'.padEnd(remoteWidth)) + ' ' +
|
|
852
|
+
chalk.bold('STATE'.padEnd(stateWidth)) + ' ' +
|
|
853
|
+
chalk.bold('RX'.padStart(rxWidth)) + ' ' +
|
|
854
|
+
chalk.bold('TX'.padStart(txWidth));
|
|
855
|
+
|
|
856
|
+
lines.push(header);
|
|
857
|
+
lines.push(chalk.dim('─'.repeat(width)));
|
|
858
|
+
|
|
859
|
+
for (const proc of data.packets.allProcesses) {
|
|
860
|
+
if (!proc.connections || proc.connections.length === 0) continue;
|
|
861
|
+
|
|
862
|
+
const summaryCmd = truncatePlain(proc.command, cmdWidth + protoWidth + localWidth + remoteWidth + stateWidth + 8);
|
|
863
|
+
const summaryRx = formatBytes(proc.rxBytes).padStart(rxWidth);
|
|
864
|
+
const summaryTx = formatBytes(proc.txBytes).padStart(txWidth);
|
|
865
|
+
|
|
866
|
+
lines.push(
|
|
867
|
+
chalk.cyan(proc.pid.toString().padEnd(pWidth)) + ' ' +
|
|
868
|
+
chalk.cyan(summaryCmd.padEnd(width - pWidth - rxWidth - txWidth - 6)) + ' ' +
|
|
869
|
+
chalk.yellow(summaryRx) + ' ' +
|
|
870
|
+
chalk.yellow(summaryTx)
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
for (const conn of proc.connections) {
|
|
874
|
+
const cProto = chalk.dim(truncatePlain(conn.protocol, protoWidth).padEnd(protoWidth));
|
|
875
|
+
const cLocal = chalk.dim(truncatePlain(conn.local, localWidth).padEnd(localWidth));
|
|
876
|
+
const cRemote = truncatePlain(conn.remote, remoteWidth).padEnd(remoteWidth);
|
|
877
|
+
|
|
878
|
+
const cStateStr = conn.state === 'Established' ? chalk.green('ESTABLISHED'.padEnd(stateWidth)) : chalk.dim(truncatePlain(conn.state, stateWidth).padEnd(stateWidth));
|
|
879
|
+
const cRx = chalk.dim(formatBytes(conn.rxBytes).padStart(rxWidth));
|
|
880
|
+
const cTx = chalk.dim(formatBytes(conn.txBytes).padStart(txWidth));
|
|
881
|
+
|
|
882
|
+
lines.push(
|
|
883
|
+
' '.repeat(pWidth) + ' ' +
|
|
884
|
+
' '.repeat(cmdWidth) + ' ' +
|
|
885
|
+
cProto + ' ' +
|
|
886
|
+
cLocal + ' ' +
|
|
887
|
+
cRemote + ' ' +
|
|
888
|
+
cStateStr + ' ' +
|
|
889
|
+
cRx + ' ' +
|
|
890
|
+
cTx
|
|
891
|
+
);
|
|
688
892
|
}
|
|
689
893
|
}
|
|
894
|
+
|
|
690
895
|
return lines;
|
|
691
896
|
}
|
|
692
897
|
case 'tasks':
|
|
@@ -758,6 +963,13 @@ function activeDetailLines(data: StatsData, activePanel: string, width: number,
|
|
|
758
963
|
}
|
|
759
964
|
return lines;
|
|
760
965
|
}
|
|
966
|
+
case 'containers': {
|
|
967
|
+
return data.containers && data.containers.length ? containersTableLines(data.containers, width - 4, theme.border) : ['No container data available'];
|
|
968
|
+
}
|
|
969
|
+
case 'blender': {
|
|
970
|
+
const renders = data.blenderRenders && data.blenderRenders.length ? data.blenderRenders : [];
|
|
971
|
+
return blenderCard(renders, contentWidth, theme);
|
|
972
|
+
}
|
|
761
973
|
default:
|
|
762
974
|
return null;
|
|
763
975
|
}
|
|
@@ -1034,7 +1246,7 @@ export function formatTable(data: StatsData, opts: TableOptions = {}): string {
|
|
|
1034
1246
|
lines.push(gaugeRow('GPU', data.gpu.utilization, contentWidth));
|
|
1035
1247
|
}
|
|
1036
1248
|
const tempC = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die;
|
|
1037
|
-
if (tempC
|
|
1249
|
+
if (tempC != null) {
|
|
1038
1250
|
const tempStr = formatTempUnit(tempC, opts.tempUnit);
|
|
1039
1251
|
lines.push(statRow('Temp', thermalColor(data.thermal.pressureLevel)(tempStr)));
|
|
1040
1252
|
}
|
package/src/formatters/themes.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface ThemeColors {
|
|
|
26
26
|
disk: (s: string) => string;
|
|
27
27
|
graphs: (s: string) => string;
|
|
28
28
|
process: (s: string) => string;
|
|
29
|
+
blender: (s: string) => string;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
@@ -41,6 +42,7 @@ export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
|
41
42
|
disk: chalk.hex('#f472b6'),
|
|
42
43
|
graphs: chalk.hex('#22d3ee'),
|
|
43
44
|
process: chalk.hex('#e5e7eb'),
|
|
45
|
+
blender: chalk.hex('#f97316'),
|
|
44
46
|
},
|
|
45
47
|
dracula: {
|
|
46
48
|
border: chalk.hex('#6272a4'),
|
|
@@ -54,6 +56,7 @@ export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
|
54
56
|
disk: chalk.hex('#ff5555'),
|
|
55
57
|
graphs: chalk.hex('#8be9fd'),
|
|
56
58
|
process: chalk.hex('#f8f8f2'),
|
|
59
|
+
blender: chalk.hex('#ffb86c'),
|
|
57
60
|
},
|
|
58
61
|
cyberpunk: {
|
|
59
62
|
border: chalk.hex('#ff0055'),
|
|
@@ -67,6 +70,7 @@ export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
|
67
70
|
disk: chalk.hex('#ff007f'),
|
|
68
71
|
graphs: chalk.hex('#00ffcc'),
|
|
69
72
|
process: chalk.hex('#ffffff'),
|
|
73
|
+
blender: chalk.hex('#ff6600'),
|
|
70
74
|
},
|
|
71
75
|
monochrome: {
|
|
72
76
|
border: chalk.gray,
|
|
@@ -80,6 +84,7 @@ export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
|
80
84
|
disk: chalk.white.bold,
|
|
81
85
|
graphs: chalk.white.bold,
|
|
82
86
|
process: chalk.white.bold,
|
|
87
|
+
blender: chalk.white.bold,
|
|
83
88
|
},
|
|
84
89
|
nord: {
|
|
85
90
|
border: chalk.hex('#4c566a'),
|
|
@@ -93,6 +98,7 @@ export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
|
93
98
|
disk: chalk.hex('#b48ead'),
|
|
94
99
|
graphs: chalk.hex('#88c0d0'),
|
|
95
100
|
process: chalk.hex('#eceff4'),
|
|
101
|
+
blender: chalk.hex('#d08770'),
|
|
96
102
|
},
|
|
97
103
|
gruvbox: {
|
|
98
104
|
border: chalk.hex('#928374'),
|
|
@@ -106,6 +112,7 @@ export const BUILTIN_THEMES: Record<string, ThemeColors> = {
|
|
|
106
112
|
disk: chalk.hex('#d3869b'),
|
|
107
113
|
graphs: chalk.hex('#fb4934'),
|
|
108
114
|
process: chalk.hex('#ebdbb2'),
|
|
115
|
+
blender: chalk.hex('#fe8019'),
|
|
109
116
|
},
|
|
110
117
|
};
|
|
111
118
|
|
|
@@ -134,6 +141,7 @@ export function loadCustomThemes(): Record<string, ThemeColors> {
|
|
|
134
141
|
disk: buildColor(data.disk),
|
|
135
142
|
graphs: buildColor(data.graphs),
|
|
136
143
|
process: buildColor(data.process),
|
|
144
|
+
blender: buildColor(data.blender),
|
|
137
145
|
};
|
|
138
146
|
} catch {
|
|
139
147
|
// ignore bad theme file
|
package/src/formatters/types.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface ThemeColors {
|
|
|
24
24
|
disk: (s: string) => string;
|
|
25
25
|
graphs: (s: string) => string;
|
|
26
26
|
process: (s: string) => string;
|
|
27
|
+
blender: (s: string) => string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export type { StatsData };
|
|
@@ -40,6 +41,8 @@ export interface VisibleItems {
|
|
|
40
41
|
tasks?: boolean;
|
|
41
42
|
disk?: boolean;
|
|
42
43
|
process?: boolean;
|
|
44
|
+
containers?: boolean;
|
|
45
|
+
blender?: boolean;
|
|
43
46
|
tree?: boolean;
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -67,7 +70,7 @@ export interface TableOptions {
|
|
|
67
70
|
/** Show process tree view instead of flat list. */
|
|
68
71
|
treeView?: boolean;
|
|
69
72
|
/** Active panel for detail/focus view. 'grid' means show all panels. */
|
|
70
|
-
activePanel?: 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process' | 'p2p' | 'anomalies';
|
|
73
|
+
activePanel?: 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process' | 'p2p' | 'anomalies' | 'containers' | 'blender';
|
|
71
74
|
/** Active anomaly alerts from the statistical detector. */
|
|
72
75
|
anomalies?: AnomalyAlert[];
|
|
73
76
|
/** Historical anomaly alerts for the anomalies tab. */
|
package/src/index.ts
CHANGED
|
@@ -82,32 +82,33 @@ program
|
|
|
82
82
|
.option('--port <port>', 'Port number for web server mode', '3000');
|
|
83
83
|
|
|
84
84
|
program.addHelpText('after', `
|
|
85
|
-
Commands:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
85
|
+
Commands:
|
|
86
|
+
live Interactive live dashboard
|
|
87
|
+
check One-line plain-English health summary
|
|
88
|
+
pipe Continuous newline-delimited JSON stream for scripting
|
|
89
|
+
stress Synthetic CPU/GPU load generator
|
|
90
|
+
ui Launch the native macOS UI dashboard
|
|
91
|
+
web Serve an auto-refreshing live web portal
|
|
92
|
+
ssh <host> Stream live stats from a remote Mac over SSH
|
|
93
|
+
fleet <host1> [host2]... Multi-host live dashboard monitoring multiple Macs
|
|
94
|
+
bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
|
|
95
|
+
benchmark Run a 1-minute CPU benchmark calculating digits of PI
|
|
96
|
+
anomalies Compute z-score anomalies and print plain-language digest
|
|
97
|
+
doctor Run system diagnostics
|
|
98
|
+
extensions System Extensions inspector
|
|
99
|
+
brew Homebrew health panel
|
|
100
|
+
update Check for pyre-cli updates
|
|
101
|
+
profile <save|load|list> Atomic configuration profile management
|
|
102
|
+
config <show|reset> View or reset persistent configuration file
|
|
103
|
+
history Graph historical resource trends from CSV logs
|
|
104
|
+
diff <file1> <file2> Compare two saved snapshot files side-by-side
|
|
105
|
+
info Concise hardware summary
|
|
106
|
+
completions <shell> Generate shell auto-completion scripts
|
|
107
|
+
xbar Generate an xbar / SwiftBar menu bar plugin script
|
|
108
|
+
p2p <server|connect> Start a P2P server or connect to one
|
|
109
|
+
server Print commands for starting P2P server/client
|
|
110
|
+
blender Track active Blender render jobs and their progress
|
|
111
|
+
`);
|
|
111
112
|
|
|
112
113
|
program.parse(process.argv);
|
|
113
114
|
|
|
@@ -233,6 +234,32 @@ async function main() {
|
|
|
233
234
|
return;
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
if (cmd === 'blender') {
|
|
238
|
+
const { collectBlenderRenders } = await import('./monitors/blender.js');
|
|
239
|
+
const renders = await collectBlenderRenders();
|
|
240
|
+
if (!renders.length) {
|
|
241
|
+
console.log(chalk.dim('No active Blender renders detected.'));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const lines: string[] = [];
|
|
245
|
+
lines.push(chalk.bold(' Blender Render Tracker'));
|
|
246
|
+
lines.push('');
|
|
247
|
+
for (const r of renders) {
|
|
248
|
+
const statusColor = r.status === 'rendering' ? chalk.green : r.status === 'finalizing' ? chalk.yellow : r.status === 'loading' ? chalk.cyan : chalk.dim;
|
|
249
|
+
lines.push(` PID: ${r.pid}`);
|
|
250
|
+
lines.push(` Blend File: ${r.blendFile}`);
|
|
251
|
+
lines.push(` Engine: ${r.renderEngine}`);
|
|
252
|
+
lines.push(` Frames: ${r.currentFrame} / ${r.totalFrames} (${r.completionPercent}%)`);
|
|
253
|
+
lines.push(` Elapsed: ${r.elapsedSec}s`);
|
|
254
|
+
lines.push(` Status: ${statusColor(r.status)}`);
|
|
255
|
+
lines.push(` Output: ${r.outputPath}`);
|
|
256
|
+
if (r.sampleCount !== undefined) lines.push(` Samples: ${r.sampleCount}`);
|
|
257
|
+
lines.push('');
|
|
258
|
+
}
|
|
259
|
+
console.log(lines.join('\n'));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
236
263
|
if (cmd === 'brew') {
|
|
237
264
|
const { printBrewHealthReport } = await import('./brewHealth.js');
|
|
238
265
|
await printBrewHealthReport();
|
package/src/live/input.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { writeConfig } from '../state/config.js';
|
|
|
38
38
|
splashAnimation: state.splashAnimation,
|
|
39
39
|
visiblePanels: { ...state.visiblePanels },
|
|
40
40
|
notificationsEnabled: state.notificationsEnabled,
|
|
41
|
+
dockerModeConfirmed: state.dockerModeConfirmed,
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -179,6 +180,51 @@ function handleInputModeKey(str: string, key: readline.Key) {
|
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
if (state.inputMode === 'docker-alert') {
|
|
184
|
+
if (isEscKey(key, str)) {
|
|
185
|
+
state.inputMode = null;
|
|
186
|
+
state.dockerModeConfirmed = true;
|
|
187
|
+
render();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (isUpKey(key, str)) {
|
|
191
|
+
state.dockerSelectionIndex = Math.max(0, state.dockerSelectionIndex - 1);
|
|
192
|
+
render();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (isDownKey(key, str)) {
|
|
196
|
+
state.dockerSelectionIndex = Math.min(1, state.dockerSelectionIndex + 1);
|
|
197
|
+
render();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (isEnterKey(key, str) || str === ' ') {
|
|
201
|
+
state.dockerModeConfirmed = true;
|
|
202
|
+
state.inputMode = null;
|
|
203
|
+
|
|
204
|
+
if (state.dockerSelectionIndex === 0) {
|
|
205
|
+
// Docker Mode
|
|
206
|
+
state.visiblePanels.battery = false;
|
|
207
|
+
state.visiblePanels.thermal = false;
|
|
208
|
+
state.visiblePanels.power = false;
|
|
209
|
+
state.visiblePanels.gpu = false;
|
|
210
|
+
state.detailed = false;
|
|
211
|
+
} else {
|
|
212
|
+
// Regular TUI
|
|
213
|
+
state.visiblePanels.battery = true;
|
|
214
|
+
state.visiblePanels.thermal = true;
|
|
215
|
+
state.visiblePanels.power = true;
|
|
216
|
+
state.visiblePanels.gpu = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
invalidateTableCache();
|
|
220
|
+
invalidateFrame();
|
|
221
|
+
persistConfig();
|
|
222
|
+
render();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
182
228
|
if (state.inputMode === 'signal') {
|
|
183
229
|
if (isUpKey(key, str)) {
|
|
184
230
|
const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer as typeof state.SIGNAL_OPTIONS[number]);
|
|
@@ -540,6 +586,18 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
540
586
|
render();
|
|
541
587
|
return;
|
|
542
588
|
}
|
|
589
|
+
case 'C': {
|
|
590
|
+
state.activePanel = state.activePanel === 'containers' ? 'grid' : 'containers';
|
|
591
|
+
setStatus(state.activePanel === 'grid' ? 'Grid view' : 'Containers panel');
|
|
592
|
+
render();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
case 'B': {
|
|
596
|
+
state.activePanel = state.activePanel === 'blender' ? 'grid' : 'blender';
|
|
597
|
+
setStatus(state.activePanel === 'grid' ? 'Grid view' : 'Blender panel');
|
|
598
|
+
render();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
543
601
|
}
|
|
544
602
|
|
|
545
603
|
switch (key.name) {
|
|
@@ -716,16 +774,16 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
716
774
|
process.once('SIGINT', () => stopLive());
|
|
717
775
|
}
|
|
718
776
|
|
|
719
|
-
function tabKeyToId(str: string):
|
|
720
|
-
const map: Record<string,
|
|
777
|
+
function tabKeyToId(str: string): ActivePanel | null {
|
|
778
|
+
const map: Record<string, ActivePanel> = { '1': 'cpu', '2': 'mem', '3': 'gpu', '4': 'power', '5': 'battery', '6': 'thermal', '7': 'network', '8': 'packets', '9': 'tasks', '0': 'disk', 'p': 'process', 'C': 'containers', 'r': 'p2p', 'A': 'anomalies', 'B': 'blender' };
|
|
721
779
|
return map[str] ?? null;
|
|
722
780
|
}
|
|
723
781
|
|
|
724
782
|
function cycleTab(direction: number) {
|
|
725
|
-
const tabs = state.PANEL_TABS.map(t => t.id);
|
|
783
|
+
const tabs = state.PANEL_TABS.map(t => t.id) as string[];
|
|
726
784
|
const current = state.activePanel === 'grid' ? -1 : tabs.indexOf(state.activePanel);
|
|
727
785
|
const next = (current + direction + tabs.length) % tabs.length;
|
|
728
|
-
state.activePanel = tabs[next];
|
|
786
|
+
state.activePanel = tabs[next] as typeof state.activePanel;
|
|
729
787
|
setStatus(`Panel: ${state.activePanel.toUpperCase()}`);
|
|
730
788
|
render();
|
|
731
789
|
}
|
package/src/live/render.ts
CHANGED
|
@@ -216,6 +216,10 @@ function render() {
|
|
|
216
216
|
writeFrame(renderCredits(cols, rows));
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
|
+
if (state.inputMode === 'docker-alert') {
|
|
220
|
+
writeFrame(renderDockerAlert(cols, rows));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
219
223
|
const lines: string[] = [];
|
|
220
224
|
const theme = THEMES[state.currentTheme] || THEMES.default;
|
|
221
225
|
|
|
@@ -564,4 +568,31 @@ function renderCredits(cols: number, rows: number): string[] {
|
|
|
564
568
|
return out;
|
|
565
569
|
}
|
|
566
570
|
|
|
571
|
+
function renderDockerAlert(cols: number, rows: number): string[] {
|
|
572
|
+
const out: string[] = [];
|
|
573
|
+
const content = [
|
|
574
|
+
chalk.bold.red('Docker Container Detected'),
|
|
575
|
+
'',
|
|
576
|
+
'Pyre is running inside a container. Some hardware sensors',
|
|
577
|
+
'(battery, thermal, power) may not be available or relevant.',
|
|
578
|
+
'',
|
|
579
|
+
chalk.cyan('Do you wish to continue in Docker mode?'),
|
|
580
|
+
''
|
|
581
|
+
];
|
|
582
|
+
|
|
583
|
+
const options = ['Yes, use Docker mode (hide hardware sensors)', 'No, launch regular TUI with all features'];
|
|
584
|
+
for (let i = 0; i < options.length; i++) {
|
|
585
|
+
const isSelected = i === state.dockerSelectionIndex;
|
|
586
|
+
const prefix = isSelected ? chalk.cyan.bold(' ▶ ') : ' ';
|
|
587
|
+
const text = isSelected ? chalk.bgCyan.black(` ${options[i]} `) : chalk.dim(` ${options[i]} `);
|
|
588
|
+
content.push(prefix + text);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
content.push('');
|
|
592
|
+
content.push(chalk.dim('Press ↑/↓ to select, Enter to confirm, Esc to skip.'));
|
|
593
|
+
|
|
594
|
+
out.push(...panel('DOCKER DETECTED', content, cols, chalk.red, chalk.dim, rows - 2));
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
|
|
567
598
|
export { render, footerLine, renderCustomizerOverlay, checkAlerts, processRowBudget, invalidateTableCache, invalidateFrame };
|