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/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  CONFIG_FILE,
4
4
  readConfig,
5
5
  writeConfig
6
- } from "./chunk-RVGKEJXT.js";
6
+ } from "./chunk-CXL4GYGQ.js";
7
7
  import {
8
8
  TAB_BAR_ROW,
9
9
  THEMES,
@@ -15,15 +15,16 @@ import {
15
15
  panel,
16
16
  sparkline,
17
17
  visLen
18
- } from "./chunk-MOAEGMFP.js";
19
- import "./chunk-SG26Y3WF.js";
18
+ } from "./chunk-3TE7MJLS.js";
19
+ import "./chunk-IM3IQMMT.js";
20
20
  import {
21
21
  collectAll
22
- } from "./chunk-PV4MXTAJ.js";
22
+ } from "./chunk-N4WJVF5M.js";
23
+ import "./chunk-25I533OF.js";
23
24
  import "./chunk-FH5WDJBF.js";
24
25
 
25
26
  // src/index.ts
26
- import fs8 from "fs";
27
+ import fs9 from "fs";
27
28
  import os2 from "os";
28
29
  import path5 from "path";
29
30
  import readline2 from "readline";
@@ -124,19 +125,39 @@ function formatHtml(data) {
124
125
  <table>
125
126
  ${rows([
126
127
  { label: "Interface", value: `${data.network.interface} (${data.network.ip})` },
127
- { label: "RX", value: `${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt` },
128
- { label: "TX", value: `${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt` }
128
+ { label: "RX", value: `${formatBytes(data.network.rxBytes)} \xB7 ${formatBytes(data.network.rxRate)}/s` },
129
+ { label: "TX", value: `${formatBytes(data.network.txBytes)} \xB7 ${formatBytes(data.network.txRate)}/s` },
130
+ ...data.network.protocols ? [
131
+ { label: "TCP", value: `${data.network.protocols.tcp.connections} conns` },
132
+ { label: "UDP", value: `${data.network.protocols.udp.connections} conns` }
133
+ ] : [],
134
+ ...data.network.connectionStates ? [
135
+ { label: "Active", value: `${data.network.connectionStates.established} ESTABLISHED` },
136
+ { label: "Listening", value: `${data.network.connectionStates.listening}` }
137
+ ] : [],
138
+ ...data.network.listeningPorts && data.network.listeningPorts.length ? [
139
+ { label: "Ports", value: data.network.listeningPorts.slice(0, 8).join(", ") }
140
+ ] : []
129
141
  ]).trim()}
130
142
  </table>`);
131
143
  if (data.packets) {
132
- sections.push(`<h2>Packets</h2>
133
- <table>
134
- ${rows([
144
+ const packetRows = [
135
145
  { label: "Total", value: `${data.packets.totalPackets} pkt` },
136
146
  { label: "RX", value: `${data.packets.rxPackets} pkt` },
137
147
  { label: "TX", value: `${data.packets.txPackets} pkt` },
138
148
  { label: "Connections", value: `${data.packets.connections} TCP` }
139
- ]).trim()}
149
+ ];
150
+ if (data.packets.protocolStats) {
151
+ packetRows.push({ label: "TCP", value: `${data.packets.protocolStats.tcp.connections} conns` });
152
+ packetRows.push({ label: "UDP", value: `${data.packets.protocolStats.udp.connections} conns` });
153
+ }
154
+ if (data.packets.connectionStates) {
155
+ packetRows.push({ label: "ESTABLISHED", value: `${data.packets.connectionStates.established}` });
156
+ packetRows.push({ label: "LISTENING", value: `${data.packets.connectionStates.listening}` });
157
+ }
158
+ sections.push(`<h2>Packets</h2>
159
+ <table>
160
+ ${rows(packetRows).trim()}
140
161
  </table>`);
141
162
  }
