pyre-cli 10.3.0 → 10.5.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 (88) hide show
  1. package/README.md +41 -0
  2. package/dist/{alertChannels-TPSQIJ7S.js → alertChannels-MNX4XH6J.js} +1 -0
  3. package/dist/{anomaliesCmd-25OFKYPK.js → anomaliesCmd-HHTY5LYL.js} +2 -0
  4. package/dist/benchGitCmd-HIXUC5EK.js +87 -0
  5. package/dist/{benchmarkCmd-A3ETK6VY.js → benchmarkCmd-B7DLP2VC.js} +2 -0
  6. package/dist/{blender-FGH3ASEQ.js → blender-6ZX5SPYC.js} +3 -1
  7. package/dist/{brewHealth-P2GW5Y5C.js → brewHealth-VFW33QB3.js} +1 -0
  8. package/dist/budgetCmd-TGO3RYLN.js +11 -0
  9. package/dist/{buildCmd-CDPZ2ZFA.js → buildCmd-E7KUCFYG.js} +3 -1
  10. package/dist/{checkCmd-WQAHAUWO.js → checkCmd-QLYTP77Y.js} +5 -3
  11. package/dist/chunk-3RG5ZIWI.js +10 -0
  12. package/dist/{chunk-VIACN2RU.js → chunk-FYCBGUTW.js} +41 -19
  13. package/dist/chunk-JH7P6KLN.js +143 -0
  14. package/dist/{chunk-QM5L43EK.js → chunk-LDJB5V4N.js} +5 -1
  15. package/dist/chunk-NMGQVRCA.js +111 -0
  16. package/dist/chunk-OCL7P7N7.js +936 -0
  17. package/dist/{chunk-23YWCI3O.js → chunk-QEH4LR6P.js} +28 -686
  18. package/dist/{chunk-KVUMOA6C.js → chunk-RKPGPVTO.js} +77 -4
  19. package/dist/chunk-UF3XQ3R3.js +3800 -0
  20. package/dist/{collectors-JDN5Y7EV.js → collectors-V7SCIIDH.js} +5 -3
  21. package/dist/{completions-ZVTNE52B.js → completions-FTPXDSR2.js} +44 -3
  22. package/dist/{config-J4YMADEF.js → config-5WW5M63G.js} +2 -1
  23. package/dist/containersCmd-FSMYOBG2.js +84 -0
  24. package/dist/{doctor-LHFW4UJP.js → doctor-O4DIAMHK.js} +3 -2
  25. package/dist/{explainCmd-MF6PIMLW.js → explainCmd-7PBPA7O5.js} +1 -0
  26. package/dist/{extensions-XJGHG7X3.js → extensions-QQEV7U3D.js} +1 -0
  27. package/dist/{firebaseSync-ON3RZ6KX.js → firebaseSync-LRN67UTV.js} +2 -0
  28. package/dist/flightRecorder-GJIAQOXC.js +162 -0
  29. package/dist/index.js +736 -4125
  30. package/dist/inspectCmd-5FXJBJTK.js +137 -0
  31. package/dist/menu-AJFGDLBP.js +1233 -0
  32. package/dist/monitors/platform/windows-ui.ps1 +31 -29
  33. package/dist/{netUsageCmd-67IBZZYN.js → netUsageCmd-JP6YKBPF.js} +7 -5
  34. package/dist/optimizeCmd-WZKLFYZU.js +1110 -0
  35. package/dist/{pipeCmd-2N4JPKYE.js → pipeCmd-CPK6FVKB.js} +5 -3
  36. package/dist/playbookCmd-FRROIEXR.js +145 -0
  37. package/dist/{profileProcCmd-N4H6MCBT.js → profileProcCmd-5Y3733E7.js} +64 -13
  38. package/dist/{prometheus-5BL5PVLC.js → prometheus-CYNUR3QT.js} +1 -0
  39. package/dist/{prometheusCmd-LEUM5WCZ.js → prometheusCmd-G2CSEZRQ.js} +5 -3
  40. package/dist/{replayCmd-Z3PYXY7V.js → replayCmd-ODB5SPOQ.js} +25 -6
  41. package/dist/{run-HOGQDDTA.js → run-UJQYT2TO.js} +1 -0
  42. package/dist/{serveCmd-L7LVJOZN.js → serveCmd-KDPWLVQI.js} +5 -3
  43. package/dist/{service-7GIX3YNC.js → service-LIYYR3UI.js} +1 -0
  44. package/dist/{smartCmd-ODWKSFNG.js → smartCmd-2BJJ37GV.js} +130 -5
  45. package/dist/statuslineCmd-EUKYRR2G.js +75 -0
  46. package/dist/{stressCmd-MFXMFXYX.js → stressCmd-GKHGY26X.js} +2 -0
  47. package/dist/{syncCmd-42IRF633.js → syncCmd-MIBFKX24.js} +16 -5
  48. package/dist/topoCmd-RTXQJ6XA.js +189 -0
  49. package/dist/{updateCheck-52X6LJG5.js → updateCheck-LJHEMG3G.js} +2 -0
  50. package/dist/{watchdogCmd-6GR4UN6B.js → watchdogCmd-XM6OPTKM.js} +41 -6
  51. package/dist/{wizard-JQMQWFAW.js → wizard-PRDOJROQ.js} +2 -1
  52. package/package.json +2 -2
  53. package/src/benchGitCmd.ts +110 -0
  54. package/src/budgetCmd.ts +199 -0
  55. package/src/completions.ts +42 -3
  56. package/src/containersCmd.ts +106 -0
  57. package/src/doctor.ts +86 -6
  58. package/src/easy_features.test.ts +83 -0
  59. package/src/flightRecorder.ts +214 -0
  60. package/src/index.ts +181 -8
  61. package/src/inspectCmd.ts +165 -0
  62. package/src/live/input.ts +70 -3
  63. package/src/live/render.ts +1 -1
  64. package/src/live/state.ts +2 -1
  65. package/src/menu/commands.ts +754 -0
  66. package/src/menu/index.ts +358 -0
  67. package/src/menu/render.ts +272 -0
  68. package/src/menu/types.ts +63 -0
  69. package/src/menu.test.ts +110 -0
  70. package/src/monitors/blender.ts +49 -19
  71. package/src/monitors/buildTracker.ts +72 -54
  72. package/src/monitors/platform/windows-ui.ps1 +31 -29
  73. package/src/monitors/platform/windows.ts +636 -280
  74. package/src/optimizeCmd.ts +1248 -0
  75. package/src/platform.test.ts +146 -0
  76. package/src/playbookCmd.ts +156 -0
  77. package/src/power_features.test.ts +251 -0
  78. package/src/profileProcCmd.ts +68 -21
  79. package/src/replayCmd.ts +15 -6
  80. package/src/smartCmd.ts +149 -10
  81. package/src/state/config.ts +13 -0
  82. package/src/statuslineCmd.ts +87 -0
  83. package/src/syncCmd.ts +12 -0
  84. package/src/topoCmd.ts +83 -50
  85. package/src/watchdogCmd.ts +40 -3
  86. package/src/xbar.ts +195 -17
  87. package/dist/chunk-M73HCQD2.js +0 -95
  88. package/dist/topoCmd-DZ3YS5IL.js +0 -148
