pyre-cli 9.5.2 → 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 (86) hide show
  1. package/README.md +73 -16
  2. package/dist/alertChannels-TPSQIJ7S.js +8 -0
  3. package/dist/{blender-7UT5J7IP.js → blender-FGH3ASEQ.js} +2 -2
  4. package/dist/brewHealth-P2GW5Y5C.js +176 -0
  5. package/dist/buildCmd-CDPZ2ZFA.js +38 -0
  6. package/dist/{checkCmd-T7O4KXXA.js → checkCmd-RKW6QGDR.js} +9 -11
  7. package/dist/{chunk-CXL4GYGQ.js → chunk-53O2RGSL.js} +44 -17
  8. package/dist/{chunk-3TE7MJLS.js → chunk-6UHZP4QP.js} +36 -10
  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-25I533OF.js → chunk-VIACN2RU.js} +1 -1
  14. package/dist/chunk-Z2YV5HKV.js +87 -0
  15. package/dist/chunk-ZLVIVFSO.js +110 -0
  16. package/dist/{collectors-JB4OGUEF.js → collectors-H43PN4PS.js} +6 -3
  17. package/dist/{completions-VQMHQCXE.js → completions-JKYEJNGR.js} +34 -1
  18. package/dist/{config-KS5ZZ3VN.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 +460 -88
  23. package/dist/ioreport.py +72 -0
  24. package/dist/netUsageCmd-WKBTW2YG.js +90 -0
  25. package/dist/{pipeCmd-TYLJHQ5B.js → pipeCmd-7REDBLBU.js} +4 -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-Z3RGA3VB.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/brewHealth.ts +157 -40
  44. package/src/buildCmd.ts +48 -0
  45. package/src/checkCmd.ts +5 -5
  46. package/src/completions.ts +35 -0
  47. package/src/doctor.ts +235 -7
  48. package/src/easy_features.test.ts +40 -0
  49. package/src/explainCmd.ts +261 -0
  50. package/src/extensions.ts +71 -7
  51. package/src/formatters/render.ts +34 -10
  52. package/src/formatters/types.ts +2 -0
  53. package/src/index.ts +251 -36
  54. package/src/live/input.ts +60 -0
  55. package/src/live/render.ts +67 -0
  56. package/src/live/state.ts +5 -0
  57. package/src/live/types.ts +1 -1
  58. package/src/monitors/batteryPredictor.ts +143 -0
  59. package/src/monitors/buildTracker.ts +121 -0
  60. package/src/monitors/collectors.ts +342 -178
  61. package/src/monitors/ioreport.py +72 -0
  62. package/src/monitors/platform/linux.ts +756 -0
  63. package/src/monitors/platform/windows.ts +519 -0
  64. package/src/monitors/run.ts +27 -3
  65. package/src/monitors/smc.ts +21 -1
  66. package/src/monitors/types.ts +15 -0
  67. package/src/netUsageCmd.ts +121 -0
  68. package/src/platform.test.ts +99 -0
  69. package/src/plugins/index.ts +143 -0
  70. package/src/profileProcCmd.ts +133 -0
  71. package/src/prometheusCmd.ts +107 -0
  72. package/src/replayCmd.ts +186 -0
  73. package/src/serveCmd.ts +180 -0
  74. package/src/service.ts +125 -7
  75. package/src/smartCmd.ts +205 -0
  76. package/src/state/config.ts +58 -17
  77. package/src/syncCmd.ts +166 -0
  78. package/src/topoCmd.ts +176 -0
  79. package/src/watchdogCmd.ts +198 -0
  80. package/src/xbar.ts +22 -11
  81. package/dist/brewHealth-F2BXF5M6.js +0 -74
  82. package/dist/chunk-FH5WDJBF.js +0 -15
  83. package/dist/chunk-N4WJVF5M.js +0 -1184
  84. package/dist/doctor-UV7TWIHX.js +0 -184
  85. package/dist/extensions-DVA5YLGH.js +0 -74
  86. package/dist/service-WTS3NBQU.js +0 -73
@@ -0,0 +1,198 @@
1
+ /**
2
+ * pyre watchdog — Persistent Autonomous Rules Engine.
3
+ *
4
+ * Evaluates configurable metric threshold and process rules continuously,
5
+ * executing remediation actions (kill, renice) and dispatching multi-channel alerts.
6
+ *
7
+ * Example watchdog configuration rule:
8
+ * {
9
+ * "name": "Stop runaway background renderers",
10
+ * "metric": "cpu",
11
+ * "process": "photoanalysisd",
12
+ * "threshold": 80,
13
+ * "durationSec": 10,
14
+ * "action": "kill", // "kill" | "notify" | "alert"
15
+ * "cooldownSec": 60
16
+ * }
17
+ */
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import chalk from 'chalk';
21
+ import { collectAll } from './monitors/collectors.js';
22
+ import { sendAlert } from './alertChannels.js';
23
+ import { getConfigDir, readConfig } from './state/config.js';
24
+
25
+ export interface WatchdogRule {
26
+ name: string;
27
+ metric?: 'cpu' | 'mem' | 'temp' | 'process_cpu' | 'process_mem';
28
+ process?: string;
29
+ threshold: number;
30
+ durationSec?: number;
31
+ action: 'kill' | 'notify' | 'alert';
32
+ cooldownSec?: number;
33
+ }
34
+
35
+ export interface WatchdogOptions {
36
+ configFile?: string;
37
+ dryRun?: boolean;
38
+ interval?: number;
39
+ }
40
+
41
+ const DEFAULT_WATCHDOG_RULES: WatchdogRule[] = [
42
+ {
43
+ name: 'High CPU System Alert',
44
+ metric: 'cpu',
45
+ threshold: 92,
46
+ durationSec: 5,
47
+ action: 'alert',
48
+ cooldownSec: 120,
49
+ },
50
+ {
51
+ name: 'Critical Temperature Protection',
52
+ metric: 'temp',
53
+ threshold: 98,
54
+ durationSec: 5,
55
+ action: 'alert',
56
+ cooldownSec: 180,
57
+ },
58
+ ];
59
+
60
+ export async function runWatchdogCommand(opts: WatchdogOptions = {}): Promise<void> {
61
+ const configDir = getConfigDir();
62
+ const defaultRulePath = path.join(configDir, 'watchdog.json');
63
+ const rulePath = opts.configFile || defaultRulePath;
64
+ const intervalSec = opts.interval || 2;
65
+ const isDryRun = !!opts.dryRun;
66
+
67
+ let rules: WatchdogRule[] = DEFAULT_WATCHDOG_RULES;
68
+
69
+ if (fs.existsSync(rulePath)) {
70
+ try {
71
+ rules = JSON.parse(fs.readFileSync(rulePath, 'utf-8'));
72
+ } catch (err: any) {
73
+ console.error(chalk.yellow(` ⚠ Could not parse watchdog config at ${rulePath}: ${err.message}. Using default rules.`));
74
+ }
75
+ } else if (!opts.configFile) {
76
+ try {
77
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
78
+ fs.writeFileSync(defaultRulePath, JSON.stringify(DEFAULT_WATCHDOG_RULES, null, 2));
79
+ } catch {
80
+ // ignore
81
+ }
82
+ }
83
+
84
+ console.log(chalk.bold('\n 🔥 pyre watchdog — Autonomous Monitoring Daemon\n'));
85
+ console.log(` ${chalk.green('✔')} Loaded ${chalk.bold(String(rules.length))} rule(s) from ${chalk.cyan(rulePath)}`);
86
+ console.log(` ${chalk.green('✔')} Refresh interval: ${intervalSec}s | Dry run mode: ${isDryRun ? chalk.yellow('ON') : chalk.dim('OFF')}`);
87
+ console.log(`\n ${chalk.dim('Active Rules:')}`);
88
+ for (const r of rules) {
89
+ const target = r.process ? `proc "${r.process}"` : r.metric || 'system';
90
+ console.log(` • ${chalk.bold(r.name)} [if ${target} > ${r.threshold} → ${chalk.cyan(r.action)}]`);
91
+ }
92
+ console.log(chalk.dim('\n Watching... Press Ctrl+C to stop.\n'));
93
+
94
+ // Track rule trigger history for cooldowns
95
+ const lastTriggered = new Map<string, number>();
96
+
97
+ const config = readConfig();
98
+ const alertChannels = {
99
+ slackUrl: config.slackAlertUrl,
100
+ discordUrl: config.discordAlertUrl,
101
+ pushoverToken: config.pushoverToken,
102
+ pushoverUser: config.pushoverUser,
103
+ ntfyUrl: config.ntfyUrl,
104
+ };
105
+
106
+ const evaluate = async () => {
107
+ try {
108
+ const data = await collectAll();
109
+ const now = Date.now();
110
+
111
+ for (const rule of rules) {
112
+ const cooldown = (rule.cooldownSec || 60) * 1000;
113
+ const last = lastTriggered.get(rule.name) || 0;
114
+ if (now - last < cooldown) continue;
115
+
116
+ let triggered = false;
117
+ let detail = '';
118
+ let targetPid: number | null = null;
119
+
120
+ if (rule.process) {
121
+ const match = data.processes.find(p => p.command.toLowerCase().includes(rule.process!.toLowerCase()));
122
+ if (match) {
123
+ const val = rule.metric === 'process_mem' ? match.mem : match.cpu;
124
+ if (val >= rule.threshold) {
125
+ triggered = true;
126
+ targetPid = match.pid;
127
+ detail = `Process "${match.command}" (PID ${match.pid}) at ${val.toFixed(1)}% (threshold ${rule.threshold}%)`;
128
+ }
129
+ }
130
+ } else if (rule.metric === 'cpu') {
131
+ if (data.cpu.usage >= rule.threshold) {
132
+ triggered = true;
133
+ detail = `System CPU usage at ${data.cpu.usage.toFixed(1)}% (threshold ${rule.threshold}%)`;
134
+ }
135
+ } else if (rule.metric === 'mem') {
136
+ if (data.memory.usagePercent >= rule.threshold) {
137
+ triggered = true;
138
+ detail = `System Memory at ${data.memory.usagePercent.toFixed(1)}% (threshold ${rule.threshold}%)`;
139
+ }
140
+ } else if (rule.metric === 'temp') {
141
+ const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die;
142
+ if (temp !== undefined && temp >= rule.threshold) {
143
+ triggered = true;
144
+ detail = `System Temperature at ${temp.toFixed(1)}°C (threshold ${rule.threshold}°C)`;
145
+ }
146
+ }
147
+
148
+ if (triggered) {
149
+ lastTriggered.set(rule.name, now);
150
+ const timeStr = new Date().toLocaleTimeString();
151
+ console.log(` [${chalk.dim(timeStr)}] ${chalk.red.bold('⚠ TRIGGERED:')} ${chalk.bold(rule.name)}: ${detail}`);
152
+
153
+ if (isDryRun) {
154
+ console.log(chalk.yellow(` [DRY RUN] Would execute action: ${rule.action}`));
155
+ continue;
156
+ }
157
+
158
+ if (rule.action === 'kill' && targetPid) {
159
+ try {
160
+ process.kill(targetPid, 'SIGTERM');
161
+ console.log(chalk.green(` ✔ Killed PID ${targetPid} with SIGTERM`));
162
+ } catch (err: any) {
163
+ console.log(chalk.red(` ✖ Failed to kill PID ${targetPid}: ${err.message}`));
164
+ }
165
+ }
166
+
167
+ if (rule.action === 'alert') {
168
+ await sendAlert({
169
+ metric: rule.name,
170
+ value: rule.threshold,
171
+ threshold: rule.threshold,
172
+ unit: '%',
173
+ host: data.header.hostname,
174
+ timestamp: new Date().toISOString(),
175
+ severity: 'critical',
176
+ }, alertChannels).catch(() => {});
177
+ console.log(chalk.green(' ✔ Multi-channel alert dispatched.'));
178
+ }
179
+ }
180
+ }
181
+ } catch {
182
+ // ignore loop error
183
+ }
184
+ };
185
+
186
+ await evaluate();
187
+ const handle = setInterval(evaluate, intervalSec * 1000);
188
+
189
+ // Keep alive until SIGINT
190
+ await new Promise<void>((resolve) => {
191
+ process.on('SIGINT', () => {
192
+ clearInterval(handle);
193
+ console.log(chalk.dim('\n Watchdog stopped.'));
194
+ resolve();
195
+ process.exit(0);
196
+ });
197
+ });
198
+ }
package/src/xbar.ts CHANGED
@@ -6,16 +6,15 @@ import chalk from 'chalk';
6
6
  export function generateXbarPlugin() {
7
7
  const pyrePath = process.argv[1];
8
8
  const nodePath = process.execPath;
9
-
9
+
10
10
  const script = `#!/bin/bash
11
11
  # <xbar.title>Pyre Monitor</xbar.title>
12
12
  # <xbar.version>v1.0</xbar.version>
13
13
  # <xbar.author>somalip</xbar.author>
14
14
  # <xbar.author.github>somalip</xbar.author.github>
15
- # <xbar.desc>Mac system monitoring powered by pyre.</xbar.desc>
15
+ # <xbar.desc>Cross-platform system monitoring powered by pyre.</xbar.desc>
16
16
 
17
17
  # Run pyre to get a JSON snapshot
18
- # We use node directly to avoid env issues in xbar
19
18
  DATA=$("${nodePath}" "${pyrePath}" --once --json)
20
19
 
21
20
  if [ -z "$DATA" ]; then
@@ -39,15 +38,27 @@ echo "---"
39
38
  echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' terminal=true"
40
39
  `;
41
40
 
42
- const pluginDir = path.join(os.homedir(), 'Library', 'Application Support', 'xbar', 'plugins');
43
41
  const filename = 'pyre-monitor.2s.sh';
44
42
  const outPath = path.join(process.cwd(), filename);
45
-
43
+
46
44
  fs.writeFileSync(outPath, script);
47
- fs.chmodSync(outPath, '755');
48
-
49
- console.log(chalk.green(`Generated xbar plugin script at ${outPath}`));
50
- console.log(`To install, move it to your xbar plugins directory:`);
51
- console.log(chalk.cyan(` mv ${filename} "${pluginDir}/"`));
52
- console.log(`Then refresh xbar.`);
45
+ try {
46
+ fs.chmodSync(outPath, 0o755);
47
+ } catch {
48
+ // ignore on non-POSIX filesystems
49
+ }
50
+
51
+ console.log(chalk.green(`Generated menu bar plugin script at ${outPath}`));
52
+
53
+ if (process.platform === 'darwin') {
54
+ const pluginDir = path.join(os.homedir(), 'Library', 'Application Support', 'xbar', 'plugins');
55
+ console.log(`To install in xbar or SwiftBar on macOS:`);
56
+ console.log(chalk.cyan(` mv ${filename} "${pluginDir}/"`));
57
+ } else if (process.platform === 'linux') {
58
+ const argosDir = path.join(os.homedir(), '.config', 'argos');
59
+ console.log(`To install in Argos (GNOME Shell) on Linux:`);
60
+ console.log(chalk.cyan(` mkdir -p "${argosDir}" && mv ${filename} "${argosDir}/"`));
61
+ } else {
62
+ console.log(chalk.dim(`Compatible with BitBar, xbar, and Argos shell plugins.`));
63
+ }
53
64
  }
@@ -1,74 +0,0 @@
1
- import {
2
- run
3
- } from "./chunk-FH5WDJBF.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
- };
@@ -1,15 +0,0 @@
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 = 3e3) {
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
- };