142
163
  if (data.tasks.length) {
@@ -167,6 +188,16 @@ function formatHtml(data) {
167
188
  sections.push(`<h2>Processes</h2>
168
189
  <table>
169
190
  ${rows(procRows).trim()}
191
+ </table>`);
192
+ }
193
+ if (data.blenderRenders && data.blenderRenders.length) {
194
+ const blenderRows = data.blenderRenders.map((r) => ({
195
+ label: `PID ${r.pid}`,
196
+ value: `${r.blendFile} \xB7 ${r.renderEngine} \xB7 ${r.currentFrame}/${r.totalFrames} frames \xB7 ${r.completionPercent}% \xB7 ${r.status} \xB7 ${r.elapsedSec}s`
197
+ }));
198
+ sections.push(`<h2>Blender Renders</h2>
199
+ <table>
200
+ ${rows(blenderRows).trim()}
170
201
  </table>`);
171
202
  }
172
203
  return `<!DOCTYPE html>
@@ -388,8 +419,19 @@ function formatMarkdown(data) {
388
419
  lines.push("| Property | Value |");
389
420
  lines.push("|---|---|");
390
421
  lines.push(`| Interface | ${mdCell(`${data.network.interface} (${data.network.ip})`)} |`);
391
- lines.push(`| RX | ${mdCell(`${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt`)} |`);
392
- lines.push(`| TX | ${mdCell(`${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt`)} |`);
422
+ lines.push(`| RX | ${mdCell(`${formatBytes(data.network.rxBytes)} \xB7 ${formatBytes(data.network.rxRate)}/s`)} |`);
423
+ lines.push(`| TX | ${mdCell(`${formatBytes(data.network.txBytes)} \xB7 ${formatBytes(data.network.txRate)}/s`)} |`);
424
+ if (data.network.protocols) {
425
+ lines.push(`| TCP | ${mdCell(`${data.network.protocols.tcp.connections} conns`)} |`);
426
+ lines.push(`| UDP | ${mdCell(`${data.network.protocols.udp.connections} conns`)} |`);
427
+ }
428
+ if (data.network.connectionStates) {
429
+ lines.push(`| Active | ${mdCell(`${data.network.connectionStates.established} ESTABLISHED`)} |`);
430
+ lines.push(`| Listening | ${mdCell(`${data.network.connectionStates.listening}`)} |`);
431
+ }
432
+ if (data.network.listeningPorts && data.network.listeningPorts.length) {
433
+ lines.push(`| Ports | ${mdCell(data.network.listeningPorts.slice(0, 8).join(", "))} |`);
434
+ }
393
435
  lines.push("");
394
436
  if (data.packets) {
395
437
  lines.push("## Packets");
@@ -400,6 +442,14 @@ function formatMarkdown(data) {
400
442
  lines.push(`| RX | ${mdCell(`${data.packets.rxPackets} pkt`)} |`);
401
443
  lines.push(`| TX | ${mdCell(`${data.packets.txPackets} pkt`)} |`);
402
444
  lines.push(`| Connections | ${mdCell(`${data.packets.connections} TCP`)} |`);
445
+ if (data.packets.protocolStats) {
446
+ lines.push(`| TCP | ${mdCell(`${data.packets.protocolStats.tcp.connections} conns`)} |`);
447
+ lines.push(`| UDP | ${mdCell(`${data.packets.protocolStats.udp.connections} conns`)} |`);
448
+ }
449
+ if (data.packets.connectionStates) {
450
+ lines.push(`| ESTABLISHED | ${mdCell(`${data.packets.connectionStates.established}`)} |`);
451
+ lines.push(`| LISTENING | ${mdCell(`${data.packets.connectionStates.listening}`)} |`);
452
+ }
403
453
  lines.push("");
404
454
  }
405
455
  if (data.tasks.length) {
@@ -432,6 +482,16 @@ function formatMarkdown(data) {
432
482
  }
433
483
  lines.push("");
434
484
  }
485
+ if (data.blenderRenders && data.blenderRenders.length) {
486
+ lines.push("## Blender Renders");
487
+ lines.push("");
488
+ lines.push("| PID | Blend File | Engine | Frames | Completion | Status | Elapsed |");
489
+ lines.push("|---|---|---|---|---|---|---|");
490
+ for (const r of data.blenderRenders) {
491
+ lines.push(`| ${r.pid} | ${mdCell(r.blendFile)} | ${mdCell(r.renderEngine)} | ${r.currentFrame}/${r.totalFrames} | ${r.completionPercent}% | ${r.status} | ${r.elapsedSec}s |`);
492
+ }
493
+ lines.push("");
494
+ }
435
495
  return lines.join("\n");
436
496
  }
437
497
  function escapeHtml(s) {
@@ -461,9 +521,16 @@ function formatCsv(data) {
461
521
  ["thermal", "cpuTempF", data.thermal.temperatures?.cpu_die != null ? String(celsiusToFahrenheit(data.thermal.temperatures.cpu_die)) : ""],
462
522
  ["network", "rxBytes", String(data.network.rxBytes)],
463
523
  ["network", "txBytes", String(data.network.txBytes)],
524
+ ["network", "rxRate", String(data.network.rxRate)],
525
+ ["network", "txRate", String(data.network.txRate)],
464
526
  ["network", "rxPackets", String(data.network.rxPackets)],
465
527
  ["network", "txPackets", String(data.network.txPackets)],
466
528
  ["network", "connections", data.network.connections ? String(data.network.connections) : ""],
529
+ ["network", "tcpConns", data.network.protocols ? String(data.network.protocols.tcp.connections) : ""],
530
+ ["network", "udpConns", data.network.protocols ? String(data.network.protocols.udp.connections) : ""],
531
+ ["network", "established", data.network.connectionStates ? String(data.network.connectionStates.established) : ""],
532
+ ["network", "listening", data.network.connectionStates ? String(data.network.connectionStates.listening) : ""],
533
+ ["network", "listeningPorts", data.network.listeningPorts ? data.network.listeningPorts.slice(0, 10).join(";") : ""],
467
534
  ["battery", "level", data.battery ? String(data.battery.level) : ""],
468
535
  ["battery", "powerSource", data.battery ? data.battery.powerSource : ""],
469
536
  ["battery", "condition", data.battery?.condition ?? ""],
@@ -487,6 +554,16 @@ function formatCsv(data) {
487
554
  rows.push(["packets", "rxPackets", String(data.packets.rxPackets)]);
488
555
  rows.push(["packets", "txPackets", String(data.packets.txPackets)]);
489
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
+ }
490
567
  for (const iface of data.packets.interfaces || []) {
491
568
  rows.push(["packets_iface", iface.iface, `${iface.rxPackets},${iface.txPackets},${iface.rxBytes},${iface.txBytes}`]);
492
569
  }
@@ -512,6 +589,20 @@ function formatCsv(data) {
512
589
  rows.push(["process", `runtime=${p.pid}`, String(p.runtime)]);
513
590
  rows.push(["process", `command=${p.pid}`, p.command]);
514
591
  }
592
+ for (const r of data.blenderRenders || []) {
593
+ rows.push(["blender", `pid=${r.pid}`, `${r.pid}`]);
594
+ rows.push(["blender", `blend_file=${r.pid}`, r.blendFile]);
595
+ rows.push(["blender", `engine=${r.pid}`, r.renderEngine]);
596
+ rows.push(["blender", `frame_start=${r.pid}`, String(r.frameStart)]);
597
+ rows.push(["blender", `frame_end=${r.pid}`, String(r.frameEnd)]);
598
+ rows.push(["blender", `current_frame=${r.pid}`, String(r.currentFrame)]);
599
+ rows.push(["blender", `total_frames=${r.pid}`, String(r.totalFrames)]);
600
+ rows.push(["blender", `completion_pct=${r.pid}`, String(r.completionPercent)]);
601
+ rows.push(["blender", `status=${r.pid}`, r.status]);
602
+ rows.push(["blender", `elapsed_sec=${r.pid}`, String(r.elapsedSec)]);
603
+ rows.push(["blender", `output_path=${r.pid}`, r.outputPath]);
604
+ if (r.sampleCount !== void 0) rows.push(["blender", `samples=${r.pid}`, String(r.sampleCount)]);
605
+ }
515
606
  return rows.map((r) => r.map((item) => `"${String(item).replace(/"/g, '""')}"`).join(",")).join("\n");
516
607
  }