package/src/topoCmd.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import net from 'node:net';
9
9
  import os from 'node:os';
10
10
  import chalk from 'chalk';
11
- import { run } from './monitors/run.js';
11
+ import { run, runPowerShell } from './monitors/run.js';
12
12
  import { collectNetwork } from './monitors/collectors.js';
13
13
 
14
14
  export interface TopoOptions {
@@ -16,7 +16,7 @@ export interface TopoOptions {
16
16
  json?: boolean;
17
17
  }
18
18
 
19
- interface LanPeer {
19
+ export interface LanPeer {
20
20
  ip: string;
21
21
  mac: string;
22
22
  interface: string;
@@ -46,21 +46,73 @@ async function probePort(ip: string, port: number, timeoutMs = 400): Promise<boo
46
46
  });
47
47
  }
48
48
 
49
- async function getDefaultGateway(): Promise<string> {
49
+ export async function getDefaultGateway(): Promise<string> {
50
+ if (process.platform === 'win32') {
51
+ try {
52
+ const raw = await runPowerShell(
53
+ `(Get-NetRoute -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty NextHop)`,
54
+ ''
55
+ );
56
+ if (raw.trim() && raw.trim() !== '0.0.0.0') return raw.trim();
57
+ } catch {}
58
+
59
+ try {
60
+ const rawCim = await runPowerShell(
61
+ `(Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty DefaultIPGateway) -join ''`,
62
+ ''
63
+ );
64
+ if (rawCim.trim()) return rawCim.trim();
65
+ } catch {}
66
+ }
67
+
50
68
  try {
51
69
  const raw = await run("route -n get default 2>/dev/null | grep gateway | awk '{print $2}'", '');
52
70
  if (raw.trim()) return raw.trim();
53
71
  } catch {}
54
72
  try {
55
- const raw = await run("netstat -nr | grep default | awk '{print $2}' | head -n1", '');
73
+ const raw = await run("netstat -nr 2>/dev/null | grep default | awk '{print $2}' | head -n1", '');
56
74
  if (raw.trim()) return raw.trim();
57
75
  } catch {}
58
76
  return '192.168.1.1';
59
77
  }
60
78
 
61
- async function getArpNeighbors(gatewayIp: string): Promise<LanPeer[]> {
79
+ export function parseWindowsArpOutput(raw: string, gatewayIp: string): LanPeer[] {
80
+ const peers: LanPeer[] = [];
81
+ let currentIface = 'Ethernet';
82
+
83
+ for (const line of raw.split('\n')) {
84
+ const trimmed = line.trim();
85
+ const ifaceMatch = trimmed.match(/Interface:\s*([\d.]+)/i);
86
+ if (ifaceMatch) {
87
+ currentIface = ifaceMatch[1];
88
+ continue;
89
+ }
90
+
91
+ const match = trimmed.match(/^([\d.]+)\s+([0-9a-fA-F-]+)\s+(\w+)/);
92
+ if (match) {
93
+ const ip = match[1];
94
+ const mac = match[2].replace(/-/g, ':');
95
+ if (ip === '255.255.255.255' || ip.startsWith('224.') || mac.includes('ff:ff:ff')) continue;
96
+ peers.push({
97
+ ip,
98
+ mac,
99
+ interface: currentIface,
100
+ isGateway: ip === gatewayIp,
101
+ });
102
+ }
103
+ }
104
+
105
+ return peers;
106
+ }
107
+
108
+ export async function getArpNeighbors(gatewayIp: string): Promise<LanPeer[]> {
62
109
  const peers: LanPeer[] = [];
63
110
  try {
111
+ if (process.platform === 'win32') {
112
+ const raw = await run('arp -a 2>nul', '');
113
+ return parseWindowsArpOutput(raw, gatewayIp);
114
+ }
115
+
64
116
  const raw = await run('arp -a -n 2>/dev/null', '');
65
117
  const lines = raw.split('\n');
66
118
  for (const line of lines) {
@@ -103,20 +155,17 @@ export async function runTopoCommand(opts: TopoOptions = {}): Promise<void> {
103
155
  );
104
156
  }
105
157
 
106
- const establishedConns = (localNet.topRemoteHosts || []).slice(0, 6);
107
-
108
158
  if (opts.json) {
109
159
  console.log(
110
160
  JSON.stringify(
111
161
  {
112
- localNode: {
162
+ local: {
113
163
  hostname,
114
164
  ip: localNet.ip,
115
165
  interface: localNet.interface,
166
+ gateway: gatewayIp,
116
167
  },
117
- gateway: gatewayIp,
118
168
  lanPeers,
119
- activeRemoteConnections: establishedConns,
120
169
  },
121
170
  null,
122
171
  2
@@ -125,52 +174,36 @@ export async function runTopoCommand(opts: TopoOptions = {}): Promise<void> {
125
174
  return;
126
175
  }
127
176
 
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(' │');
177
+ console.log(chalk.bold('\n 🗺 pyre topo — Network Topology Map\n'));
178
+ console.log(chalk.bold(' Local Node:'));
179
+ console.log(` Hostname: ${chalk.cyan(hostname)}`);
180
+ console.log(` IP: ${chalk.green(localNet.ip)}`);
181
+ console.log(` Interface: ${localNet.interface}`);
182
+ console.log(` Gateway: ${chalk.yellow(gatewayIp)}`);
183
+ console.log();
135
184
 
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(' │');
185
+ console.log(chalk.bold(` LAN Neighbors & Nodes (${lanPeers.length} discovered via ARP):`));
186
+ console.log(chalk.dim(' ' + '─'.repeat(74)));
140
187
 
141
- // LAN Neighborhood
142
- console.log(` ├── ${chalk.magenta.bold('LAN NEIGHBORS & PEERS')} (${lanPeers.length} discovered via ARP)`);
143
188
  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
- }
189
+ console.log(chalk.dim(' No active ARP neighbors found in cache.\n'));
190
+ return;
157
191
  }
158
- console.log(' │');
159
192
 
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
- }
193
+ for (const peer of lanPeers) {
194
+ const isGw = peer.isGateway ? chalk.yellow(' [Default Gateway]') : '';
195
+ const pyreBadge = peer.pyreP2P ? chalk.green.bold(' 🔥 PYRE PEER') : '';
196
+ const restBadge = peer.pyreRest ? chalk.blue(' (REST API)') : '';
197
+
198
+ console.log(` • ${chalk.bold(peer.ip.padEnd(16))} ${chalk.dim(peer.mac.padEnd(20))} [${peer.interface}]${isGw}${pyreBadge}${restBadge}`);
172
199
  }
173
200
 
174
201
  console.log();
175
- console.log(chalk.dim(' Tip: Run "pyre p2p server" on any node to allow automatic live cluster streaming.\n'));
202
+ const pyrePeers = lanPeers.filter(p => p.pyreP2P);
203
+ if (pyrePeers.length > 0) {
204
+ console.log(chalk.green(` ✔ Found ${pyrePeers.length} active Pyre node(s) on your LAN!`));
205
+ console.log(chalk.dim(` Connect using: pyre p2p connect --p2p-host ${pyrePeers[0].ip}\n`));
206
+ } else {
207
+ console.log(chalk.dim(' Tip: Pass --scan to sweep and test pyre ports on discovered LAN neighbors.\n'));
208
+ }
176
209
  }
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import fs from 'node:fs';
19
19
  import path from 'node:path';
20
+ import { execSync } from 'node:child_process';
20
21
  import chalk from 'chalk';
21
22
  import { collectAll } from './monitors/collectors.js';
22
23
  import { sendAlert } from './alertChannels.js';
@@ -28,7 +29,9 @@ export interface WatchdogRule {
28
29
  process?: string;
29
30
  threshold: number;
30
31
  durationSec?: number;
31
- action: 'kill' | 'notify' | 'alert';
32
+ action: 'kill' | 'notify' | 'alert' | 'renice' | 'command';
33
+ command?: string;
34
+ dumpFlight?: boolean;
32
35
  cooldownSec?: number;
33
36
  }
34
37
 
@@ -157,13 +160,47 @@ export async function runWatchdogCommand(opts: WatchdogOptions = {}): Promise<vo
157
160
 
158
161
  if (rule.action === 'kill' && targetPid) {
159
162
  try {
160
- process.kill(targetPid, 'SIGTERM');
161
- console.log(chalk.green(` ✔ Killed PID ${targetPid} with SIGTERM`));
163
+ if (process.platform === 'win32') {
164
+ execSync(`taskkill /F /PID ${targetPid} /T`, { stdio: 'pipe' });
165
+ console.log(chalk.green(` ✔ Killed PID ${targetPid} with taskkill /F /T`));
166
+ } else {
167
+ process.kill(targetPid, 'SIGTERM');
168
+ console.log(chalk.green(` ✔ Killed PID ${targetPid} with SIGTERM`));
169
+ }
162
170
  } catch (err: any) {
163
171
  console.log(chalk.red(` ✖ Failed to kill PID ${targetPid}: ${err.message}`));
164
172
  }
165
173
  }
166
174
 
175
+ if (rule.action === 'renice' && targetPid) {
176
+ try {
177
+ execSync(`renice +10 -p ${targetPid}`, { stdio: 'pipe' });
178
+ console.log(chalk.green(` ✔ Deprioritized runaway PID ${targetPid} to nice +10`));
179
+ } catch (err: any) {
180
+ console.log(chalk.red(` ✖ Failed to renice PID ${targetPid}: ${err.message}`));
181
+ }
182
+ }
183
+
184
+ if (rule.action === 'command' && rule.command) {
185
+ try {
186
+ console.log(chalk.cyan(` ▶ Executing remediation command: ${rule.command}`));
187
+ execSync(rule.command, { stdio: 'pipe' });
188
+ console.log(chalk.green(` ✔ Remediation command executed successfully.`));
189
+ } catch (err: any) {
190
+ console.log(chalk.red(` ✖ Remediation command failed: ${err.message}`));
191
+ }
192
+ }
193
+
194
+ if (rule.dumpFlight) {
195
+ try {
196
+ const { captureCurrentFlightFrame, globalFlightRecorder } = await import('./flightRecorder.js');
197
+ const frame = await captureCurrentFlightFrame();
198
+ globalFlightRecorder.push(frame);
199
+ const path = globalFlightRecorder.dump();
200
+ console.log(chalk.green(` ✔ Forensic flight recording dumped: ${path}`));
201
+ } catch {}
202
+ }
203
+
167
204
  if (rule.action === 'alert') {
168
205
  await sendAlert({
169
206
  metric: rule.name,
package/src/xbar.ts CHANGED
@@ -1,9 +1,55 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
+ import readline from 'node:readline';
5
+ import { execSync } from 'node:child_process';
4
6
  import chalk from 'chalk';
5
7
 
6
- export function generateXbarPlugin() {
8
+ export interface XbarOptions {
9
+ install?: boolean;
10
+ yes?: boolean;
11
+ dir?: string;
12
+ }
13
+
14
+ export function isSwiftBarInstalled(): boolean {
15
+ if (process.platform !== 'darwin') return false;
16
+ return (
17
+ fs.existsSync('/Applications/SwiftBar.app') ||
18
+ fs.existsSync(path.join(os.homedir(), 'Applications', 'SwiftBar.app'))
19
+ );
20
+ }
21
+
22
+ export function isXbarInstalled(): boolean {
23
+ if (process.platform !== 'darwin') return false;
24
+ return (
25
+ fs.existsSync('/Applications/xbar.app') ||
26
+ fs.existsSync(path.join(os.homedir(), 'Applications', 'xbar.app'))
27
+ );
28
+ }
29
+
30
+ export function isBrewAvailable(): boolean {
31
+ try {
32
+ execSync('brew --version', { stdio: 'ignore' });
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ async function confirmPrompt(question: string, yes?: boolean): Promise<boolean> {
40
+ if (yes) return true;
41
+ if (!process.stdin.isTTY) return false;
42
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
43
+ return new Promise((resolve) => {
44
+ rl.question(question, (ans) => {
45
+ rl.close();
46
+ const a = ans.trim().toLowerCase();
47
+ resolve(a === 'y' || a === 'yes');
48
+ });
49
+ });
50
+ }
51
+
52
+ export async function generateXbarPlugin(opts: XbarOptions = {}): Promise<string> {
7
53
  const pyrePath = process.argv[1];
8
54
  const nodePath = process.execPath;
9
55
 
@@ -39,26 +85,158 @@ echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' ter
39
85
  `;
40
86
 
41
87
  const filename = 'pyre-monitor.2s.sh';
42
- const outPath = path.join(process.cwd(), filename);
43
88
 
44
- fs.writeFileSync(outPath, script);
45
- try {
46
- fs.chmodSync(outPath, 0o755);
47
- } catch {
48
- // ignore on non-POSIX filesystems
89
+ // 1. Explicit target directory passed by caller
90
+ if (opts.dir) {
91
+ const targetDir = path.resolve(opts.dir);
92
+ if (!fs.existsSync(targetDir)) {
93
+ fs.mkdirSync(targetDir, { recursive: true });
94
+ }
95
+ const destFile = path.join(targetDir, filename);
96
+ fs.writeFileSync(destFile, script);
97
+ try {
98
+ fs.chmodSync(destFile, 0o755);
99
+ } catch {}
100
+ console.log(chalk.green(`\n ✔ Installed menu bar plugin script directly to:`));
101
+ console.log(chalk.bold.cyan(` ${destFile}\n`));
102
+ return destFile;
49
103
  }
50
104
 
51
- console.log(chalk.green(`Generated menu bar plugin script at ${outPath}`));
52
-
105
+ // 2. macOS handling with SwiftBar auto-installation
53
106
  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') {
107
+ let hasSwiftBar = isSwiftBarInstalled();
108
+ const hasXbar = isXbarInstalled();
109
+
110
+ if (!hasSwiftBar && !hasXbar) {
111
+ if (isBrewAvailable()) {
112
+ console.log(chalk.bold.yellow(`\n 💡 SwiftBar is required to display Pyre live stats in your macOS menu bar.`));
113
+ const proceed = await confirmPrompt(
114
+ chalk.cyan(` Would you like Pyre to install SwiftBar via Homebrew? [y/N]: `),
115
+ opts.yes
116
+ );
117
+
118
+ if (proceed) {
119
+ console.log(chalk.cyan(`\n ▶ Installing SwiftBar via Homebrew (brew install --cask swiftbar)...`));
120
+ try {
121
+ execSync('brew install --cask swiftbar', { stdio: 'inherit' });
122
+ hasSwiftBar = isSwiftBarInstalled();
123
+ if (hasSwiftBar) {
124
+ console.log(chalk.green(` ✔ SwiftBar installed successfully!`));
125
+ }
126
+ } catch (err: any) {
127
+ console.error(chalk.red(` ✖ Failed to install SwiftBar: ${err.message}`));
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // If SwiftBar is available (either pre-existing or freshly installed)
134
+ if (hasSwiftBar) {
135
+ const swiftBarDir = path.join(os.homedir(), 'Library', 'Application Support', 'SwiftBar', 'plugins');
136
+ if (!fs.existsSync(swiftBarDir)) {
137
+ fs.mkdirSync(swiftBarDir, { recursive: true });
138
+ }
139
+ const destFile = path.join(swiftBarDir, filename);
140
+ fs.writeFileSync(destFile, script);
141
+ try {
142
+ fs.chmodSync(destFile, 0o755);
143
+ } catch {}
144
+
145
+ // Configure default SwiftBar plugin directory so it doesn't show an initial folder picker modal
146
+ try {
147
+ execSync(`defaults write com.ameba.SwiftBar PluginDirectory "${swiftBarDir}"`, { stdio: 'ignore' });
148
+ } catch {}
149
+
150
+ // Launch SwiftBar if not currently running
151
+ let isRunning = false;
152
+ try {
153
+ execSync('pgrep -il swiftbar', { stdio: 'ignore' });
154
+ isRunning = true;
155
+ } catch {}
156
+
157
+ if (!isRunning) {
158
+ try {
159
+ execSync('open -a SwiftBar', { stdio: 'ignore' });
160
+ } catch {}
161
+ }
162
+
163
+ console.log(chalk.bold.green(`\n 🔥 Pyre is now active in your macOS menu bar!`));
164
+ console.log(chalk.dim(` Look at the top right of your screen for live CPU, RAM, and temperature.`));
165
+ console.log(chalk.dim(` Plugin location: ${destFile}\n`));
166
+ return destFile;
167
+ }
168
+
169
+ // If xbar is available
170
+ if (hasXbar) {
171
+ const xbarDir = path.join(os.homedir(), 'Library', 'Application Support', 'xbar', 'plugins');
172
+ if (!fs.existsSync(xbarDir)) {
173
+ fs.mkdirSync(xbarDir, { recursive: true });
174
+ }
175
+ const destFile = path.join(xbarDir, filename);
176
+ fs.writeFileSync(destFile, script);
177
+ try {
178
+ fs.chmodSync(destFile, 0o755);
179
+ } catch {}
180
+
181
+ try {
182
+ execSync('open -a xbar', { stdio: 'ignore' });
183
+ } catch {}
184
+
185
+ console.log(chalk.bold.green(`\n 🔥 Pyre is now active in your macOS menu bar via xbar!`));
186
+ console.log(chalk.dim(` Plugin location: ${destFile}\n`));
187
+ return destFile;
188
+ }
189
+
190
+ // Neither app is installed and user declined or brew wasn't available
191
+ const outPath = path.join(process.cwd(), filename);
192
+ fs.writeFileSync(outPath, script);
193
+ try {
194
+ fs.chmodSync(outPath, 0o755);
195
+ } catch {}
196
+
197
+ const swiftBarDir = path.join(os.homedir(), 'Library', 'Application Support', 'SwiftBar', 'plugins');
198
+ console.log(chalk.green(`\n ✔ Generated menu bar plugin script at ${outPath}\n`));
199
+ console.log(chalk.bold(` To view this in your menu bar, install SwiftBar or xbar:`));
200
+ console.log(chalk.cyan(` brew install --cask swiftbar`));
201
+ console.log(chalk.dim(` mkdir -p "${swiftBarDir}" && mv "${filename}" "${swiftBarDir}/"`));
202
+ console.log(chalk.dim(` open -a SwiftBar\n`));
203
+ return outPath;
204
+ }
205
+
206
+ // 3. Linux / Argos handling
207
+ if (process.platform === 'linux') {
58
208
  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.`));
209
+ if (opts.install) {
210
+ if (!fs.existsSync(argosDir)) {
211
+ fs.mkdirSync(argosDir, { recursive: true });
212
+ }
213
+ const destFile = path.join(argosDir, filename);
214
+ fs.writeFileSync(destFile, script);
215
+ try {
216
+ fs.chmodSync(destFile, 0o755);
217
+ } catch {}
218
+ console.log(chalk.green(`\n ✔ Installed menu bar plugin script to ${destFile}\n`));
219
+ return destFile;
220
+ }
221
+
222
+ const outPath = path.join(process.cwd(), filename);
223
+ fs.writeFileSync(outPath, script);
224
+ try {
225
+ fs.chmodSync(outPath, 0o755);
226
+ } catch {}
227
+ console.log(chalk.green(`\n ✔ Generated menu bar plugin script at ${outPath}\n`));
228
+ console.log(` To install in Argos (GNOME Shell) on Linux:`);
229
+ console.log(chalk.cyan(` mkdir -p "${argosDir}" && mv "${filename}" "${argosDir}/"\n`));
230
+ return outPath;
63
231
  }
232
+
233
+ // Fallback for Windows / other
234
+ const outPath = path.join(process.cwd(), filename);
235
+ fs.writeFileSync(outPath, script);
236
+ try {
237
+ fs.chmodSync(outPath, 0o755);
238
+ } catch {}
239
+ console.log(chalk.green(`\n ✔ Generated menu bar plugin script at ${outPath}\n`));
240
+ console.log(chalk.dim(` Compatible with BitBar, xbar, and Argos shell plugins.\n`));
241
+ return outPath;
64
242
  }
@@ -1,95 +0,0 @@
1
- import {
2
- run
3
- } from "./chunk-MZO5QKUR.js";
4
-
5
- // src/monitors/buildTracker.ts
6
- async function collectBuilds() {
7
- try {
8
- const raw = await run("ps -eo pid,ppid,pcpu,pmem,etime,command -r", "");
9
- if (!raw) return [];
10
- const lines = raw.split("\n").slice(1);
11
- const builds = /* @__PURE__ */ new Map();
12
- const buildToolPatterns = [
13
- { name: "xcodebuild", match: /xcodebuild/i },
14
- { name: "cargo", match: /\bcargo\s+(build|check|test|run)\b/i },
15
- { name: "swift", match: /\bswift\s+(build|test)\b/i },
16
- { name: "make", match: /\bmake\b/i },
17
- { name: "ninja", match: /\bninja\b/i },
18
- { name: "bazel", match: /\bbazel\b/i },
19
- { name: "go", match: /\bgo\s+(build|test|run)\b/i }
20
- ];
21
- const compilerMatch = /(clang|swift-frontend|rustc|go tool compile|gcc|g\+\+|ghc)/i;
22
- for (const line of lines) {
23
- if (!line.trim()) continue;
24
- const parts = line.trim().split(/\s+/);
25
- if (parts.length < 6) continue;
26
- const pid = parseInt(parts[0], 10);
27
- const cpu = parseFloat(parts[2]) || 0;
28
- const mem = parseFloat(parts[3]) || 0;
29
- const etimeStr = parts[4];
30
- const cmd = parts.slice(5).join(" ");
31
- for (const pattern of buildToolPatterns) {
32
- if (pattern.match.test(cmd)) {
33
- let elapsedSec = 0;
34
- const etimeParts = etimeStr.split(":").map(Number);
35
- if (etimeParts.length === 2) elapsedSec = etimeParts[0] * 60 + etimeParts[1];
36
- else if (etimeParts.length === 3) elapsedSec = etimeParts[0] * 3600 + etimeParts[1] * 60 + etimeParts[2];
37
- builds.set(pattern.name, {
38
- tool: pattern.name,
39
- rootPid: pid,
40
- command: cmd.slice(0, 80),
41
- compilerProcesses: 1,
42
- totalCpu: cpu,
43
- totalMem: mem,
44
- elapsedSec,
45
- target: cmd.split(" ").slice(1, 4).join(" ")
46
- });
47
- break;
48
- }
49
- }
50
- }
51
- for (const line of lines) {
52
- if (!line.trim()) continue;
53
- const parts = line.trim().split(/\s+/);
54
- if (parts.length < 6) continue;
55
- const cpu = parseFloat(parts[2]) || 0;
56
- const mem = parseFloat(parts[3]) || 0;
57
- const cmd = parts.slice(5).join(" ");
58
- if (compilerMatch.test(cmd)) {
59
- if (builds.size > 0) {
60
- const firstBuild = builds.values().next().value;
61
- if (firstBuild) {
62
- firstBuild.compilerProcesses += 1;
63
- firstBuild.totalCpu += cpu;
64
- firstBuild.totalMem += mem;
65
- }
66
- } else {
67
- const key = "compiler";
68
- const existing = builds.get(key);
69
- if (existing) {
70
- existing.compilerProcesses += 1;
71
- existing.totalCpu += cpu;
72
- existing.totalMem += mem;
73
- } else {
74
- builds.set(key, {
75
- tool: "compiler",
76
- rootPid: parseInt(parts[0], 10),
77
- command: cmd.slice(0, 80),
78
- compilerProcesses: 1,
79
- totalCpu: cpu,
80
- totalMem: mem,
81
- elapsedSec: 0
82
- });
83
- }
84
- }
85
- }
86
- }
87
- return Array.from(builds.values());
88
- } catch {
89
- return [];
90
- }
91
- }
92
-
93
- export {
94
- collectBuilds
95
- };