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/src/topoCmd.ts ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * pyre topo — Network Topology Mapper.
3
+ *
4
+ * Visualizes the local network topology, including default gateways,
5
+ * active routing paths, LAN neighbors (via ARP cache), and active remote sockets.
6
+ * Also scans LAN neighbors for running pyre instances (P2P port 9876, REST port 8080/3000).
7
+ */
8
+ import net from 'node:net';
9
+ import os from 'node:os';
10
+ import chalk from 'chalk';
11
+ import { run } from './monitors/run.js';
12
+ import { collectNetwork } from './monitors/collectors.js';
13
+
14
+ export interface TopoOptions {
15
+ scan?: boolean;
16
+ json?: boolean;
17
+ }
18
+
19
+ interface LanPeer {
20
+ ip: string;
21
+ mac: string;
22
+ interface: string;
23
+ isGateway: boolean;
24
+ pyreP2P?: boolean;
25
+ pyreRest?: boolean;
26
+ hostname?: string;
27
+ }
28
+
29
+ async function probePort(ip: string, port: number, timeoutMs = 400): Promise<boolean> {
30
+ return new Promise((resolve) => {
31
+ const socket = new net.Socket();
32
+ socket.setTimeout(timeoutMs);
33
+ socket.once('connect', () => {
34
+ socket.destroy();
35
+ resolve(true);
36
+ });
37
+ socket.once('timeout', () => {
38
+ socket.destroy();
39
+ resolve(false);
40
+ });
41
+ socket.once('error', () => {
42
+ socket.destroy();
43
+ resolve(false);
44
+ });
45
+ socket.connect(port, ip);
46
+ });
47
+ }
48
+
49
+ async function getDefaultGateway(): Promise<string> {
50
+ try {
51
+ const raw = await run("route -n get default 2>/dev/null | grep gateway | awk '{print $2}'", '');
52
+ if (raw.trim()) return raw.trim();
53
+ } catch {}
54
+ try {
55
+ const raw = await run("netstat -nr | grep default | awk '{print $2}' | head -n1", '');
56
+ if (raw.trim()) return raw.trim();
57
+ } catch {}
58
+ return '192.168.1.1';
59
+ }
60
+
61
+ async function getArpNeighbors(gatewayIp: string): Promise<LanPeer[]> {
62
+ const peers: LanPeer[] = [];
63
+ try {
64
+ const raw = await run('arp -a -n 2>/dev/null', '');
65
+ const lines = raw.split('\n');
66
+ for (const line of lines) {
67
+ // macOS format: "? (192.168.1.1) at 0:11:22:33:44:55 on en0 ifscope [ethernet]"
68
+ const match = line.match(/\(([\d.]+)\)\s+at\s+([0-9a-fA-F:]+)\s+on\s+(\S+)/);
69
+ if (match) {
70
+ const ip = match[1];
71
+ const mac = match[2];
72
+ const iface = match[3];
73
+ if (ip === '255.255.255.255' || ip.startsWith('224.') || mac.includes('incomplete')) continue;
74
+ peers.push({
75
+ ip,
76
+ mac,
77
+ interface: iface,
78
+ isGateway: ip === gatewayIp,
79
+ });
80
+ }
81
+ }
82
+ } catch {}
83
+ return peers;
84
+ }
85
+
86
+ export async function runTopoCommand(opts: TopoOptions = {}): Promise<void> {
87
+ const localNet = await collectNetwork();
88
+ const gatewayIp = await getDefaultGateway();
89
+ const lanPeers = await getArpNeighbors(gatewayIp);
90
+ const hostname = os.hostname();
91
+
92
+ // If scan is requested or small peer count, probe common pyre ports
93
+ if (opts.scan || lanPeers.length <= 15) {
94
+ await Promise.all(
95
+ lanPeers.map(async (peer) => {
96
+ const [hasP2P, hasRest] = await Promise.all([
97
+ probePort(peer.ip, 9876, 250),
98
+ probePort(peer.ip, 8080, 250),
99
+ ]);
100
+ peer.pyreP2P = hasP2P;
101
+ peer.pyreRest = hasRest;
102
+ })
103
+ );
104
+ }
105
+
106
+ const establishedConns = (localNet.topRemoteHosts || []).slice(0, 6);
107
+
108
+ if (opts.json) {
109
+ console.log(
110
+ JSON.stringify(
111
+ {
112
+ localNode: {
113
+ hostname,
114
+ ip: localNet.ip,
115
+ interface: localNet.interface,
116
+ },
117
+ gateway: gatewayIp,
118
+ lanPeers,
119
+ activeRemoteConnections: establishedConns,
120
+ },
121
+ null,
122
+ 2
123
+ )
124
+ );
125
+ return;
126
+ }
127
+
128
+ console.log(chalk.bold('\n 🔥 pyre topo — Network Topology Map\n'));
129
+
130
+ // Local Host Header Box
131
+ console.log(` ${chalk.bgCyan.black.bold(' LOCAL HOST ')} ${chalk.bold(hostname)}`);
132
+ console.log(` ├── IP Address: ${chalk.green.bold(localNet.ip || '127.0.0.1')} (${localNet.interface})`);
133
+ console.log(` ├── Active RX / TX: ↓ ${chalk.cyan(localNet.rxBytes ? `${(localNet.rxBytes / 1e6).toFixed(1)} MB` : '0 MB')} ↑ ${chalk.blue(localNet.txBytes ? `${(localNet.txBytes / 1e6).toFixed(1)} MB` : '0 MB')}`);
134
+ console.log(' │');
135
+
136
+ // Gateway Node
137
+ console.log(` ├── ${chalk.yellow.bold('DEFAULT GATEWAY / ROUTER')}`);
138
+ console.log(` │ └── IP: ${chalk.yellow(gatewayIp)} [Route → WAN / Internet]`);
139
+ console.log(' │');
140
+
141
+ // LAN Neighborhood
142
+ console.log(` ├── ${chalk.magenta.bold('LAN NEIGHBORS & PEERS')} (${lanPeers.length} discovered via ARP)`);
143
+ if (lanPeers.length === 0) {
144
+ console.log(` │ └── ${chalk.dim('No neighboring ARP entries currently cached.')}`);
145
+ } else {
146
+ for (let i = 0; i < lanPeers.length; i++) {
147
+ const peer = lanPeers[i];
148
+ const isLast = i === lanPeers.length - 1;
149
+ const prefix = isLast ? ' │ └──' : ' │ ├──';
150
+ const gwBadge = peer.isGateway ? chalk.yellow(' [Gateway]') : '';
151
+ let pyreBadge = '';
152
+ if (peer.pyreP2P) pyreBadge += chalk.hex('#ff5500').bold(' [🔥 pyre P2P :9876]');
153
+ if (peer.pyreRest) pyreBadge += chalk.hex('#00ffcc').bold(' [🔥 pyre REST :8080]');
154
+
155
+ console.log(`${prefix} ${peer.ip.padEnd(16)} ${chalk.dim(peer.mac)}${gwBadge}${pyreBadge}`);
156
+ }
157
+ }
158
+ console.log(' │');
159
+
160
+ // Active External Remote Sockets
161
+ console.log(` └── ${chalk.cyan.bold('TOP ACTIVE REMOTE CONNECTIONS')}`);
162
+ if (establishedConns.length === 0) {
163
+ console.log(` └── ${chalk.dim('No active established sockets detected.')}`);
164
+ } else {
165
+ for (let i = 0; i < establishedConns.length; i++) {
166
+ const conn = establishedConns[i];
167
+ const isLast = i === establishedConns.length - 1;
168
+ const prefix = isLast ? ' └──' : ' ├──';
169
+ const connsBadge = chalk.dim(`(${conn.connectionCount} socket${conn.connectionCount > 1 ? 's' : ''})`);
170
+ console.log(`${prefix} ${chalk.white(conn.host.padEnd(24))} ${chalk.green('ESTABLISHED')} ${connsBadge}`);
171
+ }
172
+ }
173
+
174
+ console.log();
175
+ console.log(chalk.dim(' Tip: Run "pyre p2p server" on any node to allow automatic live cluster streaming.\n'));
176
+ }
@@ -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
- };