pyre-cli 8.1.0 → 9.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.
- package/dist/{brewHealth-6NE37NAU.js → brewHealth-F2BXF5M6.js} +1 -1
- package/dist/{chunk-KBKXU2ZV.js → chunk-EQZCK2VC.js} +26 -19
- package/dist/{chunk-GLH7EKAX.js → chunk-FH5WDJBF.js} +1 -1
- package/dist/{collectors-4VYRJVRO.js → collectors-KNEN4YJD.js} +2 -2
- package/dist/{doctor-LJQAABZ4.js → doctor-L6FZYA7F.js} +1 -1
- package/dist/{extensions-QCFSVCL5.js → extensions-DVA5YLGH.js} +1 -1
- package/dist/index.js +80 -52
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/live/render.ts +16 -8
- package/src/monitors/collectors.ts +22 -14
- package/src/monitors/run.ts +1 -1
- package/src/monitors/smc.ts +6 -9
- package/src/splash.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
run
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FH5WDJBF.js";
|
|
4
4
|
|
|
5
5
|
// src/monitors/collectors.ts
|
|
6
6
|
import chalk from "chalk";
|
|
@@ -15,7 +15,8 @@ async function getSmcMetrics() {
|
|
|
15
15
|
try {
|
|
16
16
|
const pm = (await run(
|
|
17
17
|
"sudo -n powermetrics --samplers smc,cpu_power -n 1 --format text 2>/dev/null",
|
|
18
|
-
""
|
|
18
|
+
"",
|
|
19
|
+
2e3
|
|
19
20
|
)).trim();
|
|
20
21
|
if (pm) {
|
|
21
22
|
const cpuTemp = pm.match(/CPU die temperature:\s*([\d.]+)/i);
|
|
@@ -71,13 +72,9 @@ async function getSmcMetrics() {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
if (result.temps["cpu_die"] === void 0) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const m = top.match(/CPU usage:\s*[\d.]+\%\s*user,\s*[\d.]+\%\s*sys,\s*([\d.]+)\%\s*idle/);
|
|
78
|
-
if (m) loadUsage = Math.max(0, 100 - parseFloat(m[1]));
|
|
79
|
-
} catch {
|
|
80
|
-
}
|
|
75
|
+
const loadAvg = (await import("os")).loadavg();
|
|
76
|
+
const ncpus = (await import("os")).cpus().length || 1;
|
|
77
|
+
const loadUsage = Math.min(100, loadAvg[0] / ncpus * 100);
|
|
81
78
|
const estCpu = Math.round((38 + loadUsage * 0.42) * 10) / 10;
|
|
82
79
|
result.temps["cpu_die"] = estCpu;
|
|
83
80
|
if (result.temps["gpu_die"] === void 0) {
|
|
@@ -98,6 +95,7 @@ function parseSuffix(s) {
|
|
|
98
95
|
var SP_TTL_MS = 1e4;
|
|
99
96
|
var NETSTAT_TTL_MS = 1e3;
|
|
100
97
|
var prevCpuTimes = null;
|
|
98
|
+
var prevCpuTotal = { total: 0, idle: 0, ts: 0 };
|
|
101
99
|
var ROUTE_TTL_MS = 5e3;
|
|
102
100
|
var SYSCTL_TTL_MS = 6e4;
|
|
103
101
|
var spGpuCache = null;
|
|
@@ -239,7 +237,7 @@ async function collectGpu(detailed) {
|
|
|
239
237
|
if (spGpuCache && now - spGpuCache.ts < SP_TTL_MS) {
|
|
240
238
|
spRaw = spGpuCache.raw;
|
|
241
239
|
} else {
|
|
242
|
-
spRaw = (await run("system_profiler SPDisplaysDataType 2>/dev/null")).trim();
|
|
240
|
+
spRaw = (await run("system_profiler SPDisplaysDataType 2>/dev/null", "", 3e3)).trim();
|
|
243
241
|
spGpuCache = { raw: spRaw, ts: now };
|
|
244
242
|
}
|
|
245
243
|
let model = "Unknown";
|
|
@@ -376,12 +374,21 @@ async function collectCpu() {
|
|
|
376
374
|
}
|
|
377
375
|
let usage = 0;
|
|
378
376
|
try {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
377
|
+
const cpus = os.cpus();
|
|
378
|
+
let totalNow = 0;
|
|
379
|
+
let idleNow = 0;
|
|
380
|
+
for (const cpu of cpus) {
|
|
381
|
+
totalNow += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq;
|
|
382
|
+
idleNow += cpu.times.idle;
|
|
383
|
+
}
|
|
384
|
+
if (prevCpuTotal.ts > 0) {
|
|
385
|
+
const totalDelta = totalNow - prevCpuTotal.total;
|
|
386
|
+
const idleDelta = idleNow - prevCpuTotal.idle;
|
|
387
|
+
if (totalDelta > 0) {
|
|
388
|
+
usage = Math.round((totalDelta - idleDelta) / totalDelta * 100);
|
|
389
|
+
}
|
|
384
390
|
}
|
|
391
|
+
prevCpuTotal = { total: totalNow, idle: idleNow, ts: Date.now() };
|
|
385
392
|
} catch {
|
|
386
393
|
}
|
|
387
394
|
const loadAvgStr = (await run("sysctl -n vm.loadavg", "0.00 0.00 0.00")).trim();
|
|
@@ -485,7 +492,7 @@ async function getDiskIoRates() {
|
|
|
485
492
|
return { readBytesSec: cachedDiskIo.readBytesSec, writeBytesSec: cachedDiskIo.writeBytesSec };
|
|
486
493
|
}
|
|
487
494
|
try {
|
|
488
|
-
const raw = (await run("iostat -d -c 2 1 2", "")).trim();
|
|
495
|
+
const raw = (await run("iostat -d -c 2 1 2", "", 3e3)).trim();
|
|
489
496
|
const blocks = raw.split(/\n\s*\n/).filter(Boolean);
|
|
490
497
|
const targetBlock = blocks.length > 1 ? blocks[1] : blocks[0];
|
|
491
498
|
if (targetBlock) {
|
|
@@ -547,7 +554,7 @@ async function collectBattery() {
|
|
|
547
554
|
if (spBatteryCache && now - spBatteryCache.ts < SP_TTL_MS) {
|
|
548
555
|
battInfo = spBatteryCache.raw;
|
|
549
556
|
} else {
|
|
550
|
-
battInfo = (await run("system_profiler SPPowerDataType 2>/dev/null")).trim();
|
|
557
|
+
battInfo = (await run("system_profiler SPPowerDataType 2>/dev/null", "", 3e3)).trim();
|
|
551
558
|
spBatteryCache = { raw: battInfo, ts: now };
|
|
552
559
|
}
|
|
553
560
|
const cycleMatch = battInfo.match(/Cycle Count:\s+(\d+)/);
|
|
@@ -723,7 +730,7 @@ async function collectPackets() {
|
|
|
723
730
|
}
|
|
724
731
|
let connections = 0;
|
|
725
732
|
try {
|
|
726
|
-
const tcpConns = (await run("netstat -an | grep ESTABLISHED | wc -l", "0")).trim();
|
|
733
|
+
const tcpConns = (await run("netstat -an | grep ESTABLISHED | wc -l", "0", 2e3)).trim();
|
|
727
734
|
connections = parseInt(tcpConns) || 0;
|
|
728
735
|
} catch {
|
|
729
736
|
connections = 0;
|
|
@@ -731,7 +738,7 @@ async function collectPackets() {
|
|
|
731
738
|
let topProcesses = [];
|
|
732
739
|
let allProcesses = [];
|
|
733
740
|
try {
|
|
734
|
-
const lsof = (await run("lsof -i -n -P 2>/dev/null | tail -n +2 | awk '{print $1, $2}' | sort | uniq -c | sort -rn", "")).trim();
|
|
741
|
+
const lsof = (await run("lsof -i -n -P 2>/dev/null | tail -n +2 | awk '{print $1, $2}' | sort | uniq -c | sort -rn", "", 2e3)).trim();
|
|
735
742
|
const lsofLines = lsof.split("\n");
|
|
736
743
|
topProcesses = lsofLines.map((line) => {
|
|
737
744
|
const parts = line.trim().split(/\s+/);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { exec } from "child_process";
|
|
3
3
|
import { promisify } from "util";
|
|
4
4
|
var execAsync = promisify(exec);
|
|
5
|
-
async function run(cmd, fallback = "", timeout =
|
|
5
|
+
async function run(cmd, fallback = "", timeout = 3e3) {
|
|
6
6
|
try {
|
|
7
7
|
return (await execAsync(cmd, { encoding: "utf8", timeout })).stdout.trim();
|
|
8
8
|
} catch {
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
collectAll
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EQZCK2VC.js";
|
|
5
5
|
import {
|
|
6
6
|
CONFIG_FILE,
|
|
7
7
|
readConfig,
|
|
8
8
|
writeConfig
|
|
9
9
|
} from "./chunk-RVGKEJXT.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-FH5WDJBF.js";
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import fs8 from "fs";
|
|
@@ -1508,24 +1508,24 @@ function getDashboardHtml() {
|
|
|
1508
1508
|
<style>
|
|
1509
1509
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
1510
1510
|
:root{
|
|
1511
|
-
--bg: #
|
|
1512
|
-
--bg1: #
|
|
1513
|
-
--bg2: #
|
|
1511
|
+
--bg: #000000;
|
|
1512
|
+
--bg1: #0a0a0a;
|
|
1513
|
+
--bg2: #141414;
|
|
1514
1514
|
--bg3: rgba(255,255,255,0.05);
|
|
1515
|
-
--border: #
|
|
1516
|
-
--border2: #
|
|
1517
|
-
--text: #
|
|
1518
|
-
--text2: #
|
|
1519
|
-
--text3: #
|
|
1520
|
-
--accent: #
|
|
1521
|
-
--accent2: #
|
|
1522
|
-
--cyan: #
|
|
1523
|
-
--green: #
|
|
1524
|
-
--yellow: #
|
|
1525
|
-
--red: #
|
|
1526
|
-
--orange: #
|
|
1527
|
-
--purple: #
|
|
1528
|
-
--glow: rgba(
|
|
1515
|
+
--border: #333333;
|
|
1516
|
+
--border2: #444444;
|
|
1517
|
+
--text: #ffffff;
|
|
1518
|
+
--text2: #aaaaaa;
|
|
1519
|
+
--text3: #666666;
|
|
1520
|
+
--accent: #ffffff;
|
|
1521
|
+
--accent2: #dddddd;
|
|
1522
|
+
--cyan: #ffffff;
|
|
1523
|
+
--green: #ffffff;
|
|
1524
|
+
--yellow: #bbbbbb;
|
|
1525
|
+
--red: #dddddd;
|
|
1526
|
+
--orange: #cccccc;
|
|
1527
|
+
--purple: #eeeeee;
|
|
1528
|
+
--glow: rgba(255,255,255,0.15);
|
|
1529
1529
|
--font: 'Inter', -apple-system, sans-serif;
|
|
1530
1530
|
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
|
1531
1531
|
}
|
|
@@ -1567,7 +1567,7 @@ body {
|
|
|
1567
1567
|
text-transform:uppercase;font-weight:600;
|
|
1568
1568
|
}
|
|
1569
1569
|
.tab:hover{color:var(--text2);background:var(--bg3);}
|
|
1570
|
-
.tab.active{color:var(--accent);border-bottom:2px solid var(--accent);background:linear-gradient(0deg, rgba(
|
|
1570
|
+
.tab.active{color:var(--accent);border-bottom:2px solid var(--accent);background:linear-gradient(0deg, rgba(255,255,255,0.1), transparent);}
|
|
1571
1571
|
.tab-key{opacity:0.5;margin-right:6px;}
|
|
1572
1572
|
|
|
1573
1573
|
#main{flex:1;overflow-y:auto;overflow-x:hidden;padding:16px;display:flex;flex-direction:column;}
|
|
@@ -1594,7 +1594,7 @@ body {
|
|
|
1594
1594
|
transition:border-color 0.2s, box-shadow 0.2s;
|
|
1595
1595
|
display:flex;flex-direction:column;
|
|
1596
1596
|
}
|
|
1597
|
-
.card:hover{border-color:var(--cyan);box-shadow:0 0 10px rgba(
|
|
1597
|
+
.card:hover{border-color:var(--cyan);box-shadow:0 0 10px rgba(255,255,255,0.05);}
|
|
1598
1598
|
.card-title{
|
|
1599
1599
|
position:absolute;top:-9px;left:12px;
|
|
1600
1600
|
background:var(--bg);
|
|
@@ -1619,7 +1619,7 @@ body {
|
|
|
1619
1619
|
.stat-label{font-size:11px;color:var(--text2);}
|
|
1620
1620
|
.stat-val{font-family:var(--mono);font-size:12px;color:var(--text);font-weight:500;}
|
|
1621
1621
|
.big-metric{text-align:center;padding:20px 0;}
|
|
1622
|
-
.big-val{font-family:var(--mono);font-size:48px;font-weight:700;color:var(--cyan);text-shadow:0 0 15px rgba(
|
|
1622
|
+
.big-val{font-family:var(--mono);font-size:48px;font-weight:700;color:var(--cyan);text-shadow:0 0 15px rgba(255,255,255,0.3);}
|
|
1623
1623
|
.big-unit{font-family:var(--mono);font-size:14px;color:var(--text2);margin-top:8px;}
|
|
1624
1624
|
|
|
1625
1625
|
/* Gauges */
|
|
@@ -1628,9 +1628,9 @@ body {
|
|
|
1628
1628
|
.gauge-pct{font-weight:600;}
|
|
1629
1629
|
.gauge-track{height:20px;background:var(--bg2);border-radius:4px;overflow:hidden;position:relative;border:1px solid var(--border);}
|
|
1630
1630
|
.gauge-fill{height:100%;transition:width 0.4s ease;display:flex;align-items:center;justify-content:flex-end;padding-right:6px;font-family:var(--mono);font-size:10px;font-weight:700;color:#000;}
|
|
1631
|
-
.gauge-fill.ok{background:linear-gradient(90deg,var(--
|
|
1632
|
-
.gauge-fill.warn{background:linear-gradient(90deg,var(--
|
|
1633
|
-
.gauge-fill.crit{background:linear-gradient(90deg,var(--
|
|
1631
|
+
.gauge-fill.ok{background:linear-gradient(90deg,var(--text),#999);}
|
|
1632
|
+
.gauge-fill.warn{background:linear-gradient(90deg,var(--text2),#666);}
|
|
1633
|
+
.gauge-fill.crit{background:linear-gradient(90deg,var(--text3),#333);}
|
|
1634
1634
|
|
|
1635
1635
|
/* SVG Rings */
|
|
1636
1636
|
.ring-container { display:flex; justify-content:space-around; padding:20px 0; background:var(--bg1); border:1px solid var(--border); border-radius:6px; margin-bottom:12px; }
|
|
@@ -1654,9 +1654,9 @@ canvas.spark.large { height:200px; }
|
|
|
1654
1654
|
.core-col { display:flex; flex-direction:column; gap:4px; align-items:center; }
|
|
1655
1655
|
.core-bar { width:100%; height:100px; background:var(--bg2); border-radius:4px; display:flex; align-items:flex-end; overflow:hidden; border:1px solid var(--border); }
|
|
1656
1656
|
.core-fill { width:100%; transition:height 0.3s; }
|
|
1657
|
-
.core-fill.ok { background:linear-gradient(0deg,var(--
|
|
1658
|
-
.core-fill.warn { background:linear-gradient(0deg,var(--
|
|
1659
|
-
.core-fill.crit { background:linear-gradient(0deg,var(--
|
|
1657
|
+
.core-fill.ok { background:linear-gradient(0deg,var(--text),#999); }
|
|
1658
|
+
.core-fill.warn { background:linear-gradient(0deg,var(--text2),#666); }
|
|
1659
|
+
.core-fill.crit { background:linear-gradient(0deg,var(--text3),#333); }
|
|
1660
1660
|
.core-lbl { font-family:var(--mono); font-size:9px; color:var(--text2); }
|
|
1661
1661
|
|
|
1662
1662
|
/* Table */
|
|
@@ -1689,7 +1689,7 @@ table.proc td { padding:6px 12px; white-space:nowrap; overflow:hidden; text-over
|
|
|
1689
1689
|
.chip.warn { border-color:var(--yellow); color:var(--yellow); }
|
|
1690
1690
|
.chip.crit { border-color:var(--red); color:var(--red); }
|
|
1691
1691
|
|
|
1692
|
-
.anomaly { display:flex; gap:12px; padding:12px; border:1px solid var(--red); background:rgba(255,
|
|
1692
|
+
.anomaly { display:flex; gap:12px; padding:12px; border:1px solid var(--red); background:rgba(255,255,255,0.1); border-radius:6px; margin-bottom:12px; }
|
|
1693
1693
|
.anomaly-icon { font-size:20px; }
|
|
1694
1694
|
.anomaly-title { font-family:var(--mono); font-weight:700; color:var(--red); font-size:12px; }
|
|
1695
1695
|
.anomaly-desc { font-family:var(--mono); font-size:11px; color:var(--text); margin-top:4px; }
|
|
@@ -1753,7 +1753,7 @@ function svgRing(val, max, label, unit='', size=100) {
|
|
|
1753
1753
|
const pct = Math.min(100, Math.max(0, (val/max)*100));
|
|
1754
1754
|
const off = circ*(1 - pct/100);
|
|
1755
1755
|
const cls = statusCls(pct);
|
|
1756
|
-
const col = cls==='crit'?'var(--
|
|
1756
|
+
const col = cls==='crit'?'var(--text3)':cls==='warn'?'var(--text2)':'var(--text)';
|
|
1757
1757
|
return \`<div class="ring-box">
|
|
1758
1758
|
<div class="ring-title">\${label}</div>
|
|
1759
1759
|
<div class="svg-ring \${size>150?'large':''}">
|
|
@@ -2073,14 +2073,14 @@ function renderAnomalies(d) {
|
|
|
2073
2073
|
}
|
|
2074
2074
|
|
|
2075
2075
|
function drawSparks() {
|
|
2076
|
-
drawSpark('sp-ov-cpu', history.cpu, '#
|
|
2077
|
-
drawSpark('sp-ov-rx', history.rxRate, '#
|
|
2078
|
-
drawSpark('sp-ov-tx', history.txRate, '#
|
|
2079
|
-
drawSpark('sp-cpu-l', history.cpu, '#
|
|
2080
|
-
drawSpark('sp-mem-l', history.mem, '#
|
|
2081
|
-
drawSpark('sp-therm-l', history.temp, '#
|
|
2082
|
-
drawSpark('sp-net-rx-l', history.rxRate, '#
|
|
2083
|
-
drawSpark('sp-net-tx-l', history.txRate, '#
|
|
2076
|
+
drawSpark('sp-ov-cpu', history.cpu, '#ffffff', '#999999');
|
|
2077
|
+
drawSpark('sp-ov-rx', history.rxRate, '#ffffff', '#999999');
|
|
2078
|
+
drawSpark('sp-ov-tx', history.txRate, '#bbbbbb', '#666666');
|
|
2079
|
+
drawSpark('sp-cpu-l', history.cpu, '#ffffff', '#999999');
|
|
2080
|
+
drawSpark('sp-mem-l', history.mem, '#bbbbbb', '#666666');
|
|
2081
|
+
drawSpark('sp-therm-l', history.temp, '#dddddd', '#888888');
|
|
2082
|
+
drawSpark('sp-net-rx-l', history.rxRate, '#ffffff', '#999999');
|
|
2083
|
+
drawSpark('sp-net-tx-l', history.txRate, '#bbbbbb', '#666666');
|
|
2084
2084
|
}
|
|
2085
2085
|
|
|
2086
2086
|
function switchView(v) {
|
|
@@ -2697,8 +2697,7 @@ function graphsCacheParams() {
|
|
|
2697
2697
|
}
|
|
2698
2698
|
function processRowBudget() {
|
|
2699
2699
|
let overlayRows = 0;
|
|
2700
|
-
if (state.inputMode === "
|
|
2701
|
-
else if (state.inputMode === "signal") overlayRows = 6;
|
|
2700
|
+
if (state.inputMode === "signal") overlayRows = 6;
|
|
2702
2701
|
else if (state.inputMode === "filter" || state.inputMode === "kill" || state.inputMode === "p2p") overlayRows = 3;
|
|
2703
2702
|
if (state.activePanel === "process") {
|
|
2704
2703
|
return Math.max(10, state.termHeight - 12 - overlayRows);
|
|
@@ -2854,14 +2853,20 @@ function render() {
|
|
|
2854
2853
|
const footerHeight = 3;
|
|
2855
2854
|
const targetBodyHeight = Math.max(1, state.termHeight - footerHeight);
|
|
2856
2855
|
if (state.inputMode === "customizer") {
|
|
2857
|
-
const preOverlayCount = bodyLines.length;
|
|
2858
|
-
const availableForOverlay = targetBodyHeight - preOverlayCount;
|
|
2859
2856
|
const totalItems = state.CUSTOMIZER_OPTIONS.length;
|
|
2860
2857
|
const headerLines = 1;
|
|
2861
|
-
const blankLines =
|
|
2862
|
-
const
|
|
2858
|
+
const blankLines = 1;
|
|
2859
|
+
const idealOverlayHeight = totalItems + headerLines + blankLines;
|
|
2860
|
+
const minOverlayHeight = Math.min(10 + headerLines + blankLines, Math.floor(targetBodyHeight / 2));
|
|
2861
|
+
const overlayHeight = Math.min(idealOverlayHeight, Math.max(minOverlayHeight, Math.floor(targetBodyHeight * 0.6)));
|
|
2862
|
+
const maxBodyForTable = Math.max(3, targetBodyHeight - overlayHeight);
|
|
2863
|
+
if (bodyLines.length > maxBodyForTable) {
|
|
2864
|
+
bodyLines.length = maxBodyForTable;
|
|
2865
|
+
}
|
|
2866
|
+
const availableForOverlay = targetBodyHeight - bodyLines.length;
|
|
2867
|
+
const maxVisibleItems = Math.max(1, availableForOverlay - headerLines - blankLines);
|
|
2863
2868
|
const scrollOffset = totalItems > maxVisibleItems ? Math.max(0, Math.min(state.customizerIndex, totalItems - maxVisibleItems)) : 0;
|
|
2864
|
-
|
|
2869
|
+
bodyLines.push("");
|
|
2865
2870
|
bodyLines.push(...renderCustomizerOverlay(scrollOffset, maxVisibleItems).split("\n"));
|
|
2866
2871
|
} else if (state.inputMode === "filter") {
|
|
2867
2872
|
bodyLines.push(chalk4.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
@@ -4591,7 +4596,7 @@ async function showSplash(config3) {
|
|
|
4591
4596
|
process.stdin.setRawMode(true);
|
|
4592
4597
|
process.stdin.resume();
|
|
4593
4598
|
process.stdin.on("data", onEnter);
|
|
4594
|
-
setTimeout(onEnter,
|
|
4599
|
+
setTimeout(onEnter, 1500);
|
|
4595
4600
|
});
|
|
4596
4601
|
}
|
|
4597
4602
|
|
|
@@ -4772,6 +4777,29 @@ var config2 = readConfig();
|
|
|
4772
4777
|
var pkg = JSON.parse(fs8.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
4773
4778
|
var program = new Command();
|
|
4774
4779
|
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("--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("--webhook-url <url>", "URL to POST to on alert").option("--alert-cmd <cmd>", "Command to execute on alert").option("--port <port>", "Port number for web server mode", "3000");
|
|
4780
|
+
program.addHelpText("after", `
|
|
4781
|
+
Commands:
|
|
4782
|
+
live Interactive live dashboard
|
|
4783
|
+
ui Launch the native macOS UI dashboard
|
|
4784
|
+
web Serve an auto-refreshing live web portal
|
|
4785
|
+
ssh <host> Stream live stats from a remote Mac over SSH
|
|
4786
|
+
fleet <host1> [host2]... Multi-host live dashboard monitoring multiple Macs
|
|
4787
|
+
bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
|
|
4788
|
+
anomalies Compute z-score anomalies and print plain-language digest
|
|
4789
|
+
doctor Run system diagnostics
|
|
4790
|
+
extensions System Extensions inspector
|
|
4791
|
+
brew Homebrew health panel
|
|
4792
|
+
update Check for pyre-cli updates
|
|
4793
|
+
profile <save|load|list> Atomic configuration profile management
|
|
4794
|
+
config <show|reset> View or reset persistent configuration file
|
|
4795
|
+
history Graph historical resource trends from CSV logs
|
|
4796
|
+
diff <file1> <file2> Compare two saved snapshot files side-by-side
|
|
4797
|
+
info Concise hardware summary
|
|
4798
|
+
completions <shell> Generate shell auto-completion scripts
|
|
4799
|
+
xbar Generate an xbar / SwiftBar menu bar plugin script
|
|
4800
|
+
p2p <server|connect> Start a P2P server or connect to one
|
|
4801
|
+
server Print commands for starting P2P server/client
|
|
4802
|
+
`);
|
|
4775
4803
|
program.parse(process.argv);
|
|
4776
4804
|
var opts = program.opts();
|
|
4777
4805
|
if (opts.plain || opts.a11y) {
|
|
@@ -4838,12 +4866,12 @@ async function main() {
|
|
|
4838
4866
|
return;
|
|
4839
4867
|
}
|
|
4840
4868
|
if (cmd === "extensions") {
|
|
4841
|
-
const { printExtensionsReport } = await import("./extensions-
|
|
4869
|
+
const { printExtensionsReport } = await import("./extensions-DVA5YLGH.js");
|
|
4842
4870
|
await printExtensionsReport();
|
|
4843
4871
|
return;
|
|
4844
4872
|
}
|
|
4845
4873
|
if (cmd === "brew") {
|
|
4846
|
-
const { printBrewHealthReport } = await import("./brewHealth-
|
|
4874
|
+
const { printBrewHealthReport } = await import("./brewHealth-F2BXF5M6.js");
|
|
4847
4875
|
await printBrewHealthReport();
|
|
4848
4876
|
return;
|
|
4849
4877
|
}
|
|
@@ -4946,7 +4974,7 @@ async function main() {
|
|
|
4946
4974
|
return;
|
|
4947
4975
|
}
|
|
4948
4976
|
if (cmd === "doctor") {
|
|
4949
|
-
const { runDoctor, printDoctorReport } = await import("./doctor-
|
|
4977
|
+
const { runDoctor, printDoctorReport } = await import("./doctor-L6FZYA7F.js");
|
|
4950
4978
|
const checks = await runDoctor();
|
|
4951
4979
|
printDoctorReport(checks);
|
|
4952
4980
|
return;
|
|
@@ -5211,7 +5239,7 @@ async function runP2PConnect() {
|
|
|
5211
5239
|
}
|
|
5212
5240
|
async function runInfoCommand() {
|
|
5213
5241
|
const data = await collectAll({ detailed: true });
|
|
5214
|
-
const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-
|
|
5242
|
+
const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-KNEN4YJD.js");
|
|
5215
5243
|
const displays = await getDisplayInfo();
|
|
5216
5244
|
const tmStatus = await getTimeMachineStatus();
|
|
5217
5245
|
const lines = [];
|
|
@@ -5401,7 +5429,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
5401
5429
|
window.title = "Pyre"
|
|
5402
5430
|
window.minSize = NSSize(width: 800, height: 600)
|
|
5403
5431
|
window.appearance = NSAppearance(named: .darkAqua)
|
|
5404
|
-
window.backgroundColor = NSColor(
|
|
5432
|
+
window.backgroundColor = NSColor(white: 0, alpha: 1)
|
|
5405
5433
|
window.isReleasedWhenClosed = false
|
|
5406
5434
|
window.delegate = self
|
|
5407
5435
|
|
|
@@ -5409,7 +5437,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
5409
5437
|
let cfg = WKWebViewConfiguration()
|
|
5410
5438
|
webView = DraggableWebView(frame: window.contentView!.bounds, configuration: cfg)
|
|
5411
5439
|
webView.autoresizingMask = [.width, .height]
|
|
5412
|
-
webView.setValue(NSColor(
|
|
5440
|
+
webView.setValue(NSColor(white: 0, alpha: 1),
|
|
5413
5441
|
forKey: "backgroundColor")
|
|
5414
5442
|
|
|
5415
5443
|
// Load the dashboard HTML from the local server
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -74,7 +74,7 @@ program
|
|
|
74
74
|
.option('--alert-cmd <cmd>', 'Command to execute on alert')
|
|
75
75
|
.option('--port <port>', 'Port number for web server mode', '3000');
|
|
76
76
|
|
|
77
|
-
program.addHelpText('after',
|
|
77
|
+
program.addHelpText('after', `
|
|
78
78
|
Commands:
|
|
79
79
|
live Interactive live dashboard
|
|
80
80
|
ui Launch the native macOS UI dashboard
|
|
@@ -96,7 +96,7 @@ Commands:
|
|
|
96
96
|
xbar Generate an xbar / SwiftBar menu bar plugin script
|
|
97
97
|
p2p <server|connect> Start a P2P server or connect to one
|
|
98
98
|
server Print commands for starting P2P server/client
|
|
99
|
-
|
|
99
|
+
`);
|
|
100
100
|
|
|
101
101
|
program.parse(process.argv);
|
|
102
102
|
|
package/src/live/render.ts
CHANGED
|
@@ -68,8 +68,7 @@ function graphsCacheParams(): string {
|
|
|
68
68
|
|
|
69
69
|
function processRowBudget(): number {
|
|
70
70
|
let overlayRows = 0;
|
|
71
|
-
if (state.inputMode === '
|
|
72
|
-
else if (state.inputMode === 'signal') overlayRows = 6;
|
|
71
|
+
if (state.inputMode === 'signal') overlayRows = 6;
|
|
73
72
|
else if (state.inputMode === 'filter' || state.inputMode === 'kill' || state.inputMode === 'p2p') overlayRows = 3;
|
|
74
73
|
|
|
75
74
|
if (state.activePanel === 'process') {
|
|
@@ -265,17 +264,26 @@ function render() {
|
|
|
265
264
|
const targetBodyHeight = Math.max(1, state.termHeight - footerHeight);
|
|
266
265
|
|
|
267
266
|
if (state.inputMode === 'customizer') {
|
|
268
|
-
const preOverlayCount = bodyLines.length;
|
|
269
|
-
const availableForOverlay = targetBodyHeight - preOverlayCount;
|
|
270
267
|
const totalItems = state.CUSTOMIZER_OPTIONS.length;
|
|
271
|
-
const headerLines = 1;
|
|
272
|
-
const blankLines =
|
|
273
|
-
|
|
268
|
+
const headerLines = 1; // the "UI CUSTOMIZER" header
|
|
269
|
+
const blankLines = 1; // blank separator line
|
|
270
|
+
// Reserve a fixed height for the customizer: show all items if they fit,
|
|
271
|
+
// otherwise guarantee at least 10 visible items (or half the screen).
|
|
272
|
+
const idealOverlayHeight = totalItems + headerLines + blankLines;
|
|
273
|
+
const minOverlayHeight = Math.min(10 + headerLines + blankLines, Math.floor(targetBodyHeight / 2));
|
|
274
|
+
const overlayHeight = Math.min(idealOverlayHeight, Math.max(minOverlayHeight, Math.floor(targetBodyHeight * 0.6)));
|
|
275
|
+
// Truncate table body to make room for the customizer overlay
|
|
276
|
+
const maxBodyForTable = Math.max(3, targetBodyHeight - overlayHeight);
|
|
277
|
+
if (bodyLines.length > maxBodyForTable) {
|
|
278
|
+
bodyLines.length = maxBodyForTable;
|
|
279
|
+
}
|
|
280
|
+
const availableForOverlay = targetBodyHeight - bodyLines.length;
|
|
281
|
+
const maxVisibleItems = Math.max(1, availableForOverlay - headerLines - blankLines);
|
|
274
282
|
const scrollOffset = totalItems > maxVisibleItems
|
|
275
283
|
? Math.max(0, Math.min(state.customizerIndex, totalItems - maxVisibleItems))
|
|
276
284
|
: 0;
|
|
277
285
|
|
|
278
|
-
|
|
286
|
+
bodyLines.push('');
|
|
279
287
|
bodyLines.push(...renderCustomizerOverlay(scrollOffset, maxVisibleItems).split('\n'));
|
|
280
288
|
} else if (state.inputMode === 'filter') {
|
|
281
289
|
bodyLines.push(chalk.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
@@ -20,6 +20,7 @@ import type { StatsData, CpuData, MemoryData, ThermalData, BatteryData, PowerDat
|
|
|
20
20
|
const SP_TTL_MS = 10_000;
|
|
21
21
|
const NETSTAT_TTL_MS = 1000;
|
|
22
22
|
let prevCpuTimes: { total: number; idle: number }[] | null = null;
|
|
23
|
+
let prevCpuTotal = { total: 0, idle: 0, ts: 0 };
|
|
23
24
|
const ROUTE_TTL_MS = 5_000;
|
|
24
25
|
const SYSCTL_TTL_MS = 60_000;
|
|
25
26
|
|
|
@@ -236,7 +237,7 @@ export async function collectPower(): Promise<PowerData | null> {
|
|
|
236
237
|
if (spGpuCache && now - spGpuCache.ts < SP_TTL_MS) {
|
|
237
238
|
spRaw = spGpuCache.raw;
|
|
238
239
|
} else {
|
|
239
|
-
spRaw = (await run('system_profiler SPDisplaysDataType 2>/dev/null')).trim();
|
|
240
|
+
spRaw = (await run('system_profiler SPDisplaysDataType 2>/dev/null', '', 3000)).trim();
|
|
240
241
|
spGpuCache = { raw: spRaw, ts: now };
|
|
241
242
|
}
|
|
242
243
|
|
|
@@ -400,18 +401,25 @@ export async function collectCpu(): Promise<CpuData> {
|
|
|
400
401
|
}
|
|
401
402
|
}
|
|
402
403
|
|
|
404
|
+
// Use os.cpus() delta instead of the very slow `top -l 1 -n 0` which
|
|
405
|
+
// blocks for ~1-2s waiting for a sampling window. os.cpus() is instant.
|
|
403
406
|
let usage = 0;
|
|
404
407
|
try {
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
408
|
+
const cpus = os.cpus();
|
|
409
|
+
let totalNow = 0;
|
|
410
|
+
let idleNow = 0;
|
|
411
|
+
for (const cpu of cpus) {
|
|
412
|
+
totalNow += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq;
|
|
413
|
+
idleNow += cpu.times.idle;
|
|
414
|
+
}
|
|
415
|
+
if (prevCpuTotal.ts > 0) {
|
|
416
|
+
const totalDelta = totalNow - prevCpuTotal.total;
|
|
417
|
+
const idleDelta = idleNow - prevCpuTotal.idle;
|
|
418
|
+
if (totalDelta > 0) {
|
|
419
|
+
usage = Math.round(((totalDelta - idleDelta) / totalDelta) * 100);
|
|
420
|
+
}
|
|
414
421
|
}
|
|
422
|
+
prevCpuTotal = { total: totalNow, idle: idleNow, ts: Date.now() };
|
|
415
423
|
} catch {
|
|
416
424
|
// ignore
|
|
417
425
|
}
|
|
@@ -534,7 +542,7 @@ async function getDiskIoRates(): Promise<{ readBytesSec: number; writeBytesSec:
|
|
|
534
542
|
return { readBytesSec: cachedDiskIo.readBytesSec, writeBytesSec: cachedDiskIo.writeBytesSec };
|
|
535
543
|
}
|
|
536
544
|
try {
|
|
537
|
-
const raw = (await run('iostat -d -c 2 1 2', '')).trim();
|
|
545
|
+
const raw = (await run('iostat -d -c 2 1 2', '', 3000)).trim();
|
|
538
546
|
const blocks = raw.split(/\n\s*\n/).filter(Boolean);
|
|
539
547
|
const targetBlock = blocks.length > 1 ? blocks[1] : blocks[0];
|
|
540
548
|
if (targetBlock) {
|
|
@@ -606,7 +614,7 @@ export async function collectBattery(): Promise<BatteryData | null> {
|
|
|
606
614
|
if (spBatteryCache && now - spBatteryCache.ts < SP_TTL_MS) {
|
|
607
615
|
battInfo = spBatteryCache.raw;
|
|
608
616
|
} else {
|
|
609
|
-
battInfo = (await run('system_profiler SPPowerDataType 2>/dev/null')).trim();
|
|
617
|
+
battInfo = (await run('system_profiler SPPowerDataType 2>/dev/null', '', 3000)).trim();
|
|
610
618
|
spBatteryCache = { raw: battInfo, ts: now };
|
|
611
619
|
}
|
|
612
620
|
|
|
@@ -821,7 +829,7 @@ export async function collectPackets(): Promise<PacketData | null> {
|
|
|
821
829
|
|
|
822
830
|
let connections = 0;
|
|
823
831
|
try {
|
|
824
|
-
const tcpConns = (await run('netstat -an | grep ESTABLISHED | wc -l', '0')).trim();
|
|
832
|
+
const tcpConns = (await run('netstat -an | grep ESTABLISHED | wc -l', '0', 2000)).trim();
|
|
825
833
|
connections = parseInt(tcpConns) || 0;
|
|
826
834
|
} catch {
|
|
827
835
|
connections = 0;
|
|
@@ -830,7 +838,7 @@ export async function collectPackets(): Promise<PacketData | null> {
|
|
|
830
838
|
let topProcesses: { pid: number; command: string; rxBytes: number; txBytes: number }[] = [];
|
|
831
839
|
let allProcesses: { pid: number; command: string; rxBytes: number; txBytes: number; rxPackets?: number; txPackets?: number }[] = [];
|
|
832
840
|
try {
|
|
833
|
-
const lsof = (await run('lsof -i -n -P 2>/dev/null | tail -n +2 | awk \'{print $1, $2}\' | sort | uniq -c | sort -rn', '')).trim();
|
|
841
|
+
const lsof = (await run('lsof -i -n -P 2>/dev/null | tail -n +2 | awk \'{print $1, $2}\' | sort | uniq -c | sort -rn', '', 2000)).trim();
|
|
834
842
|
const lsofLines = lsof.split('\n');
|
|
835
843
|
topProcesses = lsofLines.map(line => {
|
|
836
844
|
const parts = line.trim().split(/\s+/);
|
package/src/monitors/run.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { promisify } from 'node:util';
|
|
|
11
11
|
|
|
12
12
|
const execAsync = promisify(exec);
|
|
13
13
|
|
|
14
|
-
async function run(cmd: string, fallback: string = '', timeout =
|
|
14
|
+
async function run(cmd: string, fallback: string = '', timeout = 3000): Promise<string> {
|
|
15
15
|
try {
|
|
16
16
|
return (await execAsync(cmd, { encoding: 'utf8', timeout })).stdout.trim();
|
|
17
17
|
} catch {
|
package/src/monitors/smc.ts
CHANGED
|
@@ -41,7 +41,8 @@ export async function getSmcMetrics(): Promise<SmcMetrics> {
|
|
|
41
41
|
const pm = (
|
|
42
42
|
await run(
|
|
43
43
|
'sudo -n powermetrics --samplers smc,cpu_power -n 1 --format text 2>/dev/null',
|
|
44
|
-
''
|
|
44
|
+
'',
|
|
45
|
+
2000
|
|
45
46
|
)
|
|
46
47
|
).trim();
|
|
47
48
|
|
|
@@ -116,14 +117,10 @@ export async function getSmcMetrics(): Promise<SmcMetrics> {
|
|
|
116
117
|
|
|
117
118
|
// 4. Smart load-based thermal estimation if hardware sensors return nothing
|
|
118
119
|
if (result.temps['cpu_die'] === undefined) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (m) loadUsage = Math.max(0, 100 - parseFloat(m[1]));
|
|
124
|
-
} catch {
|
|
125
|
-
// ignore
|
|
126
|
-
}
|
|
120
|
+
// Use os.loadavg() (instant) instead of the slow `top -l 1` (~1-2s)
|
|
121
|
+
const loadAvg = (await import('node:os')).loadavg();
|
|
122
|
+
const ncpus = (await import('node:os')).cpus().length || 1;
|
|
123
|
+
const loadUsage = Math.min(100, (loadAvg[0] / ncpus) * 100);
|
|
127
124
|
|
|
128
125
|
const estCpu = Math.round((38 + (loadUsage * 0.42)) * 10) / 10;
|
|
129
126
|
result.temps['cpu_die'] = estCpu;
|