pyre-cli 9.5.1 → 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.
@@ -1,7 +1,8 @@
1
- import "./chunk-SG26Y3WF.js";
1
+ import "./chunk-IM3IQMMT.js";
2
2
  import {
3
3
  collectAll
4
- } from "./chunk-HGRBBPYX.js";
4
+ } from "./chunk-N4WJVF5M.js";
5
+ import "./chunk-25I533OF.js";
5
6
  import "./chunk-FH5WDJBF.js";
6
7
 
7
8
  // src/pipeCmd.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CONFIG_FILE,
3
3
  writeConfig
4
- } from "./chunk-RVGKEJXT.js";
4
+ } from "./chunk-CXL4GYGQ.js";
5
5
 
6
6
  // src/wizard.ts
7
7
  import readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-cli",
3
- "version": "9.5.1",
3
+ "version": "9.5.2",
4
4
  "description": "CLI Mac monitoring tool: temps, system stats, live dashboard, packet monitor, battery predictor, export",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/src/anomalies.ts CHANGED
@@ -18,84 +18,138 @@ export interface AnomalyAlert {
18
18
 
19
19
  const ZSCORE_THRESHOLD = 2.5;
20
20
  const CRITICAL_ZSCORE = 3.5;
21
- const MIN_SAMPLES = 5;
21
+ const MIN_SAMPLES = 10;
22
+
23
+ // Minimum absolute deviation from the mean required to trigger an anomaly
24
+ const MIN_DEV_CPU = 10; // 10%
25
+ const MIN_DEV_MEM = 5; // 5%
26
+ const MIN_DEV_TEMP = 5; // 5 degrees
27
+ const MIN_DEV_NET = 500_000; // 500 KB
28
+ const MIN_DEV_POWER = 5; // 5 Watts
29
+ const MIN_DEV_GPU = 10; // 10%
22
30
 
23
31
  export function detectAnomalies(data: StatsData, history: History): AnomalyAlert[] {
24
32
  const alerts: AnomalyAlert[] = [];
25
33
 
26
- const cpuZ = History.zScore(data.cpu.usage, history.cpuUsage);
27
- if (Math.abs(cpuZ) > ZSCORE_THRESHOLD && history.cpuUsage.length >= MIN_SAMPLES) {
28
- alerts.push({
29
- metric: 'CPU',
30
- value: data.cpu.usage,
31
- zScore: cpuZ,
32
- severity: Math.abs(cpuZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
33
- });
34
+ if (history.cpuUsage.length >= MIN_SAMPLES) {
35
+ const cpuMean = History.mean(history.cpuUsage);
36
+ if (Math.abs(data.cpu.usage - cpuMean) > MIN_DEV_CPU) {
37
+ const cpuZ = History.zScore(data.cpu.usage, history.cpuUsage);
38
+ if (Math.abs(cpuZ) > ZSCORE_THRESHOLD) {
39
+ alerts.push({
40
+ metric: 'CPU',
41
+ value: data.cpu.usage,
42
+ zScore: cpuZ,
43
+ severity: Math.abs(cpuZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
44
+ });
45
+ }
46
+ }
47
+ }
48
+
49
+ if (history.memUsage.length >= MIN_SAMPLES) {
50
+ const memMean = History.mean(history.memUsage);
51
+ if (Math.abs(data.memory.usagePercent - memMean) > MIN_DEV_MEM) {
52
+ const memZ = History.zScore(data.memory.usagePercent, history.memUsage);
53
+ if (Math.abs(memZ) > ZSCORE_THRESHOLD) {
54
+ alerts.push({
55
+ metric: 'Memory',
56
+ value: data.memory.usagePercent,
57
+ zScore: memZ,
58
+ severity: Math.abs(memZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
59
+ });
60
+ }
61
+ }
34
62
  }
35
63
 
36
- const memZ = History.zScore(data.memory.usagePercent, history.memUsage);
37
- if (Math.abs(memZ) > ZSCORE_THRESHOLD && history.memUsage.length >= MIN_SAMPLES) {
38
- alerts.push({
39
- metric: 'Memory',
40
- value: data.memory.usagePercent,
41
- zScore: memZ,
42
- severity: Math.abs(memZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
43
- });
64
+ if (data.cpu.temperature !== undefined && history.temp.length >= MIN_SAMPLES) {
65
+ const tempMean = History.mean(history.temp);
66
+ if (Math.abs(data.cpu.temperature - tempMean) > MIN_DEV_TEMP) {
67
+ const tempZ = History.zScore(data.cpu.temperature, history.temp);
68
+ if (Math.abs(tempZ) > ZSCORE_THRESHOLD) {
69
+ alerts.push({
70
+ metric: 'Temp',
71
+ value: data.cpu.temperature,
72
+ zScore: tempZ,
73
+ severity: Math.abs(tempZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
74
+ });
75
+ }
76
+ }
44
77
  }
45
78
 
46
- if (data.cpu.temperature !== undefined) {
47
- const tempZ = History.zScore(data.cpu.temperature, history.temp);
48
- if (Math.abs(tempZ) > ZSCORE_THRESHOLD && history.temp.length >= MIN_SAMPLES) {
49
- alerts.push({
50
- metric: 'Temp',
51
- value: data.cpu.temperature,
52
- zScore: tempZ,
53
- severity: Math.abs(tempZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
54
- });
79
+ if (history.netRxRate.length >= MIN_SAMPLES) {
80
+ const currentRxRate = history.netRxRate[history.netRxRate.length - 1];
81
+ const netRxMean = History.mean(history.netRxRate);
82
+ if (Math.abs(currentRxRate - netRxMean) > MIN_DEV_NET) {
83
+ const netRxZ = History.zScore(currentRxRate, history.netRxRate);
84
+ if (Math.abs(netRxZ) > ZSCORE_THRESHOLD) {
85
+ alerts.push({
86
+ metric: 'Net RX',
87
+ value: currentRxRate,
88
+ zScore: netRxZ,
89
+ severity: Math.abs(netRxZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
90
+ });
91
+ }
55
92
  }
56
93
  }
57
94
 
58
- const netRxZ = History.zScore(data.network.rxBytes, history.netRxRate);
59
- if (Math.abs(netRxZ) > ZSCORE_THRESHOLD && history.netRxRate.length >= MIN_SAMPLES) {
60
- alerts.push({
61
- metric: 'Net RX',
62
- value: data.network.rxBytes,
63
- zScore: netRxZ,
64
- severity: Math.abs(netRxZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
65
- });
95
+ if (history.netTxRate.length >= MIN_SAMPLES) {
96
+ const currentTxRate = history.netTxRate[history.netTxRate.length - 1];
97
+ const netTxMean = History.mean(history.netTxRate);
98
+ if (Math.abs(currentTxRate - netTxMean) > MIN_DEV_NET) {
99
+ const netTxZ = History.zScore(currentTxRate, history.netTxRate);
100
+ if (Math.abs(netTxZ) > ZSCORE_THRESHOLD) {
101
+ alerts.push({
102
+ metric: 'Net TX',
103
+ value: currentTxRate,
104
+ zScore: netTxZ,
105
+ severity: Math.abs(netTxZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
106
+ });
107
+ }
108
+ }
66
109
  }
67
110
 
68
- const netTxZ = History.zScore(data.network.txBytes, history.netTxRate);
69
- if (Math.abs(netTxZ) > ZSCORE_THRESHOLD && history.netTxRate.length >= MIN_SAMPLES) {
70
- alerts.push({
71
- metric: 'Net TX',
72
- value: data.network.txBytes,
73
- zScore: netTxZ,
74
- severity: Math.abs(netTxZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
75
- });
111
+ if (data.power?.combinedWatts !== undefined && history.powerWatts.length >= MIN_SAMPLES) {
112
+ const powerMean = History.mean(history.powerWatts);
113
+ if (Math.abs(data.power.combinedWatts - powerMean) > MIN_DEV_POWER) {
114
+ const powerZ = History.zScore(data.power.combinedWatts, history.powerWatts);
115
+ if (Math.abs(powerZ) > ZSCORE_THRESHOLD) {
116
+ alerts.push({
117
+ metric: 'Power',
118
+ value: data.power.combinedWatts,
119
+ zScore: powerZ,
120
+ severity: Math.abs(powerZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
121
+ });
122
+ }
123
+ }
76
124
  }
77
125
 
78
- if (data.power?.combinedWatts !== undefined) {
79
- const powerZ = History.zScore(data.power.combinedWatts, history.powerWatts);
80
- if (Math.abs(powerZ) > ZSCORE_THRESHOLD && history.powerWatts.length >= MIN_SAMPLES) {
81
- alerts.push({
82
- metric: 'Power',
83
- value: data.power.combinedWatts,
84
- zScore: powerZ,
85
- severity: Math.abs(powerZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
86
- });
126
+ if (history.connections.length >= MIN_SAMPLES) {
127
+ const connMean = History.mean(history.connections);
128
+ if (Math.abs((data.packets?.connections || data.network.connections || 0) - connMean) > 5) {
129
+ const connZ = History.zScore(data.packets?.connections || data.network.connections || 0, history.connections);
130
+ if (Math.abs(connZ) > ZSCORE_THRESHOLD) {
131
+ alerts.push({
132
+ metric: 'Connections',
133
+ value: data.packets?.connections || data.network.connections || 0,
134
+ zScore: connZ,
135
+ severity: Math.abs(connZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
136
+ });
137
+ }
87
138
  }
88
139
  }
89
140
 
90
- if (data.gpu) {
91
- const gpuZ = History.zScore(data.gpu.utilization, history.gpuUtil);
92
- if (Math.abs(gpuZ) > ZSCORE_THRESHOLD && history.gpuUtil.length >= MIN_SAMPLES) {
93
- alerts.push({
94
- metric: 'GPU',
95
- value: data.gpu.utilization,
96
- zScore: gpuZ,
97
- severity: Math.abs(gpuZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
98
- });
141
+ if (data.gpu && history.gpuUtil.length >= MIN_SAMPLES) {
142
+ const gpuMean = History.mean(history.gpuUtil);
143
+ if (Math.abs(data.gpu.utilization - gpuMean) > MIN_DEV_GPU) {
144
+ const gpuZ = History.zScore(data.gpu.utilization, history.gpuUtil);
145
+ if (Math.abs(gpuZ) > ZSCORE_THRESHOLD) {
146
+ alerts.push({
147
+ metric: 'GPU',
148
+ value: data.gpu.utilization,
149
+ zScore: gpuZ,
150
+ severity: Math.abs(gpuZ) > CRITICAL_ZSCORE ? 'critical' : 'warning',
151
+ });
152
+ }
99
153
  }
100
154
  }
101
155
 
@@ -15,4 +15,4 @@ export { formatTable, clampWidth, gridColumns, thermalColor, capacityColor, getT
15
15
  export { formatJson, formatCsv, formatTsv, formatGraphs, formatHtml, formatMarkdown } from './output.js';
16
16
  export { getDashboardHtml } from './html_template.js';
17
17
  export type { ThemeName, ThemeColors, VisibleItems, TableOptions, AnomalyAlert } from './types.js';
18
- export { THEMES } from './themes.js';
18
+ export { THEMES } from './themes.js';
@@ -111,21 +111,38 @@ export function formatHtml(data: StatsData): string {
111
111
  <table>
112
112
  ${rows([
113
113
  { label: 'Interface', value: `${data.network.interface} (${data.network.ip})` },
114
- { label: 'RX', value: `${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt` },
115
- { label: 'TX', value: `${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt` },
114
+ { label: 'RX', value: `${formatBytes(data.network.rxBytes)} · ${formatBytes(data.network.rxRate)}/s` },
115
+ { label: 'TX', value: `${formatBytes(data.network.txBytes)} · ${formatBytes(data.network.txRate)}/s` },
116
+ ...(data.network.protocols ? [
117
+ { label: 'TCP', value: `${data.network.protocols.tcp.connections} conns` },
118
+ { label: 'UDP', value: `${data.network.protocols.udp.connections} conns` },
119
+ ] : []),
120
+ ...(data.network.connectionStates ? [
121
+ { label: 'Active', value: `${data.network.connectionStates.established} ESTABLISHED` },
122
+ { label: 'Listening', value: `${data.network.connectionStates.listening}` },
123
+ ] : []),
124
+ ...(data.network.listeningPorts && data.network.listeningPorts.length ? [
125
+ { label: 'Ports', value: data.network.listeningPorts.slice(0, 8).join(', ') },
126
+ ] : []),
116
127
  ]).trim()}
117
128
  </table>`);
118
129
 
119
130
  if (data.packets) {
120
- sections.push(`<h2>Packets</h2>
121
- <table>
122
- ${rows([
123
- { label: 'Total', value: `${data.packets.totalPackets} pkt` },
124
- { label: 'RX', value: `${data.packets.rxPackets} pkt` },
125
- { label: 'TX', value: `${data.packets.txPackets} pkt` },
126
- { label: 'Connections', value: `${data.packets.connections} TCP` },
127
- ]).trim()}
128
- </table>`);
131
+ const packetRows: { label: string; value: string }[] = [
132
+ { label: 'Total', value: `${data.packets.totalPackets} pkt` },
133
+ { label: 'RX', value: `${data.packets.rxPackets} pkt` },
134
+ { label: 'TX', value: `${data.packets.txPackets} pkt` },
135
+ { label: 'Connections', value: `${data.packets.connections} TCP` },
136
+ ];
137
+ if (data.packets.protocolStats) {
138
+ packetRows.push({ label: 'TCP', value: `${data.packets.protocolStats.tcp.connections} conns` });
139
+ packetRows.push({ label: 'UDP', value: `${data.packets.protocolStats.udp.connections} conns` });
140
+ }
141
+ if (data.packets.connectionStates) {
142
+ packetRows.push({ label: 'ESTABLISHED', value: `${data.packets.connectionStates.established}` });
143
+ packetRows.push({ label: 'LISTENING', value: `${data.packets.connectionStates.listening}` });
144
+ }
145
+ sections.push(`<h2>Packets</h2>\n<table>\n ${rows(packetRows).trim()}\n</table>`);
129
146
  }
130
147
 
131
148
  if (data.tasks.length) {
@@ -152,6 +169,14 @@ export function formatHtml(data: StatsData): string {
152
169
  sections.push(`<h2>Processes</h2>\n<table>\n ${rows(procRows).trim()}\n</table>`);
153
170
  }
154
171
 
172
+ if (data.blenderRenders && data.blenderRenders.length) {
173
+ const blenderRows = data.blenderRenders.map(r => ({
174
+ label: `PID ${r.pid}`,
175
+ value: `${r.blendFile} · ${r.renderEngine} · ${r.currentFrame}/${r.totalFrames} frames · ${r.completionPercent}% · ${r.status} · ${r.elapsedSec}s`,
176
+ }));
177
+ sections.push(`<h2>Blender Renders</h2>\n<table>\n ${rows(blenderRows).trim()}\n</table>`);
178
+ }
179
+
155
180
  return `<!DOCTYPE html>
156
181
  <html lang="en">
157
182
  <head>
@@ -380,8 +405,19 @@ export function formatMarkdown(data: StatsData): string {
380
405
  lines.push('| Property | Value |');
381
406
  lines.push('|---|---|');
382
407
  lines.push(`| Interface | ${mdCell(`${data.network.interface} (${data.network.ip})`)} |`);
383
- lines.push(`| RX | ${mdCell(`${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt`)} |`);
384
- lines.push(`| TX | ${mdCell(`${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt`)} |`);
408
+ lines.push(`| RX | ${mdCell(`${formatBytes(data.network.rxBytes)} · ${formatBytes(data.network.rxRate)}/s`)} |`);
409
+ lines.push(`| TX | ${mdCell(`${formatBytes(data.network.txBytes)} · ${formatBytes(data.network.txRate)}/s`)} |`);
410
+ if (data.network.protocols) {
411
+ lines.push(`| TCP | ${mdCell(`${data.network.protocols.tcp.connections} conns`)} |`);
412
+ lines.push(`| UDP | ${mdCell(`${data.network.protocols.udp.connections} conns`)} |`);
413
+ }
414
+ if (data.network.connectionStates) {
415
+ lines.push(`| Active | ${mdCell(`${data.network.connectionStates.established} ESTABLISHED`)} |`);
416
+ lines.push(`| Listening | ${mdCell(`${data.network.connectionStates.listening}`)} |`);
417
+ }
418
+ if (data.network.listeningPorts && data.network.listeningPorts.length) {
419
+ lines.push(`| Ports | ${mdCell(data.network.listeningPorts.slice(0, 8).join(', '))} |`);
420
+ }
385
421
  lines.push('');
386
422
 
387
423
  if (data.packets) {
@@ -393,6 +429,14 @@ export function formatMarkdown(data: StatsData): string {
393
429
  lines.push(`| RX | ${mdCell(`${data.packets.rxPackets} pkt`)} |`);
394
430
  lines.push(`| TX | ${mdCell(`${data.packets.txPackets} pkt`)} |`);
395
431
  lines.push(`| Connections | ${mdCell(`${data.packets.connections} TCP`)} |`);
432
+ if (data.packets.protocolStats) {
433
+ lines.push(`| TCP | ${mdCell(`${data.packets.protocolStats.tcp.connections} conns`)} |`);
434
+ lines.push(`| UDP | ${mdCell(`${data.packets.protocolStats.udp.connections} conns`)} |`);
435
+ }
436
+ if (data.packets.connectionStates) {
437
+ lines.push(`| ESTABLISHED | ${mdCell(`${data.packets.connectionStates.established}`)} |`);
438
+ lines.push(`| LISTENING | ${mdCell(`${data.packets.connectionStates.listening}`)} |`);
439
+ }
396
440
  lines.push('');
397
441
  }
398
442
 
@@ -429,6 +473,17 @@ export function formatMarkdown(data: StatsData): string {
429
473
  lines.push('');
430
474
  }
431
475
 
476
+ if (data.blenderRenders && data.blenderRenders.length) {
477
+ lines.push('## Blender Renders');
478
+ lines.push('');
479
+ lines.push('| PID | Blend File | Engine | Frames | Completion | Status | Elapsed |');
480
+ lines.push('|---|---|---|---|---|---|---|');
481
+ for (const r of data.blenderRenders) {
482
+ lines.push(`| ${r.pid} | ${mdCell(r.blendFile)} | ${mdCell(r.renderEngine)} | ${r.currentFrame}/${r.totalFrames} | ${r.completionPercent}% | ${r.status} | ${r.elapsedSec}s |`);
483
+ }
484
+ lines.push('');
485
+ }
486
+
432
487
  return lines.join('\n');
433
488
  }
434
489
 
@@ -465,9 +520,16 @@ export function formatCsv(data: StatsData): string {
465
520
  ['thermal', 'cpuTempF', data.thermal.temperatures?.cpu_die != null ? String(celsiusToFahrenheit(data.thermal.temperatures.cpu_die)) : ''],
466
521
  ['network', 'rxBytes', String(data.network.rxBytes)],
467
522
  ['network', 'txBytes', String(data.network.txBytes)],
523
+ ['network', 'rxRate', String(data.network.rxRate)],
524
+ ['network', 'txRate', String(data.network.txRate)],
468
525
  ['network', 'rxPackets', String(data.network.rxPackets)],
469
526
  ['network', 'txPackets', String(data.network.txPackets)],
470
527
  ['network', 'connections', data.network.connections ? String(data.network.connections) : ''],
528
+ ['network', 'tcpConns', data.network.protocols ? String(data.network.protocols.tcp.connections) : ''],
529
+ ['network', 'udpConns', data.network.protocols ? String(data.network.protocols.udp.connections) : ''],
530
+ ['network', 'established', data.network.connectionStates ? String(data.network.connectionStates.established) : ''],
531
+ ['network', 'listening', data.network.connectionStates ? String(data.network.connectionStates.listening) : ''],
532
+ ['network', 'listeningPorts', data.network.listeningPorts ? data.network.listeningPorts.slice(0, 10).join(';') : ''],
471
533
  ['battery', 'level', data.battery ? String(data.battery.level) : ''],
472
534
  ['battery', 'powerSource', data.battery ? data.battery.powerSource : ''],
473
535
  ['battery', 'condition', data.battery?.condition ?? ''],
@@ -492,6 +554,16 @@ export function formatCsv(data: StatsData): string {
492
554
  rows.push(['packets', 'rxPackets', String(data.packets.rxPackets)]);
493
555
  rows.push(['packets', 'txPackets', String(data.packets.txPackets)]);
494
556
  rows.push(['packets', 'connections', String(data.packets.connections)]);
557
+ if (data.packets.protocolStats) {
558
+ rows.push(['packets', 'tcpConns', String(data.packets.protocolStats.tcp.connections)]);
559
+ rows.push(['packets', 'udpConns', String(data.packets.protocolStats.udp.connections)]);
560
+ }
561
+ if (data.packets.connectionStates) {
562
+ rows.push(['packets', 'established', String(data.packets.connectionStates.established)]);
563
+ rows.push(['packets', 'listening', String(data.packets.connectionStates.listening)]);
564
+ rows.push(['packets', 'timeWait', String(data.packets.connectionStates.timeWait)]);
565
+ rows.push(['packets', 'closeWait', String(data.packets.connectionStates.closeWait)]);
566
+ }
495
567
  for (const iface of data.packets.interfaces || []) {
496
568
  rows.push(['packets_iface', iface.iface, `${iface.rxPackets},${iface.txPackets},${iface.rxBytes},${iface.txBytes}`]);
497
569
  }
@@ -519,6 +591,21 @@ export function formatCsv(data: StatsData): string {
519
591
  rows.push(['process', `runtime=${p.pid}`, String(p.runtime)]);
520
592
  rows.push(['process', `command=${p.pid}`, p.command]);
521
593
  }
594
+
595
+ for (const r of data.blenderRenders || []) {
596
+ rows.push(['blender', `pid=${r.pid}`, `${r.pid}`]);
597
+ rows.push(['blender', `blend_file=${r.pid}`, r.blendFile]);
598
+ rows.push(['blender', `engine=${r.pid}`, r.renderEngine]);
599
+ rows.push(['blender', `frame_start=${r.pid}`, String(r.frameStart)]);
600
+ rows.push(['blender', `frame_end=${r.pid}`, String(r.frameEnd)]);
601
+ rows.push(['blender', `current_frame=${r.pid}`, String(r.currentFrame)]);
602
+ rows.push(['blender', `total_frames=${r.pid}`, String(r.totalFrames)]);
603
+ rows.push(['blender', `completion_pct=${r.pid}`, String(r.completionPercent)]);
604
+ rows.push(['blender', `status=${r.pid}`, r.status]);
605
+ rows.push(['blender', `elapsed_sec=${r.pid}`, String(r.elapsedSec)]);
606
+ rows.push(['blender', `output_path=${r.pid}`, r.outputPath]);
607
+ if (r.sampleCount !== undefined) rows.push(['blender', `samples=${r.pid}`, String(r.sampleCount)]);
608
+ }
522
609
  return rows.map(r => r.map(item => `"${String(item).replace(/"/g, '""')}"`).join(',')).join('\n');
523
610
  }
524
611