pyre-cli 7.1.0 → 8.1.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/README.md +47 -20
- package/dist/anomaliesCmd-TO52YY2B.js +117 -0
- package/dist/brewHealth-6NE37NAU.js +74 -0
- package/dist/chunk-GLH7EKAX.js +15 -0
- package/dist/chunk-KBKXU2ZV.js +859 -0
- package/dist/chunk-RVGKEJXT.js +121 -0
- package/dist/collectors-4VYRJVRO.js +33 -0
- package/dist/completions-VQMHQCXE.js +122 -0
- package/dist/config-4ZLF4HTS.js +22 -0
- package/dist/doctor-LJQAABZ4.js +184 -0
- package/dist/extensions-QCFSVCL5.js +74 -0
- package/dist/index.js +1279 -1191
- package/dist/updateCheck-52X6LJG5.js +84 -0
- package/package.json +1 -1
- package/parity.md +16 -9
- package/src/anomaliesCmd.ts +151 -0
- package/src/brewHealth.ts +88 -0
- package/src/doctor.ts +77 -1
- package/src/easy_features.test.ts +43 -0
- package/src/extensions.ts +83 -0
- package/src/formatters/html_template.ts +655 -0
- package/src/formatters/index.ts +1 -0
- package/src/formatters/render.ts +2 -2
- package/src/index.ts +304 -26
- package/src/live/input.ts +12 -0
- package/src/monitors/collectors.ts +110 -5
- package/src/monitors/types.ts +6 -0
- package/src/sparkline.ts +63 -2
- package/src/state/config.ts +36 -2
- package/src/updateCheck.ts +94 -0
package/README.md
CHANGED
|
@@ -17,31 +17,35 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, l
|
|
|
17
17
|
- **Real-time system stats & per-core CPU** — CPU brand, cores, frequency, load, per-core utilization mini-bars, and overall usage
|
|
18
18
|
- **GPU monitoring** — GPU model, VRAM, utilization, temperature, and process count (detailed mode)
|
|
19
19
|
- **Power draw monitoring** — CPU watts, GPU watts, and combined power draw via SMC/ioreg
|
|
20
|
-
- **Memory monitoring** — usage, swap, and
|
|
20
|
+
- **Memory monitoring & pressure** — usage, swap, wired, compressed, purgeable, swapins/outs, and kernel VM pressure level
|
|
21
21
|
- **Disk I/O throughput & space** — read/write speed per volume and mounted volume space usage
|
|
22
22
|
- **Battery & power health** — level, power source, condition, charge cycles, max capacity, health trends, estimated time to empty, discharge rate, power draw in watts
|
|
23
23
|
- **Packet monitor** — network packet counts, packet rates, active TCP connections, top network processes
|
|
24
24
|
- **Task list** — running tasks/applications sorted by CPU with PID, user, memory, state, and runtime
|
|
25
25
|
- **Thermal state** — CPU temperature via `pmset` and `powermetrics` (sudo for detailed readings)
|
|
26
26
|
- **Network** — RX/TX bytes and per-second rates
|
|
27
|
-
- **Top processes & search-as-you-type** — live filtering, tree view, sorting by CPU/memory/PID, and process kill with
|
|
27
|
+
- **Top processes & search-as-you-type** — live filtering, tree view, sorting by CPU/memory/PID, and process kill with protected PID confirmation guard
|
|
28
28
|
- **Interactive live dashboard** — full-screen TUI with keyboard-driven controls, mouse support, configurable panel layout, and tab-based panel navigation
|
|
29
29
|
- **Snapshot export** — JSON, CSV, TSV, HTML, or Markdown output formats
|
|
30
|
-
- **Continuous CSV logging** — automatic per-tick
|
|
30
|
+
- **Continuous CSV logging & Anomaly Digest** — automatic per-tick logging and statistical spike analysis (`pyre anomalies`)
|
|
31
31
|
- **Visual themes & custom themes** — six built-in color themes plus user-defined JSON theme support in `~/.config/pyre/themes/`
|
|
32
|
-
- **Persistent configuration** — save
|
|
32
|
+
- **Persistent configuration & Profiles** — save themes and thresholds in `~/.config/pyre/config.json`, plus atomic profile management (`pyre profile`)
|
|
33
33
|
- **Alert system & webhooks** — configurable threshold alerts with terminal bell, desktop notifications, webhook POST payload execution (`--webhook-url`), and custom shell command execution (`--alert-cmd`)
|
|
34
|
-
- **System diagnostic tool (`pyre doctor`)** — check permissions, SMC access,
|
|
34
|
+
- **System diagnostic tool (`pyre doctor`)** — check permissions, SMC access, Gatekeeper, SIP status, XProtect definitions, and P2P reachability
|
|
35
|
+
- **Security & Ecosystem inspectors** — System Extensions inspector (`pyre extensions`), Homebrew health (`pyre brew`), and Time Machine backup status
|
|
36
|
+
- **Accessible plain-text mode** — `--plain`/`--a11y` mode for screen readers and ANSI-free logging
|
|
37
|
+
- **Grafana Integration** — ready-to-import Grafana dashboard template (`grafana/pyre-dashboard.json`)
|
|
35
38
|
- **History & Snapshot Diff** — query historical CSV log trends (`pyre history`) and compare snapshot files side-by-side (`pyre diff`)
|
|
36
39
|
- **Multi-host fleet dashboard** — stream and tile stats from multiple remote Macs (`pyre fleet`)
|
|
37
40
|
- **Menu Bar & xbar integration** — generate menu bar plugin script (`pyre xbar`) for continuous background status display
|
|
38
41
|
- **Graph mode toggle** — switch between sparkline and bar graph rendering in the live dashboard
|
|
39
42
|
- **P2P dashboard panel** — monitor P2P server status and peer events directly from the live dashboard
|
|
40
43
|
- **Temperature unit toggle** — switch between Celsius and Fahrenheit display in live mode
|
|
41
|
-
- **Hardware overview** — `pyre info` prints
|
|
44
|
+
- **Hardware & Displays overview** — `pyre info` prints hardware, connected displays, and Time Machine status
|
|
42
45
|
- **Remote SSH monitoring** — `pyre ssh <host>` streams a remote Mac's live dashboard locally
|
|
43
46
|
- **Web dashboard** — `pyre web` serves an auto-refreshing HTML dashboard with real-time SSE stream on a local port
|
|
44
|
-
- **Micro-benchmarking** — `pyre bench <cmd>` logs resource usage
|
|
47
|
+
- **Micro-benchmarking & Energy Cost** — `pyre bench <cmd>` logs resource usage and estimates energy consumption/cost (kWh)
|
|
48
|
+
- **Update checker** — `pyre update` checks npm registry for new pyre-cli releases
|
|
45
49
|
|
|
46
50
|
## Installation
|
|
47
51
|
|
|
@@ -63,17 +67,24 @@ curl -fsSL https://raw.githubusercontent.com/somalip/pyre/main/install.sh | bash
|
|
|
63
67
|
pyre # Static snapshot, all system stats
|
|
64
68
|
pyre --detailed # Include sensor / powermetrics detail
|
|
65
69
|
pyre --json # JSON output (also --csv, --tsv, --html, --md)
|
|
66
|
-
pyre --
|
|
70
|
+
pyre --plain # Plain-text accessible output without ANSI noise
|
|
67
71
|
|
|
68
72
|
pyre live # Interactive live dashboard
|
|
69
73
|
pyre live --interval 3 --log # Custom refresh interval + auto-logging
|
|
70
74
|
pyre live --alert-cpu 80 --alert-temp 85 --temp-unit f
|
|
71
75
|
|
|
72
|
-
pyre
|
|
73
|
-
pyre
|
|
74
|
-
|
|
75
|
-
pyre
|
|
76
|
-
pyre
|
|
76
|
+
pyre ui # Launch native macOS UI dashboard
|
|
77
|
+
pyre web # Serve auto-refreshing live web portal on localhost
|
|
78
|
+
|
|
79
|
+
pyre anomalies --since 7d # Statistical resource spike digest from past CSV logs
|
|
80
|
+
pyre doctor # System diagnostics (Gatekeeper, SIP, XProtect, powermetrics)
|
|
81
|
+
pyre extensions # System extensions inspector (systemextensionsctl)
|
|
82
|
+
pyre brew # Homebrew health summary & cellar size
|
|
83
|
+
pyre update # Check for pyre-cli updates
|
|
84
|
+
pyre profile save dev # Save active configuration profile
|
|
85
|
+
pyre profile load dev # Load configuration profile
|
|
86
|
+
pyre info # Concise hardware, display & Time Machine overview
|
|
87
|
+
pyre bench "make build" # Benchmark command & estimate kWh energy cost
|
|
77
88
|
```
|
|
78
89
|
|
|
79
90
|
## Options Reference
|
|
@@ -87,7 +98,9 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
87
98
|
| `--md` | Output as Markdown |
|
|
88
99
|
| `-c, --csv` | Output as CSV |
|
|
89
100
|
| `-t, --tsv` | Output as TSV |
|
|
101
|
+
| `--plain` / `--a11y` | Plain text output mode without ANSI colors or line-drawing characters |
|
|
90
102
|
| `--detailed` | Include detailed system info and sensor readings |
|
|
103
|
+
| `--since <range>` | Time range for anomaly digest (e.g. `1d`, `7d`, `yesterday`) |
|
|
91
104
|
| `--theme <name>` | Theme for live mode (`default`, `dracula`, `cyberpunk`, `monochrome`, `nord`, `gruvbox`) |
|
|
92
105
|
| `--interval <seconds>` | Refresh interval for live mode (default: `2`) |
|
|
93
106
|
| `--once` | Show a single static snapshot instead of live feed |
|
|
@@ -115,7 +128,11 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
115
128
|
| `pyre --json` | JSON output |
|
|
116
129
|
| `pyre --html` | Self-contained HTML report |
|
|
117
130
|
| `pyre --md` | Markdown tables |
|
|
118
|
-
| `pyre doctor` | Run system diagnostics (permissions,
|
|
131
|
+
| `pyre doctor` | Run system diagnostics (permissions, Gatekeeper, SIP, XProtect, P2P network) |
|
|
132
|
+
| `pyre extensions` | System Extensions inspector (`systemextensionsctl list`) |
|
|
133
|
+
| `pyre brew` | Homebrew health panel (installed formulae, outdated count, Cellar disk size, doctor summary) |
|
|
134
|
+
| `pyre update` | Check npm registry for latest `pyre-cli` release |
|
|
135
|
+
| `pyre profile <save\|load\|list> [name]` | Atomic configuration profile management (`~/.config/pyre/profiles/`) |
|
|
119
136
|
| `pyre config <show\|reset>` | View or reset persistent configuration file (`~/.config/pyre/config.json`) |
|
|
120
137
|
| `pyre completions <zsh\|bash\|fish>` | Generate shell auto-completion scripts |
|
|
121
138
|
|
|
@@ -123,7 +140,8 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
123
140
|
|
|
124
141
|
| Command | Description |
|
|
125
142
|
|---|---|
|
|
126
|
-
| `pyre info` | Concise hardware summary (CPU, memory, GPU, battery,
|
|
143
|
+
| `pyre info` | Concise hardware summary (CPU, memory pressure, GPU, battery, displays, Time Machine) |
|
|
144
|
+
| `pyre anomalies [--since N]` | Compute z-score anomalies and print plain-language resource spike digest |
|
|
127
145
|
| `pyre history [--days N]` | Graph historical resource trends from CSV logs over past N days |
|
|
128
146
|
| `pyre diff <file1.json> <file2.json>` | Compare two saved snapshot files side-by-side |
|
|
129
147
|
|
|
@@ -131,7 +149,7 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
131
149
|
|
|
132
150
|
| Command | Description |
|
|
133
151
|
|---|---|
|
|
134
|
-
| `pyre bench <cmd>` | Log CPU, memory, network, and
|
|
152
|
+
| `pyre bench <cmd>` | Log CPU, memory, network, power draw, and estimate kWh energy cost during command execution |
|
|
135
153
|
| `pyre xbar` | Generate an xbar / SwiftBar menu bar plugin script for stats display |
|
|
136
154
|
|
|
137
155
|
### Remote & Multi-host monitoring
|
|
@@ -147,11 +165,12 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
147
165
|
|---|---|
|
|
148
166
|
| `pyre live` | Start the interactive live dashboard |
|
|
149
167
|
|
|
150
|
-
### Web
|
|
168
|
+
### Web & Native dashboards
|
|
151
169
|
|
|
152
170
|
| Command | Description |
|
|
153
171
|
|---|---|
|
|
154
172
|
| `pyre web` | Serve an auto-refreshing live web portal with real-time SSE stats streaming on localhost (`--port <port>`) |
|
|
173
|
+
| `pyre ui` | Launch the native macOS UI dashboard (wraps the web portal in a native window) |
|
|
155
174
|
|
|
156
175
|
### P2P
|
|
157
176
|
|
|
@@ -425,11 +444,16 @@ timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_pa
|
|
|
425
444
|
pyre/
|
|
426
445
|
├── src/
|
|
427
446
|
│ ├── index.ts # CLI entry point (commander definitions, main())
|
|
447
|
+
│ ├── anomaliesCmd.ts # Resource spike & anomaly digest generator
|
|
448
|
+
│ ├── brewHealth.ts # Homebrew health & cellar usage inspector
|
|
449
|
+
│ ├── doctor.ts # System diagnostics (permissions, Gatekeeper, SIP, XProtect)
|
|
450
|
+
│ ├── extensions.ts # System Extensions inspector (systemextensionsctl list)
|
|
451
|
+
│ ├── updateCheck.ts # Passive and on-demand npm registry version check
|
|
428
452
|
│ ├── monitors/ # System metric collection (CPU, memory, disk, battery, thermal, network, packets, power, tasks)
|
|
429
453
|
│ │ ├── index.ts # Public API: collectAll, collectPower, collectPackets, collectTasks
|
|
430
454
|
│ │ ├── types.ts # TypeScript interfaces for all metric types
|
|
431
455
|
│ │ ├── smc.ts # SMC sensor reading with caching
|
|
432
|
-
│ │ ├── collectors.ts # Individual metric collectors and orchestrator
|
|
456
|
+
│ │ ├── collectors.ts # Individual metric collectors, displays, Time Machine, and orchestrator
|
|
433
457
|
│ │ └── run.ts # Shared shell-execution helper
|
|
434
458
|
│ ├── formatters/ # Output formatting and rendering
|
|
435
459
|
│ │ ├── index.ts # Public API: formatTable, formatJson, formatCsv, formatTsv, formatGraphs
|
|
@@ -440,7 +464,7 @@ pyre/
|
|
|
440
464
|
│ ├── live/ # Interactive live dashboard
|
|
441
465
|
│ │ ├── index.ts # Public API: startLive, stopLive
|
|
442
466
|
│ │ ├── state.ts # Shared mutable session state
|
|
443
|
-
│ │ ├── input.ts # Keypress handling
|
|
467
|
+
│ │ ├── input.ts # Keypress handling, signal picker, protected PID kill guards
|
|
444
468
|
│ │ ├── render.ts # Screen rendering and alert checking
|
|
445
469
|
│ │ ├── export.ts # Snapshot export and CSV logging
|
|
446
470
|
│ │ └── types.ts # LiveOptions, ExportFormat, InputMode, SortMode
|
|
@@ -450,10 +474,13 @@ pyre/
|
|
|
450
474
|
│ │ ├── protocol.ts # Length-prefixed JSON message framing
|
|
451
475
|
│ │ ├── server.ts # TCP server: auth, TLS, rate limiting, IP filtering, audit logging, HMAC signing, data streaming
|
|
452
476
|
│ │ └── client.ts # TCP client that connects and displays live data
|
|
477
|
+
│ ├── state/ # Config & profile state management
|
|
478
|
+
│ │ └── config.ts # Config file I/O & atomic profile save/load
|
|
453
479
|
│ ├── history.ts # Rolling history buffer for sparkline data
|
|
454
|
-
│ ├── sparkline.ts # ASCII sparkline rendering
|
|
480
|
+
│ ├── sparkline.ts # ASCII sparkline rendering & plain-text mode
|
|
455
481
|
│ └── utils/ # Shared utility functions
|
|
456
482
|
├── dist/ # Built output (esm)
|
|
483
|
+
├── grafana/ # Grafana dashboard JSON template
|
|
457
484
|
├── pyre-exports/ # Default directory for snapshot exports and logs
|
|
458
485
|
├── Formula/ # Homebrew formula
|
|
459
486
|
├── install.sh # curl installation script
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/anomaliesCmd.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
function runAnomaliesDigest(opts = {}) {
|
|
6
|
+
const dir = opts.dir || "./pyre-exports";
|
|
7
|
+
const sinceStr = opts.since || "7d";
|
|
8
|
+
console.log(chalk.bold(`
|
|
9
|
+
pyre anomalies \u2014 Resource Spike & Anomaly Digest
|
|
10
|
+
`));
|
|
11
|
+
if (!fs.existsSync(dir)) {
|
|
12
|
+
console.log(chalk.yellow(` No logs directory found for range '${sinceStr}' at ${dir}.`));
|
|
13
|
+
console.log(chalk.dim(" Enable background logging with `pyre --log` to record history.\n"));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const files = fs.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
|
|
17
|
+
if (files.length === 0) {
|
|
18
|
+
console.log(chalk.yellow(` No CSV logs found for range '${sinceStr}' in ${dir}.`));
|
|
19
|
+
console.log(chalk.dim(" Enable background logging with `pyre --log` to record history.\n"));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
23
|
+
if (sinceStr === "yesterday" || sinceStr === "1d") {
|
|
24
|
+
cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
25
|
+
} else if (sinceStr.endsWith("d")) {
|
|
26
|
+
const d = parseInt(sinceStr.slice(0, -1), 10);
|
|
27
|
+
if (!isNaN(d)) cutoff = Date.now() - d * 24 * 60 * 60 * 1e3;
|
|
28
|
+
} else if (!isNaN(Date.parse(sinceStr))) {
|
|
29
|
+
cutoff = Date.parse(sinceStr);
|
|
30
|
+
}
|
|
31
|
+
const rows = [];
|
|
32
|
+
for (const file of files.sort()) {
|
|
33
|
+
const fullPath = path.join(dir, file);
|
|
34
|
+
try {
|
|
35
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
36
|
+
const lines = content.split("\n").slice(1);
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
if (!line.trim()) continue;
|
|
39
|
+
const parts = line.split(",");
|
|
40
|
+
if (parts.length >= 3) {
|
|
41
|
+
const ts = new Date(parts[0]);
|
|
42
|
+
if (!isNaN(ts.getTime()) && ts.getTime() >= cutoff) {
|
|
43
|
+
const cpu = parseFloat(parts[1]);
|
|
44
|
+
const mem = parseFloat(parts[2]);
|
|
45
|
+
const temp = parts.length >= 4 ? parseFloat(parts[3]) : void 0;
|
|
46
|
+
const topProc = parts.length >= 5 ? parts[4] : void 0;
|
|
47
|
+
if (!isNaN(cpu) && !isNaN(mem)) {
|
|
48
|
+
rows.push({ ts, cpu, mem, temp: isNaN(temp) ? void 0 : temp, topProc });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (rows.length < 5) {
|
|
57
|
+
console.log(chalk.yellow(` Insufficient data recorded since ${sinceStr} (need at least 5 samples).`));
|
|
58
|
+
console.log(chalk.dim(" Run pyre with `--log` for longer intervals to build history.\n"));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const cpuVals = rows.map((r) => r.cpu);
|
|
62
|
+
const memVals = rows.map((r) => r.mem);
|
|
63
|
+
const tempVals = rows.filter((r) => r.temp !== void 0).map((r) => r.temp);
|
|
64
|
+
const calcMeanStd = (arr) => {
|
|
65
|
+
if (arr.length === 0) return { mean: 0, std: 0 };
|
|
66
|
+
const mean = arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
67
|
+
const variance = arr.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / arr.length;
|
|
68
|
+
return { mean, std: Math.sqrt(variance) };
|
|
69
|
+
};
|
|
70
|
+
const cpuStats = calcMeanStd(cpuVals);
|
|
71
|
+
const memStats = calcMeanStd(memVals);
|
|
72
|
+
const tempStats = calcMeanStd(tempVals);
|
|
73
|
+
const anomalies = [];
|
|
74
|
+
for (const r of rows) {
|
|
75
|
+
if (cpuStats.std > 0) {
|
|
76
|
+
const z = (r.cpu - cpuStats.mean) / cpuStats.std;
|
|
77
|
+
if (z >= 2.5) {
|
|
78
|
+
anomalies.push({ ts: r.ts, metric: "CPU", value: r.cpu, baseline: cpuStats.mean, zScore: z, topProc: r.topProc });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (memStats.std > 0) {
|
|
82
|
+
const z = (r.mem - memStats.mean) / memStats.std;
|
|
83
|
+
if (z >= 2.5) {
|
|
84
|
+
anomalies.push({ ts: r.ts, metric: "Memory", value: r.mem, baseline: memStats.mean, zScore: z, topProc: r.topProc });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (r.temp !== void 0 && tempStats.std > 0) {
|
|
88
|
+
const z = (r.temp - tempStats.mean) / tempStats.std;
|
|
89
|
+
if (z >= 2.5) {
|
|
90
|
+
anomalies.push({ ts: r.ts, metric: "Temperature", value: r.temp, baseline: tempStats.mean, zScore: z, topProc: r.topProc });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log(` Analyzed ${chalk.bold(rows.length)} samples since ${chalk.bold(sinceStr)}.`);
|
|
95
|
+
if (anomalies.length === 0) {
|
|
96
|
+
console.log(chalk.green(" \u2714 No statistical anomalies detected in this time range.\n"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(chalk.yellow(` \u26A0 Found ${anomalies.length} anomaly spike event(s):
|
|
100
|
+
`));
|
|
101
|
+
for (const a of anomalies.slice(0, 15)) {
|
|
102
|
+
const timeFormatted = a.ts.toLocaleString();
|
|
103
|
+
const severityColor = a.zScore >= 3.5 ? chalk.red : chalk.yellow;
|
|
104
|
+
const unit = a.metric === "Temperature" ? "\xB0C" : "%";
|
|
105
|
+
console.log(` \u2022 [${chalk.dim(timeFormatted)}] ${severityColor(a.metric)} spike: ${chalk.bold(a.value.toFixed(1) + unit)} (baseline: ${a.baseline.toFixed(1)}${unit}, +${a.zScore.toFixed(1)}\u03C3)`);
|
|
106
|
+
if (a.topProc) {
|
|
107
|
+
console.log(chalk.dim(` Top Process: ${a.topProc}`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (anomalies.length > 15) {
|
|
111
|
+
console.log(chalk.dim(` ... and ${anomalies.length - 15} more anomaly events.`));
|
|
112
|
+
}
|
|
113
|
+
console.log();
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
runAnomaliesDigest
|
|
117
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
run
|
|
3
|
+
} from "./chunk-GLH7EKAX.js";
|
|
4
|
+
|
|
5
|
+
// src/brewHealth.ts
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
async function getBrewHealth() {
|
|
8
|
+
try {
|
|
9
|
+
const brewPath = await run("which brew 2>&1", "");
|
|
10
|
+
if (!brewPath.trim() || brewPath.includes("not found")) {
|
|
11
|
+
return { installedCount: 0, outdatedCount: 0, cellarSize: "N/A", doctorWarnings: [], brewAvailable: false };
|
|
12
|
+
}
|
|
13
|
+
const outdatedRaw = await run("brew outdated --quiet 2>&1", "");
|
|
14
|
+
const outdatedLines = outdatedRaw.trim().split("\n").filter((l) => l.length > 0 && !l.startsWith("Homebrew") && !l.startsWith("==>"));
|
|
15
|
+
const listRaw = await run("brew list --formula -1 2>&1", "");
|
|
16
|
+
const listLines = listRaw.trim().split("\n").filter((l) => l.length > 0);
|
|
17
|
+
let cellarSize = "N/A";
|
|
18
|
+
try {
|
|
19
|
+
const cellarPath = await run("brew --cellar 2>&1", "");
|
|
20
|
+
if (cellarPath.trim()) {
|
|
21
|
+
const duRaw = await run(`du -sh "${cellarPath.trim()}" 2>&1`, "");
|
|
22
|
+
const duMatch = duRaw.match(/^([0-9\.\,]+[KMGTP]?i?B?)/i);
|
|
23
|
+
if (duMatch) cellarSize = duMatch[1];
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
cellarSize = "N/A";
|
|
27
|
+
}
|
|
28
|
+
const doctorRaw = await run("brew doctor 2>&1", "");
|
|
29
|
+
const doctorWarnings = [];
|
|
30
|
+
const docLines = doctorRaw.split("\n");
|
|
31
|
+
for (const line of docLines) {
|
|
32
|
+
if (line.startsWith("Warning:")) {
|
|
33
|
+
doctorWarnings.push(line.replace(/^Warning:\s*/, "").trim());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
installedCount: listLines.length,
|
|
38
|
+
outdatedCount: outdatedLines.length,
|
|
39
|
+
cellarSize,
|
|
40
|
+
doctorWarnings,
|
|
41
|
+
brewAvailable: true
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return { installedCount: 0, outdatedCount: 0, cellarSize: "N/A", doctorWarnings: [], brewAvailable: false };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function printBrewHealthReport() {
|
|
48
|
+
console.log(chalk.bold("\n pyre brew \u2014 Homebrew Health Summary\n"));
|
|
49
|
+
const health = await getBrewHealth();
|
|
50
|
+
if (!health.brewAvailable) {
|
|
51
|
+
console.log(chalk.yellow(" \u26A0 Homebrew is not installed or not available on PATH.\n"));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(` \u{1F4E6} Installed Formulae: ${chalk.bold(health.installedCount)}`);
|
|
55
|
+
console.log(` \u23F3 Outdated Packages: ${health.outdatedCount > 0 ? chalk.yellow.bold(health.outdatedCount) : chalk.green("0 (Up to date)")}`);
|
|
56
|
+
console.log(` \u{1F4BE} Cellar Disk Usage: ${chalk.bold(health.cellarSize)}`);
|
|
57
|
+
console.log();
|
|
58
|
+
if (health.doctorWarnings.length === 0) {
|
|
59
|
+
console.log(chalk.green(" \u2714 brew doctor: No warnings detected system-wide."));
|
|
60
|
+
} else {
|
|
61
|
+
console.log(chalk.yellow(` \u26A0 brew doctor warnings (${health.doctorWarnings.length}):`));
|
|
62
|
+
for (const warn of health.doctorWarnings.slice(0, 5)) {
|
|
63
|
+
console.log(chalk.dim(` \u2022 ${warn}`));
|
|
64
|
+
}
|
|
65
|
+
if (health.doctorWarnings.length > 5) {
|
|
66
|
+
console.log(chalk.dim(` ... and ${health.doctorWarnings.length - 5} more.`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
getBrewHealth,
|
|
73
|
+
printBrewHealthReport
|
|
74
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/monitors/run.ts
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
var execAsync = promisify(exec);
|
|
5
|
+
async function run(cmd, fallback = "", timeout = 5e3) {
|
|
6
|
+
try {
|
|
7
|
+
return (await execAsync(cmd, { encoding: "utf8", timeout })).stdout.trim();
|
|
8
|
+
} catch {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
run
|
|
15
|
+
};
|