pyre-cli 9.5.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +84 -14
  2. package/dist/alertChannels-TPSQIJ7S.js +8 -0
  3. package/dist/blender-FGH3ASEQ.js +7 -0
  4. package/dist/brewHealth-P2GW5Y5C.js +176 -0
  5. package/dist/buildCmd-CDPZ2ZFA.js +38 -0
  6. package/dist/{checkCmd-EWFNLBWE.js → checkCmd-RKW6QGDR.js} +10 -11
  7. package/dist/{chunk-RVGKEJXT.js → chunk-53O2RGSL.js} +47 -17
  8. package/dist/{chunk-MOAEGMFP.js → chunk-6UHZP4QP.js} +204 -46
  9. package/dist/chunk-GHRDW646.js +2506 -0
  10. package/dist/{prometheus-LFYHF6VO.js → chunk-HT4VQ4OH.js} +1 -0
  11. package/dist/chunk-M73HCQD2.js +95 -0
  12. package/dist/chunk-MZO5QKUR.js +32 -0
  13. package/dist/chunk-VIACN2RU.js +133 -0
  14. package/dist/chunk-Z2YV5HKV.js +87 -0
  15. package/dist/chunk-ZLVIVFSO.js +110 -0
  16. package/dist/{collectors-WD6VKH6M.js → collectors-H43PN4PS.js} +8 -2
  17. package/dist/{completions-VQMHQCXE.js → completions-JKYEJNGR.js} +34 -1
  18. package/dist/{config-4ZLF4HTS.js → config-GYGJXU2Q.js} +9 -1
  19. package/dist/doctor-GYHFTNZQ.js +387 -0
  20. package/dist/explainCmd-MF6PIMLW.js +222 -0
  21. package/dist/extensions-XJGHG7X3.js +134 -0
  22. package/dist/index.js +841 -206
  23. package/dist/ioreport.py +72 -0
  24. package/dist/netUsageCmd-WKBTW2YG.js +90 -0
  25. package/dist/{pipeCmd-7THXEGDH.js → pipeCmd-7REDBLBU.js} +5 -3
  26. package/dist/profileProcCmd-N4H6MCBT.js +107 -0
  27. package/dist/prometheus-5BL5PVLC.js +6 -0
  28. package/dist/prometheusCmd-PL64BLN5.js +93 -0
  29. package/dist/replayCmd-Z3PYXY7V.js +164 -0
  30. package/dist/run-HOGQDDTA.js +8 -0
  31. package/dist/serveCmd-2H5VLF3P.js +153 -0
  32. package/dist/service-Q74BBILO.js +181 -0
  33. package/dist/smartCmd-ODWKSFNG.js +166 -0
  34. package/dist/syncCmd-A2W3QB3S.js +126 -0
  35. package/dist/topoCmd-DU2HHZNH.js +148 -0
  36. package/dist/watchdogCmd-UF53JPZB.js +161 -0
  37. package/dist/{wizard-GVIAHIVV.js → wizard-TD7BGX6O.js} +1 -1
  38. package/install.ps1 +29 -0
  39. package/install.sh +10 -9
  40. package/package.json +7 -3
  41. package/src/ai/models.ts +138 -0
  42. package/src/alertChannels.ts +119 -0
  43. package/src/anomalies.ts +114 -60
  44. package/src/brewHealth.ts +157 -40
  45. package/src/buildCmd.ts +48 -0
  46. package/src/checkCmd.ts +5 -5
  47. package/src/completions.ts +35 -0
  48. package/src/doctor.ts +235 -7
  49. package/src/easy_features.test.ts +40 -0
  50. package/src/explainCmd.ts +261 -0
  51. package/src/extensions.ts +71 -7
  52. package/src/formatters/index.ts +1 -1
  53. package/src/formatters/output.ts +100 -13
  54. package/src/formatters/render.ts +274 -38
  55. package/src/formatters/themes.ts +8 -0
  56. package/src/formatters/types.ts +6 -1
  57. package/src/index.ts +298 -56
  58. package/src/live/input.ts +122 -4
  59. package/src/live/render.ts +98 -0
  60. package/src/live/state.ts +70 -52
  61. package/src/live/types.ts +2 -2
  62. package/src/monitors/batteryPredictor.ts +143 -0
  63. package/src/monitors/blender.ts +144 -0
  64. package/src/monitors/buildTracker.ts +121 -0
  65. package/src/monitors/collectors.ts +558 -147
  66. package/src/monitors/dns_cache.ts +48 -0
  67. package/src/monitors/index.ts +3 -2
  68. package/src/monitors/ioreport.py +72 -0
  69. package/src/monitors/platform/linux.ts +756 -0
  70. package/src/monitors/platform/windows.ts +519 -0
  71. package/src/monitors/run.ts +27 -3
  72. package/src/monitors/smc.ts +21 -1
  73. package/src/monitors/types.ts +112 -14
  74. package/src/netUsageCmd.ts +121 -0
  75. package/src/platform.test.ts +99 -0
  76. package/src/plugins/index.ts +143 -0
  77. package/src/profileProcCmd.ts +133 -0
  78. package/src/prometheusCmd.ts +107 -0
  79. package/src/replayCmd.ts +186 -0
  80. package/src/serveCmd.ts +180 -0
  81. package/src/service.ts +125 -7
  82. package/src/smartCmd.ts +205 -0
  83. package/src/state/config.ts +64 -17
  84. package/src/syncCmd.ts +166 -0
  85. package/src/topoCmd.ts +176 -0
  86. package/src/watchdogCmd.ts +198 -0
  87. package/src/xbar.ts +22 -11
  88. package/dist/brewHealth-F2BXF5M6.js +0 -74
  89. package/dist/chunk-FH5WDJBF.js +0 -15
  90. package/dist/chunk-HGRBBPYX.js +0 -937
  91. package/dist/doctor-L6FZYA7F.js +0 -184
  92. package/dist/extensions-DVA5YLGH.js +0 -74
  93. package/dist/service-WTS3NBQU.js +0 -73
  94. /package/dist/{chunk-SG26Y3WF.js → chunk-IM3IQMMT.js} +0 -0
package/dist/index.js CHANGED
@@ -1,9 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- CONFIG_FILE,
4
- readConfig,
5
- writeConfig
6
- } from "./chunk-RVGKEJXT.js";
7
2
  import {
8
3
  TAB_BAR_ROW,
9
4
  THEMES,
@@ -15,17 +10,31 @@ import {
15
10
  panel,
16
11
  sparkline,
17
12
  visLen
18
- } from "./chunk-MOAEGMFP.js";
19
- import "./chunk-SG26Y3WF.js";
13
+ } from "./chunk-6UHZP4QP.js";
14
+ import {
15
+ AVAILABLE_AI_MODELS
16
+ } from "./chunk-ZLVIVFSO.js";
17
+ import {
18
+ sendAlert
19
+ } from "./chunk-Z2YV5HKV.js";
20
+ import {
21
+ CONFIG_FILE,
22
+ getConfigDir,
23
+ readConfig,
24
+ writeConfig
25
+ } from "./chunk-53O2RGSL.js";
26
+ import "./chunk-IM3IQMMT.js";
20
27
  import {
21
28
  collectAll
22
- } from "./chunk-HGRBBPYX.js";
23
- import "./chunk-FH5WDJBF.js";
29
+ } from "./chunk-GHRDW646.js";
30
+ import "./chunk-M73HCQD2.js";
31
+ import "./chunk-VIACN2RU.js";
32
+ import "./chunk-MZO5QKUR.js";
24
33
 
25
34
  // src/index.ts
26
- import fs8 from "fs";
35
+ import fs10 from "fs";
27
36
  import os2 from "os";
28
- import path5 from "path";
37
+ import path6 from "path";
29
38
  import readline2 from "readline";
30
39
  import { Command } from "commander";
31
40
  import chalk10 from "chalk";
