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.
- package/README.md +41 -0
- package/dist/{alertChannels-TPSQIJ7S.js → alertChannels-MNX4XH6J.js} +1 -0
- package/dist/{anomaliesCmd-25OFKYPK.js → anomaliesCmd-HHTY5LYL.js} +2 -0
- package/dist/benchGitCmd-HIXUC5EK.js +87 -0
- package/dist/{benchmarkCmd-A3ETK6VY.js → benchmarkCmd-B7DLP2VC.js} +2 -0
- package/dist/{blender-FGH3ASEQ.js → blender-6ZX5SPYC.js} +3 -1
- package/dist/{brewHealth-P2GW5Y5C.js → brewHealth-VFW33QB3.js} +1 -0
- package/dist/budgetCmd-TGO3RYLN.js +11 -0
- package/dist/{buildCmd-CDPZ2ZFA.js → buildCmd-E7KUCFYG.js} +3 -1
- package/dist/{checkCmd-WQAHAUWO.js → checkCmd-QLYTP77Y.js} +5 -3
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/{chunk-VIACN2RU.js → chunk-FYCBGUTW.js} +41 -19
- package/dist/chunk-JH7P6KLN.js +143 -0
- package/dist/{chunk-QM5L43EK.js → chunk-LDJB5V4N.js} +5 -1
- package/dist/chunk-NMGQVRCA.js +111 -0
- package/dist/chunk-OCL7P7N7.js +936 -0
- package/dist/{chunk-23YWCI3O.js → chunk-QEH4LR6P.js} +28 -686
- package/dist/{chunk-KVUMOA6C.js → chunk-RKPGPVTO.js} +77 -4
- package/dist/chunk-UF3XQ3R3.js +3800 -0
- package/dist/{collectors-JDN5Y7EV.js → collectors-V7SCIIDH.js} +5 -3
- package/dist/{completions-ZVTNE52B.js → completions-FTPXDSR2.js} +44 -3
- package/dist/{config-J4YMADEF.js → config-5WW5M63G.js} +2 -1
- package/dist/containersCmd-FSMYOBG2.js +84 -0
- package/dist/{doctor-LHFW4UJP.js → doctor-O4DIAMHK.js} +3 -2
- package/dist/{explainCmd-MF6PIMLW.js → explainCmd-7PBPA7O5.js} +1 -0
- package/dist/{extensions-XJGHG7X3.js → extensions-QQEV7U3D.js} +1 -0
- package/dist/{firebaseSync-ON3RZ6KX.js → firebaseSync-LRN67UTV.js} +2 -0
- package/dist/flightRecorder-GJIAQOXC.js +162 -0
- package/dist/index.js +736 -4125
- package/dist/inspectCmd-5FXJBJTK.js +137 -0
- package/dist/menu-AJFGDLBP.js +1233 -0
- package/dist/monitors/platform/windows-ui.ps1 +31 -29
- package/dist/{netUsageCmd-67IBZZYN.js → netUsageCmd-JP6YKBPF.js} +7 -5
- package/dist/optimizeCmd-WZKLFYZU.js +1110 -0
- package/dist/{pipeCmd-2N4JPKYE.js → pipeCmd-CPK6FVKB.js} +5 -3
- package/dist/playbookCmd-FRROIEXR.js +145 -0
- package/dist/{profileProcCmd-N4H6MCBT.js → profileProcCmd-5Y3733E7.js} +64 -13
- package/dist/{prometheus-5BL5PVLC.js → prometheus-CYNUR3QT.js} +1 -0
- package/dist/{prometheusCmd-LEUM5WCZ.js → prometheusCmd-G2CSEZRQ.js} +5 -3
- package/dist/{replayCmd-Z3PYXY7V.js → replayCmd-ODB5SPOQ.js} +25 -6
- package/dist/{run-HOGQDDTA.js → run-UJQYT2TO.js} +1 -0
- package/dist/{serveCmd-L7LVJOZN.js → serveCmd-KDPWLVQI.js} +5 -3
- package/dist/{service-7GIX3YNC.js → service-LIYYR3UI.js} +1 -0
- package/dist/{smartCmd-ODWKSFNG.js → smartCmd-2BJJ37GV.js} +130 -5
- package/dist/statuslineCmd-EUKYRR2G.js +75 -0
- package/dist/{stressCmd-MFXMFXYX.js → stressCmd-GKHGY26X.js} +2 -0
- package/dist/{syncCmd-42IRF633.js → syncCmd-MIBFKX24.js} +16 -5
- package/dist/topoCmd-RTXQJ6XA.js +189 -0
- package/dist/{updateCheck-52X6LJG5.js → updateCheck-LJHEMG3G.js} +2 -0
- package/dist/{watchdogCmd-6GR4UN6B.js → watchdogCmd-XM6OPTKM.js} +41 -6
- package/dist/{wizard-JQMQWFAW.js → wizard-PRDOJROQ.js} +2 -1
- package/package.json +2 -2
- package/src/benchGitCmd.ts +110 -0
- package/src/budgetCmd.ts +199 -0
- package/src/completions.ts +42 -3
- package/src/containersCmd.ts +106 -0
- package/src/doctor.ts +86 -6
- package/src/easy_features.test.ts +83 -0
- package/src/flightRecorder.ts +214 -0
- package/src/index.ts +181 -8
- package/src/inspectCmd.ts +165 -0
- package/src/live/input.ts +70 -3
- package/src/live/render.ts +1 -1
- package/src/live/state.ts +2 -1
- package/src/menu/commands.ts +754 -0
- package/src/menu/index.ts +358 -0
- package/src/menu/render.ts +272 -0
- package/src/menu/types.ts +63 -0
- package/src/menu.test.ts +110 -0
- package/src/monitors/blender.ts +49 -19
- package/src/monitors/buildTracker.ts +72 -54
- package/src/monitors/platform/windows-ui.ps1 +31 -29
- package/src/monitors/platform/windows.ts +636 -280
- package/src/optimizeCmd.ts +1248 -0
- package/src/platform.test.ts +146 -0
- package/src/playbookCmd.ts +156 -0
- package/src/power_features.test.ts +251 -0
- package/src/profileProcCmd.ts +68 -21
- package/src/replayCmd.ts +15 -6
- package/src/smartCmd.ts +149 -10
- package/src/state/config.ts +13 -0
- package/src/statuslineCmd.ts +87 -0
- package/src/syncCmd.ts +12 -0
- package/src/topoCmd.ts +83 -50
- package/src/watchdogCmd.ts +40 -3
- package/src/xbar.ts +195 -17
- package/dist/chunk-M73HCQD2.js +0 -95
- 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
|
-
|
|
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
|
-
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.log(`
|
|
132
|
-
console.log(`
|
|
133
|
-
console.log(`
|
|
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
|
-
|
|
137
|
-
console.log(
|
|
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(
|
|
145
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/watchdogCmd.ts
CHANGED
|
@@ -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.
|
|
161
|
-
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
105
|
+
// 2. macOS handling with SwiftBar auto-installation
|
|
53
106
|
if (process.platform === 'darwin') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
}
|
package/dist/chunk-M73HCQD2.js
DELETED
|
@@ -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
|
-
};
|