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/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # pyre
2
2
 
3
- Mac system monitoring CLI: temps, CPU, memory, disk, battery, GPU, power draw, live dashboard, packet monitor, process management, export, alerts, P2P live data streaming, web dashboard, SSH monitoring, and micro-benchmarking.
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
  ![Version](https://img.shields.io/badge/version-5.0.0-blue)
6
- ![macOS](https://img.shields.io/badge/macos-14%2B-lightgrey)
6
+ ![macOS](https://img.shields.io/badge/macOS-14%2B-lightgrey)
7
+ ![Linux](https://img.shields.io/badge/Linux-x86__64%20%7C%20arm64-orange)
8
+ ![Windows](https://img.shields.io/badge/Windows-10%20%7C%2011%20%7C%20Server-blue)
7
9
  ![Node](https://img.shields.io/badge/node-18%2B-green)
8
10
  ![License](https://img.shields.io/badge/license-MIT-green)
9
11
 
10
- > **Note:** things may not work as intended if your firewall settings do not allow inbound connections for this program.
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
- - **Optional:** `sudo` for `powermetrics` detailed temperatures in `--detailed` mode
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 # Homebrew health & cellar usage inspector
451
- │ ├── doctor.ts # System diagnostics (permissions, Gatekeeper, SIP, XProtect)
452
- │ ├── extensions.ts # System Extensions inspector (systemextensionsctl list)
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 # Individual metric collectors, displays, Time Machine, and orchestrator
459
- │ │ └── run.ts # Shared shell-execution helper
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,8 @@
1
+ import {
2
+ sendAlert,
3
+ sendTestAlert
4
+ } from "./chunk-Z2YV5HKV.js";
5
+ export {
6
+ sendAlert,
7
+ sendTestAlert
8
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ collectBlenderRenders
3
+ } from "./chunk-VIACN2RU.js";
4
+ import "./chunk-MZO5QKUR.js";
5
+ export {
6
+ collectBlenderRenders
7
+ };
@@ -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-HGRBBPYX.js";
8
- import "./chunk-FH5WDJBF.js";
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.mainVolume;
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 freeBytes = disk.free;
56
- const freeStr = formatBytes(freeBytes);
57
- sentences.push(`${freeStr} free on ${disk.mount}.`);
58
- if (disk.usePercent >= 90) {
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
- if (!fs.existsSync(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
51
- const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
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
- if (!fs.existsSync(CONFIG_DIR)) {
61
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
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(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
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
- if (!fs.existsSync(PROFILES_DIR)) {
74
- fs.mkdirSync(PROFILES_DIR, { recursive: true });
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(PROFILES_DIR, `${name}.json`);
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 profileFile = path.join(PROFILES_DIR, `${name}.json`);
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
- if (!fs.existsSync(PROFILES_DIR)) return [];
91
- const files = fs.readdirSync(PROFILES_DIR);
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 CONFIG_FILE;
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,