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.
- package/README.md +84 -14
- package/dist/alertChannels-TPSQIJ7S.js +8 -0
- package/dist/blender-FGH3ASEQ.js +7 -0
- package/dist/brewHealth-P2GW5Y5C.js +176 -0
- package/dist/buildCmd-CDPZ2ZFA.js +38 -0
- package/dist/{checkCmd-EWFNLBWE.js → checkCmd-RKW6QGDR.js} +10 -11
- package/dist/{chunk-RVGKEJXT.js → chunk-53O2RGSL.js} +47 -17
- package/dist/{chunk-MOAEGMFP.js → chunk-6UHZP4QP.js} +204 -46
- package/dist/chunk-GHRDW646.js +2506 -0
- package/dist/{prometheus-LFYHF6VO.js → chunk-HT4VQ4OH.js} +1 -0
- package/dist/chunk-M73HCQD2.js +95 -0
- package/dist/chunk-MZO5QKUR.js +32 -0
- package/dist/chunk-VIACN2RU.js +133 -0
- package/dist/chunk-Z2YV5HKV.js +87 -0
- package/dist/chunk-ZLVIVFSO.js +110 -0
- package/dist/{collectors-WD6VKH6M.js → collectors-H43PN4PS.js} +8 -2
- package/dist/{completions-VQMHQCXE.js → completions-JKYEJNGR.js} +34 -1
- package/dist/{config-4ZLF4HTS.js → config-GYGJXU2Q.js} +9 -1
- package/dist/doctor-GYHFTNZQ.js +387 -0
- package/dist/explainCmd-MF6PIMLW.js +222 -0
- package/dist/extensions-XJGHG7X3.js +134 -0
- package/dist/index.js +841 -206
- package/dist/ioreport.py +72 -0
- package/dist/netUsageCmd-WKBTW2YG.js +90 -0
- package/dist/{pipeCmd-7THXEGDH.js → pipeCmd-7REDBLBU.js} +5 -3
- package/dist/profileProcCmd-N4H6MCBT.js +107 -0
- package/dist/prometheus-5BL5PVLC.js +6 -0
- package/dist/prometheusCmd-PL64BLN5.js +93 -0
- package/dist/replayCmd-Z3PYXY7V.js +164 -0
- package/dist/run-HOGQDDTA.js +8 -0
- package/dist/serveCmd-2H5VLF3P.js +153 -0
- package/dist/service-Q74BBILO.js +181 -0
- package/dist/smartCmd-ODWKSFNG.js +166 -0
- package/dist/syncCmd-A2W3QB3S.js +126 -0
- package/dist/topoCmd-DU2HHZNH.js +148 -0
- package/dist/watchdogCmd-UF53JPZB.js +161 -0
- package/dist/{wizard-GVIAHIVV.js → wizard-TD7BGX6O.js} +1 -1
- package/install.ps1 +29 -0
- package/install.sh +10 -9
- package/package.json +7 -3
- package/src/ai/models.ts +138 -0
- package/src/alertChannels.ts +119 -0
- package/src/anomalies.ts +114 -60
- package/src/brewHealth.ts +157 -40
- package/src/buildCmd.ts +48 -0
- package/src/checkCmd.ts +5 -5
- package/src/completions.ts +35 -0
- package/src/doctor.ts +235 -7
- package/src/easy_features.test.ts +40 -0
- package/src/explainCmd.ts +261 -0
- package/src/extensions.ts +71 -7
- package/src/formatters/index.ts +1 -1
- package/src/formatters/output.ts +100 -13
- package/src/formatters/render.ts +274 -38
- package/src/formatters/themes.ts +8 -0
- package/src/formatters/types.ts +6 -1
- package/src/index.ts +298 -56
- package/src/live/input.ts +122 -4
- package/src/live/render.ts +98 -0
- package/src/live/state.ts +70 -52
- package/src/live/types.ts +2 -2
- package/src/monitors/batteryPredictor.ts +143 -0
- package/src/monitors/blender.ts +144 -0
- package/src/monitors/buildTracker.ts +121 -0
- package/src/monitors/collectors.ts +558 -147
- package/src/monitors/dns_cache.ts +48 -0
- package/src/monitors/index.ts +3 -2
- package/src/monitors/ioreport.py +72 -0
- package/src/monitors/platform/linux.ts +756 -0
- package/src/monitors/platform/windows.ts +519 -0
- package/src/monitors/run.ts +27 -3
- package/src/monitors/smc.ts +21 -1
- package/src/monitors/types.ts +112 -14
- package/src/netUsageCmd.ts +121 -0
- package/src/platform.test.ts +99 -0
- package/src/plugins/index.ts +143 -0
- package/src/profileProcCmd.ts +133 -0
- package/src/prometheusCmd.ts +107 -0
- package/src/replayCmd.ts +186 -0
- package/src/serveCmd.ts +180 -0
- package/src/service.ts +125 -7
- package/src/smartCmd.ts +205 -0
- package/src/state/config.ts +64 -17
- package/src/syncCmd.ts +166 -0
- package/src/topoCmd.ts +176 -0
- package/src/watchdogCmd.ts +198 -0
- package/src/xbar.ts +22 -11
- package/dist/brewHealth-F2BXF5M6.js +0 -74
- package/dist/chunk-FH5WDJBF.js +0 -15
- package/dist/chunk-HGRBBPYX.js +0 -937
- package/dist/doctor-L6FZYA7F.js +0 -184
- package/dist/extensions-DVA5YLGH.js +0 -74
- package/dist/service-WTS3NBQU.js +0 -73
- /package/dist/{chunk-SG26Y3WF.js → chunk-IM3IQMMT.js} +0 -0
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# pyre
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cross-platform system monitoring CLI for **macOS**, **Linux**, and **Windows**: temps, CPU, memory, disk, battery, GPU, power draw, live dashboard, packet monitor, process management, export, alerts, P2P live data streaming, web dashboard, SSH monitoring, micro-benchmarking, and background service management.
|
|
4
4
|
|
|
5
5
|