517
608
  function formatTsv(data) {
@@ -1237,6 +1328,9 @@ document.addEventListener('keydown', e => { if(keyMap[e.key.toLowerCase()]) swit
1237
1328
  // src/live/input.ts
1238
1329
  import readline from "readline";
1239
1330
 
1331
+ // src/live/state.ts
1332
+ import fs from "fs";
1333
+
1240
1334
  // src/history.ts
1241
1335
  var History = class _History {
1242
1336
  /** Maximum number of samples retained in the rolling window. */
@@ -1396,6 +1490,7 @@ var History = class _History {
1396
1490
  var SIGNAL_OPTIONS = ["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGSTOP", "SIGCONT"];
1397
1491
  var MENU_OPTIONS = ["Resume Dashboard", "Settings (Customizer)", "Readme", "Credits", "Quit pyre"];
1398
1492
  var config = readConfig();
1493
+ var isDockerEnv = fs.existsSync("/.dockerenv") || fs.existsSync("/run/.containerenv");
1399
1494
  var state = {
1400
1495
  intervalHandle: null,
1401
1496
  uiIntervalHandle: null,
@@ -1420,7 +1515,9 @@ var state = {
1420
1515
  packets: config.visiblePanels.packets,
1421
1516
  tasks: config.visiblePanels.tasks,
1422
1517
  disk: config.visiblePanels.disk,
1423
- process: config.visiblePanels.process
1518
+ process: config.visiblePanels.process,
1519
+ containers: config.visiblePanels.containers,
1520
+ blender: config.visiblePanels.blender
1424
1521
  },
1425
1522
  panelLayout: config.panelLayout || ["mem", "disk", "net"],
1426
1523
  logging: false,
@@ -1438,7 +1535,7 @@ var state = {
1438
1535
  processScrollOffset: 0,
1439
1536
  trackedPid: null,
1440
1537
  inspectingProcess: null,
1441
- inputMode: null,
1538
+ inputMode: isDockerEnv && !config.dockerModeConfirmed ? "docker-alert" : null,
1442
1539
  inputBuffer: "",
1443
1540
  targetPid: "",
1444
1541
  selectedSignal: "SIGTERM",
@@ -1460,10 +1557,12 @@ var state = {
1460
1557
  "Toggle Battery",
1461
1558
  "Toggle Thermal",
1462
1559
  "Toggle Network",
1463
- "Toggle Packets",
1560
+ "Toggle Conns",
1464
1561
  "Toggle Tasks",
1465
1562
  "Toggle Disk",
1466
1563
  "Toggle Processes",
1564
+ "Toggle Containers",
1565
+ "Toggle Blender",
1467
1566
  "Toggle Tree View"
1468
1567
  ],
1469
1568
  CPU_ALERT_PCT: config.cpuAlertPct,
@@ -1485,12 +1584,14 @@ var state = {
1485
1584
  { id: "battery", label: "Battery", key: "5" },
1486
1585
  { id: "thermal", label: "Thermal", key: "6" },
1487
1586
  { id: "network", label: "Network", key: "7" },
1488
- { id: "packets", label: "Packets", key: "8" },
1587
+ { id: "packets", label: "Conns", key: "8" },
1489
1588
  { id: "tasks", label: "Tasks", key: "9" },
1490
1589
  { id: "disk", label: "Disk", key: "0" },
1491
1590
  { id: "process", label: "Process", key: "P" },
1591
+ { id: "containers", label: "Containers", key: "C" },
1492
1592
  { id: "p2p", label: "P2P", key: "R" },
1493
- { id: "anomalies", label: "Anomalies", key: "A" }
1593
+ { id: "anomalies", label: "Anomalies", key: "A" },
1594
+ { id: "blender", label: "Blender", key: "B" }
1494
1595
  ],
1495
1596
  p2pServer: null,
1496
1597
  p2pClient: null,
@@ -1505,7 +1606,10 @@ var state = {
1505
1606
  splashColorScheme: config.splashColorScheme,
1506
1607
  splashAnimation: config.splashAnimation,
1507
1608
  menuSelectionIndex: 0,
1508
- readmeScrollOffset: 0
1609
+ readmeScrollOffset: 0,
1610
+ isDocker: isDockerEnv,
1611
+ dockerModeConfirmed: config.dockerModeConfirmed || false,
1612
+ dockerSelectionIndex: 0
1509
1613
  };
1510
1614
  function setStatus(msg, ms = 3e3) {
1511
1615
  state.statusMessage = msg;
@@ -1523,20 +1627,22 @@ function getToggleKey(opt) {
1523
1627
  "Toggle Battery": "battery",
1524
1628
  "Toggle Thermal": "thermal",
1525
1629
  "Toggle Network": "network",
1526
- "Toggle Packets": "packets",
1630
+ "Toggle Conns": "packets",
1527
1631
  "Toggle Tasks": "tasks",
1528
1632
  "Toggle Disk": "disk",
1529
1633
  "Toggle Processes": "process",
1634
+ "Toggle Containers": "containers",
1635
+ "Toggle Blender": "blender",
1530
1636
  "Toggle Tree View": "tree"
1531
1637
  };
1532
1638
  return map[opt] ?? null;
1533
1639
  }
1534
1640
 
1535
1641
  // src/live/export.ts
1536
- import fs from "fs";
1642
+ import fs2 from "fs";
1537
1643
  import path from "path";
1538
1644
  function ensureDir(dir) {
1539
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1645
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
1540
1646
  }
1541
1647
  function timestampForFile() {
1542
1648
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -1568,7 +1674,7 @@ function exportSnapshot() {
1568
1674
  content = formatMarkdown(state.lastData);
1569
1675
  break;
1570
1676
  }
1571
- fs.writeFileSync(file, content);
1677
+ fs2.writeFileSync(file, content);
1572
1678
  setStatus(`Exported snapshot -> ${file}`);
1573
1679
  } catch (err) {
1574
1680
  setStatus(`Export failed: ${err.message}`);
@@ -1579,7 +1685,7 @@ function startLogging() {
1579
1685
  ensureDir(state.exportDir);
1580
1686
  rotateLogs();
1581
1687
  const file = path.join(state.exportDir, `pyre-log-${timestampForFile()}.csv`);
1582
- state.logStream = fs.createWriteStream(file, { flags: "a" });
1688
+ state.logStream = fs2.createWriteStream(file, { flags: "a" });
1583
1689
  state.logStream.write("timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_packets,net_tx_packets,connections,thermal_state\n");
1584
1690
  state.logging = true;
1585
1691
  setStatus(`Logging every tick -> ${file}`);
@@ -1593,37 +1699,37 @@ var MAX_LOG_BYTES = 50 * 1024 * 1024;
1593
1699
  function rotateLogs() {
1594
1700
  try {
1595
1701
  const dir = state.exportDir;
1596
- if (!fs.existsSync(dir)) return;
1597
- const files = fs.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv")).map((f) => ({ name: f, path: path.join(dir, f), mtime: fs.statSync(path.join(dir, f)).mtimeMs, size: fs.statSync(path.join(dir, f)).size })).sort((a, b) => a.mtime - b.mtime);
1702
+ if (!fs2.existsSync(dir)) return;
1703
+ const files = fs2.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv")).map((f) => ({ name: f, path: path.join(dir, f), mtime: fs2.statSync(path.join(dir, f)).mtimeMs, size: fs2.statSync(path.join(dir, f)).size })).sort((a, b) => a.mtime - b.mtime);
1598
1704
  const now = Date.now();
1599
1705
  for (const f of files) {
1600
1706
  if (now - f.mtime > MAX_LOG_AGE_MS) {
1601
1707
  try {
1602
- fs.unlinkSync(f.path);
1708
+ fs2.unlinkSync(f.path);
1603
1709
  } catch {
1604
1710
  }
1605
1711
  }
1606
1712
  }
1607
- const remaining = fs.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
1713
+ const remaining = fs2.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
1608
1714
  if (remaining.length > MAX_LOG_COUNT) {
1609
1715
  const toDelete = remaining.sort().slice(0, remaining.length - MAX_LOG_COUNT);
1610
1716
  for (const f of toDelete) {
1611
1717
  try {
1612
- fs.unlinkSync(path.join(dir, f));
1718
+ fs2.unlinkSync(path.join(dir, f));
1613
1719
  } catch {
1614
1720
  }
1615
1721
  }
1616
1722
  }
1617
1723
  let totalSize = 0;
1618
1724
  for (const f of remaining) {
1619
- totalSize += fs.statSync(path.join(dir, f)).size;
1725
+ totalSize += fs2.statSync(path.join(dir, f)).size;
1620
1726
  }
1621
1727
  if (totalSize > MAX_LOG_BYTES) {
1622
1728
  const sorted = remaining.sort();
1623
1729
  for (const f of sorted) {
1624
- const sz = fs.statSync(path.join(dir, f)).size;
1730
+ const sz = fs2.statSync(path.join(dir, f)).size;
1625
1731
  try {
1626
- fs.unlinkSync(path.join(dir, f));
1732
+ fs2.unlinkSync(path.join(dir, f));
1627
1733
  } catch {
1628
1734
  }
1629
1735
  totalSize -= sz;
@@ -1660,7 +1766,7 @@ function writeLogRow(data) {
1660
1766
 
1661
1767
  // src/live/render.ts
1662
1768
  import { execFile, exec } from "child_process";
1663
- import fs2 from "fs";
1769
+ import fs3 from "fs";
1664
1770
  import path2 from "path";
1665
1771
  import { fileURLToPath } from "url";
1666
1772
  import chalk2 from "chalk";
@@ -1668,76 +1774,127 @@ import chalk2 from "chalk";
1668
1774
  // src/anomalies.ts
1669
1775
  var ZSCORE_THRESHOLD = 2.5;
1670
1776
  var CRITICAL_ZSCORE = 3.5;
1671
- var MIN_SAMPLES = 5;
1777
+ var MIN_SAMPLES = 10;
1778
+ var MIN_DEV_CPU = 10;
1779
+ var MIN_DEV_MEM = 5;
1780
+ var MIN_DEV_TEMP = 5;
1781
+ var MIN_DEV_NET = 5e5;
1782
+ var MIN_DEV_POWER = 5;
1783
+ var MIN_DEV_GPU = 10;
1672
1784
  function detectAnomalies(data, history) {
1673
1785
  const alerts = [];
1674
- const cpuZ = History.zScore(data.cpu.usage, history.cpuUsage);
1675
- if (Math.abs(cpuZ) > ZSCORE_THRESHOLD && history.cpuUsage.length >= MIN_SAMPLES) {
1676
- alerts.push({
1677
- metric: "CPU",
1678
- value: data.cpu.usage,
1679
- zScore: cpuZ,
1680
- severity: Math.abs(cpuZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1681
- });
1786
+ if (history.cpuUsage.length >= MIN_SAMPLES) {
1787
+ const cpuMean = History.mean(history.cpuUsage);
1788
+ if (Math.abs(data.cpu.usage - cpuMean) > MIN_DEV_CPU) {
1789
+ const cpuZ = History.zScore(data.cpu.usage, history.cpuUsage);
1790
+ if (Math.abs(cpuZ) > ZSCORE_THRESHOLD) {
1791
+ alerts.push({
1792
+ metric: "CPU",
1793
+ value: data.cpu.usage,
1794
+ zScore: cpuZ,
1795
+ severity: Math.abs(cpuZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1796
+ });
1797
+ }
1798
+ }
1682
1799
  }
1683
- const memZ = History.zScore(data.memory.usagePercent, history.memUsage);
1684
- if (Math.abs(memZ) > ZSCORE_THRESHOLD && history.memUsage.length >= MIN_SAMPLES) {
1685
- alerts.push({
1686
- metric: "Memory",
1687
- value: data.memory.usagePercent,
1688
- zScore: memZ,
1689
- severity: Math.abs(memZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1690
- });
1800
+ if (history.memUsage.length >= MIN_SAMPLES) {
1801
+ const memMean = History.mean(history.memUsage);
1802
+ if (Math.abs(data.memory.usagePercent - memMean) > MIN_DEV_MEM) {
1803
+ const memZ = History.zScore(data.memory.usagePercent, history.memUsage);
1804
+ if (Math.abs(memZ) > ZSCORE_THRESHOLD) {
1805
+ alerts.push({
1806
+ metric: "Memory",
1807
+ value: data.memory.usagePercent,
1808
+ zScore: memZ,
1809
+ severity: Math.abs(memZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1810
+ });
1811
+ }
1812
+ }
1691
1813
  }
1692
- if (data.cpu.temperature !== void 0) {
1693
- const tempZ = History.zScore(data.cpu.temperature, history.temp);
1694
- if (Math.abs(tempZ) > ZSCORE_THRESHOLD && history.temp.length >= MIN_SAMPLES) {
1695
- alerts.push({
1696
- metric: "Temp",
1697
- value: data.cpu.temperature,
1698
- zScore: tempZ,
1699
- severity: Math.abs(tempZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1700
- });
1814
+ if (data.cpu.temperature !== void 0 && history.temp.length >= MIN_SAMPLES) {
1815
+ const tempMean = History.mean(history.temp);
1816
+ if (Math.abs(data.cpu.temperature - tempMean) > MIN_DEV_TEMP) {
1817
+ const tempZ = History.zScore(data.cpu.temperature, history.temp);
1818
+ if (Math.abs(tempZ) > ZSCORE_THRESHOLD) {
1819
+ alerts.push({
1820
+ metric: "Temp",
1821
+ value: data.cpu.temperature,
1822
+ zScore: tempZ,
1823
+ severity: Math.abs(tempZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1824
+ });
1825
+ }
1701
1826
  }
1702
1827
  }
1703
- const netRxZ = History.zScore(data.network.rxBytes, history.netRxRate);
1704
- if (Math.abs(netRxZ) > ZSCORE_THRESHOLD && history.netRxRate.length >= MIN_SAMPLES) {
1705
- alerts.push({
1706
- metric: "Net RX",
1707
- value: data.network.rxBytes,
1708
- zScore: netRxZ,
1709
- severity: Math.abs(netRxZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1710
- });
1828
+ if (history.netRxRate.length >= MIN_SAMPLES) {
1829
+ const currentRxRate = history.netRxRate[history.netRxRate.length - 1];
1830
+ const netRxMean = History.mean(history.netRxRate);
1831
+ if (Math.abs(currentRxRate - netRxMean) > MIN_DEV_NET) {
1832
+ const netRxZ = History.zScore(currentRxRate, history.netRxRate);
1833
+ if (Math.abs(netRxZ) > ZSCORE_THRESHOLD) {
1834
+ alerts.push({
1835
+ metric: "Net RX",
1836
+ value: currentRxRate,
1837
+ zScore: netRxZ,
1838
+ severity: Math.abs(netRxZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1839
+ });
1840
+ }
1841
+ }
1711
1842
  }
1712
- const netTxZ = History.zScore(data.network.txBytes, history.netTxRate);
1713
- if (Math.abs(netTxZ) > ZSCORE_THRESHOLD && history.netTxRate.length >= MIN_SAMPLES) {
1714
- alerts.push({
1715
- metric: "Net TX",
1716
- value: data.network.txBytes,
1717
- zScore: netTxZ,
1718
- severity: Math.abs(netTxZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1719
- });
1843
+ if (history.netTxRate.length >= MIN_SAMPLES) {
1844
+ const currentTxRate = history.netTxRate[history.netTxRate.length - 1];
1845
+ const netTxMean = History.mean(history.netTxRate);
1846
+ if (Math.abs(currentTxRate - netTxMean) > MIN_DEV_NET) {
1847
+ const netTxZ = History.zScore(currentTxRate, history.netTxRate);
1848
+ if (Math.abs(netTxZ) > ZSCORE_THRESHOLD) {
1849
+ alerts.push({
1850
+ metric: "Net TX",
1851
+ value: currentTxRate,
1852
+ zScore: netTxZ,
1853
+ severity: Math.abs(netTxZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1854
+ });
1855
+ }
1856
+ }
1720
1857
  }
1721
- if (data.power?.combinedWatts !== void 0) {
1722
- const powerZ = History.zScore(data.power.combinedWatts, history.powerWatts);
1723
- if (Math.abs(powerZ) > ZSCORE_THRESHOLD && history.powerWatts.length >= MIN_SAMPLES) {
1724
- alerts.push({
1725
- metric: "Power",
1726
- value: data.power.combinedWatts,
1727
- zScore: powerZ,
1728
- severity: Math.abs(powerZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1729
- });
1858
+ if (data.power?.combinedWatts !== void 0 && history.powerWatts.length >= MIN_SAMPLES) {
1859
+ const powerMean = History.mean(history.powerWatts);
1860
+ if (Math.abs(data.power.combinedWatts - powerMean) > MIN_DEV_POWER) {
1861
+ const powerZ = History.zScore(data.power.combinedWatts, history.powerWatts);
1862
+ if (Math.abs(powerZ) > ZSCORE_THRESHOLD) {
1863
+ alerts.push({
1864
+ metric: "Power",
1865
+ value: data.power.combinedWatts,
1866
+ zScore: powerZ,
1867
+ severity: Math.abs(powerZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1868
+ });
1869
+ }
1730
1870
  }
1731
1871
  }
1732
- if (data.gpu) {
1733
- const gpuZ = History.zScore(data.gpu.utilization, history.gpuUtil);
1734
- if (Math.abs(gpuZ) > ZSCORE_THRESHOLD && history.gpuUtil.length >= MIN_SAMPLES) {
1735
- alerts.push({
1736
- metric: "GPU",
1737
- value: data.gpu.utilization,
1738
- zScore: gpuZ,
1739
- severity: Math.abs(gpuZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1740
- });
1872
+ if (history.connections.length >= MIN_SAMPLES) {
1873
+ const connMean = History.mean(history.connections);
1874
+ if (Math.abs((data.packets?.connections || data.network.connections || 0) - connMean) > 5) {
1875
+ const connZ = History.zScore(data.packets?.connections || data.network.connections || 0, history.connections);
1876
+ if (Math.abs(connZ) > ZSCORE_THRESHOLD) {
1877
+ alerts.push({
1878
+ metric: "Connections",
1879
+ value: data.packets?.connections || data.network.connections || 0,
1880
+ zScore: connZ,
1881
+ severity: Math.abs(connZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1882
+ });
1883
+ }
1884
+ }
1885
+ }
1886
+ if (data.gpu && history.gpuUtil.length >= MIN_SAMPLES) {
1887
+ const gpuMean = History.mean(history.gpuUtil);
1888
+ if (Math.abs(data.gpu.utilization - gpuMean) > MIN_DEV_GPU) {
1889
+ const gpuZ = History.zScore(data.gpu.utilization, history.gpuUtil);
1890
+ if (Math.abs(gpuZ) > ZSCORE_THRESHOLD) {
1891
+ alerts.push({
1892
+ metric: "GPU",
1893
+ value: data.gpu.utilization,
1894
+ zScore: gpuZ,
1895
+ severity: Math.abs(gpuZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1896
+ });
1897
+ }
1741
1898
  }
1742
1899
  }
1743
1900
  return alerts;
@@ -1906,6 +2063,10 @@ function render() {
1906
2063
  writeFrame(renderCredits(cols, rows));
1907
2064
  return;
1908
2065
  }
2066
+ if (state.inputMode === "docker-alert") {
2067
+ writeFrame(renderDockerAlert(cols, rows));
2068
+ return;
2069
+ }
1909
2070
  const lines = [];
1910
2071
  const theme = THEMES[state.currentTheme] || THEMES.default;
1911
2072
  const params = tableCacheParams();
@@ -2161,7 +2322,7 @@ function getReadmeLines() {
2161
2322
  const __dirname = path2.dirname(__filename);
2162
2323
  const rootDir = path2.resolve(__dirname, "..", "..");
2163
2324
  const readmePath = path2.join(rootDir, "README.md");
2164
- const content = fs2.readFileSync(readmePath, "utf8");
2325
+ const content = fs3.readFileSync(readmePath, "utf8");
2165
2326
  cachedReadmeLines = content.split("\n");
2166
2327
  return cachedReadmeLines;
2167
2328
  } catch (err) {
@@ -2206,11 +2367,34 @@ function renderCredits(cols, rows) {
2206
2367
  out.push(...panel("CREDITS", content, cols, chalk2.magenta, chalk2.dim, rows - 2));
2207
2368
  return out;
2208
2369
  }
2370
+ function renderDockerAlert(cols, rows) {
2371
+ const out = [];
2372
+ const content = [
2373
+ chalk2.bold.red("Docker Container Detected"),
2374
+ "",
2375
+ "Pyre is running inside a container. Some hardware sensors",
2376
+ "(battery, thermal, power) may not be available or relevant.",
2377
+ "",
2378
+ chalk2.cyan("Do you wish to continue in Docker mode?"),
2379
+ ""
2380
+ ];
2381
+ const options = ["Yes, use Docker mode (hide hardware sensors)", "No, launch regular TUI with all features"];
2382
+ for (let i = 0; i < options.length; i++) {
2383
+ const isSelected = i === state.dockerSelectionIndex;
2384
+ const prefix = isSelected ? chalk2.cyan.bold(" \u25B6 ") : " ";
2385
+ const text = isSelected ? chalk2.bgCyan.black(` ${options[i]} `) : chalk2.dim(` ${options[i]} `);
2386
+ content.push(prefix + text);
2387
+ }
2388
+ content.push("");
2389
+ content.push(chalk2.dim("Press \u2191/\u2193 to select, Enter to confirm, Esc to skip."));
2390
+ out.push(...panel("DOCKER DETECTED", content, cols, chalk2.red, chalk2.dim, rows - 2));
2391
+ return out;
2392
+ }
2209
2393
 
2210
2394
  // src/p2p/server.ts
2211
2395
  import net from "net";
2212
2396
  import tls from "tls";
2213
- import fs3 from "fs";
2397
+ import fs4 from "fs";
2214
2398
  import crypto2 from "crypto";
2215
2399
  import chalk3 from "chalk";
2216
2400
 
@@ -2355,9 +2539,9 @@ var P2PServer = class {
2355
2539
  async start() {
2356
2540
  if (this.options.auditLog) {
2357
2541
  try {
2358
- fs3.mkdirSync(this.options.auditLog, { recursive: true });
2542
+ fs4.mkdirSync(this.options.auditLog, { recursive: true });
2359
2543
  const logFile = `${this.options.auditLog}/p2p-audit.log`;
2360
- this.auditStream = fs3.createWriteStream(logFile, { flags: "a" });
2544
+ this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
2361
2545
  } catch {
2362
2546
  this.log(chalk3.yellow("Warning: could not open audit log"));
2363
2547
  }
@@ -2366,11 +2550,11 @@ var P2PServer = class {
2366
2550
  const useTLS = !!(this.options.tlsCert && this.options.tlsKey);
2367
2551
  if (useTLS) {
2368
2552
  const tlsOptions = {
2369
- key: fs3.readFileSync(this.options.tlsKey),
2370
- cert: fs3.readFileSync(this.options.tlsCert)
2553
+ key: fs4.readFileSync(this.options.tlsKey),
2554
+ cert: fs4.readFileSync(this.options.tlsCert)
2371
2555
  };
2372
2556
  if (this.options.tlsCA) {
2373
- tlsOptions.ca = fs3.readFileSync(this.options.tlsCA);
2557
+ tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
2374
2558
  }
2375
2559
  this.server = tls.createServer(tlsOptions, (socket) => this.onConnection(socket));
2376
2560
  } else {
@@ -2638,7 +2822,7 @@ async function startP2PServer(options) {
2638
2822
  // src/p2p/client.ts
2639
2823
  import net2 from "net";
2640
2824
  import tls2 from "tls";
2641
- import fs4 from "fs";
2825
+ import fs5 from "fs";
2642
2826
  import crypto3 from "crypto";
2643
2827
  import chalk4 from "chalk";
2644
2828
  var P2PClient = class {
@@ -2666,9 +2850,9 @@ var P2PClient = class {
2666
2850
  async connect() {
2667
2851
  if (this.options.auditLog) {
2668
2852
  try {
2669
- fs4.mkdirSync(this.options.auditLog, { recursive: true });
2853
+ fs5.mkdirSync(this.options.auditLog, { recursive: true });
2670
2854
  const logFile = `${this.options.auditLog}/p2p-client-audit.log`;
2671
- this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
2855
+ this.auditStream = fs5.createWriteStream(logFile, { flags: "a" });
2672
2856
  } catch {
2673
2857
  console.log(chalk4.yellow("Warning: could not open audit log"));
2674
2858
  }
@@ -2683,7 +2867,7 @@ var P2PClient = class {
2683
2867
  rejectUnauthorized: !this.options.tlsInsecure
2684
2868
  };
2685
2869
  if (this.options.tlsCA) {
2686
- tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
2870
+ tlsOptions.ca = fs5.readFileSync(this.options.tlsCA);
2687
2871
  }
2688
2872
  this.socket = tls2.connect(tlsOptions, () => {
2689
2873
  this.running = true;
@@ -2893,7 +3077,8 @@ function persistConfig() {
2893
3077
  splashColorScheme: state.splashColorScheme,
2894
3078
  splashAnimation: state.splashAnimation,
2895
3079
  visiblePanels: { ...state.visiblePanels },
2896
- notificationsEnabled: state.notificationsEnabled
3080
+ notificationsEnabled: state.notificationsEnabled,
3081
+ dockerModeConfirmed: state.dockerModeConfirmed
2897
3082
  });
2898
3083
  }
2899
3084
  function isUpKey(key, str) {
@@ -3028,6 +3213,46 @@ function handleInputModeKey(str, key) {
3028
3213
  }
3029
3214
  return;
3030
3215
  }
3216
+ if (state.inputMode === "docker-alert") {
3217
+ if (isEscKey(key, str)) {
3218
+ state.inputMode = null;
3219
+ state.dockerModeConfirmed = true;
3220
+ render();
3221
+ return;
3222
+ }
3223
+ if (isUpKey(key, str)) {
3224
+ state.dockerSelectionIndex = Math.max(0, state.dockerSelectionIndex - 1);
3225
+ render();
3226
+ return;
3227
+ }
3228
+ if (isDownKey(key, str)) {
3229
+ state.dockerSelectionIndex = Math.min(1, state.dockerSelectionIndex + 1);
3230
+ render();
3231
+ return;
3232
+ }
3233
+ if (isEnterKey(key, str) || str === " ") {
3234
+ state.dockerModeConfirmed = true;
3235
+ state.inputMode = null;
3236
+ if (state.dockerSelectionIndex === 0) {
3237
+ state.visiblePanels.battery = false;
3238
+ state.visiblePanels.thermal = false;
3239
+ state.visiblePanels.power = false;
3240
+ state.visiblePanels.gpu = false;
3241
+ state.detailed = false;
3242
+ } else {
3243
+ state.visiblePanels.battery = true;
3244
+ state.visiblePanels.thermal = true;
3245
+ state.visiblePanels.power = true;
3246
+ state.visiblePanels.gpu = true;
3247
+ }
3248
+ invalidateTableCache();
3249
+ invalidateFrame();
3250
+ persistConfig();
3251
+ render();
3252
+ return;
3253
+ }
3254
+ return;
3255
+ }
3031
3256
  if (state.inputMode === "signal") {
3032
3257
  if (isUpKey(key, str)) {
3033
3258
  const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
@@ -3358,6 +3583,18 @@ async function startLive(opts2, splashPromise) {
3358
3583
  render();
3359
3584
  return;
3360
3585
  }
3586
+ case "C": {
3587
+ state.activePanel = state.activePanel === "containers" ? "grid" : "containers";
3588
+ setStatus(state.activePanel === "grid" ? "Grid view" : "Containers panel");
3589
+ render();
3590
+ return;
3591
+ }
3592
+ case "B": {
3593
+ state.activePanel = state.activePanel === "blender" ? "grid" : "blender";
3594
+ setStatus(state.activePanel === "grid" ? "Grid view" : "Blender panel");
3595
+ render();
3596
+ return;
3597
+ }
3361
3598
  }
3362
3599
  switch (key.name) {
3363
3600
  case "r":
@@ -3531,7 +3768,7 @@ async function startLive(opts2, splashPromise) {
3531
3768
  process.once("SIGINT", () => stopLive());
3532
3769
  }
3533
3770
  function tabKeyToId(str) {
3534
- const map = { "1": "cpu", "2": "mem", "3": "gpu", "4": "power", "5": "battery", "6": "thermal", "7": "network", "8": "packets", "9": "tasks", "0": "disk", "p": "process", "r": "p2p", "A": "anomalies" };
3771
+ const map = { "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" };
3535
3772
  return map[str] ?? null;
3536
3773
  }
3537
3774
  function cycleTab(direction) {
@@ -3955,7 +4192,7 @@ async function showSplash(config3) {
3955
4192
  }
3956
4193
 
3957
4194
  // src/historyCmd.ts
3958
- import fs5 from "fs";
4195
+ import fs6 from "fs";
3959
4196
  import path3 from "path";
3960
4197
  import chalk6 from "chalk";
3961
4198
  function runHistoryCommand(opts2 = {}) {
@@ -3964,11 +4201,11 @@ function runHistoryCommand(opts2 = {}) {
3964
4201
  console.log(chalk6.bold(`
3965
4202
  pyre history \u2014 Past ${days} Days Trend
3966
4203
  `));
3967
- if (!fs5.existsSync(dir)) {
4204
+ if (!fs6.existsSync(dir)) {
3968
4205
  console.log(chalk6.yellow(` No logs directory found at ${dir}`));
3969
4206
  return;
3970
4207
  }
3971
- const files = fs5.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4208
+ const files = fs6.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
3972
4209
  if (files.length === 0) {
3973
4210
  console.log(chalk6.yellow(` No CSV logs found in ${dir}`));
3974
4211
  return;
@@ -3980,7 +4217,7 @@ function runHistoryCommand(opts2 = {}) {
3980
4217
  for (const file of files.sort()) {
3981
4218
  const fullPath = path3.join(dir, file);
3982
4219
  try {
3983
- const content = fs5.readFileSync(fullPath, "utf-8");
4220
+ const content = fs6.readFileSync(fullPath, "utf-8");
3984
4221
  const lines = content.split("\n").slice(1);
3985
4222
  for (const line of lines) {
3986
4223
  if (!line.trim()) continue;
@@ -4015,7 +4252,7 @@ function runHistoryCommand(opts2 = {}) {
4015
4252
  }
4016
4253
 
4017
4254
  // src/diffCmd.ts
4018
- import fs6 from "fs";
4255
+ import fs7 from "fs";
4019
4256
  import chalk7 from "chalk";
4020
4257
  function runDiffCommand(file1, file2) {
4021
4258
  console.log(chalk7.bold(`
@@ -4025,8 +4262,8 @@ function runDiffCommand(file1, file2) {
4025
4262
  console.log(chalk7.dim(` Snapshot B: ${file2}
4026
4263
  `));
4027
4264
  try {
4028
- const dataA = JSON.parse(fs6.readFileSync(file1, "utf-8"));
4029
- const dataB = JSON.parse(fs6.readFileSync(file2, "utf-8"));
4265
+ const dataA = JSON.parse(fs7.readFileSync(file1, "utf-8"));
4266
+ const dataB = JSON.parse(fs7.readFileSync(file2, "utf-8"));
4030
4267
  const diffNum = (label, valA, valB, unit = "") => {
4031
4268
  const delta = valB - valA;
4032
4269
  const sign = delta > 0 ? "+" : "";
@@ -4050,7 +4287,7 @@ function runDiffCommand(file1, file2) {
4050
4287
  }
4051
4288
 
4052
4289
  // src/xbar.ts
4053
- import fs7 from "fs";
4290
+ import fs8 from "fs";
4054
4291
  import path4 from "path";
4055
4292
  import os from "os";
4056
4293
  import chalk8 from "chalk";
@@ -4091,8 +4328,8 @@ echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' ter
4091
4328
  const pluginDir = path4.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
4092
4329
  const filename = "pyre-monitor.2s.sh";
4093
4330
  const outPath = path4.join(process.cwd(), filename);
4094
- fs7.writeFileSync(outPath, script);
4095
- fs7.chmodSync(outPath, "755");
4331
+ fs8.writeFileSync(outPath, script);
4332
+ fs8.chmodSync(outPath, "755");
4096
4333
  console.log(chalk8.green(`Generated xbar plugin script at ${outPath}`));
4097
4334
  console.log(`To install, move it to your xbar plugins directory:`);
4098
4335
  console.log(chalk8.cyan(` mv ${filename} "${pluginDir}/"`));
@@ -4130,36 +4367,37 @@ async function runFleetCommand(hosts) {
4130
4367
 
4131
4368
  // src/index.ts
4132
4369
  var config2 = readConfig();
4133
- var pkg = JSON.parse(fs8.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4370
+ var pkg = JSON.parse(fs9.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4134
4371
  var program = new Command();
4135
4372
  program.name("pyre").version(pkg.version).description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("--html", "Output as HTML").option("--md", "Output as Markdown").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--theme <name>", "Default theme for live mode (default, dracula, cyberpunk, monochrome, nord, gruvbox)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", config2.exportDir).option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", config2.sortMode).option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--minimal", "Render minimal macmon-style glanceable view (CPU, GPU, Memory, Temp)").option("--compact", "Launch pyre ui in compact widget mode").option("--ontop", "Keep pyre ui window always on top").option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)").option("--since <range>", "Time range for anomalies digest (e.g. 1d, 7d, yesterday)").option("--plain", "Output in plain accessible text without ANSI colors or line-drawing characters").option("--a11y", "Alias for --plain").option("-d, --duration <seconds>", "Duration in seconds for stress/bench commands", "30").option("--cores <n>", "Number of worker threads for stress command").option("--exit-code", "Exit non-zero if system health checks trigger alerts (for check command)").option("--no-wizard", "Skip first-run interactive setup wizard").option("--install", "Install pyre web as a background launchd agent").option("--uninstall", "Uninstall pyre web launchd agent").option("--port <port>", "Port number for web server mode", "3000");
4136
4373
  program.addHelpText("after", `
4137
- Commands:
4138
- live Interactive live dashboard
4139
- check One-line plain-English health summary
4140
- pipe Continuous newline-delimited JSON stream for scripting
4141
- stress Synthetic CPU/GPU load generator
4142
- ui Launch the native macOS UI dashboard
4143
- web Serve an auto-refreshing live web portal
4144
- ssh <host> Stream live stats from a remote Mac over SSH
4145
- fleet <host1> [host2]... Multi-host live dashboard monitoring multiple Macs
4146
- bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
4147
- benchmark Run a 1-minute CPU benchmark calculating digits of PI
4148
- anomalies Compute z-score anomalies and print plain-language digest
4149
- doctor Run system diagnostics
4150
- extensions System Extensions inspector
4151
- brew Homebrew health panel
4152
- update Check for pyre-cli updates
4153
- profile <save|load|list> Atomic configuration profile management
4154
- config <show|reset> View or reset persistent configuration file
4155
- history Graph historical resource trends from CSV logs
4156
- diff <file1> <file2> Compare two saved snapshot files side-by-side
4157
- info Concise hardware summary
4158
- completions <shell> Generate shell auto-completion scripts
4159
- xbar Generate an xbar / SwiftBar menu bar plugin script
4160
- p2p <server|connect> Start a P2P server or connect to one
4161
- server Print commands for starting P2P server/client
4162
- `);
4374
+ Commands:
4375
+ live Interactive live dashboard
4376
+ check One-line plain-English health summary
4377
+ pipe Continuous newline-delimited JSON stream for scripting
4378
+ stress Synthetic CPU/GPU load generator
4379
+ ui Launch the native macOS UI dashboard
4380
+ web Serve an auto-refreshing live web portal
4381
+ ssh <host> Stream live stats from a remote Mac over SSH
4382
+ fleet <host1> [host2]... Multi-host live dashboard monitoring multiple Macs
4383
+ bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
4384
+ benchmark Run a 1-minute CPU benchmark calculating digits of PI
4385
+ anomalies Compute z-score anomalies and print plain-language digest
4386
+ doctor Run system diagnostics
4387
+ extensions System Extensions inspector
4388
+ brew Homebrew health panel
4389
+ update Check for pyre-cli updates
4390
+ profile <save|load|list> Atomic configuration profile management
4391
+ config <show|reset> View or reset persistent configuration file
4392
+ history Graph historical resource trends from CSV logs
4393
+ diff <file1> <file2> Compare two saved snapshot files side-by-side
4394
+ info Concise hardware summary
4395
+ completions <shell> Generate shell auto-completion scripts
4396
+ xbar Generate an xbar / SwiftBar menu bar plugin script
4397
+ p2p <server|connect> Start a P2P server or connect to one
4398
+ server Print commands for starting P2P server/client
4399
+ blender Track active Blender render jobs and their progress
4400
+ `);
4163
4401
  program.parse(process.argv);
4164
4402
  var opts = program.opts();
4165
4403
  if (opts.plain || opts.a11y) {
@@ -4219,11 +4457,11 @@ async function runServerCommand() {
4219
4457
  console.log();
4220
4458
  }
4221
4459
  async function main() {
4222
- const { runSetupWizardIfNeeded } = await import("./wizard-GVIAHIVV.js");
4460
+ const { runSetupWizardIfNeeded } = await import("./wizard-Z3RGA3VB.js");
4223
4461
  await runSetupWizardIfNeeded(!opts.wizard);
4224
4462
  const cmd = program.args[0];
4225
4463
  if (cmd === "check") {
4226
- const { runCheckCommand } = await import("./checkCmd-ULNENOPZ.js");
4464
+ const { runCheckCommand } = await import("./checkCmd-T7O4KXXA.js");
4227
4465
  await runCheckCommand({
4228
4466
  exitCode: !!opts.exitCode,
4229
4467
  cpuAlertPct: config2.cpuAlertPct,
@@ -4233,7 +4471,7 @@ async function main() {
4233
4471
  return;
4234
4472
  }
4235
4473
  if (cmd === "pipe") {
4236
- const { runPipeCommand } = await import("./pipeCmd-BWNMUV2X.js");
4474
+ const { runPipeCommand } = await import("./pipeCmd-TYLJHQ5B.js");
4237
4475
  const intervalSec = parseFloat(opts.interval) || 1;
4238
4476
  const samples = parseInt(opts.samples, 10) || 0;
4239
4477
  await runPipeCommand({
@@ -4260,6 +4498,31 @@ async function main() {
4260
4498
  await printExtensionsReport();
4261
4499
  return;
4262
4500
  }
4501
+ if (cmd === "blender") {
4502
+ const { collectBlenderRenders } = await import("./blender-7UT5J7IP.js");
4503
+ const renders = await collectBlenderRenders();
4504
+ if (!renders.length) {
4505
+ console.log(chalk10.dim("No active Blender renders detected."));
4506
+ return;
4507
+ }
4508
+ const lines = [];
4509
+ lines.push(chalk10.bold(" Blender Render Tracker"));
4510
+ lines.push("");
4511
+ for (const r of renders) {
4512
+ const statusColor = r.status === "rendering" ? chalk10.green : r.status === "finalizing" ? chalk10.yellow : r.status === "loading" ? chalk10.cyan : chalk10.dim;
4513
+ lines.push(` PID: ${r.pid}`);
4514
+ lines.push(` Blend File: ${r.blendFile}`);
4515
+ lines.push(` Engine: ${r.renderEngine}`);
4516
+ lines.push(` Frames: ${r.currentFrame} / ${r.totalFrames} (${r.completionPercent}%)`);
4517
+ lines.push(` Elapsed: ${r.elapsedSec}s`);
4518
+ lines.push(` Status: ${statusColor(r.status)}`);
4519
+ lines.push(` Output: ${r.outputPath}`);
4520
+ if (r.sampleCount !== void 0) lines.push(` Samples: ${r.sampleCount}`);
4521
+ lines.push("");
4522
+ }
4523
+ console.log(lines.join("\n"));
4524
+ return;
4525
+ }
4263
4526
  if (cmd === "brew") {
4264
4527
  const { printBrewHealthReport } = await import("./brewHealth-F2BXF5M6.js");
4265
4528
  await printBrewHealthReport();
@@ -4273,7 +4536,7 @@ async function main() {
4273
4536
  if (cmd === "profile") {
4274
4537
  const sub = program.args[1];
4275
4538
  const name = program.args[2];
4276
- const { saveProfile, loadProfile, listProfiles } = await import("./config-4ZLF4HTS.js");
4539
+ const { saveProfile, loadProfile, listProfiles } = await import("./config-KS5ZZ3VN.js");
4277
4540
  if (sub === "save" && name) {
4278
4541
  saveProfile(name);
4279
4542
  console.log(chalk10.green(` \u2714 Profile '${name}' saved successfully.`));
@@ -4364,7 +4627,7 @@ async function main() {
4364
4627
  const cfg = readConfig();
4365
4628
  console.log(JSON.stringify(cfg, null, 2));
4366
4629
  } else if (sub === "reset") {
4367
- fs8.rmSync(CONFIG_FILE, { force: true });
4630
+ fs9.rmSync(CONFIG_FILE, { force: true });
4368
4631
  console.log(chalk10.green("Config reset to defaults"));
4369
4632
  } else {
4370
4633
  console.log("Usage: pyre config <show|reset>");
@@ -4380,7 +4643,7 @@ async function main() {
4380
4643
  return;
4381
4644
  }
4382
4645
  if (cmd === "doctor") {
4383
- const { runDoctor, printDoctorReport } = await import("./doctor-L6FZYA7F.js");
4646
+ const { runDoctor, printDoctorReport } = await import("./doctor-UV7TWIHX.js");
4384
4647
  const checks = await runDoctor();
4385
4648
  printDoctorReport(checks);
4386
4649
  return;
@@ -4461,7 +4724,7 @@ async function main() {
4461
4724
  else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config2.treeView, minimal: !!opts.minimal, visible: { packets: opts.packets ? true : void 0 } });
4462
4725
  console.log(output);
4463
4726
  if (opts.out) {
4464
- fs8.writeFileSync(opts.out, stripAnsi(output));
4727
+ fs9.writeFileSync(opts.out, stripAnsi(output));
4465
4728
  console.log(chalk10.dim(`
4466
4729
  Saved to ${opts.out}`));
4467
4730
  }
@@ -4645,7 +4908,7 @@ async function runP2PConnect() {
4645
4908
  }
4646
4909
  async function runInfoCommand() {
4647
4910
  const data = await collectAll({ detailed: true });
4648
- const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-VYUH3UWY.js");
4911
+ const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-JB4OGUEF.js");
4649
4912
  const displays = await getDisplayInfo();
4650
4913
  const tmStatus = await getTimeMachineStatus();
4651
4914
  const lines = [];
@@ -4759,8 +5022,8 @@ async function runWebCommand() {
4759
5022
  } else if (url === "/setup") {
4760
5023
  res.writeHead(200, { "Content-Type": "text/html" });
4761
5024
  const indexPath = path5.join(process.cwd(), "index.html");
4762
- if (fs8.existsSync(indexPath)) {
4763
- res.end(fs8.readFileSync(indexPath, "utf-8"));
5025
+ if (fs9.existsSync(indexPath)) {
5026
+ res.end(fs9.readFileSync(indexPath, "utf-8"));
4764
5027
  } else {
4765
5028
  res.end(getDashboardHtml());
4766
5029
  }
@@ -4916,7 +5179,7 @@ app.delegate = delegate
4916
5179
  app.run()
4917
5180
  `;
4918
5181
  const scriptPath = path5.join(os2.tmpdir(), "pyre-ui.swift");
4919
- fs8.writeFileSync(scriptPath, swiftScript);
5182
+ fs9.writeFileSync(scriptPath, swiftScript);
4920
5183
  const { spawn } = await import("child_process");
4921
5184
  console.log(chalk10.cyan(`
4922
5185
  Launching native UI window...`));
@@ -4934,8 +5197,8 @@ async function runBenchCommand(benchCmd) {
4934
5197
  const { spawn } = await import("child_process");
4935
5198
  const logDir = opts.exportDir || "./pyre-exports";
4936
5199
  const file = path5.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
4937
- if (!fs8.existsSync(logDir)) fs8.mkdirSync(logDir, { recursive: true });
4938
- const stream = fs8.createWriteStream(file, { flags: "a" });
5200
+ if (!fs9.existsSync(logDir)) fs9.mkdirSync(logDir, { recursive: true });
5201
+ const stream = fs9.createWriteStream(file, { flags: "a" });
4939
5202
  stream.write("timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_packets,net_tx_packets,connections,thermal_state,power_watts\n");
4940
5203
  console.log(chalk10.bold(`
4941
5204
  pyre bench`));