@@ -124,19 +133,39 @@ function formatHtml(data) {
124
133
  <table>
125
134
  ${rows([
126
135
  { 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` }
136
+ { label: "RX", value: `${formatBytes(data.network.rxBytes)} \xB7 ${formatBytes(data.network.rxRate)}/s` },
137
+ { label: "TX", value: `${formatBytes(data.network.txBytes)} \xB7 ${formatBytes(data.network.txRate)}/s` },
138
+ ...data.network.protocols ? [
139
+ { label: "TCP", value: `${data.network.protocols.tcp.connections} conns` },
140
+ { label: "UDP", value: `${data.network.protocols.udp.connections} conns` }
141
+ ] : [],
142
+ ...data.network.connectionStates ? [
143
+ { label: "Active", value: `${data.network.connectionStates.established} ESTABLISHED` },
144
+ { label: "Listening", value: `${data.network.connectionStates.listening}` }
145
+ ] : [],
146
+ ...data.network.listeningPorts && data.network.listeningPorts.length ? [
147
+ { label: "Ports", value: data.network.listeningPorts.slice(0, 8).join(", ") }
148
+ ] : []
129
149
  ]).trim()}
130
150
  </table>`);
131
151
  if (data.packets) {
132
- sections.push(`<h2>Packets</h2>
133
- <table>
134
- ${rows([
152
+ const packetRows = [
135
153
  { label: "Total", value: `${data.packets.totalPackets} pkt` },
136
154
  { label: "RX", value: `${data.packets.rxPackets} pkt` },
137
155
  { label: "TX", value: `${data.packets.txPackets} pkt` },
138
156
  { label: "Connections", value: `${data.packets.connections} TCP` }
139
- ]).trim()}
157
+ ];
158
+ if (data.packets.protocolStats) {
159
+ packetRows.push({ label: "TCP", value: `${data.packets.protocolStats.tcp.connections} conns` });
160
+ packetRows.push({ label: "UDP", value: `${data.packets.protocolStats.udp.connections} conns` });
161
+ }
162
+ if (data.packets.connectionStates) {
163
+ packetRows.push({ label: "ESTABLISHED", value: `${data.packets.connectionStates.established}` });
164
+ packetRows.push({ label: "LISTENING", value: `${data.packets.connectionStates.listening}` });
165
+ }
166
+ sections.push(`<h2>Packets</h2>
167
+ <table>
168
+ ${rows(packetRows).trim()}
140
169
  </table>`);
141
170
  }
142
171
  if (data.tasks.length) {
@@ -167,6 +196,16 @@ function formatHtml(data) {
167
196
  sections.push(`<h2>Processes</h2>
168
197
  <table>
169
198
  ${rows(procRows).trim()}
199
+ </table>`);
200
+ }
201
+ if (data.blenderRenders && data.blenderRenders.length) {
202
+ const blenderRows = data.blenderRenders.map((r) => ({
203
+ label: `PID ${r.pid}`,
204
+ value: `${r.blendFile} \xB7 ${r.renderEngine} \xB7 ${r.currentFrame}/${r.totalFrames} frames \xB7 ${r.completionPercent}% \xB7 ${r.status} \xB7 ${r.elapsedSec}s`
205
+ }));
206
+ sections.push(`<h2>Blender Renders</h2>
207
+ <table>
208
+ ${rows(blenderRows).trim()}
170
209
  </table>`);
171
210
  }
172
211
  return `<!DOCTYPE html>
@@ -388,8 +427,19 @@ function formatMarkdown(data) {
388
427
  lines.push("| Property | Value |");
389
428
  lines.push("|---|---|");
390
429
  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`)} |`);
430
+ lines.push(`| RX | ${mdCell(`${formatBytes(data.network.rxBytes)} \xB7 ${formatBytes(data.network.rxRate)}/s`)} |`);
431
+ lines.push(`| TX | ${mdCell(`${formatBytes(data.network.txBytes)} \xB7 ${formatBytes(data.network.txRate)}/s`)} |`);
432
+ if (data.network.protocols) {
433
+ lines.push(`| TCP | ${mdCell(`${data.network.protocols.tcp.connections} conns`)} |`);
434
+ lines.push(`| UDP | ${mdCell(`${data.network.protocols.udp.connections} conns`)} |`);
435
+ }
436
+ if (data.network.connectionStates) {
437
+ lines.push(`| Active | ${mdCell(`${data.network.connectionStates.established} ESTABLISHED`)} |`);
438
+ lines.push(`| Listening | ${mdCell(`${data.network.connectionStates.listening}`)} |`);
439
+ }
440
+ if (data.network.listeningPorts && data.network.listeningPorts.length) {
441
+ lines.push(`| Ports | ${mdCell(data.network.listeningPorts.slice(0, 8).join(", "))} |`);
442
+ }
393
443
  lines.push("");
394
444
  if (data.packets) {
395
445
  lines.push("## Packets");
@@ -400,6 +450,14 @@ function formatMarkdown(data) {
400
450
  lines.push(`| RX | ${mdCell(`${data.packets.rxPackets} pkt`)} |`);
401
451
  lines.push(`| TX | ${mdCell(`${data.packets.txPackets} pkt`)} |`);
402
452
  lines.push(`| Connections | ${mdCell(`${data.packets.connections} TCP`)} |`);
453
+ if (data.packets.protocolStats) {
454
+ lines.push(`| TCP | ${mdCell(`${data.packets.protocolStats.tcp.connections} conns`)} |`);
455
+ lines.push(`| UDP | ${mdCell(`${data.packets.protocolStats.udp.connections} conns`)} |`);
456
+ }
457
+ if (data.packets.connectionStates) {
458
+ lines.push(`| ESTABLISHED | ${mdCell(`${data.packets.connectionStates.established}`)} |`);
459
+ lines.push(`| LISTENING | ${mdCell(`${data.packets.connectionStates.listening}`)} |`);
460
+ }
403
461
  lines.push("");
404
462
  }
405
463
  if (data.tasks.length) {
@@ -432,6 +490,16 @@ function formatMarkdown(data) {
432
490
  }
433
491
  lines.push("");
434
492
  }
493
+ if (data.blenderRenders && data.blenderRenders.length) {
494
+ lines.push("## Blender Renders");
495
+ lines.push("");
496
+ lines.push("| PID | Blend File | Engine | Frames | Completion | Status | Elapsed |");
497
+ lines.push("|---|---|---|---|---|---|---|");
498
+ for (const r of data.blenderRenders) {
499
+ lines.push(`| ${r.pid} | ${mdCell(r.blendFile)} | ${mdCell(r.renderEngine)} | ${r.currentFrame}/${r.totalFrames} | ${r.completionPercent}% | ${r.status} | ${r.elapsedSec}s |`);
500
+ }
501
+ lines.push("");
502
+ }
435
503
  return lines.join("\n");
436
504
  }
437
505
  function escapeHtml(s) {
@@ -461,9 +529,16 @@ function formatCsv(data) {
461
529
  ["thermal", "cpuTempF", data.thermal.temperatures?.cpu_die != null ? String(celsiusToFahrenheit(data.thermal.temperatures.cpu_die)) : ""],
462
530
  ["network", "rxBytes", String(data.network.rxBytes)],
463
531
  ["network", "txBytes", String(data.network.txBytes)],
532
+ ["network", "rxRate", String(data.network.rxRate)],
533
+ ["network", "txRate", String(data.network.txRate)],
464
534
  ["network", "rxPackets", String(data.network.rxPackets)],
465
535
  ["network", "txPackets", String(data.network.txPackets)],
466
536
  ["network", "connections", data.network.connections ? String(data.network.connections) : ""],
537
+ ["network", "tcpConns", data.network.protocols ? String(data.network.protocols.tcp.connections) : ""],
538
+ ["network", "udpConns", data.network.protocols ? String(data.network.protocols.udp.connections) : ""],
539
+ ["network", "established", data.network.connectionStates ? String(data.network.connectionStates.established) : ""],
540
+ ["network", "listening", data.network.connectionStates ? String(data.network.connectionStates.listening) : ""],
541
+ ["network", "listeningPorts", data.network.listeningPorts ? data.network.listeningPorts.slice(0, 10).join(";") : ""],
467
542
  ["battery", "level", data.battery ? String(data.battery.level) : ""],
468
543
  ["battery", "powerSource", data.battery ? data.battery.powerSource : ""],
469
544
  ["battery", "condition", data.battery?.condition ?? ""],
@@ -487,6 +562,16 @@ function formatCsv(data) {
487
562
  rows.push(["packets", "rxPackets", String(data.packets.rxPackets)]);
488
563
  rows.push(["packets", "txPackets", String(data.packets.txPackets)]);
489
564
  rows.push(["packets", "connections", String(data.packets.connections)]);
565
+ if (data.packets.protocolStats) {
566
+ rows.push(["packets", "tcpConns", String(data.packets.protocolStats.tcp.connections)]);
567
+ rows.push(["packets", "udpConns", String(data.packets.protocolStats.udp.connections)]);
568
+ }
569
+ if (data.packets.connectionStates) {
570
+ rows.push(["packets", "established", String(data.packets.connectionStates.established)]);
571
+ rows.push(["packets", "listening", String(data.packets.connectionStates.listening)]);
572
+ rows.push(["packets", "timeWait", String(data.packets.connectionStates.timeWait)]);
573
+ rows.push(["packets", "closeWait", String(data.packets.connectionStates.closeWait)]);
574
+ }
490
575
  for (const iface of data.packets.interfaces || []) {
491
576
  rows.push(["packets_iface", iface.iface, `${iface.rxPackets},${iface.txPackets},${iface.rxBytes},${iface.txBytes}`]);
492
577
  }
@@ -512,6 +597,20 @@ function formatCsv(data) {
512
597
  rows.push(["process", `runtime=${p.pid}`, String(p.runtime)]);
513
598
  rows.push(["process", `command=${p.pid}`, p.command]);
514
599
  }
600
+ for (const r of data.blenderRenders || []) {
601
+ rows.push(["blender", `pid=${r.pid}`, `${r.pid}`]);
602
+ rows.push(["blender", `blend_file=${r.pid}`, r.blendFile]);
603
+ rows.push(["blender", `engine=${r.pid}`, r.renderEngine]);
604
+ rows.push(["blender", `frame_start=${r.pid}`, String(r.frameStart)]);
605
+ rows.push(["blender", `frame_end=${r.pid}`, String(r.frameEnd)]);
606
+ rows.push(["blender", `current_frame=${r.pid}`, String(r.currentFrame)]);
607
+ rows.push(["blender", `total_frames=${r.pid}`, String(r.totalFrames)]);
608
+ rows.push(["blender", `completion_pct=${r.pid}`, String(r.completionPercent)]);
609
+ rows.push(["blender", `status=${r.pid}`, r.status]);
610
+ rows.push(["blender", `elapsed_sec=${r.pid}`, String(r.elapsedSec)]);
611
+ rows.push(["blender", `output_path=${r.pid}`, r.outputPath]);
612
+ if (r.sampleCount !== void 0) rows.push(["blender", `samples=${r.pid}`, String(r.sampleCount)]);
613
+ }
515
614
  return rows.map((r) => r.map((item) => `"${String(item).replace(/"/g, '""')}"`).join(",")).join("\n");
516
615
  }
517
616
  function formatTsv(data) {
@@ -1237,6 +1336,9 @@ document.addEventListener('keydown', e => { if(keyMap[e.key.toLowerCase()]) swit
1237
1336
  // src/live/input.ts
1238
1337
  import readline from "readline";
1239
1338
 
1339
+ // src/live/state.ts
1340
+ import fs from "fs";
1341
+
1240
1342
  // src/history.ts
1241
1343
  var History = class _History {
1242
1344
  /** Maximum number of samples retained in the rolling window. */
@@ -1396,6 +1498,7 @@ var History = class _History {
1396
1498
  var SIGNAL_OPTIONS = ["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGSTOP", "SIGCONT"];
1397
1499
  var MENU_OPTIONS = ["Resume Dashboard", "Settings (Customizer)", "Readme", "Credits", "Quit pyre"];
1398
1500
  var config = readConfig();
1501
+ var isDockerEnv = fs.existsSync("/.dockerenv") || fs.existsSync("/run/.containerenv");
1399
1502
  var state = {
1400
1503
  intervalHandle: null,
1401
1504
  uiIntervalHandle: null,
@@ -1420,7 +1523,9 @@ var state = {
1420
1523
  packets: config.visiblePanels.packets,
1421
1524
  tasks: config.visiblePanels.tasks,
1422
1525
  disk: config.visiblePanels.disk,
1423
- process: config.visiblePanels.process
1526
+ process: config.visiblePanels.process,
1527
+ containers: config.visiblePanels.containers,
1528
+ blender: config.visiblePanels.blender
1424
1529
  },
1425
1530
  panelLayout: config.panelLayout || ["mem", "disk", "net"],
1426
1531
  logging: false,
@@ -1438,7 +1543,7 @@ var state = {
1438
1543
  processScrollOffset: 0,
1439
1544
  trackedPid: null,
1440
1545
  inspectingProcess: null,
1441
- inputMode: null,
1546
+ inputMode: isDockerEnv && !config.dockerModeConfirmed ? "docker-alert" : null,
1442
1547
  inputBuffer: "",
1443
1548
  targetPid: "",
1444
1549
  selectedSignal: "SIGTERM",
@@ -1453,6 +1558,8 @@ var state = {
1453
1558
  "Splash Animation",
1454
1559
  "Notifications",
1455
1560
  "Temperature Unit",
1561
+ "AI Model",
1562
+ "Grid Panel Order",
1456
1563
  "Toggle CPU",
1457
1564
  "Toggle Memory",
1458
1565
  "Toggle GPU",
@@ -1460,10 +1567,12 @@ var state = {
1460
1567
  "Toggle Battery",
1461
1568
  "Toggle Thermal",
1462
1569
  "Toggle Network",
1463
- "Toggle Packets",
1570
+ "Toggle Conns",
1464
1571
  "Toggle Tasks",
1465
1572
  "Toggle Disk",
1466
1573
  "Toggle Processes",
1574
+ "Toggle Containers",
1575
+ "Toggle Blender",
1467
1576
  "Toggle Tree View"
1468
1577
  ],
1469
1578
  CPU_ALERT_PCT: config.cpuAlertPct,
@@ -1485,12 +1594,14 @@ var state = {
1485
1594
  { id: "battery", label: "Battery", key: "5" },
1486
1595
  { id: "thermal", label: "Thermal", key: "6" },
1487
1596
  { id: "network", label: "Network", key: "7" },
1488
- { id: "packets", label: "Packets", key: "8" },
1597
+ { id: "packets", label: "Conns", key: "8" },
1489
1598
  { id: "tasks", label: "Tasks", key: "9" },
1490
1599
  { id: "disk", label: "Disk", key: "0" },
1491
1600
  { id: "process", label: "Process", key: "P" },
1601
+ { id: "containers", label: "Containers", key: "C" },
1492
1602
  { id: "p2p", label: "P2P", key: "R" },
1493
- { id: "anomalies", label: "Anomalies", key: "A" }
1603
+ { id: "anomalies", label: "Anomalies", key: "A" },
1604
+ { id: "blender", label: "Blender", key: "B" }
1494
1605
  ],
1495
1606
  p2pServer: null,
1496
1607
  p2pClient: null,
@@ -1505,7 +1616,13 @@ var state = {
1505
1616
  splashColorScheme: config.splashColorScheme,
1506
1617
  splashAnimation: config.splashAnimation,
1507
1618
  menuSelectionIndex: 0,
1508
- readmeScrollOffset: 0
1619
+ readmeScrollOffset: 0,
1620
+ isDocker: isDockerEnv,
1621
+ dockerModeConfirmed: config.dockerModeConfirmed || false,
1622
+ dockerSelectionIndex: 0,
1623
+ aiModel: config.aiModel || "expert-rules-v1",
1624
+ aiBackend: config.aiBackend || "builtin",
1625
+ modelSelectionIndex: 0
1509
1626
  };
1510
1627
  function setStatus(msg, ms = 3e3) {
1511
1628
  state.statusMessage = msg;
@@ -1523,20 +1640,22 @@ function getToggleKey(opt) {
1523
1640
  "Toggle Battery": "battery",
1524
1641
  "Toggle Thermal": "thermal",
1525
1642
  "Toggle Network": "network",
1526
- "Toggle Packets": "packets",
1643
+ "Toggle Conns": "packets",
1527
1644
  "Toggle Tasks": "tasks",
1528
1645
  "Toggle Disk": "disk",
1529
1646
  "Toggle Processes": "process",
1647
+ "Toggle Containers": "containers",
1648
+ "Toggle Blender": "blender",
1530
1649
  "Toggle Tree View": "tree"
1531
1650
  };
1532
1651
  return map[opt] ?? null;
1533
1652
  }
1534
1653
 
1535
1654
  // src/live/export.ts
1536
- import fs from "fs";
1655
+ import fs2 from "fs";
1537
1656
  import path from "path";
1538
1657
  function ensureDir(dir) {
1539
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1658
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
1540
1659
  }
1541
1660
  function timestampForFile() {
1542
1661
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -1568,7 +1687,7 @@ function exportSnapshot() {
1568
1687
  content = formatMarkdown(state.lastData);
1569
1688
  break;
1570
1689
  }
1571
- fs.writeFileSync(file, content);
1690
+ fs2.writeFileSync(file, content);
1572
1691
  setStatus(`Exported snapshot -> ${file}`);
1573
1692
  } catch (err) {
1574
1693
  setStatus(`Export failed: ${err.message}`);
@@ -1579,7 +1698,7 @@ function startLogging() {
1579
1698
  ensureDir(state.exportDir);
1580
1699
  rotateLogs();
1581
1700
  const file = path.join(state.exportDir, `pyre-log-${timestampForFile()}.csv`);
1582
- state.logStream = fs.createWriteStream(file, { flags: "a" });
1701
+ state.logStream = fs2.createWriteStream(file, { flags: "a" });
1583
1702
  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
1703
  state.logging = true;
1585
1704
  setStatus(`Logging every tick -> ${file}`);
@@ -1593,37 +1712,37 @@ var MAX_LOG_BYTES = 50 * 1024 * 1024;
1593
1712
  function rotateLogs() {
1594
1713
  try {
1595
1714
  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);
1715
+ if (!fs2.existsSync(dir)) return;
1716
+ 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
1717
  const now = Date.now();
1599
1718
  for (const f of files) {
1600
1719
  if (now - f.mtime > MAX_LOG_AGE_MS) {
1601
1720
  try {
1602
- fs.unlinkSync(f.path);
1721
+ fs2.unlinkSync(f.path);
1603
1722
  } catch {
1604
1723
  }
1605
1724
  }
1606
1725
  }
1607
- const remaining = fs.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
1726
+ const remaining = fs2.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
1608
1727
  if (remaining.length > MAX_LOG_COUNT) {
1609
1728
  const toDelete = remaining.sort().slice(0, remaining.length - MAX_LOG_COUNT);
1610
1729
  for (const f of toDelete) {
1611
1730
  try {
1612
- fs.unlinkSync(path.join(dir, f));
1731
+ fs2.unlinkSync(path.join(dir, f));
1613
1732
  } catch {
1614
1733
  }
1615
1734
  }
1616
1735
  }
1617
1736
  let totalSize = 0;
1618
1737
  for (const f of remaining) {
1619
- totalSize += fs.statSync(path.join(dir, f)).size;
1738
+ totalSize += fs2.statSync(path.join(dir, f)).size;
1620
1739
  }
1621
1740
  if (totalSize > MAX_LOG_BYTES) {
1622
1741
  const sorted = remaining.sort();
1623
1742
  for (const f of sorted) {
1624
- const sz = fs.statSync(path.join(dir, f)).size;
1743
+ const sz = fs2.statSync(path.join(dir, f)).size;
1625
1744
  try {
1626
- fs.unlinkSync(path.join(dir, f));
1745
+ fs2.unlinkSync(path.join(dir, f));
1627
1746
  } catch {
1628
1747
  }
1629
1748
  totalSize -= sz;
@@ -1660,7 +1779,7 @@ function writeLogRow(data) {
1660
1779
 
1661
1780
  // src/live/render.ts
1662
1781
  import { execFile, exec } from "child_process";
1663
- import fs2 from "fs";
1782
+ import fs3 from "fs";
1664
1783
  import path2 from "path";
1665
1784
  import { fileURLToPath } from "url";
1666
1785
  import chalk2 from "chalk";
@@ -1668,76 +1787,127 @@ import chalk2 from "chalk";
1668
1787
  // src/anomalies.ts
1669
1788
  var ZSCORE_THRESHOLD = 2.5;
1670
1789
  var CRITICAL_ZSCORE = 3.5;
1671
- var MIN_SAMPLES = 5;
1790
+ var MIN_SAMPLES = 10;
1791
+ var MIN_DEV_CPU = 10;
1792
+ var MIN_DEV_MEM = 5;
1793
+ var MIN_DEV_TEMP = 5;
1794
+ var MIN_DEV_NET = 5e5;
1795
+ var MIN_DEV_POWER = 5;
1796
+ var MIN_DEV_GPU = 10;
1672
1797
  function detectAnomalies(data, history) {
1673
1798
  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
- });
1799
+ if (history.cpuUsage.length >= MIN_SAMPLES) {
1800
+ const cpuMean = History.mean(history.cpuUsage);
1801
+ if (Math.abs(data.cpu.usage - cpuMean) > MIN_DEV_CPU) {
1802
+ const cpuZ = History.zScore(data.cpu.usage, history.cpuUsage);
1803
+ if (Math.abs(cpuZ) > ZSCORE_THRESHOLD) {
1804
+ alerts.push({
1805
+ metric: "CPU",
1806
+ value: data.cpu.usage,
1807
+ zScore: cpuZ,
1808
+ severity: Math.abs(cpuZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1809
+ });
1810
+ }
1811
+ }
1682
1812
  }
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
- });
1813
+ if (history.memUsage.length >= MIN_SAMPLES) {
1814
+ const memMean = History.mean(history.memUsage);
1815
+ if (Math.abs(data.memory.usagePercent - memMean) > MIN_DEV_MEM) {
1816
+ const memZ = History.zScore(data.memory.usagePercent, history.memUsage);
1817
+ if (Math.abs(memZ) > ZSCORE_THRESHOLD) {
1818
+ alerts.push({
1819
+ metric: "Memory",
1820
+ value: data.memory.usagePercent,
1821
+ zScore: memZ,
1822
+ severity: Math.abs(memZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1823
+ });
1824
+ }
1825
+ }
1691
1826
  }
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
- });
1827
+ if (data.cpu.temperature !== void 0 && history.temp.length >= MIN_SAMPLES) {
1828
+ const tempMean = History.mean(history.temp);
1829
+ if (Math.abs(data.cpu.temperature - tempMean) > MIN_DEV_TEMP) {
1830
+ const tempZ = History.zScore(data.cpu.temperature, history.temp);
1831
+ if (Math.abs(tempZ) > ZSCORE_THRESHOLD) {
1832
+ alerts.push({
1833
+ metric: "Temp",
1834
+ value: data.cpu.temperature,
1835
+ zScore: tempZ,
1836
+ severity: Math.abs(tempZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1837
+ });
1838
+ }
1701
1839
  }
1702
1840
  }
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
- });
1841
+ if (history.netRxRate.length >= MIN_SAMPLES) {
1842
+ const currentRxRate = history.netRxRate[history.netRxRate.length - 1];
1843
+ const netRxMean = History.mean(history.netRxRate);
1844
+ if (Math.abs(currentRxRate - netRxMean) > MIN_DEV_NET) {
1845
+ const netRxZ = History.zScore(currentRxRate, history.netRxRate);
1846
+ if (Math.abs(netRxZ) > ZSCORE_THRESHOLD) {
1847
+ alerts.push({
1848
+ metric: "Net RX",
1849
+ value: currentRxRate,
1850
+ zScore: netRxZ,
1851
+ severity: Math.abs(netRxZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1852
+ });
1853
+ }
1854
+ }
1711
1855
  }
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
- });
1856
+ if (history.netTxRate.length >= MIN_SAMPLES) {
1857
+ const currentTxRate = history.netTxRate[history.netTxRate.length - 1];
1858
+ const netTxMean = History.mean(history.netTxRate);
1859
+ if (Math.abs(currentTxRate - netTxMean) > MIN_DEV_NET) {
1860
+ const netTxZ = History.zScore(currentTxRate, history.netTxRate);
1861
+ if (Math.abs(netTxZ) > ZSCORE_THRESHOLD) {
1862
+ alerts.push({
1863
+ metric: "Net TX",
1864
+ value: currentTxRate,
1865
+ zScore: netTxZ,
1866
+ severity: Math.abs(netTxZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1867
+ });
1868
+ }
1869
+ }
1720
1870
  }
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
- });
1871
+ if (data.power?.combinedWatts !== void 0 && history.powerWatts.length >= MIN_SAMPLES) {
1872
+ const powerMean = History.mean(history.powerWatts);
1873
+ if (Math.abs(data.power.combinedWatts - powerMean) > MIN_DEV_POWER) {
1874
+ const powerZ = History.zScore(data.power.combinedWatts, history.powerWatts);
1875
+ if (Math.abs(powerZ) > ZSCORE_THRESHOLD) {
1876
+ alerts.push({
1877
+ metric: "Power",
1878
+ value: data.power.combinedWatts,
1879
+ zScore: powerZ,
1880
+ severity: Math.abs(powerZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1881
+ });
1882
+ }
1730
1883
  }
1731
1884
  }
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
- });
1885
+ if (history.connections.length >= MIN_SAMPLES) {
1886
+ const connMean = History.mean(history.connections);
1887
+ if (Math.abs((data.packets?.connections || data.network.connections || 0) - connMean) > 5) {
1888
+ const connZ = History.zScore(data.packets?.connections || data.network.connections || 0, history.connections);
1889
+ if (Math.abs(connZ) > ZSCORE_THRESHOLD) {
1890
+ alerts.push({
1891
+ metric: "Connections",
1892
+ value: data.packets?.connections || data.network.connections || 0,
1893
+ zScore: connZ,
1894
+ severity: Math.abs(connZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1895
+ });
1896
+ }
1897
+ }
1898
+ }
1899
+ if (data.gpu && history.gpuUtil.length >= MIN_SAMPLES) {
1900
+ const gpuMean = History.mean(history.gpuUtil);
1901
+ if (Math.abs(data.gpu.utilization - gpuMean) > MIN_DEV_GPU) {
1902
+ const gpuZ = History.zScore(data.gpu.utilization, history.gpuUtil);
1903
+ if (Math.abs(gpuZ) > ZSCORE_THRESHOLD) {
1904
+ alerts.push({
1905
+ metric: "GPU",
1906
+ value: data.gpu.utilization,
1907
+ zScore: gpuZ,
1908
+ severity: Math.abs(gpuZ) > CRITICAL_ZSCORE ? "critical" : "warning"
1909
+ });
1910
+ }
1741
1911
  }
1742
1912
  }
1743
1913
  return alerts;
@@ -1781,7 +1951,8 @@ function tableCacheParams() {
1781
1951
  state.treeView ? "1" : "0",
1782
1952
  state.history.version,
1783
1953
  JSON.stringify(state.visiblePanels),
1784
- JSON.stringify(state.panelLayout)
1954
+ JSON.stringify(state.panelLayout),
1955
+ state.aiModel
1785
1956
  ].join("|");
1786
1957
  }
1787
1958
  function graphsCacheParams() {
@@ -1850,6 +2021,11 @@ function renderCustomizerOverlay(scrollOffset, maxVisibleItems) {
1850
2021
  lines.push(`${prefix}${opt}: ${status}`);
1851
2022
  } else if (opt === "Temperature Unit") {
1852
2023
  lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.tempUnit.toUpperCase())} [C, F]`);
2024
+ } else if (opt === "AI Model") {
2025
+ const curModel = AVAILABLE_AI_MODELS.find((m) => m.id === state.aiModel) || AVAILABLE_AI_MODELS[0];
2026
+ lines.push(`${prefix}${opt}: ${chalk2.bold.cyan(curModel.name)} ${chalk2.dim(`[${curModel.badge}]`)}`);
2027
+ } else if (opt === "Grid Panel Order") {
2028
+ lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.panelLayout.join(" \u2192 "))}`);
1853
2029
  } else {
1854
2030
  const toggleKey = getToggleKey(opt);
1855
2031
  const isVisible = toggleKey ? toggleKey === "tree" ? state.treeView : state.visiblePanels[toggleKey] !== false : true;
@@ -1906,6 +2082,14 @@ function render() {
1906
2082
  writeFrame(renderCredits(cols, rows));
1907
2083
  return;
1908
2084
  }
2085
+ if (state.inputMode === "docker-alert") {
2086
+ writeFrame(renderDockerAlert(cols, rows));
2087
+ return;
2088
+ }
2089
+ if (state.inputMode === "ai-model") {
2090
+ writeFrame(renderAiModelSelector(cols, rows));
2091
+ return;
2092
+ }
1909
2093
  const lines = [];
1910
2094
  const theme = THEMES[state.currentTheme] || THEMES.default;
1911
2095
  const params = tableCacheParams();
@@ -1945,7 +2129,8 @@ function render() {
1945
2129
  panelLayout: state.panelLayout,
1946
2130
  selectedProcessIndex: state.processSelectionIndex,
1947
2131
  trackedPid: state.trackedPid,
1948
- inspectProcess: state.inspectingProcess
2132
+ inspectProcess: state.inspectingProcess,
2133
+ aiModel: state.aiModel
1949
2134
  });
1950
2135
  _cachedTableData = state.lastData;
1951
2136
  _cachedTableParams = cacheParams;
@@ -2042,7 +2227,8 @@ function footerLine() {
2042
2227
  ["l", state.logging ? "stop log" : "log"],
2043
2228
  ["f", state.exportFormat],
2044
2229
  ["+/-", `${state.interval}s`],
2045
- ["r", state.p2pServerRunning ? "stop p2p" : "start p2p"]
2230
+ ["r", state.p2pServerRunning ? "stop p2p" : "start p2p"],
2231
+ ["m", `ai:${state.aiModel.split("-")[0]}`]
2046
2232
  ];
2047
2233
  const fmt = (list) => list.map(([k, label]) => `${chalk2.hex("#50fa7b").bold(k)} ${chalk2.dim(label)}`).join(chalk2.dim(" | "));
2048
2234
  const str1 = fmt(row1);
@@ -2052,6 +2238,7 @@ function footerLine() {
2052
2238
  if (state.logging) badges.push(chalk2.red.bold("\u25CF REC"));
2053
2239
  if (state.activePanel !== "grid") badges.push(chalk2.cyan.bold(`\u25C9 ${state.activePanel.toUpperCase()}`));
2054
2240
  if (state.p2pServerRunning) badges.push(chalk2.green.bold(`P2P ${state.p2pBind || "0.0.0.0"}:${state.p2pPort}`));
2241
+ badges.push(chalk2.magenta.bold(`AI:${state.aiModel}`));
2055
2242
  const badgeStr = badges.join(" ") || " ";
2056
2243
  return [str1, str2, badgeStr];
2057
2244
  }
@@ -2113,6 +2300,28 @@ function checkAlerts(data) {
2113
2300
  exec(state.alertCmd, { env: { ...process.env, PYRE_ALERT: alertMsg } }, () => {
2114
2301
  });
2115
2302
  }
2303
+ const cfg = readConfig();
2304
+ const channelConfig = {
2305
+ slackUrl: cfg.slackAlertUrl || void 0,
2306
+ discordUrl: cfg.discordAlertUrl || void 0,
2307
+ pushoverToken: cfg.pushoverToken || void 0,
2308
+ pushoverUser: cfg.pushoverUser || void 0,
2309
+ ntfyUrl: cfg.ntfyUrl || void 0
2310
+ };
2311
+ const hasChannels = Object.values(channelConfig).some((v) => !!v);
2312
+ if (hasChannels) {
2313
+ const severity = hasCriticalAnomaly ? "critical" : "warning";
2314
+ sendAlert({
2315
+ metric: reasons[0]?.split(" ")[0] ?? "System",
2316
+ value: data.cpu.usage,
2317
+ threshold: state.CPU_ALERT_PCT,
2318
+ unit: "%",
2319
+ host: data.header.hostname,
2320
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2321
+ severity
2322
+ }, channelConfig).catch(() => {
2323
+ });
2324
+ }
2116
2325
  } else if (!hot && !anomalyTriggered && !watchdogTriggered) {
2117
2326
  state.alerted = false;
2118
2327
  }
@@ -2161,7 +2370,7 @@ function getReadmeLines() {
2161
2370
  const __dirname = path2.dirname(__filename);
2162
2371
  const rootDir = path2.resolve(__dirname, "..", "..");
2163
2372
  const readmePath = path2.join(rootDir, "README.md");
2164
- const content = fs2.readFileSync(readmePath, "utf8");
2373
+ const content = fs3.readFileSync(readmePath, "utf8");
2165
2374
  cachedReadmeLines = content.split("\n");
2166
2375
  return cachedReadmeLines;
2167
2376
  } catch (err) {
@@ -2206,11 +2415,57 @@ function renderCredits(cols, rows) {
2206
2415
  out.push(...panel("CREDITS", content, cols, chalk2.magenta, chalk2.dim, rows - 2));
2207
2416
  return out;
2208
2417
  }
2418
+ function renderDockerAlert(cols, rows) {
2419
+ const out = [];
2420
+ const content = [
2421
+ chalk2.bold.red("Docker Container Detected"),
2422
+ "",
2423
+ "Pyre is running inside a container. Some hardware sensors",
2424
+ "(battery, thermal, power) may not be available or relevant.",
2425
+ "",
2426
+ chalk2.cyan("Do you wish to continue in Docker mode?"),
2427
+ ""
2428
+ ];
2429
+ const options = ["Yes, use Docker mode (hide hardware sensors)", "No, launch regular TUI with all features"];
2430
+ for (let i = 0; i < options.length; i++) {
2431
+ const isSelected = i === state.dockerSelectionIndex;
2432
+ const prefix = isSelected ? chalk2.cyan.bold(" \u25B6 ") : " ";
2433
+ const text = isSelected ? chalk2.bgCyan.black(` ${options[i]} `) : chalk2.dim(` ${options[i]} `);
2434
+ content.push(prefix + text);
2435
+ }
2436
+ content.push("");
2437
+ content.push(chalk2.dim("Press \u2191/\u2193 to select, Enter to confirm, Esc to skip."));
2438
+ out.push(...panel("DOCKER DETECTED", content, cols, chalk2.red, chalk2.dim, rows - 2));
2439
+ return out;
2440
+ }
2441
+ function renderAiModelSelector(cols, rows) {
2442
+ const out = [];
2443
+ const content = [
2444
+ chalk2.bold.hex("#ff6004")("Choose AI Anomaly Explanation Model"),
2445
+ chalk2.dim("Select your preferred diagnosis engine. Offline models run entirely in-process."),
2446
+ ""
2447
+ ];
2448
+ for (let i = 0; i < AVAILABLE_AI_MODELS.length; i++) {
2449
+ const m = AVAILABLE_AI_MODELS[i];
2450
+ const isSelected = i === state.modelSelectionIndex;
2451
+ const isCurrent = m.id === state.aiModel;
2452
+ const prefix = isSelected ? chalk2.cyan.bold(" \u25B6 ") : " ";
2453
+ const curBadge = isCurrent ? chalk2.bgGreen.black.bold(" ACTIVE ") : "";
2454
+ const badgeStr = m.offline ? chalk2.green(`[${m.badge}]`) : chalk2.yellow(`[${m.badge}]`);
2455
+ const nameStr = isSelected ? chalk2.bgCyan.black.bold(` ${m.name} `) : chalk2.bold.white(m.name);
2456
+ content.push(`${prefix}${nameStr} ${badgeStr} ${curBadge}`);
2457
+ content.push(chalk2.dim(` ${m.description}`));
2458
+ content.push("");
2459
+ }
2460
+ content.push(chalk2.dim("Use \u2191/\u2193 or J/K to navigate \xB7 Enter/Space to select \xB7 Esc to cancel"));
2461
+ out.push(...panel("AI MODEL SELECTOR", content, cols, chalk2.cyan, chalk2.dim, Math.max(1, rows - 2)));
2462
+ return out;
2463
+ }
2209
2464
 
2210
2465
  // src/p2p/server.ts
2211
2466
  import net from "net";
2212
2467
  import tls from "tls";
2213
- import fs3 from "fs";
2468
+ import fs4 from "fs";
2214
2469
  import crypto2 from "crypto";
2215
2470
  import chalk3 from "chalk";
2216
2471
 
@@ -2355,9 +2610,9 @@ var P2PServer = class {
2355
2610
  async start() {
2356
2611
  if (this.options.auditLog) {
2357
2612
  try {
2358
- fs3.mkdirSync(this.options.auditLog, { recursive: true });
2613
+ fs4.mkdirSync(this.options.auditLog, { recursive: true });
2359
2614
  const logFile = `${this.options.auditLog}/p2p-audit.log`;
2360
- this.auditStream = fs3.createWriteStream(logFile, { flags: "a" });
2615
+ this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
2361
2616
  } catch {
2362
2617
  this.log(chalk3.yellow("Warning: could not open audit log"));
2363
2618
  }
@@ -2366,11 +2621,11 @@ var P2PServer = class {
2366
2621
  const useTLS = !!(this.options.tlsCert && this.options.tlsKey);
2367
2622
  if (useTLS) {
2368
2623
  const tlsOptions = {
2369
- key: fs3.readFileSync(this.options.tlsKey),
2370
- cert: fs3.readFileSync(this.options.tlsCert)
2624
+ key: fs4.readFileSync(this.options.tlsKey),
2625
+ cert: fs4.readFileSync(this.options.tlsCert)
2371
2626
  };
2372
2627
  if (this.options.tlsCA) {
2373
- tlsOptions.ca = fs3.readFileSync(this.options.tlsCA);
2628
+ tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
2374
2629
  }
2375
2630
  this.server = tls.createServer(tlsOptions, (socket) => this.onConnection(socket));
2376
2631
  } else {
@@ -2638,7 +2893,7 @@ async function startP2PServer(options) {
2638
2893
  // src/p2p/client.ts
2639
2894
  import net2 from "net";
2640
2895
  import tls2 from "tls";
2641
- import fs4 from "fs";
2896
+ import fs5 from "fs";
2642
2897
  import crypto3 from "crypto";
2643
2898
  import chalk4 from "chalk";
2644
2899
  var P2PClient = class {
@@ -2666,9 +2921,9 @@ var P2PClient = class {
2666
2921
  async connect() {
2667
2922
  if (this.options.auditLog) {
2668
2923
  try {
2669
- fs4.mkdirSync(this.options.auditLog, { recursive: true });
2924
+ fs5.mkdirSync(this.options.auditLog, { recursive: true });
2670
2925
  const logFile = `${this.options.auditLog}/p2p-client-audit.log`;
2671
- this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
2926
+ this.auditStream = fs5.createWriteStream(logFile, { flags: "a" });
2672
2927
  } catch {
2673
2928
  console.log(chalk4.yellow("Warning: could not open audit log"));
2674
2929
  }
@@ -2683,7 +2938,7 @@ var P2PClient = class {
2683
2938
  rejectUnauthorized: !this.options.tlsInsecure
2684
2939
  };
2685
2940
  if (this.options.tlsCA) {
2686
- tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
2941
+ tlsOptions.ca = fs5.readFileSync(this.options.tlsCA);
2687
2942
  }
2688
2943
  this.socket = tls2.connect(tlsOptions, () => {
2689
2944
  this.running = true;
@@ -2872,6 +3127,58 @@ function deriveHMACKey2(password) {
2872
3127
  return crypto3.createHash("sha256").update(password).digest("hex").slice(0, 32);
2873
3128
  }
2874
3129
 
3130
+ // src/plugins/index.ts
3131
+ import fs6 from "fs";
3132
+ import path3 from "path";
3133
+ import { pathToFileURL } from "url";
3134
+ var loadedPlugins = /* @__PURE__ */ new Map();
3135
+ function getPluginsDir() {
3136
+ return path3.join(getConfigDir(), "plugins");
3137
+ }
3138
+ async function loadPlugins() {
3139
+ const pluginsDir = getPluginsDir();
3140
+ if (!fs6.existsSync(pluginsDir)) {
3141
+ try {
3142
+ fs6.mkdirSync(pluginsDir, { recursive: true });
3143
+ } catch {
3144
+ return loadedPlugins;
3145
+ }
3146
+ }
3147
+ let files = [];
3148
+ try {
3149
+ files = fs6.readdirSync(pluginsDir).filter((f) => f.endsWith(".js") || f.endsWith(".mjs"));
3150
+ } catch {
3151
+ return loadedPlugins;
3152
+ }
3153
+ for (const file of files) {
3154
+ const filePath = path3.join(pluginsDir, file);
3155
+ try {
3156
+ const fileUrl = pathToFileURL(filePath).href;
3157
+ const mod = await import(`${fileUrl}?t=${Date.now()}`);
3158
+ const plugin = mod.default || mod;
3159
+ if (plugin && typeof plugin.collect === "function") {
3160
+ const name = plugin.name || path3.basename(file, path3.extname(file));
3161
+ if (!loadedPlugins.has(name)) {
3162
+ loadedPlugins.set(name, {
3163
+ plugin: { ...plugin, name },
3164
+ lastRun: 0,
3165
+ lastOutput: { title: plugin.title || name, lines: ["Collecting..."] }
3166
+ });
3167
+ }
3168
+ }
3169
+ } catch (err) {
3170
+ const name = path3.basename(file, path3.extname(file));
3171
+ loadedPlugins.set(name, {
3172
+ plugin: { name, collect: async () => ({ lines: [`Error loading plugin: ${err.message}`] }) },
3173
+ lastRun: Date.now(),
3174
+ lastOutput: { title: name, lines: [`Failed to load: ${err.message}`] },
3175
+ error: err.message
3176
+ });
3177
+ }
3178
+ }
3179
+ return loadedPlugins;
3180
+ }
3181
+
2875
3182
  // src/live/input.ts
2876
3183
  function persistConfig() {
2877
3184
  writeConfig({
@@ -2893,7 +3200,10 @@ function persistConfig() {
2893
3200
  splashColorScheme: state.splashColorScheme,
2894
3201
  splashAnimation: state.splashAnimation,
2895
3202
  visiblePanels: { ...state.visiblePanels },
2896
- notificationsEnabled: state.notificationsEnabled
3203
+ notificationsEnabled: state.notificationsEnabled,
3204
+ dockerModeConfirmed: state.dockerModeConfirmed,
3205
+ aiModel: state.aiModel,
3206
+ aiBackend: state.aiBackend
2897
3207
  });
2898
3208
  }
2899
3209
  function isUpKey(key, str) {
@@ -2947,6 +3257,24 @@ function handleInputModeKey(str, key) {
2947
3257
  } else if (selected === "Temperature Unit") {
2948
3258
  state.tempUnit = state.tempUnit === "c" ? "f" : "c";
2949
3259
  setStatus(`Temperature unit: ${state.tempUnit.toUpperCase()}`);
3260
+ } else if (selected === "AI Model") {
3261
+ const curIdx = AVAILABLE_AI_MODELS.findIndex((m) => m.id === state.aiModel);
3262
+ const nextIdx = (curIdx + 1) % AVAILABLE_AI_MODELS.length;
3263
+ state.aiModel = AVAILABLE_AI_MODELS[nextIdx].id;
3264
+ state.aiBackend = AVAILABLE_AI_MODELS[nextIdx].backend;
3265
+ setStatus(`AI Model: ${AVAILABLE_AI_MODELS[nextIdx].name}`);
3266
+ } else if (selected === "Grid Panel Order") {
3267
+ const layouts = [
3268
+ ["mem", "disk", "net"],
3269
+ ["disk", "mem", "net"],
3270
+ ["net", "mem", "disk"],
3271
+ ["mem", "net", "disk"]
3272
+ ];
3273
+ const currentStr = state.panelLayout.join(",");
3274
+ const currentIdx = layouts.findIndex((l) => l.join(",") === currentStr);
3275
+ const nextIdx = (currentIdx + 1) % layouts.length;
3276
+ state.panelLayout = layouts[nextIdx];
3277
+ setStatus(`Panel layout: ${state.panelLayout.join(" \u2192 ")}`);
2950
3278
  } else {
2951
3279
  const toggleKey = getToggleKey(selected);
2952
3280
  if (toggleKey) {
@@ -3028,6 +3356,76 @@ function handleInputModeKey(str, key) {
3028
3356
  }
3029
3357
  return;
3030
3358
  }
3359
+ if (state.inputMode === "docker-alert") {
3360
+ if (isEscKey(key, str)) {
3361
+ state.inputMode = null;
3362
+ state.dockerModeConfirmed = true;
3363
+ render();
3364
+ return;
3365
+ }
3366
+ if (isUpKey(key, str)) {
3367
+ state.dockerSelectionIndex = Math.max(0, state.dockerSelectionIndex - 1);
3368
+ render();
3369
+ return;
3370
+ }
3371
+ if (isDownKey(key, str)) {
3372
+ state.dockerSelectionIndex = Math.min(1, state.dockerSelectionIndex + 1);
3373
+ render();
3374
+ return;
3375
+ }
3376
+ if (isEnterKey(key, str) || str === " ") {
3377
+ state.dockerModeConfirmed = true;
3378
+ state.inputMode = null;
3379
+ if (state.dockerSelectionIndex === 0) {
3380
+ state.visiblePanels.battery = false;
3381
+ state.visiblePanels.thermal = false;
3382
+ state.visiblePanels.power = false;
3383
+ state.visiblePanels.gpu = false;
3384
+ state.detailed = false;
3385
+ } else {
3386
+ state.visiblePanels.battery = true;
3387
+ state.visiblePanels.thermal = true;
3388
+ state.visiblePanels.power = true;
3389
+ state.visiblePanels.gpu = true;
3390
+ }
3391
+ invalidateTableCache();
3392
+ invalidateFrame();
3393
+ persistConfig();
3394
+ render();
3395
+ return;
3396
+ }
3397
+ return;
3398
+ }
3399
+ if (state.inputMode === "ai-model") {
3400
+ if (isEscKey(key, str)) {
3401
+ state.inputMode = null;
3402
+ render();
3403
+ return;
3404
+ }
3405
+ if (isUpKey(key, str) || str === "k") {
3406
+ state.modelSelectionIndex = (state.modelSelectionIndex - 1 + AVAILABLE_AI_MODELS.length) % AVAILABLE_AI_MODELS.length;
3407
+ render();
3408
+ return;
3409
+ }
3410
+ if (isDownKey(key, str) || str === "j") {
3411
+ state.modelSelectionIndex = (state.modelSelectionIndex + 1) % AVAILABLE_AI_MODELS.length;
3412
+ render();
3413
+ return;
3414
+ }
3415
+ if (isEnterKey(key, str) || str === " ") {
3416
+ const chosen = AVAILABLE_AI_MODELS[state.modelSelectionIndex];
3417
+ state.aiModel = chosen.id;
3418
+ state.aiBackend = chosen.backend;
3419
+ state.inputMode = null;
3420
+ setStatus(`Active AI Model: ${chosen.name}`, 4e3);
3421
+ invalidateTableCache();
3422
+ invalidateFrame();
3423
+ persistConfig();
3424
+ render();
3425
+ return;
3426
+ }
3427
+ return;
3428
+ }
3031
3429
  if (state.inputMode === "signal") {
3032
3430
  if (isUpKey(key, str)) {
3033
3431
  const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
@@ -3260,6 +3658,8 @@ async function startLive(opts2, splashPromise) {
3260
3658
  state.termHeight = process.stdout.rows || 24;
3261
3659
  state.history.reset();
3262
3660
  state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
3661
+ await loadPlugins().catch(() => {
3662
+ });
3263
3663
  const warmupPromise = doWarmup();
3264
3664
  if (splashPromise) {
3265
3665
  await splashPromise;
@@ -3358,6 +3758,18 @@ async function startLive(opts2, splashPromise) {
3358
3758
  render();
3359
3759
  return;
3360
3760
  }
3761
+ case "C": {
3762
+ state.activePanel = state.activePanel === "containers" ? "grid" : "containers";
3763
+ setStatus(state.activePanel === "grid" ? "Grid view" : "Containers panel");
3764
+ render();
3765
+ return;
3766
+ }
3767
+ case "B": {
3768
+ state.activePanel = state.activePanel === "blender" ? "grid" : "blender";
3769
+ setStatus(state.activePanel === "grid" ? "Grid view" : "Blender panel");
3770
+ render();
3771
+ return;
3772
+ }
3361
3773
  }
3362
3774
  switch (key.name) {
3363
3775
  case "r":
@@ -3385,6 +3797,11 @@ async function startLive(opts2, splashPromise) {
3385
3797
  state.customizerIndex = 0;
3386
3798
  render();
3387
3799
  break;
3800
+ case "m":
3801
+ state.inputMode = "ai-model";
3802
+ state.modelSelectionIndex = Math.max(0, AVAILABLE_AI_MODELS.findIndex((m) => m.id === state.aiModel));
3803
+ render();
3804
+ break;
3388
3805
  case "p":
3389
3806
  state.paused = !state.paused;
3390
3807
  setStatus(state.paused ? "Paused" : "Resumed");
@@ -3531,7 +3948,7 @@ async function startLive(opts2, splashPromise) {
3531
3948
  process.once("SIGINT", () => stopLive());
3532
3949
  }
3533
3950
  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" };
3951
+ 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
3952
  return map[str] ?? null;
3536
3953
  }
3537
3954
  function cycleTab(direction) {
@@ -3955,8 +4372,8 @@ async function showSplash(config3) {
3955
4372
  }
3956
4373
 
3957
4374
  // src/historyCmd.ts
3958
- import fs5 from "fs";
3959
- import path3 from "path";
4375
+ import fs7 from "fs";
4376
+ import path4 from "path";
3960
4377
  import chalk6 from "chalk";
3961
4378
  function runHistoryCommand(opts2 = {}) {
3962
4379
  const days = opts2.days || 7;
@@ -3964,11 +4381,11 @@ function runHistoryCommand(opts2 = {}) {
3964
4381
  console.log(chalk6.bold(`
3965
4382
  pyre history \u2014 Past ${days} Days Trend
3966
4383
  `));
3967
- if (!fs5.existsSync(dir)) {
4384
+ if (!fs7.existsSync(dir)) {
3968
4385
  console.log(chalk6.yellow(` No logs directory found at ${dir}`));
3969
4386
  return;
3970
4387
  }
3971
- const files = fs5.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4388
+ const files = fs7.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
3972
4389
  if (files.length === 0) {
3973
4390
  console.log(chalk6.yellow(` No CSV logs found in ${dir}`));
3974
4391
  return;
@@ -3978,9 +4395,9 @@ function runHistoryCommand(opts2 = {}) {
3978
4395
  const memPoints = [];
3979
4396
  const tempPoints = [];
3980
4397
  for (const file of files.sort()) {
3981
- const fullPath = path3.join(dir, file);
4398
+ const fullPath = path4.join(dir, file);
3982
4399
  try {
3983
- const content = fs5.readFileSync(fullPath, "utf-8");
4400
+ const content = fs7.readFileSync(fullPath, "utf-8");
3984
4401
  const lines = content.split("\n").slice(1);
3985
4402
  for (const line of lines) {
3986
4403
  if (!line.trim()) continue;
@@ -4015,7 +4432,7 @@ function runHistoryCommand(opts2 = {}) {
4015
4432
  }
4016
4433
 
4017
4434
  // src/diffCmd.ts
4018
- import fs6 from "fs";
4435
+ import fs8 from "fs";
4019
4436
  import chalk7 from "chalk";
4020
4437
  function runDiffCommand(file1, file2) {
4021
4438
  console.log(chalk7.bold(`
@@ -4025,8 +4442,8 @@ function runDiffCommand(file1, file2) {
4025
4442
  console.log(chalk7.dim(` Snapshot B: ${file2}
4026
4443
  `));
4027
4444
  try {
4028
- const dataA = JSON.parse(fs6.readFileSync(file1, "utf-8"));
4029
- const dataB = JSON.parse(fs6.readFileSync(file2, "utf-8"));
4445
+ const dataA = JSON.parse(fs8.readFileSync(file1, "utf-8"));
4446
+ const dataB = JSON.parse(fs8.readFileSync(file2, "utf-8"));
4030
4447
  const diffNum = (label, valA, valB, unit = "") => {
4031
4448
  const delta = valB - valA;
4032
4449
  const sign = delta > 0 ? "+" : "";
@@ -4050,8 +4467,8 @@ function runDiffCommand(file1, file2) {
4050
4467
  }
4051
4468
 
4052
4469
  // src/xbar.ts
4053
- import fs7 from "fs";
4054
- import path4 from "path";
4470
+ import fs9 from "fs";
4471
+ import path5 from "path";
4055
4472
  import os from "os";
4056
4473
  import chalk8 from "chalk";
4057
4474
  function generateXbarPlugin() {
@@ -4062,10 +4479,9 @@ function generateXbarPlugin() {
4062
4479
  # <xbar.version>v1.0</xbar.version>
4063
4480
  # <xbar.author>somalip</xbar.author>
4064
4481
  # <xbar.author.github>somalip</xbar.author.github>
4065
- # <xbar.desc>Mac system monitoring powered by pyre.</xbar.desc>
4482
+ # <xbar.desc>Cross-platform system monitoring powered by pyre.</xbar.desc>
4066
4483
 
4067
4484
  # Run pyre to get a JSON snapshot
4068
- # We use node directly to avoid env issues in xbar
4069
4485
  DATA=$("${nodePath}" "${pyrePath}" --once --json)
4070
4486
 
4071
4487
  if [ -z "$DATA" ]; then
@@ -4088,15 +4504,25 @@ echo "\u{1F525} C:\${CPU}% M:\${MEM}% T:\${TEMP}\xB0C | font=Menlo"
4088
4504
  echo "---"
4089
4505
  echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' terminal=true"
4090
4506
  `;
4091
- const pluginDir = path4.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
4092
4507
  const filename = "pyre-monitor.2s.sh";
4093
- const outPath = path4.join(process.cwd(), filename);
4094
- fs7.writeFileSync(outPath, script);
4095
- fs7.chmodSync(outPath, "755");
4096
- console.log(chalk8.green(`Generated xbar plugin script at ${outPath}`));
4097
- console.log(`To install, move it to your xbar plugins directory:`);
4098
- console.log(chalk8.cyan(` mv ${filename} "${pluginDir}/"`));
4099
- console.log(`Then refresh xbar.`);
4508
+ const outPath = path5.join(process.cwd(), filename);
4509
+ fs9.writeFileSync(outPath, script);
4510
+ try {
4511
+ fs9.chmodSync(outPath, 493);
4512
+ } catch {
4513
+ }
4514
+ console.log(chalk8.green(`Generated menu bar plugin script at ${outPath}`));
4515
+ if (process.platform === "darwin") {
4516
+ const pluginDir = path5.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
4517
+ console.log(`To install in xbar or SwiftBar on macOS:`);
4518
+ console.log(chalk8.cyan(` mv ${filename} "${pluginDir}/"`));
4519
+ } else if (process.platform === "linux") {
4520
+ const argosDir = path5.join(os.homedir(), ".config", "argos");
4521
+ console.log(`To install in Argos (GNOME Shell) on Linux:`);
4522
+ console.log(chalk8.cyan(` mkdir -p "${argosDir}" && mv ${filename} "${argosDir}/"`));
4523
+ } else {
4524
+ console.log(chalk8.dim(`Compatible with BitBar, xbar, and Argos shell plugins.`));
4525
+ }
4100
4526
  }
4101
4527
 
4102
4528
  // src/fleet.ts
@@ -4130,36 +4556,49 @@ async function runFleetCommand(hosts) {
4130
4556
 
4131
4557
  // src/index.ts
4132
4558
  var config2 = readConfig();
4133
- var pkg = JSON.parse(fs8.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4559
+ var pkg = JSON.parse(fs10.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4134
4560
  var program = new Command();
4135
- 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");
4561
+ program.name("pyre").version(pkg.version).description("Cross-platform 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").option("--prometheus-port <port>", "Port number for Prometheus exporter (default: 9090)", "9090").option("--speed <n>", "Playback speed multiplier for pyre replay (default: 1)", "1").option("--llm-backend <backend>", "AI backend: builtin (local zero-setup), ollama, or openai (default: builtin)", "builtin").option("--llm-model <model>", "AI model name for pyre explain (default: expert-rules-v1 / llama3.2 / gpt-4o-mini)").option("--api-key <key>", "API key for OpenAI-compatible LLM backend (or set OPENAI_API_KEY env)").option("--alert-slack <url>", "Slack webhook URL for alert notifications").option("--alert-discord <url>", "Discord webhook URL for alert notifications").option("--alert-pushover-token <token>", "Pushover application token for alert notifications").option("--alert-pushover-user <user>", "Pushover user key for alert notifications").option("--alert-ntfy <url>", "ntfy.sh topic URL for alert notifications (e.g. https://ntfy.sh/my-topic)").option("--watchdog-config <file>", "Path to custom watchdog JSON configuration rules").option("--dry-run", "Simulate actions in watchdog mode without executing kills").option("--top <n>", "Number of items to display in netusage command (default: 15)").option("--raw", "Print unparsed output for profiler").option("--sync-dir <dir>", "Custom directory path for cloud profile/config sync").option("--scan", "Perform active network sweep on LAN neighbors for topology map");
4136
4562
  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
- `);
4563
+ Commands:
4564
+ live Interactive live dashboard
4565
+ check One-line plain-English health summary
4566
+ pipe Continuous newline-delimited JSON stream for scripting
4567
+ stress Synthetic CPU/GPU load generator
4568
+ ui Launch the live UI dashboard window
4569
+ web Serve an auto-refreshing live web portal
4570
+ ssh <host> Stream live stats from a remote host over SSH
4571
+ fleet <host1> [host2]... Multi-host live dashboard monitoring multiple hosts
4572
+ bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
4573
+ benchmark Run a 1-minute CPU benchmark calculating digits of PI
4574
+ anomalies Compute z-score anomalies and print plain-language digest
4575
+ doctor Run system diagnostics
4576
+ extensions System Extensions / Driver inspector
4577
+ brew Package manager health panel (brew/apt/pacman/winget)
4578
+ update Check for pyre-cli updates
4579
+ profile <save|load|list> Atomic configuration profile management
4580
+ config <show|reset> View or reset persistent configuration file
4581
+ history Graph historical resource trends from CSV logs
4582
+ diff <file1> <file2> Compare two saved snapshot files side-by-side
4583
+ info Concise hardware summary
4584
+ completions <shell> Generate shell auto-completion scripts (zsh, bash, fish, powershell)
4585
+ xbar Generate an xbar / SwiftBar menu bar plugin script
4586
+ p2p <server|connect> Start a P2P server or connect to one
4587
+ server Print commands for starting P2P server/client
4588
+ blender Track active Blender render jobs and their progress
4589
+ prometheus Start a Prometheus metrics exporter on localhost
4590
+ smart S.M.A.R.T. disk health report (requires smartmontools)
4591
+ replay <file> Play back a pyre CSV log file in the TUI
4592
+ explain AI-powered plain-English anomaly explanation (Ollama/OpenAI)
4593
+ alert test Send a test notification to all configured alert channels
4594
+ netusage Real-time per-application network usage breakdown
4595
+ serve Lightweight structured JSON REST API server
4596
+ profile-proc <pid> CPU call-tree profiler and hot function sampler
4597
+ watchdog Persistent autonomous monitoring daemon and rules engine
4598
+ build Active compiler and build system tracker
4599
+ topo Network topology mapper and pyre peer auto-discovery
4600
+ sync <push|pull|status> Synchronize config profiles across Macs via iCloud/Drive
4601
+ `);
4163
4602
  program.parse(process.argv);
4164
4603
  var opts = program.opts();
4165
4604
  if (opts.plain || opts.a11y) {
@@ -4219,11 +4658,11 @@ async function runServerCommand() {
4219
4658
  console.log();
4220
4659
  }
4221
4660
  async function main() {
4222
- const { runSetupWizardIfNeeded } = await import("./wizard-GVIAHIVV.js");
4661
+ const { runSetupWizardIfNeeded } = await import("./wizard-TD7BGX6O.js");
4223
4662
  await runSetupWizardIfNeeded(!opts.wizard);
4224
4663
  const cmd = program.args[0];
4225
4664
  if (cmd === "check") {
4226
- const { runCheckCommand } = await import("./checkCmd-EWFNLBWE.js");
4665
+ const { runCheckCommand } = await import("./checkCmd-RKW6QGDR.js");
4227
4666
  await runCheckCommand({
4228
4667
  exitCode: !!opts.exitCode,
4229
4668
  cpuAlertPct: config2.cpuAlertPct,
@@ -4233,7 +4672,7 @@ async function main() {
4233
4672
  return;
4234
4673
  }
4235
4674
  if (cmd === "pipe") {
4236
- const { runPipeCommand } = await import("./pipeCmd-7THXEGDH.js");
4675
+ const { runPipeCommand } = await import("./pipeCmd-7REDBLBU.js");
4237
4676
  const intervalSec = parseFloat(opts.interval) || 1;
4238
4677
  const samples = parseInt(opts.samples, 10) || 0;
4239
4678
  await runPipeCommand({
@@ -4256,12 +4695,176 @@ async function main() {
4256
4695
  return;
4257
4696
  }
4258
4697
  if (cmd === "extensions") {
4259
- const { printExtensionsReport } = await import("./extensions-DVA5YLGH.js");
4698
+ const { printExtensionsReport } = await import("./extensions-XJGHG7X3.js");
4260
4699
  await printExtensionsReport();
4261
4700
  return;
4262
4701
  }
4702
+ if (cmd === "blender") {
4703
+ const { collectBlenderRenders } = await import("./blender-FGH3ASEQ.js");
4704
+ const renders = await collectBlenderRenders();
4705
+ if (!renders.length) {
4706
+ console.log(chalk10.dim("No active Blender renders detected."));
4707
+ return;
4708
+ }
4709
+ const lines = [];
4710
+ lines.push(chalk10.bold(" Blender Render Tracker"));
4711
+ lines.push("");
4712
+ for (const r of renders) {
4713
+ const statusColor = r.status === "rendering" ? chalk10.green : r.status === "finalizing" ? chalk10.yellow : r.status === "loading" ? chalk10.cyan : chalk10.dim;
4714
+ lines.push(` PID: ${r.pid}`);
4715
+ lines.push(` Blend File: ${r.blendFile}`);
4716
+ lines.push(` Engine: ${r.renderEngine}`);
4717
+ lines.push(` Frames: ${r.currentFrame} / ${r.totalFrames} (${r.completionPercent}%)`);
4718
+ lines.push(` Elapsed: ${r.elapsedSec}s`);
4719
+ lines.push(` Status: ${statusColor(r.status)}`);
4720
+ lines.push(` Output: ${r.outputPath}`);
4721
+ if (r.sampleCount !== void 0) lines.push(` Samples: ${r.sampleCount}`);
4722
+ lines.push("");
4723
+ }
4724
+ console.log(lines.join("\n"));
4725
+ return;
4726
+ }
4727
+ if (cmd === "prometheus") {
4728
+ const { runPrometheusServer } = await import("./prometheusCmd-PL64BLN5.js");
4729
+ const prometheusPort = parseInt(opts.prometheusPort, 10) || 9090;
4730
+ const intervalSec = parseFloat(opts.interval) || 15;
4731
+ await runPrometheusServer({ port: prometheusPort, interval: intervalSec, detailed: opts.detailed });
4732
+ return;
4733
+ }
4734
+ if (cmd === "smart") {
4735
+ const { runSmartCommand } = await import("./smartCmd-ODWKSFNG.js");
4736
+ await runSmartCommand();
4737
+ return;
4738
+ }
4739
+ if (cmd === "replay") {
4740
+ const file = program.args[1];
4741
+ if (!file) {
4742
+ console.error(chalk10.red(" \u2716 Usage: pyre replay <logfile.csv>"));
4743
+ process.exit(1);
4744
+ }
4745
+ const { runReplayCommand } = await import("./replayCmd-Z3PYXY7V.js");
4746
+ await runReplayCommand({
4747
+ file,
4748
+ speed: parseFloat(opts.speed) || 1,
4749
+ plain: opts.plain || opts.a11y
4750
+ });
4751
+ return;
4752
+ }
4753
+ if (cmd === "explain") {
4754
+ const { runExplainCommand } = await import("./explainCmd-MF6PIMLW.js");
4755
+ const backend = opts.llmBackend === "openai" ? "openai" : opts.llmBackend === "ollama" ? "ollama" : "builtin";
4756
+ await runExplainCommand({
4757
+ since: opts.since || "7d",
4758
+ backend,
4759
+ model: opts.llmModel,
4760
+ apiKey: opts.apiKey,
4761
+ dir: opts.exportDir
4762
+ });
4763
+ return;
4764
+ }
4765
+ if (cmd === "alert") {
4766
+ const sub = program.args[1];
4767
+ if (sub === "test") {
4768
+ const { sendTestAlert } = await import("./alertChannels-TPSQIJ7S.js");
4769
+ const { writeConfig: writeConfig2 } = await import("./config-GYGJXU2Q.js");
4770
+ if (opts.alertSlack || opts.alertDiscord || opts.alertPushoverToken || opts.alertPushoverUser || opts.alertNtfy) {
4771
+ writeConfig2({
4772
+ slackAlertUrl: opts.alertSlack || config2.slackAlertUrl,
4773
+ discordAlertUrl: opts.alertDiscord || config2.discordAlertUrl,
4774
+ pushoverToken: opts.alertPushoverToken || config2.pushoverToken,
4775
+ pushoverUser: opts.alertPushoverUser || config2.pushoverUser,
4776
+ ntfyUrl: opts.alertNtfy || config2.ntfyUrl
4777
+ });
4778
+ }
4779
+ const testConfig = {
4780
+ slackUrl: opts.alertSlack || config2.slackAlertUrl || void 0,
4781
+ discordUrl: opts.alertDiscord || config2.discordAlertUrl || void 0,
4782
+ pushoverToken: opts.alertPushoverToken || config2.pushoverToken || void 0,
4783
+ pushoverUser: opts.alertPushoverUser || config2.pushoverUser || void 0,
4784
+ ntfyUrl: opts.alertNtfy || config2.ntfyUrl || void 0
4785
+ };
4786
+ const hasAny = Object.values(testConfig).some((v) => !!v);
4787
+ if (!hasAny) {
4788
+ console.log(chalk10.yellow("\n \u26A0 No alert channels configured."));
4789
+ console.log(chalk10.dim(" Use --alert-slack <url>, --alert-discord <url>, --alert-ntfy <url>, etc.\n"));
4790
+ return;
4791
+ }
4792
+ console.log(chalk10.bold("\n \u{1F525} pyre alert test \u2014 firing test notification...\n"));
4793
+ await sendTestAlert(testConfig);
4794
+ console.log(chalk10.green(" \u2714 Test alert sent to all configured channels.\n"));
4795
+ } else {
4796
+ console.log(chalk10.yellow(" Usage: pyre alert test"));
4797
+ }
4798
+ return;
4799
+ }
4800
+ if (cmd === "netusage") {
4801
+ const { runNetUsageCommand } = await import("./netUsageCmd-WKBTW2YG.js");
4802
+ await runNetUsageCommand({
4803
+ top: opts.top ? parseInt(opts.top, 10) : 15,
4804
+ sort: opts.sort === "rx" || opts.sort === "tx" ? opts.sort : "total",
4805
+ json: !!opts.json
4806
+ });
4807
+ return;
4808
+ }
4809
+ if (cmd === "serve") {
4810
+ const { runServeCommand } = await import("./serveCmd-2H5VLF3P.js");
4811
+ const port = parseInt(opts.port, 10) || 8080;
4812
+ await runServeCommand({
4813
+ port,
4814
+ apiKey: opts.apiKey,
4815
+ detailed: opts.detailed
4816
+ });
4817
+ return;
4818
+ }
4819
+ if (cmd === "profile-proc") {
4820
+ const targetPid = parseInt(program.args[1], 10);
4821
+ if (isNaN(targetPid)) {
4822
+ console.error(chalk10.red(" \u2716 Usage: pyre profile-proc <pid> [--duration <sec>]"));
4823
+ process.exit(1);
4824
+ }
4825
+ const { runProfileProcCommand } = await import("./profileProcCmd-N4H6MCBT.js");
4826
+ const durationSec = parseFloat(opts.duration) || 5;
4827
+ await runProfileProcCommand({
4828
+ pid: targetPid,
4829
+ durationSec,
4830
+ raw: !!opts.raw
4831
+ });
4832
+ return;
4833
+ }
4834
+ if (cmd === "watchdog") {
4835
+ const { runWatchdogCommand } = await import("./watchdogCmd-UF53JPZB.js");
4836
+ await runWatchdogCommand({
4837
+ configFile: opts.watchdogConfig,
4838
+ dryRun: !!opts.dryRun,
4839
+ interval: parseFloat(opts.interval) || 2
4840
+ });
4841
+ return;
4842
+ }
4843
+ if (cmd === "build") {
4844
+ const { runBuildCommand } = await import("./buildCmd-CDPZ2ZFA.js");
4845
+ await runBuildCommand({
4846
+ json: !!opts.json
4847
+ });
4848
+ return;
4849
+ }
4850
+ if (cmd === "topo") {
4851
+ const { runTopoCommand } = await import("./topoCmd-DU2HHZNH.js");
4852
+ await runTopoCommand({
4853
+ scan: !!opts.scan,
4854
+ json: !!opts.json
4855
+ });
4856
+ return;
4857
+ }
4858
+ if (cmd === "sync") {
4859
+ const subcommand = program.args[1] || "status";
4860
+ const { runSyncCommand } = await import("./syncCmd-A2W3QB3S.js");
4861
+ await runSyncCommand(subcommand, {
4862
+ syncDir: opts.syncDir
4863
+ });
4864
+ return;
4865
+ }
4263
4866
  if (cmd === "brew") {
4264
- const { printBrewHealthReport } = await import("./brewHealth-F2BXF5M6.js");
4867
+ const { printBrewHealthReport } = await import("./brewHealth-P2GW5Y5C.js");
4265
4868
  await printBrewHealthReport();
4266
4869
  return;
4267
4870
  }
@@ -4273,7 +4876,7 @@ async function main() {
4273
4876
  if (cmd === "profile") {
4274
4877
  const sub = program.args[1];
4275
4878
  const name = program.args[2];
4276
- const { saveProfile, loadProfile, listProfiles } = await import("./config-4ZLF4HTS.js");
4879
+ const { saveProfile, loadProfile, listProfiles } = await import("./config-GYGJXU2Q.js");
4277
4880
  if (sub === "save" && name) {
4278
4881
  saveProfile(name);
4279
4882
  console.log(chalk10.green(` \u2714 Profile '${name}' saved successfully.`));
@@ -4327,13 +4930,13 @@ async function main() {
4327
4930
  }
4328
4931
  if (cmd === "web") {
4329
4932
  if (opts.install) {
4330
- const { installLaunchdAgent } = await import("./service-WTS3NBQU.js");
4933
+ const { installLaunchdAgent } = await import("./service-Q74BBILO.js");
4331
4934
  const port = parseInt(opts.port || "3000", 10) || 3e3;
4332
4935
  installLaunchdAgent(port);
4333
4936
  return;
4334
4937
  }
4335
4938
  if (opts.uninstall) {
4336
- const { uninstallLaunchdAgent } = await import("./service-WTS3NBQU.js");
4939
+ const { uninstallLaunchdAgent } = await import("./service-Q74BBILO.js");
4337
4940
  uninstallLaunchdAgent();
4338
4941
  return;
4339
4942
  }
@@ -4364,7 +4967,7 @@ async function main() {
4364
4967
  const cfg = readConfig();
4365
4968
  console.log(JSON.stringify(cfg, null, 2));
4366
4969
  } else if (sub === "reset") {
4367
- fs8.rmSync(CONFIG_FILE, { force: true });
4970
+ fs10.rmSync(CONFIG_FILE, { force: true });
4368
4971
  console.log(chalk10.green("Config reset to defaults"));
4369
4972
  } else {
4370
4973
  console.log("Usage: pyre config <show|reset>");
@@ -4380,7 +4983,7 @@ async function main() {
4380
4983
  return;
4381
4984
  }
4382
4985
  if (cmd === "doctor") {
4383
- const { runDoctor, printDoctorReport } = await import("./doctor-L6FZYA7F.js");
4986
+ const { runDoctor, printDoctorReport } = await import("./doctor-GYHFTNZQ.js");
4384
4987
  const checks = await runDoctor();
4385
4988
  printDoctorReport(checks);
4386
4989
  return;
@@ -4390,7 +4993,7 @@ async function main() {
4390
4993
  return;
4391
4994
  }
4392
4995
  if (cmd === "completions") {
4393
- const { printCompletions } = await import("./completions-VQMHQCXE.js");
4996
+ const { printCompletions } = await import("./completions-JKYEJNGR.js");
4394
4997
  const shell = program.args[1] || "zsh";
4395
4998
  printCompletions(shell);
4396
4999
  return;
@@ -4409,6 +5012,11 @@ async function main() {
4409
5012
  if (opts.webhookUrl) config2.webhookUrl = opts.webhookUrl;
4410
5013
  if (opts.alertCmd) config2.alertCmd = opts.alertCmd;
4411
5014
  if (opts.exportDir) config2.exportDir = opts.exportDir;
5015
+ if (opts.alertSlack) config2.slackAlertUrl = opts.alertSlack;
5016
+ if (opts.alertDiscord) config2.discordAlertUrl = opts.alertDiscord;
5017
+ if (opts.alertPushoverToken) config2.pushoverToken = opts.alertPushoverToken;
5018
+ if (opts.alertPushoverUser) config2.pushoverUser = opts.alertPushoverUser;
5019
+ if (opts.alertNtfy) config2.ntfyUrl = opts.alertNtfy;
4412
5020
  const tempUnit = opts.tempUnit === "f" ? "f" : "c";
4413
5021
  if (cmd === "live" || !isExportMode() && !opts.once && cmd !== "p2p") {
4414
5022
  if (!process.stdout.isTTY && cmd !== "live") {
@@ -4461,7 +5069,7 @@ async function main() {
4461
5069
  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
5070
  console.log(output);
4463
5071
  if (opts.out) {
4464
- fs8.writeFileSync(opts.out, stripAnsi(output));
5072
+ fs10.writeFileSync(opts.out, stripAnsi(output));
4465
5073
  console.log(chalk10.dim(`
4466
5074
  Saved to ${opts.out}`));
4467
5075
  }
@@ -4645,11 +5253,11 @@ async function runP2PConnect() {
4645
5253
  }
4646
5254
  async function runInfoCommand() {
4647
5255
  const data = await collectAll({ detailed: true });
4648
- const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-WD6VKH6M.js");
5256
+ const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-H43PN4PS.js");
4649
5257
  const displays = await getDisplayInfo();
4650
5258
  const tmStatus = await getTimeMachineStatus();
4651
5259
  const lines = [];
4652
- lines.push(chalk10.bold(" pyre info \u2014 Mac System Information"));
5260
+ lines.push(chalk10.bold(` pyre info \u2014 System Information (${data.header.os})`));
4653
5261
  lines.push("");
4654
5262
  lines.push(` Hostname: ${data.header.hostname}`);
4655
5263
  lines.push(` OS: ${data.header.os}`);
@@ -4674,13 +5282,15 @@ async function runInfoCommand() {
4674
5282
  lines.push(` \u2022 ${d.name}: ${d.resolution}${d.isMain ? " (Main)" : ""}${d.connectionType ? ` [${d.connectionType}]` : ""}`);
4675
5283
  }
4676
5284
  }
4677
- lines.push("");
4678
- lines.push(chalk10.cyan.bold(" \u23F1 Time Machine Backup:"));
4679
- if (!tmStatus.configured) {
4680
- lines.push(" \u2022 Not configured");
4681
- } else {
4682
- lines.push(` \u2022 Status: ${tmStatus.running ? chalk10.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk10.green("Idle")}`);
4683
- lines.push(` \u2022 Latest Backup: ${tmStatus.latestBackup}`);
5285
+ if (process.platform === "darwin") {
5286
+ lines.push("");
5287
+ lines.push(chalk10.cyan.bold(" \u23F1 Time Machine Backup:"));
5288
+ if (!tmStatus.configured) {
5289
+ lines.push(" \u2022 Not configured");
5290
+ } else {
5291
+ lines.push(` \u2022 Status: ${tmStatus.running ? chalk10.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk10.green("Idle")}`);
5292
+ lines.push(` \u2022 Latest Backup: ${tmStatus.latestBackup}`);
5293
+ }
4684
5294
  }
4685
5295
  console.log(lines.join("\n"));
4686
5296
  }
@@ -4724,7 +5334,7 @@ async function runWebCommand() {
4724
5334
  return;
4725
5335
  }
4726
5336
  if (url === "/metrics") {
4727
- const { formatPrometheusMetrics } = await import("./prometheus-LFYHF6VO.js");
5337
+ const { formatPrometheusMetrics } = await import("./prometheus-5BL5PVLC.js");
4728
5338
  const data = await collectAll({ detailed: true });
4729
5339
  res.writeHead(200, { "Content-Type": "text/plain; version=0.0.4; charset=utf-8" });
4730
5340
  res.end(formatPrometheusMetrics(data));
@@ -4758,9 +5368,9 @@ async function runWebCommand() {
4758
5368
  res.end(getDashboardHtml());
4759
5369
  } else if (url === "/setup") {
4760
5370
  res.writeHead(200, { "Content-Type": "text/html" });
4761
- const indexPath = path5.join(process.cwd(), "index.html");
4762
- if (fs8.existsSync(indexPath)) {
4763
- res.end(fs8.readFileSync(indexPath, "utf-8"));
5371
+ const indexPath = path6.join(process.cwd(), "index.html");
5372
+ if (fs10.existsSync(indexPath)) {
5373
+ res.end(fs10.readFileSync(indexPath, "utf-8"));
4764
5374
  } else {
4765
5375
  res.end(getDashboardHtml());
4766
5376
  }
@@ -4810,10 +5420,26 @@ Error: Port ${port} is already in use.`));
4810
5420
  });
4811
5421
  });
4812
5422
  }
5423
+ async function openBrowserUrl(url) {
5424
+ const { spawn } = await import("child_process");
5425
+ const platform = process.platform;
5426
+ if (platform === "win32") {
5427
+ spawn("cmd.exe", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
5428
+ } else if (platform === "darwin") {
5429
+ spawn("open", [url], { detached: true, stdio: "ignore" });
5430
+ } else {
5431
+ spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
5432
+ }
5433
+ }
4813
5434
  async function runUiCommand() {
4814
5435
  opts.port = "0";
4815
5436
  const port = await runWebCommand();
4816
- const swiftScript = `
5437
+ const url = `http://localhost:${port}`;
5438
+ if (process.platform === "darwin") {
5439
+ const { run } = await import("./run-HOGQDDTA.js");
5440
+ const hasSwift = (await run("which swift 2>/dev/null", "")).trim().length > 0;
5441
+ if (hasSwift) {
5442
+ const swiftScript = `
4817
5443
  import Cocoa
4818
5444
  import WebKit
4819
5445
 
@@ -4915,27 +5541,36 @@ let delegate = AppDelegate()
4915
5541
  app.delegate = delegate
4916
5542
  app.run()
4917
5543
  `;
4918
- const scriptPath = path5.join(os2.tmpdir(), "pyre-ui.swift");
4919
- fs8.writeFileSync(scriptPath, swiftScript);
4920
- const { spawn } = await import("child_process");
4921
- console.log(chalk10.cyan(`
5544
+ const scriptPath = path6.join(os2.tmpdir(), "pyre-ui.swift");
5545
+ fs10.writeFileSync(scriptPath, swiftScript);
5546
+ const { spawn } = await import("child_process");
5547
+ console.log(chalk10.cyan(`
4922
5548
  Launching native UI window...`));
4923
- const ui = spawn("swift", [scriptPath], { stdio: "inherit" });
4924
- ui.on("close", () => {
4925
- console.log(chalk10.dim("UI window closed, shutting down server..."));
4926
- process.exit(0);
4927
- });
4928
- process.once("SIGINT", () => {
4929
- ui.kill();
4930
- process.exit(0);
5549
+ const ui = spawn("swift", [scriptPath], { stdio: "inherit" });
5550
+ ui.on("close", () => {
5551
+ console.log(chalk10.dim("UI window closed, shutting down server..."));
5552
+ process.exit(0);
5553
+ });
5554
+ process.once("SIGINT", () => {
5555
+ ui.kill();
5556
+ process.exit(0);
5557
+ });
5558
+ return;
5559
+ }
5560
+ }
5561
+ console.log(chalk10.cyan(`
5562
+ Opening live UI dashboard at ${url}...`));
5563
+ await openBrowserUrl(url);
5564
+ console.log(chalk10.dim(" Press Ctrl+C to stop server.\n"));
5565
+ await new Promise(() => {
4931
5566
  });
4932
5567
  }
4933
5568
  async function runBenchCommand(benchCmd) {
4934
5569
  const { spawn } = await import("child_process");
4935
5570
  const logDir = opts.exportDir || "./pyre-exports";
4936
- 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" });
5571
+ const file = path6.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
5572
+ if (!fs10.existsSync(logDir)) fs10.mkdirSync(logDir, { recursive: true });
5573
+ const stream = fs10.createWriteStream(file, { flags: "a" });
4939
5574
  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
5575
  console.log(chalk10.bold(`
4941
5576
  pyre bench`));
@@ -4945,7 +5580,7 @@ async function runBenchCommand(benchCmd) {
4945
5580
  const startTime = Date.now();
4946
5581
  let accumulatedWattSeconds = 0;
4947
5582
  let powerSamplesCount = 0;
4948
- const child = spawn("sh", ["-c", benchCmd]);
5583
+ const child = process.platform === "win32" ? spawn("cmd.exe", ["/c", benchCmd], { stdio: "inherit" }) : spawn("sh", ["-c", benchCmd]);
4949
5584
  child.stdout.on("data", (d) => process.stdout.write(d));
4950
5585
  child.stderr.on("data", (d) => process.stdout.write(d));
4951
5586
  const interval = parseFloat(opts.interval) || 2;