|
|
6
|
-

|
|
7
|
+

|
|
8
|
+

|
|
7
9
|

|
|
8
10
|

|
|
9
11
|
|
|
10
|
-
> **Note:** things may not work as intended if your firewall settings do not allow inbound connections for
|
|
12
|
+
> **Note:** things may not work as intended if your firewall settings do not allow inbound connections for P2P streaming or Web dashboard mode.
|
|
11
13
|
>
|
|
12
14
|
> You can skip init with Enter, but you may encounter some UI bugs. This is being worked on.
|
|
13
15
|
|
|
@@ -21,6 +23,7 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, l
|
|
|
21
23
|
- **Disk I/O throughput & space** — read/write speed per volume and mounted volume space usage
|
|
22
24
|
- **Battery & power health** — level, power source, condition, charge cycles, max capacity, health trends, estimated time to empty, discharge rate, power draw in watts
|
|
23
25
|
- **Packet monitor** — network packet counts, packet rates, active TCP connections, top network processes
|
|
26
|
+
- **Advanced network monitoring** — real-time bandwidth rates (RX/TX bytes/sec), protocol breakdown (TCP/UDP), connection state analysis (ESTABLISHED, LISTENING, TIME_WAIT, CLOSE_WAIT), listening port detection, top remote hosts, per-process network I/O tracking, and GlassWire-style modern TUI panel with sparkline graphs
|
|
24
27
|
- **Task list** — running tasks/applications sorted by CPU with PID, user, memory, state, and runtime
|
|
25
28
|
- **Thermal state** — CPU temperature via `pmset` and `powermetrics` (sudo for detailed readings)
|
|
26
29
|
- **Network** — RX/TX bytes and per-second rates
|
|
@@ -28,6 +31,7 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, l
|
|
|
28
31
|
- **Interactive live dashboard** — full-screen TUI with keyboard-driven controls, mouse support, configurable panel layout, and tab-based panel navigation
|
|
29
32
|
- **Snapshot export** — JSON, CSV, TSV, HTML, or Markdown output formats
|
|
30
33
|
- **Continuous CSV logging & Anomaly Digest** — automatic per-tick logging and statistical spike analysis (`pyre anomalies`)
|
|
34
|
+
- **Blender render tracking** — automatically detects running Blender background renders, tracks elapsed time, frame progress, completion percentage, render engine, and blend file; fully loggable via CSV/JSON/Markdown exports
|
|
31
35
|
- **Visual themes & custom themes** — six built-in color themes plus user-defined JSON theme support in `~/.config/pyre/themes/`
|
|
32
36
|
- **Persistent configuration & Profiles** — save themes and thresholds in `~/.config/pyre/config.json`, plus atomic profile management (`pyre profile`)
|
|
33
37
|
- **Alert system & webhooks** — configurable threshold alerts with terminal bell, desktop notifications, webhook POST payload execution (`--webhook-url`), and custom shell command execution (`--alert-cmd`)
|
|
@@ -49,18 +53,24 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, l
|
|
|
49
53
|
|
|
50
54
|
## Installation
|
|
51
55
|
|
|
52
|
-
### npm
|
|
56
|
+
### npm (macOS, Linux, Windows)
|
|
53
57
|
|
|
54
58
|
```bash
|
|
55
59
|
npm install -g pyre-cli
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
### curl
|
|
62
|
+
### curl (macOS & Linux)
|
|
59
63
|
|
|
60
64
|
```bash
|
|
61
65
|
curl -fsSL https://raw.githubusercontent.com/somalip/pyre/main/install.sh | bash
|
|
62
66
|
```
|
|
63
67
|
|
|
68
|
+
### PowerShell (Windows)
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
irm https://raw.githubusercontent.com/somalip/pyre/main/install.ps1 | iex
|
|
72
|
+
```
|
|
73
|
+
|
|
64
74
|
## Quick Start
|
|
65
75
|
|
|
66
76
|
```bash
|
|
@@ -132,6 +142,7 @@ These options apply to both static snapshots and `pyre live`, except where noted
|
|
|
132
142
|
| `pyre doctor` | Run system diagnostics (permissions, Gatekeeper, SIP, XProtect, P2P network) |
|
|
133
143
|
| `pyre extensions` | System Extensions inspector (`systemextensionsctl list`) |
|
|
134
144
|
| `pyre brew` | Homebrew health panel (installed formulae, outdated count, Cellar disk size, doctor summary) |
|
|
145
|
+
| `pyre blender` | Track active Blender background renders: PID, blend file, engine, frame progress, elapsed time, and completion status |
|
|
135
146
|
| `pyre update` | Check npm registry for latest `pyre-cli` release |
|
|
136
147
|
| `pyre profile <save\|load\|list> [name]` | Atomic configuration profile management (`~/.config/pyre/profiles/`) |
|
|
137
148
|
| `pyre config <show\|reset>` | View or reset persistent configuration file (`~/.config/pyre/config.json`) |
|
|
@@ -314,6 +325,8 @@ Run with `pyre live`.
|
|
|
314
325
|
| `←` / `→` / `Tab` | Cycle to previous / next panel tab |
|
|
315
326
|
| `1`–`9`, `0` | Jump to panel by number (cpu, mem, gpu, power, battery, thermal, network, packets, tasks, disk) |
|
|
316
327
|
| `P` | Toggle process panel |
|
|
328
|
+
| `C` | Toggle containers panel |
|
|
329
|
+
| `B` | Toggle Blender render tracker panel |
|
|
317
330
|
| `Esc` | Close customizer or clear filter/kill input |
|
|
318
331
|
|
|
319
332
|
### UI Customizer
|
|
@@ -321,7 +334,7 @@ Run with `pyre live`.
|
|
|
321
334
|
Press `c` to open the customizer overlay. From there:
|
|
322
335
|
|
|
323
336
|
- Cycle through six built-in themes (Default, Dracula, Cyberpunk, Monochrome, Nord, Gruvbox) — `↑`/`↓` or `j`/`k` to navigate, `Enter`/`Space` to select
|
|
324
|
-
- Toggle individual panels (CPU, Memory, GPU, Power, Battery, Thermal, Network, Packets, Tasks, Disk, Processes) on or off
|
|
337
|
+
- Toggle individual panels (CPU, Memory, GPU, Power, Battery, Thermal, Network, Packets, Tasks, Disk, Processes, Containers, Blender) on or off
|
|
325
338
|
- Switch graph mode between sparkline and bar charts
|
|
326
339
|
- Toggle process tree view
|
|
327
340
|
- Toggle temperature unit between Celsius and Fahrenheit
|
|
@@ -436,9 +449,16 @@ timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_pa
|
|
|
436
449
|
|
|
437
450
|
## Requirements
|
|
438
451
|
|
|
439
|
-
- **macOS 14+** (Sonoma or later)
|
|
440
452
|
- **Node.js 18+**
|
|
441
|
-
- **
|
|
453
|
+
- **macOS:** macOS 14+ (Sonoma or later recommended). Optional `sudo` for `powermetrics` detailed thermal/power readings.
|
|
454
|
+
- **Linux:** Any modern Linux distribution (Ubuntu/Debian, Fedora/RHEL, Arch) with kernel 4.x+; reads `/proc` and `/sys`. Optional `smartmontools` for disk health.
|
|
455
|
+
- **Windows:** Windows 10, Windows 11, or Windows Server 2016+ with PowerShell 5.1+.
|
|
456
|
+
|
|
457
|
+
On macOS, to enable password‑less access for `powermetrics` (optional, for detailed sensor readings), run:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
echo "$(whoami) ALL=(root) NOPASSWD: /usr/bin/powermetrics" | sudo tee /etc/sudoers.d/pyre-powermetrics
|
|
461
|
+
```
|
|
442
462
|
|
|
443
463
|
## Project Structure
|
|
444
464
|
|
|
@@ -447,16 +467,20 @@ pyre/
|
|
|
447
467
|
├── src/
|
|
448
468
|
│ ├── index.ts # CLI entry point (commander definitions, main())
|
|
449
469
|
│ ├── anomaliesCmd.ts # Resource spike & anomaly digest generator
|
|
450
|
-
│ ├── brewHealth.ts #
|
|
451
|
-
│ ├── doctor.ts # System diagnostics (
|
|
452
|
-
│ ├── extensions.ts #
|
|
470
|
+
│ ├── brewHealth.ts # Package manager health inspector (Brew, APT, Pacman, DNF, Winget, Scoop)
|
|
471
|
+
│ ├── doctor.ts # System diagnostics (macOS SIP/Gatekeeper, Linux /proc/sys, Windows WMI)
|
|
472
|
+
│ ├── extensions.ts # Extensions/modules inspector (macOS extensions, Linux lsmod, Windows driverquery)
|
|
473
|
+
│ ├── service.ts # Background service daemon manager (launchd, systemd, schtasks)
|
|
453
474
|
│ ├── updateCheck.ts # Passive and on-demand npm registry version check
|
|
454
475
|
│ ├── monitors/ # System metric collection (CPU, memory, disk, battery, thermal, network, packets, power, tasks)
|
|
455
476
|
│ │ ├── index.ts # Public API: collectAll, collectPower, collectPackets, collectTasks
|
|
456
477
|
│ │ ├── types.ts # TypeScript interfaces for all metric types
|
|
457
|
-
│ │ ├── smc.ts # SMC sensor reading with caching
|
|
458
|
-
│ │ ├── collectors.ts #
|
|
459
|
-
│ │
|
|
478
|
+
│ │ ├── smc.ts # SMC sensor reading with caching (macOS)
|
|
479
|
+
│ │ ├── collectors.ts # Metric collection orchestrator & platform dispatch
|
|
480
|
+
│ │ ├── platform/ # OS-specific native metric collectors
|
|
481
|
+
│ │ │ ├── linux.ts # Linux /proc, /sys, df, ps, and netstat collectors
|
|
482
|
+
│ │ │ └── windows.ts # Windows os, WMI/PowerShell, netstat, and tasklist collectors
|
|
483
|
+
│ │ └── run.ts # Shared shell & PowerShell execution helpers
|
|
460
484
|
│ ├── formatters/ # Output formatting and rendering
|
|
461
485
|
│ │ ├── index.ts # Public API: formatTable, formatJson, formatCsv, formatTsv, formatGraphs
|
|
462
486
|
│ │ ├── output.ts # JSON/CSV/TSV serialisation and sparkline graphs
|
|
@@ -510,6 +534,52 @@ npm install
|
|
|
510
534
|
| `npm run dev` | Run directly via `tsx` (no build step) |
|
|
511
535
|
| `npm test` | Verify the built binary responds to `--help` |
|
|
512
536
|
|
|
537
|
+
## Roadmap
|
|
538
|
+
|
|
539
|
+
Proposed features for upcoming versions, organized by effort and impact.
|
|
540
|
+
|
|
541
|
+
### Tier 1 — High Impact, Achievable
|
|
542
|
+
|
|
543
|
+
| Feature | Description |
|
|
544
|
+
|---|---|
|
|
545
|
+
| **AI Anomaly Explanation** (`pyre explain`) | Translate z-score spikes into plain-English diagnoses using a local (Ollama) or API-based LLM |
|
|
546
|
+
| **S.M.A.R.T. Disk Health** (`pyre smart`) | Display SSD/HDD wear level, reallocated sectors, power-on hours, and remaining lifespan via `smartctl` |
|
|
547
|
+
| **Docker / Container Panel** | Show running Docker/OrbStack containers with CPU%, memory, network I/O, and status — toggle with `C` in the live dashboard |
|
|
548
|
+
| **Fan Speed Monitoring** | Display current RPM for all system fans via SMC readings in the Thermal panel |
|
|
549
|
+
| **Log Replay Mode** (`pyre replay`) | Load a historical CSV log and play it back inside the interactive TUI with scrubbing controls |
|
|
550
|
+
| **Multi-Channel Alert Integrations** | Native Slack, Discord, Pushover, and ntfy.sh alert targets with pre-configured payload templates (extends `--webhook-url`) |
|
|
551
|
+
| **Prometheus Exporter** | Expose a `/metrics` endpoint in Prometheus text format — `src/prometheus.ts` is already stubbed |
|
|
552
|
+
|
|
553
|
+
### Tier 2 — Strong Differentiation
|
|
554
|
+
|
|
555
|
+
| Feature | Description |
|
|
556
|
+
|---|---|
|
|
557
|
+
| **Plugin System** | User-authored metric collectors as JS scripts in `~/.config/pyre/plugins/` rendered as custom dashboard panels |
|
|
558
|
+
| **Per-App Network Usage** (`pyre netusage`) | Real-time and cumulative network I/O broken down per application, backed by `nettop` (parser already in repo) |
|
|
559
|
+
| **Configurable Dashboard Grid** | Define custom panel layout, order, and sizing in `~/.config/pyre/config.json` |
|
|
560
|
+
| **Smart Battery Predictor** | Use accumulated power-drain history from CSV logs to produce a personalized, heuristic battery time-remaining estimate |
|
|
561
|
+
| **REST API Mode** (`pyre serve`) | Expose all metrics as structured JSON REST endpoints (`GET /api/cpu`, `/api/all`, etc.) with optional API key auth |
|
|
562
|
+
| **Process CPU Profiling** (`pyre profile-proc <pid>`) | Capture a 5-second `sample`/`spindump` for a misbehaving process and render an annotated call-tree summary |
|
|
563
|
+
| **Watchdog Rules Engine** (`pyre watchdog`) | Persistent daemon that monitors metrics and executes configurable rules — e.g. "if CPU > 90% for 5 min → kill `photoanalysisd` → notify Slack" |
|
|
564
|
+
| **Build System Tracker** (`pyre build`) | First-class tracking for `xcodebuild`, `cargo`, `make`, `bazel` — extends the existing Blender render tracker pattern |
|
|
565
|
+
|
|
566
|
+
### Tier 3 — Big Bets
|
|
567
|
+
|
|
568
|
+
| Feature | Description |
|
|
569
|
+
|---|---|
|
|
570
|
+
| **Network Topology Mapper** (`pyre topo`) | Visualize LAN peers, active connections, and routing hops as an ASCII diagram; auto-detects pyre-capable peers |
|
|
571
|
+
| **Cloud Sync** (`pyre sync`) | Sync config profiles, alert thresholds, and anomaly history across multiple Macs via iCloud Drive or a custom S3/WebDAV backend |
|
|
572
|
+
|
|
573
|
+
### Quality of Life
|
|
574
|
+
|
|
575
|
+
| Feature | Description |
|
|
576
|
+
|---|---|
|
|
577
|
+
| **Gaming / Performance Mode Profile** | `pyre live --profile gaming` — GPU + Thermal + Power panels only, 1 s interval, bar graph mode, minimal overlay |
|
|
578
|
+
| **iOS Shortcut Integration** | Documented iOS Shortcut that runs `pyre check --plain` over SSH and displays a summary notification |
|
|
579
|
+
| **Setup Wizard Polish** (`pyre setup`) | Guided first-run experience for `powermetrics` sudo, alert thresholds, theme selection, and webhook setup |
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
513
583
|
## License
|
|
514
584
|
|
|
515
585
|
MIT
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {
|
|
2
|
+
run
|
|
3
|
+
} from "./chunk-MZO5QKUR.js";
|
|
4
|
+
|
|
5
|
+
// src/brewHealth.ts
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
async function getBrewHealth() {
|
|
8
|
+
const isWindows = process.platform === "win32";
|
|
9
|
+
try {
|
|
10
|
+
const brewPath = await run(isWindows ? "where brew 2>nul" : "which brew 2>&1", "");
|
|
11
|
+
if (brewPath.trim() && !brewPath.includes("not found") && !brewPath.includes("Could not find")) {
|
|
12
|
+
const outdatedRaw = await run("brew outdated --quiet 2>&1", "");
|
|
13
|
+
const outdatedLines = outdatedRaw.trim().split("\n").filter((l) => l.length > 0 && !l.startsWith("Homebrew") && !l.startsWith("==>"));
|
|
14
|
+
const listRaw = await run("brew list --formula -1 2>&1", "");
|
|
15
|
+
const listLines = listRaw.trim().split("\n").filter((l) => l.length > 0);
|
|
16
|
+
let cellarSize = "N/A";
|
|
17
|
+
try {
|
|
18
|
+
const cellarPath = await run("brew --cellar 2>&1", "");
|
|
19
|
+
if (cellarPath.trim()) {
|
|
20
|
+
const duRaw = await run(`du -sh "${cellarPath.trim()}" 2>&1`, "");
|
|
21
|
+
const duMatch = duRaw.match(/^([0-9.,]+[KMGTP]?i?B?)/i);
|
|
22
|
+
if (duMatch) cellarSize = duMatch[1];
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
cellarSize = "N/A";
|
|
26
|
+
}
|
|
27
|
+
const doctorRaw = await run("brew doctor 2>&1", "");
|
|
28
|
+
const doctorWarnings = [];
|
|
29
|
+
const docLines = doctorRaw.split("\n");
|
|
30
|
+
for (const line of docLines) {
|
|
31
|
+
if (line.startsWith("Warning:")) {
|
|
32
|
+
doctorWarnings.push(line.replace(/^Warning:\s*/, "").trim());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
installedCount: listLines.length,
|
|
37
|
+
outdatedCount: outdatedLines.length,
|
|
38
|
+
cellarSize,
|
|
39
|
+
doctorWarnings,
|
|
40
|
+
brewAvailable: true,
|
|
41
|
+
managerName: "Homebrew"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
if (process.platform === "linux") {
|
|
47
|
+
try {
|
|
48
|
+
const whichApt = (await run("which apt-get 2>/dev/null", "")).trim();
|
|
49
|
+
if (whichApt) {
|
|
50
|
+
const listRaw = (await run("dpkg-query -l 2>/dev/null", "")).trim();
|
|
51
|
+
const installed = listRaw ? listRaw.split("\n").filter((l) => l.startsWith("ii")).length : 0;
|
|
52
|
+
const upgradeRaw = (await run("apt list --upgradable 2>/dev/null", "")).trim();
|
|
53
|
+
const outdated = upgradeRaw ? upgradeRaw.split("\n").filter((l) => l.includes("/") && !l.startsWith("Listing")).length : 0;
|
|
54
|
+
return {
|
|
55
|
+
installedCount: installed,
|
|
56
|
+
outdatedCount: outdated,
|
|
57
|
+
cellarSize: "/var/cache/apt",
|
|
58
|
+
doctorWarnings: [],
|
|
59
|
+
brewAvailable: true,
|
|
60
|
+
managerName: "APT (Debian/Ubuntu)"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const whichPacman = (await run("which pacman 2>/dev/null", "")).trim();
|
|
67
|
+
if (whichPacman) {
|
|
68
|
+
const listRaw = (await run("pacman -Q 2>/dev/null", "")).trim();
|
|
69
|
+
const installed = listRaw ? listRaw.split("\n").filter(Boolean).length : 0;
|
|
70
|
+
const upgradeRaw = (await run("checkupdates 2>/dev/null || pacman -Qu 2>/dev/null", "")).trim();
|
|
71
|
+
const outdated = upgradeRaw ? upgradeRaw.split("\n").filter(Boolean).length : 0;
|
|
72
|
+
return {
|
|
73
|
+
installedCount: installed,
|
|
74
|
+
outdatedCount: outdated,
|
|
75
|
+
cellarSize: "/var/cache/pacman",
|
|
76
|
+
doctorWarnings: [],
|
|
77
|
+
brewAvailable: true,
|
|
78
|
+
managerName: "Pacman (Arch)"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const whichDnf = (await run("which dnf 2>/dev/null", "")).trim();
|
|
85
|
+
if (whichDnf) {
|
|
86
|
+
const listRaw = (await run("dnf list installed 2>/dev/null", "")).trim();
|
|
87
|
+
const installed = listRaw ? listRaw.split("\n").filter(Boolean).length - 1 : 0;
|
|
88
|
+
return {
|
|
89
|
+
installedCount: Math.max(0, installed),
|
|
90
|
+
outdatedCount: 0,
|
|
91
|
+
cellarSize: "/var/cache/dnf",
|
|
92
|
+
doctorWarnings: [],
|
|
93
|
+
brewAvailable: true,
|
|
94
|
+
managerName: "DNF (Fedora/RHEL)"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (isWindows) {
|
|
101
|
+
try {
|
|
102
|
+
const whichWinget = (await run("where winget 2>nul", "")).trim();
|
|
103
|
+
if (whichWinget) {
|
|
104
|
+
const listRaw = (await run("winget list 2>nul", "", 4e3)).trim();
|
|
105
|
+
const installed = listRaw ? listRaw.split("\n").filter(Boolean).length - 2 : 0;
|
|
106
|
+
return {
|
|
107
|
+
installedCount: Math.max(0, installed),
|
|
108
|
+
outdatedCount: 0,
|
|
109
|
+
cellarSize: "N/A",
|
|
110
|
+
doctorWarnings: [],
|
|
111
|
+
brewAvailable: true,
|
|
112
|
+
managerName: "Windows Package Manager (winget)"
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const whichScoop = (await run("where scoop 2>nul", "")).trim();
|
|
119
|
+
if (whichScoop) {
|
|
120
|
+
const listRaw = (await run("scoop list 2>nul", "")).trim();
|
|
121
|
+
const installed = listRaw ? listRaw.split("\n").filter(Boolean).length - 4 : 0;
|
|
122
|
+
return {
|
|
123
|
+
installedCount: Math.max(0, installed),
|
|
124
|
+
outdatedCount: 0,
|
|
125
|
+
cellarSize: "N/A",
|
|
126
|
+
doctorWarnings: [],
|
|
127
|
+
brewAvailable: true,
|
|
128
|
+
managerName: "Scoop"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
installedCount: 0,
|
|
136
|
+
outdatedCount: 0,
|
|
137
|
+
cellarSize: "N/A",
|
|
138
|
+
doctorWarnings: [],
|
|
139
|
+
brewAvailable: false,
|
|
140
|
+
managerName: "None"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function printBrewHealthReport() {
|
|
144
|
+
const health = await getBrewHealth();
|
|
145
|
+
const label = health.brewAvailable ? health.managerName : "Package Manager";
|
|
146
|
+
console.log(chalk.bold(`
|
|
147
|
+
pyre brew \u2014 ${label} Health Summary
|
|
148
|
+
`));
|
|
149
|
+
if (!health.brewAvailable) {
|
|
150
|
+
console.log(chalk.yellow(" \u26A0 No supported package manager detected on PATH.\n"));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
console.log(` \u{1F4E6} Package Manager: ${chalk.cyan.bold(health.managerName)}`);
|
|
154
|
+
console.log(` \u{1F4E6} Installed Packages: ${chalk.bold(health.installedCount)}`);
|
|
155
|
+
console.log(` \u23F3 Outdated Packages: ${health.outdatedCount > 0 ? chalk.yellow.bold(health.outdatedCount) : chalk.green("0 (Up to date)")}`);
|
|
156
|
+
if (health.cellarSize !== "N/A") {
|
|
157
|
+
console.log(` \u{1F4BE} Cache / Cellar: ${chalk.bold(health.cellarSize)}`);
|
|
158
|
+
}
|
|
159
|
+
console.log();
|
|
160
|
+
if (health.doctorWarnings.length === 0) {
|
|
161
|
+
console.log(chalk.green(` \u2714 ${health.managerName}: Ready and healthy.`));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.yellow(` \u26A0 Diagnostics warnings (${health.doctorWarnings.length}):`));
|
|
164
|
+
for (const warn of health.doctorWarnings.slice(0, 5)) {
|
|
165
|
+
console.log(chalk.dim(` \u2022 ${warn}`));
|
|
166
|
+
}
|
|
167
|
+
if (health.doctorWarnings.length > 5) {
|
|
168
|
+
console.log(chalk.dim(` ... and ${health.doctorWarnings.length - 5} more.`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
getBrewHealth,
|
|
175
|
+
printBrewHealthReport
|
|
176
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectBuilds
|
|
3
|
+
} from "./chunk-M73HCQD2.js";
|
|
4
|
+
import "./chunk-MZO5QKUR.js";
|
|
5
|
+
|
|
6
|
+
// src/buildCmd.ts
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
async function runBuildCommand(opts = {}) {
|
|
9
|
+
const builds = await collectBuilds();
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify({ activeBuilds: builds }, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log(chalk.bold("\n \u{1F525} pyre build \u2014 Active Build System Tracker\n"));
|
|
15
|
+
if (builds.length === 0) {
|
|
16
|
+
console.log(chalk.dim(" No active build or compilation processes detected."));
|
|
17
|
+
console.log(chalk.dim(" (Supports: xcodebuild, cargo, swift build, make, ninja, bazel, go build)\n"));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const b of builds) {
|
|
21
|
+
const elapsedMins = Math.floor(b.elapsedSec / 60);
|
|
22
|
+
const elapsedSecs = b.elapsedSec % 60;
|
|
23
|
+
const timeStr = elapsedMins > 0 ? `${elapsedMins}m ${elapsedSecs}s` : `${elapsedSecs}s`;
|
|
24
|
+
console.log(` ${chalk.bgCyan.black.bold(` ${b.tool.toUpperCase()} `)} ${chalk.bold(`PID ${b.rootPid}`)}`);
|
|
25
|
+
console.log(` Command: ${chalk.white(b.command)}`);
|
|
26
|
+
if (b.target) {
|
|
27
|
+
console.log(` Target: ${chalk.cyan(b.target)}`);
|
|
28
|
+
}
|
|
29
|
+
console.log(` Compiler Workers: ${chalk.green.bold(String(b.compilerProcesses))} active thread/processes`);
|
|
30
|
+
console.log(` Combined CPU: ${chalk.yellow.bold(b.totalCpu.toFixed(1) + "%")}`);
|
|
31
|
+
console.log(` Combined RAM: ${chalk.magenta(b.totalMem.toFixed(1) + "%")}`);
|
|
32
|
+
console.log(` Elapsed Time: ${chalk.dim(timeStr)}`);
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
runBuildCommand
|
|
38
|
+
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
formatBytes
|
|
3
|
-
} from "./chunk-MOAEGMFP.js";
|
|
4
|
-
import "./chunk-SG26Y3WF.js";
|
|
1
|
+
import "./chunk-IM3IQMMT.js";
|
|
5
2
|
import {
|
|
6
3
|
collectAll
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-GHRDW646.js";
|
|
5
|
+
import "./chunk-M73HCQD2.js";
|
|
6
|
+
import "./chunk-VIACN2RU.js";
|
|
7
|
+
import "./chunk-MZO5QKUR.js";
|
|
9
8
|
|
|
10
9
|
// src/checkCmd.ts
|
|
11
10
|
import chalk from "chalk";
|
|
@@ -14,7 +13,7 @@ async function runCheckCommand(opts = {}) {
|
|
|
14
13
|
const cpuUsage = data.cpu.usage;
|
|
15
14
|
const tempC = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die;
|
|
16
15
|
const memUsage = data.memory.usagePercent;
|
|
17
|
-
const disk = data.disk.
|
|
16
|
+
const disk = data.disk && data.disk.length > 0 ? data.disk[0] : null;
|
|
18
17
|
const sentences = [];
|
|
19
18
|
let isWarning = false;
|
|
20
19
|
const tempStr = tempC !== void 0 ? `${Math.round(tempC)}\xB0C` : null;
|
|
@@ -52,10 +51,10 @@ async function runCheckCommand(opts = {}) {
|
|
|
52
51
|
sentences.push(`Memory at ${Math.round(memUsage)}% used.`);
|
|
53
52
|
}
|
|
54
53
|
if (disk) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
54
|
+
const freeStr = disk.available;
|
|
55
|
+
sentences.push(`${freeStr} free on ${disk.mountpoint}.`);
|
|
56
|
+
const cap = parseInt(disk.capacity, 10);
|
|
57
|
+
if (!isNaN(cap) && cap >= 90) {
|
|
59
58
|
isWarning = true;
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import os from "os";
|
|
4
4
|
import path from "path";
|
|
5
|
-
var CONFIG_DIR = path.join(os.homedir(), ".config", "pyre");
|
|
6
|
-
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
5
|
var DEFAULT_CONFIG = {
|
|
8
6
|
theme: "default",
|
|
9
7
|
interval: 2,
|
|
@@ -29,6 +27,15 @@ var DEFAULT_CONFIG = {
|
|
|
29
27
|
splashAnimation: "classic",
|
|
30
28
|
webhookUrl: "",
|
|
31
29
|
alertCmd: "",
|
|
30
|
+
slackAlertUrl: "",
|
|
31
|
+
discordAlertUrl: "",
|
|
32
|
+
pushoverToken: "",
|
|
33
|
+
pushoverUser: "",
|
|
34
|
+
ntfyUrl: "",
|
|
35
|
+
aiBackend: "builtin",
|
|
36
|
+
aiModel: "expert-rules-v1",
|
|
37
|
+
aiApiKey: "",
|
|
38
|
+
dockerModeConfirmed: false,
|
|
32
39
|
visiblePanels: {
|
|
33
40
|
cpu: true,
|
|
34
41
|
mem: true,
|
|
@@ -41,14 +48,29 @@ var DEFAULT_CONFIG = {
|
|
|
41
48
|
tasks: true,
|
|
42
49
|
disk: true,
|
|
43
50
|
process: true,
|
|
51
|
+
containers: true,
|
|
52
|
+
blender: true,
|
|
44
53
|
tree: false
|
|
45
54
|
},
|
|
46
55
|
panelLayout: ["mem", "disk", "net"]
|
|
47
56
|
};
|
|
57
|
+
function getConfigDir() {
|
|
58
|
+
return process.env.PYRE_CONFIG_DIR || path.join(os.homedir(), ".config", "pyre");
|
|
59
|
+
}
|
|
60
|
+
function getConfigFile() {
|
|
61
|
+
return path.join(getConfigDir(), "config.json");
|
|
62
|
+
}
|
|
63
|
+
function getProfilesDir() {
|
|
64
|
+
return path.join(getConfigDir(), "profiles");
|
|
65
|
+
}
|
|
66
|
+
var CONFIG_DIR = getConfigDir();
|
|
67
|
+
var CONFIG_FILE = getConfigFile();
|
|
68
|
+
var PROFILES_DIR = getProfilesDir();
|
|
48
69
|
function readConfig() {
|
|
49
70
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
const file = getConfigFile();
|
|
72
|
+
if (!fs.existsSync(file)) return { ...DEFAULT_CONFIG };
|
|
73
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
52
74
|
const parsed = JSON.parse(raw);
|
|
53
75
|
return deepMerge(DEFAULT_CONFIG, parsed);
|
|
54
76
|
} catch {
|
|
@@ -57,28 +79,31 @@ function readConfig() {
|
|
|
57
79
|
}
|
|
58
80
|
function writeConfig(config) {
|
|
59
81
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
const dir = getConfigDir();
|
|
83
|
+
const file = getConfigFile();
|
|
84
|
+
if (!fs.existsSync(dir)) {
|
|
85
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
86
|
}
|
|
63
87
|
const merged = deepMerge(readConfig(), config);
|
|
64
|
-
fs.writeFileSync(
|
|
88
|
+
fs.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
|
|
65
89
|
} catch {
|
|
66
90
|
}
|
|
67
91
|
}
|
|
68
|
-
var PROFILES_DIR = path.join(CONFIG_DIR, "profiles");
|
|
69
92
|
function saveProfile(name) {
|
|
70
93
|
if (!name || name.includes("/") || name.includes("\\")) {
|
|
71
94
|
throw new Error("Invalid profile name.");
|
|
72
95
|
}
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
const profilesDir = getProfilesDir();
|
|
97
|
+
if (!fs.existsSync(profilesDir)) {
|
|
98
|
+
fs.mkdirSync(profilesDir, { recursive: true });
|
|
75
99
|
}
|
|
76
100
|
const currentConfig = readConfig();
|
|
77
|
-
const profileFile = path.join(
|
|
101
|
+
const profileFile = path.join(profilesDir, `${name}.json`);
|
|
78
102
|
fs.writeFileSync(profileFile, JSON.stringify(currentConfig, null, 2) + "\n");
|
|
79
103
|
}
|
|
80
104
|
function loadProfile(name) {
|
|
81
|
-
const
|
|
105
|
+
const profilesDir = getProfilesDir();
|
|
106
|
+
const profileFile = path.join(profilesDir, `${name}.json`);
|
|
82
107
|
if (!fs.existsSync(profileFile)) {
|
|
83
108
|
throw new Error(`Profile '${name}' does not exist.`);
|
|
84
109
|
}
|
|
@@ -87,8 +112,9 @@ function loadProfile(name) {
|
|
|
87
112
|
writeConfig(parsed);
|
|
88
113
|
}
|
|
89
114
|
function listProfiles() {
|
|
90
|
-
|
|
91
|
-
|
|
115
|
+
const profilesDir = getProfilesDir();
|
|
116
|
+
if (!fs.existsSync(profilesDir)) return [];
|
|
117
|
+
const files = fs.readdirSync(profilesDir);
|
|
92
118
|
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
|
|
93
119
|
}
|
|
94
120
|
function deepMerge(target, source) {
|
|
@@ -105,15 +131,19 @@ function deepMerge(target, source) {
|
|
|
105
131
|
return result;
|
|
106
132
|
}
|
|
107
133
|
function getConfigPath() {
|
|
108
|
-
return
|
|
134
|
+
return getConfigFile();
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
export {
|
|
112
|
-
CONFIG_FILE,
|
|
113
138
|
DEFAULT_CONFIG,
|
|
139
|
+
getConfigDir,
|
|
140
|
+
getConfigFile,
|
|
141
|
+
getProfilesDir,
|
|
142
|
+
CONFIG_DIR,
|
|
143
|
+
CONFIG_FILE,
|
|
144
|
+
PROFILES_DIR,
|
|
114
145
|
readConfig,
|
|
115
146
|
writeConfig,
|
|
116
|
-
PROFILES_DIR,
|
|
117
147
|
saveProfile,
|
|
118
148
|
loadProfile,
|
|
119
149
|
listProfiles,
|