pyre-cli 9.3.0 → 9.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 +2 -0
- package/dist/benchmarkCmd-A3ETK6VY.js +58 -0
- package/dist/checkCmd-ULNENOPZ.js +74 -0
- package/dist/chunk-MOAEGMFP.js +1042 -0
- package/dist/{chunk-QOLZ7T32.js → chunk-PV4MXTAJ.js} +46 -3
- package/dist/chunk-SG26Y3WF.js +0 -0
- package/dist/{collectors-VACLPHRP.js → collectors-VYUH3UWY.js} +1 -1
- package/dist/index.js +349 -1271
- package/dist/pipeCmd-BWNMUV2X.js +39 -0
- package/dist/prometheus-LFYHF6VO.js +56 -0
- package/dist/service-WTS3NBQU.js +73 -0
- package/dist/stressCmd-MFXMFXYX.js +51 -0
- package/dist/wizard-GVIAHIVV.js +55 -0
- package/package.json +1 -1
- package/src/benchmarkCmd.ts +69 -0
- package/src/checkCmd.ts +83 -0
- package/src/fleet.ts +17 -16
- package/src/formatters/render.ts +46 -9
- package/src/formatters/types.ts +2 -0
- package/src/index.ts +84 -7
- package/src/monitors/collectors.ts +50 -3
- package/src/monitors/types.ts +10 -0
- package/src/pipeCmd.ts +42 -0
- package/src/prometheus.ts +74 -0
- package/src/service.ts +77 -0
- package/src/stressCmd.ts +59 -0
- package/src/wizard.ts +61 -0
package/dist/index.js
CHANGED
|
@@ -1,1024 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
collectAll
|
|
4
|
-
} from "./chunk-QOLZ7T32.js";
|
|
5
2
|
import {
|
|
6
3
|
CONFIG_FILE,
|
|
7
4
|
readConfig,
|
|
8
5
|
writeConfig
|
|
9
6
|
} from "./chunk-RVGKEJXT.js";
|
|
7
|
+
import {
|
|
8
|
+
TAB_BAR_ROW,
|
|
9
|
+
THEMES,
|
|
10
|
+
celsiusToFahrenheit,
|
|
11
|
+
fitVisible,
|
|
12
|
+
formatBytes,
|
|
13
|
+
formatTable,
|
|
14
|
+
getTabHitboxes,
|
|
15
|
+
panel,
|
|
16
|
+
sparkline,
|
|
17
|
+
visLen
|
|
18
|
+
} from "./chunk-MOAEGMFP.js";
|
|
19
|
+
import "./chunk-SG26Y3WF.js";
|
|
20
|
+
import {
|
|
21
|
+
collectAll
|
|
22
|
+
} from "./chunk-PV4MXTAJ.js";
|
|
10
23
|
import "./chunk-FH5WDJBF.js";
|
|
11
24
|
|
|
12
25
|
// src/index.ts
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
26
|
+
import fs8 from "fs";
|
|
27
|
+
import os2 from "os";
|
|
28
|
+
import path5 from "path";
|
|
16
29
|
import readline2 from "readline";
|
|
17
30
|
import { Command } from "commander";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
// src/formatters/render.ts
|
|
21
|
-
import chalk2 from "chalk";
|
|
22
|
-
|
|
23
|
-
// src/sparkline.ts
|
|
24
|
-
var BLOCKS = " \u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
25
|
-
function sparkline(values, opts2 = {}) {
|
|
26
|
-
if (!values.length) return "";
|
|
27
|
-
if (opts2.plainText) {
|
|
28
|
-
const first = values[0];
|
|
29
|
-
const last = values[values.length - 1];
|
|
30
|
-
const diff = last - first;
|
|
31
|
-
const sign = diff >= 0 ? "+" : "";
|
|
32
|
-
return `[${first.toFixed(1)} -> ${last.toFixed(1)} (\u0394:${sign}${diff.toFixed(1)})]`;
|
|
33
|
-
}
|
|
34
|
-
let min = opts2.min ?? Infinity;
|
|
35
|
-
let max = opts2.max ?? -Infinity;
|
|
36
|
-
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
37
|
-
for (const v of values) {
|
|
38
|
-
if (v < min) min = v;
|
|
39
|
-
if (v > max) max = v;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const range = max - min || 1;
|
|
43
|
-
return values.map((v) => {
|
|
44
|
-
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
45
|
-
const clamped = Math.min(BLOCKS.length - 1, Math.max(0, idx));
|
|
46
|
-
return BLOCKS[clamped];
|
|
47
|
-
}).join("");
|
|
48
|
-
}
|
|
49
|
-
function multiRowBrailleGraph(values, height, opts2 = {}) {
|
|
50
|
-
if (!values.length || height <= 0) return Array(height).fill("");
|
|
51
|
-
let min = opts2.min ?? Infinity;
|
|
52
|
-
let max = opts2.max ?? -Infinity;
|
|
53
|
-
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
54
|
-
for (const v of values) {
|
|
55
|
-
if (v < min) min = v;
|
|
56
|
-
if (v > max) max = v;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const range = max - min || 1;
|
|
60
|
-
const levels = height * 4;
|
|
61
|
-
const dotsCol0 = [64, 4, 2, 1];
|
|
62
|
-
const dotsCol1 = [128, 32, 16, 8];
|
|
63
|
-
const rows = Array.from({ length: height }, () => []);
|
|
64
|
-
for (let i = 0; i < values.length; i += 2) {
|
|
65
|
-
const v0 = values[i];
|
|
66
|
-
const v1 = i + 1 < values.length ? values[i + 1] : v0;
|
|
67
|
-
const rawSteps0 = Math.round((v0 - min) / range * levels);
|
|
68
|
-
const totalSteps0 = Math.min(levels, Math.max(0, rawSteps0));
|
|
69
|
-
const rawSteps1 = Math.round((v1 - min) / range * levels);
|
|
70
|
-
const totalSteps1 = Math.min(levels, Math.max(0, rawSteps1));
|
|
71
|
-
for (let r = 0; r < height; r++) {
|
|
72
|
-
const rowFromBottom = height - 1 - r;
|
|
73
|
-
const minForThisRow = rowFromBottom * 4;
|
|
74
|
-
const stepsInThisRow0 = Math.min(4, Math.max(0, totalSteps0 - minForThisRow));
|
|
75
|
-
const stepsInThisRow1 = Math.min(4, Math.max(0, totalSteps1 - minForThisRow));
|
|
76
|
-
let code = 10240;
|
|
77
|
-
for (let d = 0; d < stepsInThisRow0; d++) code |= dotsCol0[d];
|
|
78
|
-
for (let d = 0; d < stepsInThisRow1; d++) code |= dotsCol1[d];
|
|
79
|
-
rows[r].push(String.fromCharCode(code));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return rows.map((r) => r.join(""));
|
|
83
|
-
}
|
|
84
|
-
function brailleGraph(values, opts2 = {}) {
|
|
85
|
-
if (!values.length) return "";
|
|
86
|
-
let min = opts2.min ?? Infinity;
|
|
87
|
-
let max = opts2.max ?? -Infinity;
|
|
88
|
-
if (opts2.min === void 0 || opts2.max === void 0) {
|
|
89
|
-
for (const v of values) {
|
|
90
|
-
if (v < min) min = v;
|
|
91
|
-
if (v > max) max = v;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const range = max - min || 1;
|
|
95
|
-
const dotsCol0 = [64, 4, 2, 1];
|
|
96
|
-
const dotsCol1 = [128, 32, 16, 8];
|
|
97
|
-
let result = "";
|
|
98
|
-
for (let i = 0; i < values.length; i += 2) {
|
|
99
|
-
const v0 = values[i];
|
|
100
|
-
const v1 = i + 1 < values.length ? values[i + 1] : v0;
|
|
101
|
-
const fill0 = Math.min(4, Math.max(0, Math.round((v0 - min) / range * 4)));
|
|
102
|
-
const fill1 = Math.min(4, Math.max(0, Math.round((v1 - min) / range * 4)));
|
|
103
|
-
let code = 10240;
|
|
104
|
-
for (let d = 0; d < fill0; d++) code |= dotsCol0[d];
|
|
105
|
-
for (let d = 0; d < fill1; d++) code |= dotsCol1[d];
|
|
106
|
-
result += String.fromCharCode(code);
|
|
107
|
-
}
|
|
108
|
-
return result;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// src/formatters/themes.ts
|
|
112
|
-
import chalk from "chalk";
|
|
113
|
-
import fs from "fs";
|
|
114
|
-
import os from "os";
|
|
115
|
-
import path from "path";
|
|
116
|
-
var BUILTIN_THEMES = {
|
|
117
|
-
default: {
|
|
118
|
-
border: chalk.hex("#4b5563"),
|
|
119
|
-
cpu: chalk.hex("#22d3ee"),
|
|
120
|
-
mem: chalk.hex("#a78bfa"),
|
|
121
|
-
gpu: chalk.hex("#f472b6"),
|
|
122
|
-
power: chalk.hex("#fbbf24"),
|
|
123
|
-
battery: chalk.hex("#34d399"),
|
|
124
|
-
thermal: chalk.hex("#fb923c"),
|
|
125
|
-
network: chalk.hex("#60a5fa"),
|
|
126
|
-
disk: chalk.hex("#f472b6"),
|
|
127
|
-
graphs: chalk.hex("#22d3ee"),
|
|
128
|
-
process: chalk.hex("#e5e7eb")
|
|
129
|
-
},
|
|
130
|
-
dracula: {
|
|
131
|
-
border: chalk.hex("#6272a4"),
|
|
132
|
-
cpu: chalk.hex("#8be9fd"),
|
|
133
|
-
mem: chalk.hex("#bd93f9"),
|
|
134
|
-
gpu: chalk.hex("#ff5555"),
|
|
135
|
-
power: chalk.hex("#f1fa8c"),
|
|
136
|
-
battery: chalk.hex("#50fa7b"),
|
|
137
|
-
thermal: chalk.hex("#ffb86c"),
|
|
138
|
-
network: chalk.hex("#ff79c6"),
|
|
139
|
-
disk: chalk.hex("#ff5555"),
|
|
140
|
-
graphs: chalk.hex("#8be9fd"),
|
|
141
|
-
process: chalk.hex("#f8f8f2")
|
|
142
|
-
},
|
|
143
|
-
cyberpunk: {
|
|
144
|
-
border: chalk.hex("#ff0055"),
|
|
145
|
-
cpu: chalk.hex("#00ffcc"),
|
|
146
|
-
mem: chalk.hex("#ff00ff"),
|
|
147
|
-
gpu: chalk.hex("#ffff00"),
|
|
148
|
-
power: chalk.hex("#ffff00"),
|
|
149
|
-
battery: chalk.hex("#00ff00"),
|
|
150
|
-
thermal: chalk.hex("#ff6600"),
|
|
151
|
-
network: chalk.hex("#00ffff"),
|
|
152
|
-
disk: chalk.hex("#ff007f"),
|
|
153
|
-
graphs: chalk.hex("#00ffcc"),
|
|
154
|
-
process: chalk.hex("#ffffff")
|
|
155
|
-
},
|
|
156
|
-
monochrome: {
|
|
157
|
-
border: chalk.gray,
|
|
158
|
-
cpu: chalk.white.bold,
|
|
159
|
-
mem: chalk.white.bold,
|
|
160
|
-
gpu: chalk.white.bold,
|
|
161
|
-
power: chalk.white.bold,
|
|
162
|
-
battery: chalk.white.bold,
|
|
163
|
-
thermal: chalk.white.bold,
|
|
164
|
-
network: chalk.white.bold,
|
|
165
|
-
disk: chalk.white.bold,
|
|
166
|
-
graphs: chalk.white.bold,
|
|
167
|
-
process: chalk.white.bold
|
|
168
|
-
},
|
|
169
|
-
nord: {
|
|
170
|
-
border: chalk.hex("#4c566a"),
|
|
171
|
-
cpu: chalk.hex("#88c0d0"),
|
|
172
|
-
mem: chalk.hex("#b48ead"),
|
|
173
|
-
gpu: chalk.hex("#bf616a"),
|
|
174
|
-
power: chalk.hex("#ebcb8b"),
|
|
175
|
-
battery: chalk.hex("#a3be8c"),
|
|
176
|
-
thermal: chalk.hex("#d08770"),
|
|
177
|
-
network: chalk.hex("#81a1c1"),
|
|
178
|
-
disk: chalk.hex("#b48ead"),
|
|
179
|
-
graphs: chalk.hex("#88c0d0"),
|
|
180
|
-
process: chalk.hex("#eceff4")
|
|
181
|
-
},
|
|
182
|
-
gruvbox: {
|
|
183
|
-
border: chalk.hex("#928374"),
|
|
184
|
-
cpu: chalk.hex("#fb4934"),
|
|
185
|
-
mem: chalk.hex("#b8bb26"),
|
|
186
|
-
gpu: chalk.hex("#d3869b"),
|
|
187
|
-
power: chalk.hex("#fabd2f"),
|
|
188
|
-
battery: chalk.hex("#b8bb26"),
|
|
189
|
-
thermal: chalk.hex("#fb4934"),
|
|
190
|
-
network: chalk.hex("#83a598"),
|
|
191
|
-
disk: chalk.hex("#d3869b"),
|
|
192
|
-
graphs: chalk.hex("#fb4934"),
|
|
193
|
-
process: chalk.hex("#ebdbb2")
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
function loadCustomThemes() {
|
|
197
|
-
const themes = { ...BUILTIN_THEMES };
|
|
198
|
-
const themesDir = path.join(os.homedir(), ".config", "pyre", "themes");
|
|
199
|
-
try {
|
|
200
|
-
if (fs.existsSync(themesDir)) {
|
|
201
|
-
const files = fs.readdirSync(themesDir);
|
|
202
|
-
for (const file of files) {
|
|
203
|
-
if (file.endsWith(".json")) {
|
|
204
|
-
const name = path.basename(file, ".json");
|
|
205
|
-
try {
|
|
206
|
-
const raw = fs.readFileSync(path.join(themesDir, file), "utf-8");
|
|
207
|
-
const data = JSON.parse(raw);
|
|
208
|
-
const buildColor = (val) => val ? val.startsWith("#") ? chalk.hex(val) : chalk[val] || chalk.white : chalk.white;
|
|
209
|
-
themes[name] = {
|
|
210
|
-
border: buildColor(data.border),
|
|
211
|
-
cpu: buildColor(data.cpu),
|
|
212
|
-
mem: buildColor(data.mem),
|
|
213
|
-
gpu: buildColor(data.gpu),
|
|
214
|
-
power: buildColor(data.power),
|
|
215
|
-
battery: buildColor(data.battery),
|
|
216
|
-
thermal: buildColor(data.thermal),
|
|
217
|
-
network: buildColor(data.network),
|
|
218
|
-
disk: buildColor(data.disk),
|
|
219
|
-
graphs: buildColor(data.graphs),
|
|
220
|
-
process: buildColor(data.process)
|
|
221
|
-
};
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
} catch {
|
|
228
|
-
}
|
|
229
|
-
return themes;
|
|
230
|
-
}
|
|
231
|
-
var THEMES = loadCustomThemes();
|
|
232
|
-
|
|
233
|
-
// src/formatters/render.ts
|
|
234
|
-
function visLen(s) {
|
|
235
|
-
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
236
|
-
}
|
|
237
|
-
function fitVisible(s, width) {
|
|
238
|
-
const w = Math.max(0, width);
|
|
239
|
-
const len = visLen(s);
|
|
240
|
-
if (len === w) return s;
|
|
241
|
-
if (len < w) return s + " ".repeat(w - len);
|
|
242
|
-
let visibleCount = 0;
|
|
243
|
-
let out = "";
|
|
244
|
-
let inAnsi = false;
|
|
245
|
-
for (let i = 0; i < s.length; i++) {
|
|
246
|
-
const char = s[i];
|
|
247
|
-
if (char === "\x1B") {
|
|
248
|
-
inAnsi = true;
|
|
249
|
-
out += char;
|
|
250
|
-
} else if (inAnsi) {
|
|
251
|
-
out += char;
|
|
252
|
-
if (char >= "a" && char <= "z" || char >= "A" && char <= "Z") {
|
|
253
|
-
inAnsi = false;
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
if (visibleCount < w) {
|
|
257
|
-
out += char;
|
|
258
|
-
visibleCount++;
|
|
259
|
-
} else {
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
out += "\x1B[0m";
|
|
265
|
-
const resLen = visLen(out);
|
|
266
|
-
if (resLen < w) {
|
|
267
|
-
out += " ".repeat(w - resLen);
|
|
268
|
-
}
|
|
269
|
-
return out;
|
|
270
|
-
}
|
|
271
|
-
function truncatePlain(s, width) {
|
|
272
|
-
if (s.length <= width) return s;
|
|
273
|
-
return width <= 1 ? s.slice(0, width) : s.slice(0, width - 1) + "\u2026";
|
|
274
|
-
}
|
|
275
|
-
function panel(title, lines, width, accent, borderAccent, height, rightTitle) {
|
|
276
|
-
const h = height ?? lines.length;
|
|
277
|
-
const contentWidth = Math.max(1, width - 4);
|
|
278
|
-
const leftLabel = title ? ` ${title} ` : "";
|
|
279
|
-
const rightLabel = rightTitle ? ` ${rightTitle} ` : "";
|
|
280
|
-
const innerW = Math.max(0, width - 2);
|
|
281
|
-
let topInner = leftLabel + "\u2500".repeat(Math.max(0, innerW - visLen(leftLabel) - visLen(rightLabel))) + rightLabel;
|
|
282
|
-
if (visLen(topInner) > innerW) {
|
|
283
|
-
topInner = fitVisible(topInner, innerW);
|
|
284
|
-
} else if (visLen(topInner) < innerW) {
|
|
285
|
-
topInner = topInner + "\u2500".repeat(innerW - visLen(topInner));
|
|
286
|
-
}
|
|
287
|
-
const top = borderAccent("\u256D" + topInner + "\u256E");
|
|
288
|
-
const bottom = borderAccent("\u2570" + "\u2500".repeat(innerW) + "\u256F");
|
|
289
|
-
const body = [];
|
|
290
|
-
for (let i = 0; i < h; i++) {
|
|
291
|
-
const line = lines[i] ?? "";
|
|
292
|
-
body.push(`${borderAccent("\u2502")} ${fitVisible(line, contentWidth)} ${borderAccent("\u2502")}`);
|
|
293
|
-
}
|
|
294
|
-
return [top, ...body, bottom];
|
|
295
|
-
}
|
|
296
|
-
function gaugeBar(percent, width = 12) {
|
|
297
|
-
const w = Math.max(1, width);
|
|
298
|
-
const pct = Math.min(100, Math.max(0, percent));
|
|
299
|
-
const totalEighths = Math.round(pct / 100 * w * 8);
|
|
300
|
-
const fullBlocks = Math.floor(totalEighths / 8);
|
|
301
|
-
const remainder = totalEighths % 8;
|
|
302
|
-
const blocks = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
|
|
303
|
-
let str = "\u2588".repeat(fullBlocks);
|
|
304
|
-
if (fullBlocks < w && remainder > 0) {
|
|
305
|
-
str += blocks[remainder];
|
|
306
|
-
}
|
|
307
|
-
const empty = Math.max(0, w - visLen(str));
|
|
308
|
-
const color = pct > 85 ? chalk2.red : pct > 65 ? chalk2.yellow : chalk2.cyan;
|
|
309
|
-
return color(str) + chalk2.dim("\u2500".repeat(empty));
|
|
310
|
-
}
|
|
311
|
-
function hstack(blocks, gap = 2, targetWidth) {
|
|
312
|
-
if (!blocks.length) return [];
|
|
313
|
-
const height = Math.max(...blocks.map((b) => b.length));
|
|
314
|
-
const widths = blocks.map((b) => Math.max(0, ...b.map((l) => visLen(l))));
|
|
315
|
-
const padded = blocks.map((b, i) => {
|
|
316
|
-
const w = widths[i];
|
|
317
|
-
const filler = " ".repeat(w);
|
|
318
|
-
const arr = b.map((line) => fitVisible(line, w));
|
|
319
|
-
while (arr.length < height) arr.push(filler);
|
|
320
|
-
return arr;
|
|
321
|
-
});
|
|
322
|
-
const out = [];
|
|
323
|
-
for (let row = 0; row < height; row++) {
|
|
324
|
-
let line = padded.map((a) => a[row]).join(" ".repeat(gap));
|
|
325
|
-
if (targetWidth !== void 0) {
|
|
326
|
-
line = fitVisible(line, targetWidth);
|
|
327
|
-
}
|
|
328
|
-
out.push(line);
|
|
329
|
-
}
|
|
330
|
-
return out;
|
|
331
|
-
}
|
|
332
|
-
function statRow(label, value, width = 9) {
|
|
333
|
-
const w = Math.max(width, label.length + 1);
|
|
334
|
-
return `${chalk2.dim(label.padEnd(w))}${value}`;
|
|
335
|
-
}
|
|
336
|
-
function gaugeRow(label, percent, contentWidth) {
|
|
337
|
-
const labelW = 9;
|
|
338
|
-
const pctText = `${Math.round(percent)}%`;
|
|
339
|
-
const barW = Math.max(6, contentWidth - labelW - pctText.length - 1);
|
|
340
|
-
const pct = Math.min(100, Math.max(0, percent));
|
|
341
|
-
const totalEighths = Math.round(pct / 100 * barW * 8);
|
|
342
|
-
const fullBlocks = Math.floor(totalEighths / 8);
|
|
343
|
-
const remainder = totalEighths % 8;
|
|
344
|
-
const blocks = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
|
|
345
|
-
let str = "\u2588".repeat(fullBlocks);
|
|
346
|
-
if (fullBlocks < barW && remainder > 0) {
|
|
347
|
-
str += blocks[remainder];
|
|
348
|
-
}
|
|
349
|
-
const empty = Math.max(0, barW - visLen(str));
|
|
350
|
-
const color = pct > 90 ? chalk2.red : pct > 70 ? chalk2.yellow : chalk2.green;
|
|
351
|
-
return `${chalk2.dim(label.padEnd(labelW))}${color(str)}${chalk2.dim("\u2500".repeat(empty))} ${pctText}`;
|
|
352
|
-
}
|
|
353
|
-
function thermalColor(pressureLevel) {
|
|
354
|
-
if (pressureLevel >= 3) return chalk2.red;
|
|
355
|
-
if (pressureLevel === 2) return chalk2.yellow;
|
|
356
|
-
if (pressureLevel === 1) return chalk2.yellowBright;
|
|
357
|
-
return chalk2.green;
|
|
358
|
-
}
|
|
359
|
-
function capacityColor(percent) {
|
|
360
|
-
if (percent < 80) return chalk2.red;
|
|
361
|
-
if (percent < 90) return chalk2.yellow;
|
|
362
|
-
return chalk2.green;
|
|
363
|
-
}
|
|
364
|
-
function formatBytes(bytes) {
|
|
365
|
-
if (isNaN(bytes) || bytes < 0) return "0 B";
|
|
366
|
-
if (bytes < 1024) return `${Math.round(bytes)} B`;
|
|
367
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
368
|
-
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
369
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
370
|
-
}
|
|
371
|
-
function celsiusToFahrenheit(c) {
|
|
372
|
-
return c * (9 / 5) + 32;
|
|
373
|
-
}
|
|
374
|
-
function formatTemp(c) {
|
|
375
|
-
return `${c.toFixed(1)}\xB0C / ${celsiusToFahrenheit(c).toFixed(1)}\xB0F`;
|
|
376
|
-
}
|
|
377
|
-
function formatTempUnit(c, unit = "c") {
|
|
378
|
-
if (unit === "f") {
|
|
379
|
-
return `${(c * (9 / 5) + 32).toFixed(1)}\xB0F`;
|
|
380
|
-
}
|
|
381
|
-
return `${c.toFixed(1)}\xB0C`;
|
|
382
|
-
}
|
|
383
|
-
function cpuCard(data, contentWidth) {
|
|
384
|
-
const lines = [
|
|
385
|
-
statRow("Brand", truncatePlain(data.cpu.brand, Math.max(4, contentWidth - 8))),
|
|
386
|
-
statRow("Cores", `${data.cpu.physicalCores}/${data.cpu.cores} phys/log`),
|
|
387
|
-
statRow("Freq", `${data.cpu.frequency} MHz`),
|
|
388
|
-
gaugeRow("Usage", data.cpu.usage, contentWidth),
|
|
389
|
-
statRow("Load", data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" "))
|
|
390
|
-
];
|
|
391
|
-
if (data.cpu.coreUsage && data.cpu.coreUsage.length) {
|
|
392
|
-
const slotW = 2;
|
|
393
|
-
const maxCores = Math.max(1, Math.floor((contentWidth - 10) / (slotW + 1)));
|
|
394
|
-
const cores = data.cpu.coreUsage.slice(0, maxCores);
|
|
395
|
-
const bars = cores.map((u) => {
|
|
396
|
-
const fill = Math.round(Math.min(100, u) / 100 * slotW);
|
|
397
|
-
const str = "\u2588".repeat(Math.max(0, fill)) + "\u2591".repeat(slotW - fill);
|
|
398
|
-
const color = u > 90 ? chalk2.red : u > 70 ? chalk2.yellow : chalk2.green;
|
|
399
|
-
return color(str);
|
|
400
|
-
});
|
|
401
|
-
lines.push(statRow("Cores", bars.join(" ")));
|
|
402
|
-
}
|
|
403
|
-
if (data.cpu.temperature) lines.push(statRow("Temp", formatTemp(data.cpu.temperature)));
|
|
404
|
-
return lines;
|
|
405
|
-
}
|
|
406
|
-
function memCard(data, contentWidth) {
|
|
407
|
-
return [
|
|
408
|
-
statRow("Total", formatBytes(data.memory.total)),
|
|
409
|
-
statRow("Used", formatBytes(data.memory.used)),
|
|
410
|
-
statRow("Cached", formatBytes(data.memory.total - data.memory.free - data.memory.used)),
|
|
411
|
-
statRow("Free", formatBytes(data.memory.free)),
|
|
412
|
-
gaugeRow("Usage", data.memory.usagePercent, contentWidth),
|
|
413
|
-
statRow("Swap", `${formatBytes(data.memory.swapUsed)} / ${formatBytes(data.memory.swapTotal)}`)
|
|
414
|
-
];
|
|
415
|
-
}
|
|
416
|
-
function gpuCard(data, contentWidth) {
|
|
417
|
-
if (!data.gpu) return null;
|
|
418
|
-
const lines = [];
|
|
419
|
-
lines.push(statRow("Model", truncatePlain(data.gpu.model, Math.max(4, contentWidth - 8))));
|
|
420
|
-
lines.push(statRow("Memory", formatBytes(data.gpu.memory)));
|
|
421
|
-
lines.push(statRow("Util", `${data.gpu.utilization}%`));
|
|
422
|
-
if (data.gpu.temperature) lines.push(statRow("Temp", formatTemp(data.gpu.temperature)));
|
|
423
|
-
lines.push(statRow("Procs", String(data.gpu.processes)));
|
|
424
|
-
return lines;
|
|
425
|
-
}
|
|
426
|
-
function powerCard(data) {
|
|
427
|
-
const lines = [];
|
|
428
|
-
if (data.power) {
|
|
429
|
-
if (data.power.cpuWatts !== void 0) lines.push(statRow("CPU", `${data.power.cpuWatts.toFixed(2)} W`));
|
|
430
|
-
if (data.power.gpuWatts !== void 0) lines.push(statRow("GPU", `${data.power.gpuWatts.toFixed(2)} W`));
|
|
431
|
-
if (data.power.combinedWatts !== void 0) lines.push(statRow("Total", `${data.power.combinedWatts.toFixed(2)} W`));
|
|
432
|
-
}
|
|
433
|
-
if (data.battery?.powerWatts !== void 0 && data.battery.powerWatts > 0) {
|
|
434
|
-
lines.push(statRow("Battery", `${data.battery.powerWatts.toFixed(2)} W`));
|
|
435
|
-
}
|
|
436
|
-
return lines.length ? lines : null;
|
|
437
|
-
}
|
|
438
|
-
function batteryCard(data, contentWidth) {
|
|
439
|
-
if (!data.battery) return null;
|
|
440
|
-
const valW = Math.max(6, contentWidth - 10);
|
|
441
|
-
const lines = [
|
|
442
|
-
statRow("Level", `${data.battery.level}% (${data.battery.state})`),
|
|
443
|
-
statRow("Time", data.battery.timeRemaining),
|
|
444
|
-
statRow("Cycles", String(data.battery.cycles ?? "N/A")),
|
|
445
|
-
statRow("Condition", data.battery.condition ?? "N/A")
|
|
446
|
-
];
|
|
447
|
-
if (data.battery.maxCapacityPercent !== void 0) {
|
|
448
|
-
lines.push(statRow("Capacity", capacityColor(data.battery.maxCapacityPercent)(`${data.battery.maxCapacityPercent}%`)));
|
|
449
|
-
}
|
|
450
|
-
lines.push(statRow("Source", data.battery.powerSource));
|
|
451
|
-
if (data.battery.estimatedTimeToEmpty && data.battery.state !== "charged") {
|
|
452
|
-
lines.push(statRow("Est. Empty", data.battery.estimatedTimeToEmpty));
|
|
453
|
-
}
|
|
454
|
-
if (data.battery.dischargeRatePerHour !== void 0 && data.battery.state !== "charged") {
|
|
455
|
-
lines.push(statRow("Disch. Rate", `${data.battery.dischargeRatePerHour}%/h`));
|
|
456
|
-
}
|
|
457
|
-
return lines;
|
|
458
|
-
}
|
|
459
|
-
function thermalCard(data, contentWidth) {
|
|
460
|
-
const valW = Math.max(6, contentWidth - 10);
|
|
461
|
-
const lines = [statRow("State", thermalColor(data.thermal.pressureLevel)(truncatePlain(data.thermal.state, valW)))];
|
|
462
|
-
if (data.thermal.temperatures) {
|
|
463
|
-
for (const [k, v] of Object.entries(data.thermal.temperatures)) {
|
|
464
|
-
lines.push(statRow(k.replace("_", " "), v !== null && v !== void 0 ? formatTemp(v) : "N/A"));
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (data.thermal.error) lines.push(statRow("Error", truncatePlain(data.thermal.error, valW)));
|
|
468
|
-
return lines;
|
|
469
|
-
}
|
|
470
|
-
function networkCard(data, contentWidth) {
|
|
471
|
-
const valW = Math.max(6, contentWidth - 8);
|
|
472
|
-
return [
|
|
473
|
-
statRow("Iface", truncatePlain(`${data.network.interface} (${data.network.ip})`, valW)),
|
|
474
|
-
statRow("RX", `${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt`),
|
|
475
|
-
statRow("TX", `${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt`)
|
|
476
|
-
];
|
|
477
|
-
}
|
|
478
|
-
function tasksCard(data, contentWidth) {
|
|
479
|
-
if (!data.tasks || !data.tasks.length) return null;
|
|
480
|
-
const lines = [];
|
|
481
|
-
for (const task of data.tasks.slice(0, 6)) {
|
|
482
|
-
const cmd = truncatePlain(task.command, Math.max(4, contentWidth - 28));
|
|
483
|
-
lines.push(statRow(`${task.pid}`, `${task.user.padEnd(8)} ${task.cpu.toFixed(1).padStart(5)}% ${task.mem.toFixed(1).padStart(5)}% ${task.state} ${cmd}`));
|
|
484
|
-
}
|
|
485
|
-
return lines;
|
|
486
|
-
}
|
|
487
|
-
function header(data, width, badges = [], borderAccent = THEMES.default.border) {
|
|
488
|
-
const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
489
|
-
const left = `${chalk2.hex("#ff5500").bold(" PYRE ")} ${chalk2.bold(data.header.hostname)} ${chalk2.dim(data.header.os)} ${chalk2.dim("up " + data.header.uptime)}`;
|
|
490
|
-
const right = `${chalk2.dim(now)} ${badges.join(" ")}`;
|
|
491
|
-
const gap = Math.max(1, width - visLen(left) - visLen(right));
|
|
492
|
-
const line = fitVisible(left + " ".repeat(gap) + right, width);
|
|
493
|
-
return [line, borderAccent("\u2500".repeat(width))];
|
|
494
|
-
}
|
|
495
|
-
var TAB_DEFS = [
|
|
496
|
-
{ id: "cpu", label: "CPU", key: "1" },
|
|
497
|
-
{ id: "mem", label: "Memory", key: "2" },
|
|
498
|
-
{ id: "gpu", label: "GPU", key: "3" },
|
|
499
|
-
{ id: "power", label: "Power", key: "4" },
|
|
500
|
-
{ id: "battery", label: "Battery", key: "5" },
|
|
501
|
-
{ id: "thermal", label: "Thermal", key: "6" },
|
|
502
|
-
{ id: "network", label: "Network", key: "7" },
|
|
503
|
-
{ id: "packets", label: "Packets", key: "8" },
|
|
504
|
-
{ id: "tasks", label: "Tasks", key: "9" },
|
|
505
|
-
{ id: "disk", label: "Disk", key: "0" },
|
|
506
|
-
{ id: "process", label: "Process", key: "P" },
|
|
507
|
-
{ id: "p2p", label: "P2P", key: "R" },
|
|
508
|
-
{ id: "anomalies", label: "Anomalies", key: "A" }
|
|
509
|
-
];
|
|
510
|
-
var TAB_BAR_INDENT = 2;
|
|
511
|
-
var TAB_SEPARATOR_WIDTH = 3;
|
|
512
|
-
function getTabHitboxes() {
|
|
513
|
-
const boxes = [];
|
|
514
|
-
let col = TAB_BAR_INDENT;
|
|
515
|
-
TAB_DEFS.forEach((t, i) => {
|
|
516
|
-
const text = `[${t.key}]${t.label}`;
|
|
517
|
-
const start = col;
|
|
518
|
-
const end = col + text.length;
|
|
519
|
-
boxes.push({ id: t.id, start, end });
|
|
520
|
-
col = end;
|
|
521
|
-
if (i < TAB_DEFS.length - 1) col += TAB_SEPARATOR_WIDTH;
|
|
522
|
-
});
|
|
523
|
-
return boxes;
|
|
524
|
-
}
|
|
525
|
-
var TAB_BAR_ROW = 4;
|
|
526
|
-
function tabBar(activePanel, width, theme) {
|
|
527
|
-
const rendered = TAB_DEFS.map((t) => {
|
|
528
|
-
const isActive = activePanel === t.id;
|
|
529
|
-
const label = isActive ? chalk2.bold(t.label) : chalk2.dim(t.label);
|
|
530
|
-
const keyHint = chalk2.dim(`[${t.key}]`);
|
|
531
|
-
return `${keyHint}${label}`;
|
|
532
|
-
});
|
|
533
|
-
const separator = chalk2.dim(" \u2502 ");
|
|
534
|
-
const joined = rendered.join(separator);
|
|
535
|
-
const indent = " ";
|
|
536
|
-
const available = width - visLen(indent);
|
|
537
|
-
let result = joined;
|
|
538
|
-
if (visLen(joined) > available) {
|
|
539
|
-
let used = 0;
|
|
540
|
-
const kept = [];
|
|
541
|
-
for (let i = 0; i < rendered.length; i++) {
|
|
542
|
-
const tabW = visLen(rendered[i]) + (kept.length > 0 ? visLen(separator) : 0);
|
|
543
|
-
if (used + tabW > available && kept.length > 0) break;
|
|
544
|
-
kept.push(rendered[i]);
|
|
545
|
-
used += tabW;
|
|
546
|
-
}
|
|
547
|
-
if (kept.length < rendered.length) {
|
|
548
|
-
result = kept.join(separator) + chalk2.dim("\u22EF");
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
const pad = Math.max(0, available - visLen(result));
|
|
552
|
-
return fitVisible(chalk2.dim(indent) + result + " ".repeat(pad), width);
|
|
553
|
-
}
|
|
554
|
-
function sortProcesses(processes, sortBy) {
|
|
555
|
-
const copy = [...processes];
|
|
556
|
-
switch (sortBy) {
|
|
557
|
-
case "pid":
|
|
558
|
-
return copy.sort((a, b) => a.pid - b.pid);
|
|
559
|
-
case "mem":
|
|
560
|
-
return copy.sort((a, b) => b.mem - a.mem);
|
|
561
|
-
case "user":
|
|
562
|
-
return copy.sort((a, b) => a.user.localeCompare(b.user));
|
|
563
|
-
case "command":
|
|
564
|
-
return copy.sort((a, b) => a.command.localeCompare(b.command));
|
|
565
|
-
case "state":
|
|
566
|
-
return copy.sort((a, b) => a.state.localeCompare(b.state));
|
|
567
|
-
case "threads":
|
|
568
|
-
return copy.sort((a, b) => b.threads - a.threads);
|
|
569
|
-
case "runtime":
|
|
570
|
-
return copy.sort((a, b) => b.runtime - a.runtime);
|
|
571
|
-
default:
|
|
572
|
-
return copy.sort((a, b) => b.cpu - a.cpu);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
function pctColor(v) {
|
|
576
|
-
return v > 80 ? chalk2.red : v > 50 ? chalk2.yellow : chalk2.dim;
|
|
577
|
-
}
|
|
578
|
-
function processTableLines(processes, contentWidth, borderAccent = THEMES.default.border, selectedIdx = -1) {
|
|
579
|
-
const pidW = 8, ppidW = 8, userW = 12, cpuW = 8, memW = 8, stateW = 8, thrW = 6, rtW = 10;
|
|
580
|
-
const cmdW = Math.max(10, contentWidth - pidW - ppidW - userW - cpuW - memW - stateW - thrW - rtW - 8);
|
|
581
|
-
const head = chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" PID ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" PPID ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" USER ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" CPU% ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" MEM% ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" STATE ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" THREADS") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" RUNTIME ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" COMMAND".padEnd(cmdW + 1));
|
|
582
|
-
const rows = processes.map((p, idx) => {
|
|
583
|
-
const isSelected = idx === selectedIdx;
|
|
584
|
-
const cpuStr = pctColor(p.cpu)(`${p.cpu.toFixed(1)}`.padEnd(cpuW));
|
|
585
|
-
const memStr = pctColor(p.mem)(`${p.mem.toFixed(1)}`.padEnd(memW));
|
|
586
|
-
const stateStr = chalk2.dim(p.state.padEnd(stateW));
|
|
587
|
-
const thrStr = String(p.threads).padEnd(thrW);
|
|
588
|
-
const rtStr = formatRuntime(p.runtime).padEnd(rtW);
|
|
589
|
-
const line = String(p.pid).padEnd(pidW) + String(p.ppid).padEnd(ppidW) + truncatePlain(p.user, userW - 1).padEnd(userW) + cpuStr + memStr + stateStr + thrStr + rtStr + truncatePlain(p.command, cmdW);
|
|
590
|
-
return isSelected ? chalk2.bgCyan.black(line) : line;
|
|
591
|
-
});
|
|
592
|
-
return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
|
|
593
|
-
}
|
|
594
|
-
function formatRuntime(seconds) {
|
|
595
|
-
if (seconds < 60) return `${seconds}s`;
|
|
596
|
-
const mins = Math.floor(seconds / 60);
|
|
597
|
-
if (mins < 60) return `${mins}m ${seconds % 60}s`;
|
|
598
|
-
const hrs = Math.floor(mins / 60);
|
|
599
|
-
return `${hrs}h ${mins % 60}m`;
|
|
600
|
-
}
|
|
601
|
-
function buildProcessTree(processes) {
|
|
602
|
-
const map = /* @__PURE__ */ new Map();
|
|
603
|
-
const roots = [];
|
|
604
|
-
for (const p of processes) map.set(p.pid, p);
|
|
605
|
-
for (const p of processes) {
|
|
606
|
-
if (p.ppid === 0 || !map.has(p.ppid)) roots.push(p);
|
|
607
|
-
}
|
|
608
|
-
const childrenMap = /* @__PURE__ */ new Map();
|
|
609
|
-
for (const p of processes) {
|
|
610
|
-
const list = childrenMap.get(p.ppid) || [];
|
|
611
|
-
list.push(p);
|
|
612
|
-
childrenMap.set(p.ppid, list);
|
|
613
|
-
}
|
|
614
|
-
for (const list of childrenMap.values()) {
|
|
615
|
-
list.sort((a, b) => b.cpu - a.cpu);
|
|
616
|
-
}
|
|
617
|
-
const lines = [];
|
|
618
|
-
const visited = /* @__PURE__ */ new Set();
|
|
619
|
-
function renderNode(proc, prefix, isLast) {
|
|
620
|
-
if (visited.has(proc.pid)) return;
|
|
621
|
-
visited.add(proc.pid);
|
|
622
|
-
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
623
|
-
const pidStr = String(proc.pid).padEnd(8);
|
|
624
|
-
const cpuStr = pctColor(proc.cpu)(`${proc.cpu.toFixed(1)}%`.padEnd(8));
|
|
625
|
-
const memStr = pctColor(proc.mem)(`${proc.mem.toFixed(1)}%`.padEnd(8));
|
|
626
|
-
const stateStr = chalk2.dim(proc.state.padEnd(8));
|
|
627
|
-
const thrStr = String(proc.threads).padEnd(6);
|
|
628
|
-
const cmd = truncatePlain(proc.command, Math.max(10, 100 - pidStr.length - cpuStr.length - memStr.length - stateStr.length - thrStr.length - 10));
|
|
629
|
-
lines.push(`${prefix}${connector}${pidStr}${cpuStr}${memStr}${stateStr}${thrStr} ${cmd}`);
|
|
630
|
-
const children = childrenMap.get(proc.pid);
|
|
631
|
-
if (children) {
|
|
632
|
-
let idx = 0;
|
|
633
|
-
let total = 0;
|
|
634
|
-
for (const c of children) {
|
|
635
|
-
if (!visited.has(c.pid)) total++;
|
|
636
|
-
}
|
|
637
|
-
for (const child of children) {
|
|
638
|
-
if (visited.has(child.pid)) continue;
|
|
639
|
-
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
640
|
-
renderNode(child, nextPrefix, ++idx === total);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
roots.sort((a, b) => b.cpu - a.cpu);
|
|
645
|
-
roots.forEach((root, i) => renderNode(root, "", i === roots.length - 1));
|
|
646
|
-
return lines;
|
|
647
|
-
}
|
|
648
|
-
function diskTableLines(disks, contentWidth, borderAccent = THEMES.default.border) {
|
|
649
|
-
const fsW = 20, sizeW = 10, usedW = 10, availW = 10, capW = 8;
|
|
650
|
-
const mountW = Math.max(8, contentWidth - fsW - sizeW - usedW - availW - capW - 5);
|
|
651
|
-
const head = chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" FILESYSTEM ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" SIZE ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" USED ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" AVAIL ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" CAP ") + chalk2.bold.hex("#f8f8f2").bgHex("#44475a")(" MOUNT".padEnd(mountW + 1));
|
|
652
|
-
const rows = disks.map((d) => {
|
|
653
|
-
const capNum = parseInt(d.capacity, 10) || 0;
|
|
654
|
-
const capStr = pctColor(capNum)(d.capacity.padEnd(capW));
|
|
655
|
-
return truncatePlain(d.filesystem, fsW - 1).padEnd(fsW) + d.size.padEnd(sizeW) + d.used.padEnd(usedW) + d.available.padEnd(availW) + capStr + truncatePlain(d.mountpoint, mountW);
|
|
656
|
-
});
|
|
657
|
-
return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
|
|
658
|
-
}
|
|
659
|
-
function detailPanelTitle(panel2, trackedPid) {
|
|
660
|
-
const map = {
|
|
661
|
-
cpu: "CPU",
|
|
662
|
-
mem: "Memory",
|
|
663
|
-
gpu: "GPU",
|
|
664
|
-
power: "Power",
|
|
665
|
-
battery: "Battery",
|
|
666
|
-
thermal: "Thermal",
|
|
667
|
-
network: "Network",
|
|
668
|
-
packets: "Packets",
|
|
669
|
-
tasks: "Tasks",
|
|
670
|
-
disk: "Disk",
|
|
671
|
-
process: trackedPid !== void 0 && trackedPid !== null ? `Processes (Following PID ${trackedPid})` : "Processes"
|
|
672
|
-
};
|
|
673
|
-
return map[panel2] || panel2;
|
|
674
|
-
}
|
|
675
|
-
function activeDetailLines(data, activePanel, width, opts2 = {}) {
|
|
676
|
-
const contentWidth = width - 4;
|
|
677
|
-
const themeName = opts2.theme || "default";
|
|
678
|
-
const theme = THEMES[themeName] || THEMES.default;
|
|
679
|
-
switch (activePanel) {
|
|
680
|
-
case "cpu":
|
|
681
|
-
return cpuCard(data, contentWidth);
|
|
682
|
-
case "mem":
|
|
683
|
-
return memCard(data, contentWidth);
|
|
684
|
-
case "gpu": {
|
|
685
|
-
const gpu = gpuCard(data, contentWidth);
|
|
686
|
-
return gpu ?? ["No GPU data available"];
|
|
687
|
-
}
|
|
688
|
-
case "power": {
|
|
689
|
-
const power = powerCard(data);
|
|
690
|
-
if (!power && !data.battery) return ["No power data available"];
|
|
691
|
-
const lines = [];
|
|
692
|
-
if (power) lines.push(...power);
|
|
693
|
-
if (data.battery?.powerWatts !== void 0 && data.battery.powerWatts > 0) {
|
|
694
|
-
lines.push(statRow("Battery", `${data.battery.powerWatts.toFixed(2)} W`));
|
|
695
|
-
}
|
|
696
|
-
return lines.length ? lines : ["No power data available"];
|
|
697
|
-
}
|
|
698
|
-
case "battery":
|
|
699
|
-
return data.battery ? batteryCard(data, contentWidth) : ["No battery data available"];
|
|
700
|
-
case "thermal":
|
|
701
|
-
return thermalCard(data, contentWidth);
|
|
702
|
-
case "network":
|
|
703
|
-
return networkCard(data, contentWidth);
|
|
704
|
-
case "packets": {
|
|
705
|
-
if (!data.packets) return ["No packet data available"];
|
|
706
|
-
const lines = [];
|
|
707
|
-
lines.push(statRow("Total", `${data.packets.totalPackets} pkt`));
|
|
708
|
-
lines.push(statRow("RX", `${data.packets.rxPackets} pkt`));
|
|
709
|
-
lines.push(statRow("TX", `${data.packets.txPackets} pkt`));
|
|
710
|
-
lines.push(statRow("Conns", `${data.packets.connections} TCP`));
|
|
711
|
-
if (data.packets.interfaces && data.packets.interfaces.length) {
|
|
712
|
-
lines.push(statRow("Ifaces", `${data.packets.interfaces.length} active`));
|
|
713
|
-
for (const iface of data.packets.interfaces.slice(0, 8)) {
|
|
714
|
-
lines.push(statRow(` ${iface.iface}`, `RX ${formatBytes(iface.rxBytes)} / TX ${formatBytes(iface.txBytes)}`));
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
if (data.packets.allProcesses && data.packets.allProcesses.length) {
|
|
718
|
-
lines.push(statRow("Net Procs", `${data.packets.allProcesses.length} total`));
|
|
719
|
-
for (const proc of data.packets.allProcesses.slice(0, 10)) {
|
|
720
|
-
lines.push(statRow(` ${proc.pid}`, `${truncatePlain(proc.command, 20)} TX ${formatBytes(proc.txBytes)}`));
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
return lines;
|
|
724
|
-
}
|
|
725
|
-
case "tasks":
|
|
726
|
-
return data.tasks?.length ? tasksCard(data, contentWidth) : ["No task data available"];
|
|
727
|
-
case "disk":
|
|
728
|
-
return data.disk.length ? diskTableLines(data.disk, width - 4, theme.border) : ["No disk data available"];
|
|
729
|
-
case "process": {
|
|
730
|
-
const filtered = opts2.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts2.filter.toLowerCase())) : data.processes;
|
|
731
|
-
const sorted = sortProcesses(filtered, opts2.sortBy ?? "cpu");
|
|
732
|
-
let selectedIdx = opts2.selectedProcessIndex ?? -1;
|
|
733
|
-
const isTracking = opts2.trackedPid !== void 0 && opts2.trackedPid !== null;
|
|
734
|
-
if (isTracking) {
|
|
735
|
-
const trackedIdx = sorted.findIndex((p) => p.pid === opts2.trackedPid);
|
|
736
|
-
if (trackedIdx >= 0) {
|
|
737
|
-
selectedIdx = trackedIdx;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
const trackingLines = [];
|
|
741
|
-
if (isTracking) {
|
|
742
|
-
const tp = sorted.find((p) => p.pid === opts2.trackedPid);
|
|
743
|
-
if (tp) {
|
|
744
|
-
const cpuColor = tp.cpu > 50 ? chalk2.red.bold : tp.cpu > 20 ? chalk2.yellow : chalk2.green;
|
|
745
|
-
const memColor = tp.mem > 50 ? chalk2.red.bold : tp.mem > 20 ? chalk2.yellow : chalk2.green;
|
|
746
|
-
trackingLines.push(chalk2.bgHex("#1a1a2e").hex("#e94560").bold(` \u25C9 FOLLOWING PID ${tp.pid} \u2014 ${truncatePlain(tp.command, Math.max(10, contentWidth - 30))} `));
|
|
747
|
-
trackingLines.push(
|
|
748
|
-
` ${chalk2.bold("CPU:")} ${cpuColor(tp.cpu.toFixed(1) + "%")} ${chalk2.bold("MEM:")} ${memColor(tp.mem.toFixed(1) + "%")} ${chalk2.bold("State:")} ${chalk2.dim(tp.state)} ${chalk2.bold("Threads:")} ${tp.threads} ${chalk2.bold("Runtime:")} ${formatRuntime(tp.runtime)} ${chalk2.bold("User:")} ${tp.user}`
|
|
749
|
-
);
|
|
750
|
-
trackingLines.push(chalk2.dim(` Full: ${tp.command}`));
|
|
751
|
-
trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
|
|
752
|
-
} else {
|
|
753
|
-
trackingLines.push(chalk2.yellow(` \u25C9 PID ${opts2.trackedPid} exited \u2014 no longer in process list`));
|
|
754
|
-
trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
const availableRows = Math.max(1, (opts2.processLimit || sorted.length) - trackingLines.length);
|
|
758
|
-
let offset = 0;
|
|
759
|
-
if (selectedIdx >= 0) {
|
|
760
|
-
if (selectedIdx >= availableRows) {
|
|
761
|
-
offset = selectedIdx - availableRows + 1;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
const visible = sorted.slice(offset, offset + availableRows);
|
|
765
|
-
if (opts2.treeView) {
|
|
766
|
-
return [...trackingLines, ...buildProcessTree(visible)];
|
|
767
|
-
}
|
|
768
|
-
return [...trackingLines, ...processTableLines(visible, width - 4, theme.border, selectedIdx - offset)];
|
|
769
|
-
}
|
|
770
|
-
case "anomalies": {
|
|
771
|
-
if (!opts2.anomalyHistory || opts2.anomalyHistory.length === 0) return ["No anomalies detected yet."];
|
|
772
|
-
const lines = [];
|
|
773
|
-
const sorted = [...opts2.anomalyHistory].sort((a, b) => (b.timestamp?.getTime() || 0) - (a.timestamp?.getTime() || 0));
|
|
774
|
-
for (const a of sorted) {
|
|
775
|
-
const color = a.severity === "critical" ? chalk2.red.bold : chalk2.yellow;
|
|
776
|
-
const direction = a.zScore > 0 ? "\u2191 spike" : "\u2193 drop";
|
|
777
|
-
const valStr = a.metric.toLowerCase().includes("net") ? `${formatBytes(a.value)}/s` : `${a.value.toFixed(1)}%`;
|
|
778
|
-
const timeStr = a.timestamp ? a.timestamp.toLocaleTimeString() : "";
|
|
779
|
-
lines.push(color(` ${timeStr.padEnd(10)} \u26A0 ${a.metric.padEnd(8)}: ${valStr.padEnd(12)} (${direction}, \u03C3=${a.zScore.toFixed(1)})`));
|
|
780
|
-
}
|
|
781
|
-
return lines;
|
|
782
|
-
}
|
|
783
|
-
default:
|
|
784
|
-
return null;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
function anomalyPanel(anomalies, width, theme) {
|
|
788
|
-
const lines = [];
|
|
789
|
-
for (const a of anomalies) {
|
|
790
|
-
const color = a.severity === "critical" ? chalk2.red.bold : chalk2.yellow;
|
|
791
|
-
const direction = a.zScore > 0 ? "\u2191 spike" : "\u2193 drop";
|
|
792
|
-
const valStr = a.metric.toLowerCase().includes("net") ? `${formatBytes(a.value)}/s` : `${a.value.toFixed(1)}%`;
|
|
793
|
-
lines.push(color(` \u26A0 ${a.metric}: ${valStr} (${direction}, \u03C3=${a.zScore.toFixed(1)})`));
|
|
794
|
-
}
|
|
795
|
-
return lines;
|
|
796
|
-
}
|
|
797
|
-
function btopCpuBox(data, width, theme, opts2) {
|
|
798
|
-
const contentWidth = width - 4;
|
|
799
|
-
const rightTitle = `${data.header.hostname} \xB7 up ${data.header.uptime} \xB7 ${data.cpu.usage.toFixed(0)}%`;
|
|
800
|
-
if (contentWidth >= 65) {
|
|
801
|
-
const leftW = Math.floor(contentWidth * 0.48);
|
|
802
|
-
const rightW = contentWidth - leftW - 2;
|
|
803
|
-
const leftLines = [];
|
|
804
|
-
leftLines.push(fitVisible(theme.cpu("CPU Usage History"), leftW));
|
|
805
|
-
const cpuHistory = opts2.history?.cpuUsage || [];
|
|
806
|
-
if (cpuHistory.length > 1) {
|
|
807
|
-
const graphRows = multiRowBrailleGraph(cpuHistory, 3, { min: 0, max: 100 });
|
|
808
|
-
for (const gr of graphRows) {
|
|
809
|
-
leftLines.push(theme.cpu(fitVisible(gr, leftW)));
|
|
810
|
-
}
|
|
811
|
-
} else {
|
|
812
|
-
leftLines.push(theme.cpu(fitVisible(gaugeBar(data.cpu.usage, leftW), leftW)));
|
|
813
|
-
leftLines.push(" ".repeat(leftW));
|
|
814
|
-
leftLines.push(" ".repeat(leftW));
|
|
815
|
-
}
|
|
816
|
-
const rightLines = [];
|
|
817
|
-
rightLines.push(fitVisible(chalk2.bold(truncatePlain(data.cpu.brand, rightW)), rightW));
|
|
818
|
-
const cpuBarW = Math.max(6, rightW - 18);
|
|
819
|
-
const tempStr = data.cpu.temperature ? formatTempUnit(data.cpu.temperature, opts2.tempUnit) : "";
|
|
820
|
-
rightLines.push(fitVisible(`CPU ${gaugeBar(data.cpu.usage, cpuBarW)} ${data.cpu.usage.toFixed(0).padStart(3)}% ${tempStr}`, rightW));
|
|
821
|
-
const loadStr = data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" ");
|
|
822
|
-
rightLines.push(fitVisible(`${chalk2.dim("Load:")} ${loadStr}`, rightW));
|
|
823
|
-
if (data.cpu.coreUsage && data.cpu.coreUsage.length) {
|
|
824
|
-
const coreColW = Math.floor((rightW - 1) / 2);
|
|
825
|
-
const half = Math.ceil(data.cpu.coreUsage.length / 2);
|
|
826
|
-
for (let i = 0; i < half; i++) {
|
|
827
|
-
const u1 = data.cpu.coreUsage[i];
|
|
828
|
-
const u2 = data.cpu.coreUsage[i + half];
|
|
829
|
-
const c1BarW = Math.max(3, coreColW - 8);
|
|
830
|
-
const c1Str = `C${i} ${gaugeBar(u1, c1BarW)} ${Math.round(u1)}%`;
|
|
831
|
-
const c2Str = u2 !== void 0 ? `C${i + half} ${gaugeBar(u2, c1BarW)} ${Math.round(u2)}%` : "";
|
|
832
|
-
rightLines.push(fitVisible(fitVisible(c1Str, coreColW) + " " + fitVisible(c2Str, coreColW), rightW));
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (data.gpu) {
|
|
836
|
-
rightLines.push(fitVisible(`GPU ${gaugeBar(data.gpu.utilization, cpuBarW)} ${data.gpu.utilization}% ${formatBytes(data.gpu.memory)}`, rightW));
|
|
837
|
-
}
|
|
838
|
-
const combinedBody = hstack([leftLines, rightLines], 2, contentWidth);
|
|
839
|
-
return panel("1 cpu", combinedBody, width, theme.cpu, theme.border, combinedBody.length, rightTitle);
|
|
840
|
-
} else {
|
|
841
|
-
const lines = [];
|
|
842
|
-
lines.push(chalk2.bold(truncatePlain(data.cpu.brand, contentWidth)));
|
|
843
|
-
lines.push(`CPU ${gaugeBar(data.cpu.usage, Math.max(4, contentWidth - 12))} ${data.cpu.usage.toFixed(0)}%`);
|
|
844
|
-
if (opts2.history?.cpuUsage.length) {
|
|
845
|
-
const gMode = opts2.graphMode || "spark";
|
|
846
|
-
const gStr = gMode === "bar" ? sparkline(opts2.history.cpuUsage, { min: 0, max: 100 }) : brailleGraph(opts2.history.cpuUsage, { min: 0, max: 100 });
|
|
847
|
-
lines.push(theme.cpu(gStr));
|
|
848
|
-
}
|
|
849
|
-
lines.push(`Load: ${data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" ")}`);
|
|
850
|
-
return panel("1 cpu", lines, width, theme.cpu, theme.border, lines.length, rightTitle);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
function btopLeftColumn(data, width, theme, opts2) {
|
|
854
|
-
const contentWidth = width - 4;
|
|
855
|
-
const out = [];
|
|
856
|
-
const layout = opts2.panelLayout || ["mem", "disk", "net"];
|
|
857
|
-
for (const panelName of layout) {
|
|
858
|
-
if (panelName === "mem") {
|
|
859
|
-
const memLines = [];
|
|
860
|
-
memLines.push(`RAM [${gaugeBar(data.memory.usagePercent, Math.max(4, contentWidth - 12))}] ${data.memory.usagePercent}%`);
|
|
861
|
-
memLines.push(`Used: ${formatBytes(data.memory.used)} / ${formatBytes(data.memory.total)}`);
|
|
862
|
-
memLines.push(`Free: ${formatBytes(data.memory.free)} \xB7 Cache: ${formatBytes(data.memory.total - data.memory.free - data.memory.used)}`);
|
|
863
|
-
if (data.memory.swapTotal > 0) {
|
|
864
|
-
const swapPct = Math.round(data.memory.swapUsed / data.memory.swapTotal * 100);
|
|
865
|
-
memLines.push(`Swap [${gaugeBar(swapPct, Math.max(4, contentWidth - 12))}] ${swapPct}%`);
|
|
866
|
-
}
|
|
867
|
-
out.push(...panel("2 mem", memLines, width, theme.mem, theme.border, memLines.length, `${formatBytes(data.memory.used)}`));
|
|
868
|
-
} else if (panelName === "disk" && data.disk.length) {
|
|
869
|
-
const diskLines = [];
|
|
870
|
-
for (const d of data.disk.slice(0, 2)) {
|
|
871
|
-
const capNum = parseInt(d.capacity, 10) || 0;
|
|
872
|
-
const mountShort = truncatePlain(d.mountpoint, 8);
|
|
873
|
-
let ioStr = "";
|
|
874
|
-
if (d.readBytesSec !== void 0 || d.writeBytesSec !== void 0) {
|
|
875
|
-
const rStr = formatBytes(d.readBytesSec || 0);
|
|
876
|
-
const wStr = formatBytes(d.writeBytesSec || 0);
|
|
877
|
-
ioStr = ` R:${rStr}/s W:${wStr}/s`;
|
|
878
|
-
}
|
|
879
|
-
diskLines.push(`${mountShort} ${d.used}/${d.size} [${gaugeBar(capNum, Math.max(3, contentWidth - 18 - ioStr.length))}] ${d.capacity}${ioStr}`);
|
|
880
|
-
}
|
|
881
|
-
const totalRead = data.disk.reduce((acc, d) => acc + (d.readBytesSec || 0), 0);
|
|
882
|
-
const totalWrite = data.disk.reduce((acc, d) => acc + (d.writeBytesSec || 0), 0);
|
|
883
|
-
const diskHeaderRight = `R:${formatBytes(totalRead)}/s W:${formatBytes(totalWrite)}/s`;
|
|
884
|
-
out.push(...panel("0 disk io", diskLines, width, theme.disk, theme.border, diskLines.length, diskHeaderRight));
|
|
885
|
-
} else if (panelName === "net") {
|
|
886
|
-
const netLines = [];
|
|
887
|
-
const rxRate = opts2.history?.netRxRate.length ? opts2.history.netRxRate[opts2.history.netRxRate.length - 1] : 0;
|
|
888
|
-
const txRate = opts2.history?.netTxRate.length ? opts2.history.netTxRate[opts2.history.netTxRate.length - 1] : 0;
|
|
889
|
-
netLines.push(`down \u25BC ${formatBytes(rxRate)}/s (${formatBytes(data.network.rxBytes)})`);
|
|
890
|
-
if (opts2.history?.netRxRate.length) {
|
|
891
|
-
const gMode = opts2.graphMode || "spark";
|
|
892
|
-
const gStr = gMode === "bar" ? sparkline(opts2.history.netRxRate, {}) : brailleGraph(opts2.history.netRxRate, {});
|
|
893
|
-
netLines.push(chalk2.cyan(fitVisible(gStr, contentWidth)));
|
|
894
|
-
}
|
|
895
|
-
netLines.push(`up \u25B2 ${formatBytes(txRate)}/s (${formatBytes(data.network.txBytes)})`);
|
|
896
|
-
if (opts2.history?.netTxRate.length) {
|
|
897
|
-
const gMode = opts2.graphMode || "spark";
|
|
898
|
-
const gStr = gMode === "bar" ? sparkline(opts2.history.netTxRate, {}) : brailleGraph(opts2.history.netTxRate, {});
|
|
899
|
-
netLines.push(theme.network(fitVisible(gStr, contentWidth)));
|
|
900
|
-
}
|
|
901
|
-
out.push(...panel("7 net", netLines, width, theme.network, theme.border, netLines.length, `${data.network.interface}`));
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
return out;
|
|
905
|
-
}
|
|
906
|
-
function btopProcBox(data, width, targetHeight, theme, opts2) {
|
|
907
|
-
const contentWidth = width - 4;
|
|
908
|
-
const filtered = opts2.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts2.filter.toLowerCase())) : data.processes;
|
|
909
|
-
const sorted = sortProcesses(filtered, opts2.sortBy ?? "cpu");
|
|
910
|
-
const headerRowOverhead = 2;
|
|
911
|
-
const tableHeaderOverhead = 2;
|
|
912
|
-
const isTracking = opts2.trackedPid !== void 0 && opts2.trackedPid !== null;
|
|
913
|
-
const trackingLines = [];
|
|
914
|
-
if (isTracking) {
|
|
915
|
-
const tp = sorted.find((p) => p.pid === opts2.trackedPid);
|
|
916
|
-
if (tp) {
|
|
917
|
-
const cpuColor = tp.cpu > 50 ? chalk2.red.bold : tp.cpu > 20 ? chalk2.yellow : chalk2.green;
|
|
918
|
-
const memColor = tp.mem > 50 ? chalk2.red.bold : tp.mem > 20 ? chalk2.yellow : chalk2.green;
|
|
919
|
-
trackingLines.push(chalk2.bgHex("#1a1a2e").hex("#e94560").bold(` \u25C9 PID ${tp.pid} ${truncatePlain(tp.command, Math.max(6, contentWidth - 20))} `));
|
|
920
|
-
trackingLines.push(
|
|
921
|
-
` ${cpuColor("CPU " + tp.cpu.toFixed(1) + "%")} ${memColor("MEM " + tp.mem.toFixed(1) + "%")} ${chalk2.dim(tp.state)} THR:${tp.threads} ${formatRuntime(tp.runtime)}`
|
|
922
|
-
);
|
|
923
|
-
trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
|
|
924
|
-
} else {
|
|
925
|
-
trackingLines.push(chalk2.yellow(` \u25C9 PID ${opts2.trackedPid} exited`));
|
|
926
|
-
trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
const availableRows = Math.max(1, targetHeight - headerRowOverhead - tableHeaderOverhead - trackingLines.length);
|
|
930
|
-
let selectedIdx = opts2.selectedProcessIndex ?? -1;
|
|
931
|
-
if (isTracking) {
|
|
932
|
-
const trackedIdx = sorted.findIndex((p) => p.pid === opts2.trackedPid);
|
|
933
|
-
if (trackedIdx >= 0) {
|
|
934
|
-
selectedIdx = trackedIdx;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
let offset = 0;
|
|
938
|
-
if (selectedIdx >= 0) {
|
|
939
|
-
if (selectedIdx >= availableRows) {
|
|
940
|
-
offset = selectedIdx - availableRows + 1;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
const visible = sorted.slice(offset, offset + availableRows);
|
|
944
|
-
const pidW = 6, userW = 7, thrW = 3, memW = 6, cpuW = 6;
|
|
945
|
-
const fixedW = pidW + userW + thrW + memW + cpuW + 5;
|
|
946
|
-
const cmdW = Math.max(8, contentWidth - fixedW);
|
|
947
|
-
const head = chalk2.bold("PID".padEnd(pidW)) + " " + chalk2.bold("PROGRAM".padEnd(cmdW)) + " " + chalk2.bold("USER".padEnd(userW)) + " " + chalk2.bold("THR".padStart(thrW)) + " " + chalk2.bold("MEM%".padStart(memW)) + " " + chalk2.bold("CPU%".padStart(cpuW));
|
|
948
|
-
if (opts2.treeView) {
|
|
949
|
-
const treeLines = buildProcessTree(visible);
|
|
950
|
-
let rightTitle2 = opts2.filter ? `filter: "${opts2.filter}"` : `tree \xB7 sort: ${opts2.sortBy || "cpu"}`;
|
|
951
|
-
if (isTracking) rightTitle2 += ` \xB7 following PID ${opts2.trackedPid}`;
|
|
952
|
-
return panel("P proc", [...trackingLines, head, theme.border("\u2500".repeat(contentWidth)), ...treeLines], width, theme.process, theme.border, targetHeight - 2, rightTitle2);
|
|
953
|
-
}
|
|
954
|
-
const rows = visible.map((p, idx) => {
|
|
955
|
-
const isSelected = offset + idx === selectedIdx;
|
|
956
|
-
const cpuVal = p.cpu.toFixed(1) + "%";
|
|
957
|
-
const cpuStr = p.cpu > 50 ? chalk2.red.bold(cpuVal.padStart(cpuW)) : p.cpu > 20 ? chalk2.yellow(cpuVal.padStart(cpuW)) : chalk2.green(cpuVal.padStart(cpuW));
|
|
958
|
-
const memStr = (p.mem.toFixed(1) + "%").padStart(memW);
|
|
959
|
-
const userStr = truncatePlain(p.user, userW).padEnd(userW);
|
|
960
|
-
const thrStr = String(p.threads).padStart(thrW);
|
|
961
|
-
const pidStr = String(p.pid).padEnd(pidW);
|
|
962
|
-
const cmdStr = truncatePlain(p.command, cmdW).padEnd(cmdW);
|
|
963
|
-
const line = `${pidStr} ${cmdStr} ${userStr} ${thrStr} ${memStr} ${cpuStr}`;
|
|
964
|
-
return isSelected ? chalk2.bgCyan.black(line) : line;
|
|
965
|
-
});
|
|
966
|
-
const lines = [...trackingLines, head, theme.border("\u2500".repeat(contentWidth)), ...rows];
|
|
967
|
-
if (rows.length === 0) {
|
|
968
|
-
lines.push(chalk2.dim(`No processes match "${opts2.filter || ""}"`));
|
|
969
|
-
}
|
|
970
|
-
let rightTitle = opts2.filter ? `filter: "${opts2.filter}"` : `sort: ${opts2.sortBy || "cpu"}`;
|
|
971
|
-
if (isTracking) {
|
|
972
|
-
rightTitle += ` \xB7 following PID ${opts2.trackedPid}`;
|
|
973
|
-
}
|
|
974
|
-
return panel("P proc", lines, width, theme.process, theme.border, targetHeight - 2, rightTitle);
|
|
975
|
-
}
|
|
976
|
-
function formatTable(data, opts2 = {}) {
|
|
977
|
-
const width = clampWidth(opts2.width);
|
|
978
|
-
const activePanel = opts2.activePanel || "grid";
|
|
979
|
-
const themeName = opts2.theme || "default";
|
|
980
|
-
const theme = THEMES[themeName] || THEMES.default;
|
|
981
|
-
const out = [];
|
|
982
|
-
out.push(...header(data, width, [], theme.border));
|
|
983
|
-
out.push("");
|
|
984
|
-
const anomalies = opts2.anomalies ?? [];
|
|
985
|
-
if (anomalies.length && activePanel === "grid") {
|
|
986
|
-
out.push(...anomalyPanel(anomalies, width, theme));
|
|
987
|
-
out.push("");
|
|
988
|
-
}
|
|
989
|
-
out.push(tabBar(activePanel, width, theme));
|
|
990
|
-
out.push("");
|
|
991
|
-
if (activePanel !== "grid") {
|
|
992
|
-
const lines = activeDetailLines(data, activePanel, width, opts2);
|
|
993
|
-
if (lines) {
|
|
994
|
-
out.push(...panel(detailPanelTitle(activePanel, opts2.trackedPid), lines, width, theme[activePanel] ?? theme.cpu, theme.border));
|
|
995
|
-
}
|
|
996
|
-
} else {
|
|
997
|
-
out.push(...btopCpuBox(data, width, theme, opts2));
|
|
998
|
-
out.push("");
|
|
999
|
-
if (width >= 85) {
|
|
1000
|
-
const leftColW = Math.max(32, Math.floor(width * 0.38));
|
|
1001
|
-
const rightColW = width - leftColW - 2;
|
|
1002
|
-
const leftLines = btopLeftColumn(data, leftColW, theme, opts2);
|
|
1003
|
-
const procLines = btopProcBox(data, rightColW, leftLines.length, theme, opts2);
|
|
1004
|
-
out.push(...hstack([leftLines, procLines], 2, width));
|
|
1005
|
-
} else {
|
|
1006
|
-
const leftLines = btopLeftColumn(data, width, theme, opts2);
|
|
1007
|
-
out.push(...leftLines);
|
|
1008
|
-
out.push("");
|
|
1009
|
-
const procLines = btopProcBox(data, width, 12, theme, opts2);
|
|
1010
|
-
out.push(...procLines);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
return out.join("\n");
|
|
1014
|
-
}
|
|
1015
|
-
function clampWidth(width) {
|
|
1016
|
-
if (!width || Number.isNaN(width)) return 80;
|
|
1017
|
-
return Math.max(40, Math.min(width, 240));
|
|
1018
|
-
}
|
|
31
|
+
import chalk10 from "chalk";
|
|
1019
32
|
|
|
1020
33
|
// src/formatters/output.ts
|
|
1021
|
-
import
|
|
34
|
+
import chalk from "chalk";
|
|
1022
35
|
function formatJson(data) {
|
|
1023
36
|
return JSON.stringify(data, null, 2);
|
|
1024
37
|
}
|
|
@@ -1531,17 +544,17 @@ function formatGraphs(history, width = 80, themeName = "default", graphMode = "s
|
|
|
1531
544
|
}
|
|
1532
545
|
function graphRow(label, values, bounds, fmt, sparkWidth = 40, emptyMessage = "collecting data...", mode = "spark", contentWidth = 76) {
|
|
1533
546
|
if (!values.length) {
|
|
1534
|
-
return `${
|
|
547
|
+
return `${chalk.dim(label.padEnd(10))} ${chalk.dim(emptyMessage)}`;
|
|
1535
548
|
}
|
|
1536
549
|
const visible = values.slice(-sparkWidth);
|
|
1537
550
|
const spark = mode === "spark" ? sparkline(visible, bounds) : barGraph(visible, bounds, sparkWidth);
|
|
1538
551
|
const current = values[values.length - 1];
|
|
1539
|
-
let color =
|
|
552
|
+
let color = chalk.cyan;
|
|
1540
553
|
if (bounds.min !== void 0 && bounds.max !== void 0) {
|
|
1541
554
|
const pct = (current - bounds.min) / (bounds.max - bounds.min || 1) * 100;
|
|
1542
|
-
color = pct > 90 ?
|
|
555
|
+
color = pct > 90 ? chalk.red : pct > 70 ? chalk.yellow : chalk.green;
|
|
1543
556
|
}
|
|
1544
|
-
const raw = `${
|
|
557
|
+
const raw = `${chalk.dim(label.padEnd(10))}${color(spark)} ${chalk.bold(fmt(current))}`;
|
|
1545
558
|
return fitVisible(raw, contentWidth);
|
|
1546
559
|
}
|
|
1547
560
|
function barGraph(values, bounds, width) {
|
|
@@ -2520,10 +1533,10 @@ function getToggleKey(opt) {
|
|
|
2520
1533
|
}
|
|
2521
1534
|
|
|
2522
1535
|
// src/live/export.ts
|
|
2523
|
-
import
|
|
2524
|
-
import
|
|
1536
|
+
import fs from "fs";
|
|
1537
|
+
import path from "path";
|
|
2525
1538
|
function ensureDir(dir) {
|
|
2526
|
-
if (!
|
|
1539
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
2527
1540
|
}
|
|
2528
1541
|
function timestampForFile() {
|
|
2529
1542
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -2536,7 +1549,7 @@ function exportSnapshot() {
|
|
|
2536
1549
|
try {
|
|
2537
1550
|
ensureDir(state.exportDir);
|
|
2538
1551
|
const ext = state.exportFormat === "json" ? "json" : state.exportFormat === "csv" ? "csv" : state.exportFormat === "tsv" ? "tsv" : state.exportFormat === "html" ? "html" : "md";
|
|
2539
|
-
const file =
|
|
1552
|
+
const file = path.join(state.exportDir, `pyre-${timestampForFile()}.${ext}`);
|
|
2540
1553
|
let content;
|
|
2541
1554
|
switch (state.exportFormat) {
|
|
2542
1555
|
case "json":
|
|
@@ -2555,7 +1568,7 @@ function exportSnapshot() {
|
|
|
2555
1568
|
content = formatMarkdown(state.lastData);
|
|
2556
1569
|
break;
|
|
2557
1570
|
}
|
|
2558
|
-
|
|
1571
|
+
fs.writeFileSync(file, content);
|
|
2559
1572
|
setStatus(`Exported snapshot -> ${file}`);
|
|
2560
1573
|
} catch (err) {
|
|
2561
1574
|
setStatus(`Export failed: ${err.message}`);
|
|
@@ -2565,8 +1578,8 @@ function startLogging() {
|
|
|
2565
1578
|
try {
|
|
2566
1579
|
ensureDir(state.exportDir);
|
|
2567
1580
|
rotateLogs();
|
|
2568
|
-
const file =
|
|
2569
|
-
state.logStream =
|
|
1581
|
+
const file = path.join(state.exportDir, `pyre-log-${timestampForFile()}.csv`);
|
|
1582
|
+
state.logStream = fs.createWriteStream(file, { flags: "a" });
|
|
2570
1583
|
state.logStream.write("timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_packets,net_tx_packets,connections,thermal_state\n");
|
|
2571
1584
|
state.logging = true;
|
|
2572
1585
|
setStatus(`Logging every tick -> ${file}`);
|
|
@@ -2580,37 +1593,37 @@ var MAX_LOG_BYTES = 50 * 1024 * 1024;
|
|
|
2580
1593
|
function rotateLogs() {
|
|
2581
1594
|
try {
|
|
2582
1595
|
const dir = state.exportDir;
|
|
2583
|
-
if (!
|
|
2584
|
-
const files =
|
|
1596
|
+
if (!fs.existsSync(dir)) return;
|
|
1597
|
+
const files = fs.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv")).map((f) => ({ name: f, path: path.join(dir, f), mtime: fs.statSync(path.join(dir, f)).mtimeMs, size: fs.statSync(path.join(dir, f)).size })).sort((a, b) => a.mtime - b.mtime);
|
|
2585
1598
|
const now = Date.now();
|
|
2586
1599
|
for (const f of files) {
|
|
2587
1600
|
if (now - f.mtime > MAX_LOG_AGE_MS) {
|
|
2588
1601
|
try {
|
|
2589
|
-
|
|
1602
|
+
fs.unlinkSync(f.path);
|
|
2590
1603
|
} catch {
|
|
2591
1604
|
}
|
|
2592
1605
|
}
|
|
2593
1606
|
}
|
|
2594
|
-
const remaining =
|
|
1607
|
+
const remaining = fs.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
|
|
2595
1608
|
if (remaining.length > MAX_LOG_COUNT) {
|
|
2596
1609
|
const toDelete = remaining.sort().slice(0, remaining.length - MAX_LOG_COUNT);
|
|
2597
1610
|
for (const f of toDelete) {
|
|
2598
1611
|
try {
|
|
2599
|
-
|
|
1612
|
+
fs.unlinkSync(path.join(dir, f));
|
|
2600
1613
|
} catch {
|
|
2601
1614
|
}
|
|
2602
1615
|
}
|
|
2603
1616
|
}
|
|
2604
1617
|
let totalSize = 0;
|
|
2605
1618
|
for (const f of remaining) {
|
|
2606
|
-
totalSize +=
|
|
1619
|
+
totalSize += fs.statSync(path.join(dir, f)).size;
|
|
2607
1620
|
}
|
|
2608
1621
|
if (totalSize > MAX_LOG_BYTES) {
|
|
2609
1622
|
const sorted = remaining.sort();
|
|
2610
1623
|
for (const f of sorted) {
|
|
2611
|
-
const sz =
|
|
1624
|
+
const sz = fs.statSync(path.join(dir, f)).size;
|
|
2612
1625
|
try {
|
|
2613
|
-
|
|
1626
|
+
fs.unlinkSync(path.join(dir, f));
|
|
2614
1627
|
} catch {
|
|
2615
1628
|
}
|
|
2616
1629
|
totalSize -= sz;
|
|
@@ -2647,10 +1660,10 @@ function writeLogRow(data) {
|
|
|
2647
1660
|
|
|
2648
1661
|
// src/live/render.ts
|
|
2649
1662
|
import { execFile, exec } from "child_process";
|
|
2650
|
-
import
|
|
2651
|
-
import
|
|
1663
|
+
import fs2 from "fs";
|
|
1664
|
+
import path2 from "path";
|
|
2652
1665
|
import { fileURLToPath } from "url";
|
|
2653
|
-
import
|
|
1666
|
+
import chalk2 from "chalk";
|
|
2654
1667
|
|
|
2655
1668
|
// src/anomalies.ts
|
|
2656
1669
|
var ZSCORE_THRESHOLD = 2.5;
|
|
@@ -2792,55 +1805,55 @@ function p2pPanelLines() {
|
|
|
2792
1805
|
const events = state.p2pServer ? state.p2pServer.peerEventHistory : state.p2pEvents;
|
|
2793
1806
|
const body = [];
|
|
2794
1807
|
const bindAddr = state.p2pBind || `0.0.0.0:${state.p2pPort}`;
|
|
2795
|
-
body.push(`Status: ${state.p2pServerRunning ?
|
|
1808
|
+
body.push(`Status: ${state.p2pServerRunning ? chalk2.green("Running") : chalk2.red("Stopped")}`);
|
|
2796
1809
|
body.push(`Bind: ${bindAddr}`);
|
|
2797
1810
|
body.push(`Password: ${state.p2pPassword}`);
|
|
2798
1811
|
body.push("");
|
|
2799
1812
|
const typeColor = (t) => {
|
|
2800
|
-
if (t === "auth") return
|
|
2801
|
-
if (t === "disconnect") return
|
|
2802
|
-
if (t === "error" || t === "blocked" || t === "rate_limited") return
|
|
2803
|
-
return
|
|
1813
|
+
if (t === "auth") return chalk2.green;
|
|
1814
|
+
if (t === "disconnect") return chalk2.yellow;
|
|
1815
|
+
if (t === "error" || t === "blocked" || t === "rate_limited") return chalk2.red;
|
|
1816
|
+
return chalk2.cyan;
|
|
2804
1817
|
};
|
|
2805
|
-
body.push(
|
|
1818
|
+
body.push(chalk2.dim("Events (most recent):"));
|
|
2806
1819
|
const recent = events.slice(-20).reverse();
|
|
2807
1820
|
for (const evt of recent) {
|
|
2808
1821
|
const time = new Date(evt.ts).toLocaleTimeString();
|
|
2809
|
-
body.push(`${
|
|
1822
|
+
body.push(`${chalk2.dim(time)} ${typeColor(evt.type)(evt.type.padEnd(14))} ${evt.detail}`);
|
|
2810
1823
|
}
|
|
2811
1824
|
const title = `P2P \xB7 ${state.p2pServerRunning ? "ON" : "OFF"} \xB7 ${events.length} events`;
|
|
2812
1825
|
return { title, body };
|
|
2813
1826
|
}
|
|
2814
1827
|
function renderCustomizerOverlay(scrollOffset, maxVisibleItems) {
|
|
2815
1828
|
const themesList = Object.keys(THEMES);
|
|
2816
|
-
const headerLine =
|
|
1829
|
+
const headerLine = chalk2.bgCyan.black.bold(" UI CUSTOMIZER (Use \u2191/\u2193 or J/K to navigate, Enter/Space to toggle, Esc to exit) ");
|
|
2817
1830
|
const totalItems = state.CUSTOMIZER_OPTIONS.length;
|
|
2818
1831
|
const end = Math.min(scrollOffset + maxVisibleItems, totalItems);
|
|
2819
1832
|
const lines = [headerLine];
|
|
2820
1833
|
for (let i = scrollOffset; i < end; i++) {
|
|
2821
1834
|
const opt = state.CUSTOMIZER_OPTIONS[i];
|
|
2822
1835
|
const isSelected = i === state.customizerIndex;
|
|
2823
|
-
const prefix = isSelected ?
|
|
1836
|
+
const prefix = isSelected ? chalk2.yellow("\u25B6 ") : " ";
|
|
2824
1837
|
if (opt === "Theme") {
|
|
2825
|
-
lines.push(`${prefix}${opt}: ${
|
|
1838
|
+
lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.currentTheme)} [${themesList.join(", ")}]`);
|
|
2826
1839
|
} else if (opt === "Graph Mode") {
|
|
2827
|
-
lines.push(`${prefix}${opt}: ${
|
|
1840
|
+
lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.graphMode)} [spark, bar]`);
|
|
2828
1841
|
} else if (opt === "Splash Screen") {
|
|
2829
|
-
const status = state.splashEnabled ?
|
|
1842
|
+
const status = state.splashEnabled ? chalk2.green("[ON]") : chalk2.red("[OFF]");
|
|
2830
1843
|
lines.push(`${prefix}${opt}: ${status}`);
|
|
2831
1844
|
} else if (opt === "Splash Color") {
|
|
2832
|
-
lines.push(`${prefix}${opt}: ${
|
|
1845
|
+
lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.splashColorScheme)} [fire, ocean, forest, purple, monochrome]`);
|
|
2833
1846
|
} else if (opt === "Splash Animation") {
|
|
2834
|
-
lines.push(`${prefix}${opt}: ${
|
|
1847
|
+
lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.splashAnimation)} [classic, wave, sparks]`);
|
|
2835
1848
|
} else if (opt === "Notifications") {
|
|
2836
|
-
const status = state.notificationsEnabled ?
|
|
1849
|
+
const status = state.notificationsEnabled ? chalk2.green("[ON]") : chalk2.red("[OFF]");
|
|
2837
1850
|
lines.push(`${prefix}${opt}: ${status}`);
|
|
2838
1851
|
} else if (opt === "Temperature Unit") {
|
|
2839
|
-
lines.push(`${prefix}${opt}: ${
|
|
1852
|
+
lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.tempUnit.toUpperCase())} [C, F]`);
|
|
2840
1853
|
} else {
|
|
2841
1854
|
const toggleKey = getToggleKey(opt);
|
|
2842
1855
|
const isVisible = toggleKey ? toggleKey === "tree" ? state.treeView : state.visiblePanels[toggleKey] !== false : true;
|
|
2843
|
-
const status = isVisible ?
|
|
1856
|
+
const status = isVisible ? chalk2.green("[VISIBLE]") : chalk2.red("[HIDDEN]");
|
|
2844
1857
|
lines.push(`${prefix}${opt}: ${status}`);
|
|
2845
1858
|
}
|
|
2846
1859
|
}
|
|
@@ -2872,7 +1885,7 @@ function render() {
|
|
|
2872
1885
|
const rows = state.termHeight || 24;
|
|
2873
1886
|
const cols = state.termWidth || 80;
|
|
2874
1887
|
if (cols < 50 || rows < 15) {
|
|
2875
|
-
const msg =
|
|
1888
|
+
const msg = chalk2.yellow(`pyre ${rows}x${cols} too small. Resize terminal to >= 50x20.`);
|
|
2876
1889
|
const centered = Math.max(0, Math.floor((rows - 2) / 2));
|
|
2877
1890
|
const out = [];
|
|
2878
1891
|
for (let i = 0; i < centered; i++) out.push("");
|
|
@@ -2982,17 +1995,17 @@ function render() {
|
|
|
2982
1995
|
bodyLines.push("");
|
|
2983
1996
|
bodyLines.push(...renderCustomizerOverlay(scrollOffset, maxVisibleItems).split("\n"));
|
|
2984
1997
|
} else if (state.inputMode === "filter") {
|
|
2985
|
-
bodyLines.push(
|
|
1998
|
+
bodyLines.push(chalk2.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
2986
1999
|
} else if (state.inputMode === "kill") {
|
|
2987
|
-
bodyLines.push(
|
|
2000
|
+
bodyLines.push(chalk2.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
|
|
2988
2001
|
} else if (state.inputMode === "signal") {
|
|
2989
2002
|
const sigIdx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
|
|
2990
|
-
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ?
|
|
2991
|
-
bodyLines.push(
|
|
2992
|
-
bodyLines.push(
|
|
2993
|
-
bodyLines.push(
|
|
2003
|
+
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ? chalk2.yellow(s) : chalk2.dim(s)).join(" ");
|
|
2004
|
+
bodyLines.push(chalk2.cyan(` Signal: ${state.inputBuffer}_`));
|
|
2005
|
+
bodyLines.push(chalk2.dim(` ${sigList}`));
|
|
2006
|
+
bodyLines.push(chalk2.dim(" \u2191/\u2193 to select, Enter to confirm, Esc to cancel"));
|
|
2994
2007
|
} else if (state.inputMode === "p2p") {
|
|
2995
|
-
bodyLines.push(
|
|
2008
|
+
bodyLines.push(chalk2.cyan(` P2P password (${state.p2pPort}): ${state.inputBuffer}_ (enter to start, esc to cancel)`));
|
|
2996
2009
|
} else if (state.statusMessage) {
|
|
2997
2010
|
bodyLines.push(` ${state.statusMessage}`);
|
|
2998
2011
|
}
|
|
@@ -3031,14 +2044,14 @@ function footerLine() {
|
|
|
3031
2044
|
["+/-", `${state.interval}s`],
|
|
3032
2045
|
["r", state.p2pServerRunning ? "stop p2p" : "start p2p"]
|
|
3033
2046
|
];
|
|
3034
|
-
const fmt = (list) => list.map(([k, label]) => `${
|
|
2047
|
+
const fmt = (list) => list.map(([k, label]) => `${chalk2.hex("#50fa7b").bold(k)} ${chalk2.dim(label)}`).join(chalk2.dim(" | "));
|
|
3035
2048
|
const str1 = fmt(row1);
|
|
3036
2049
|
const str2 = fmt(row2);
|
|
3037
2050
|
const badges = [];
|
|
3038
|
-
if (state.paused) badges.push(
|
|
3039
|
-
if (state.logging) badges.push(
|
|
3040
|
-
if (state.activePanel !== "grid") badges.push(
|
|
3041
|
-
if (state.p2pServerRunning) badges.push(
|
|
2051
|
+
if (state.paused) badges.push(chalk2.yellow.bold("\u23F8 PAUSED"));
|
|
2052
|
+
if (state.logging) badges.push(chalk2.red.bold("\u25CF REC"));
|
|
2053
|
+
if (state.activePanel !== "grid") badges.push(chalk2.cyan.bold(`\u25C9 ${state.activePanel.toUpperCase()}`));
|
|
2054
|
+
if (state.p2pServerRunning) badges.push(chalk2.green.bold(`P2P ${state.p2pBind || "0.0.0.0"}:${state.p2pPort}`));
|
|
3042
2055
|
const badgeStr = badges.join(" ") || " ";
|
|
3043
2056
|
return [str1, str2, badgeStr];
|
|
3044
2057
|
}
|
|
@@ -3085,7 +2098,7 @@ function checkAlerts(data) {
|
|
|
3085
2098
|
for (const a of anomalies) {
|
|
3086
2099
|
reasons.push(`${a.metric} anomaly (\u03C3=${a.zScore.toFixed(1)})`);
|
|
3087
2100
|
}
|
|
3088
|
-
setStatus(
|
|
2101
|
+
setStatus(chalk2.red(`\u26A0 Alert: ${reasons.join("; ")}`), 5e3);
|
|
3089
2102
|
sendNotification("pyre Alert", reasons.join("; "));
|
|
3090
2103
|
const alertMsg = reasons.join("; ");
|
|
3091
2104
|
if (state.webhookUrl) {
|
|
@@ -3108,30 +2121,30 @@ function renderSplashMenu(cols, rows) {
|
|
|
3108
2121
|
const out = [];
|
|
3109
2122
|
const contentLines = [];
|
|
3110
2123
|
const logo = [
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
2124
|
+
chalk2.hex("#ff6004").bold("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"),
|
|
2125
|
+
chalk2.hex("#ff8004").bold("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D"),
|
|
2126
|
+
chalk2.hex("#ffa004").bold("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 "),
|
|
2127
|
+
chalk2.hex("#ffc004").bold("\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D "),
|
|
2128
|
+
chalk2.hex("#ffe004").bold("\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"),
|
|
2129
|
+
chalk2.hex("#ffff40").bold("\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D")
|
|
3117
2130
|
];
|
|
3118
2131
|
const menuTop = Math.max(0, Math.floor(rows / 2) - 8);
|
|
3119
2132
|
for (let i = 0; i < menuTop; i++) out.push("");
|
|
3120
2133
|
for (const line of logo) contentLines.push(line);
|
|
3121
2134
|
contentLines.push("");
|
|
3122
|
-
contentLines.push(
|
|
2135
|
+
contentLines.push(chalk2.dim("system monitor v9.0.0"));
|
|
3123
2136
|
contentLines.push("");
|
|
3124
2137
|
contentLines.push("");
|
|
3125
2138
|
for (let i = 0; i < MENU_OPTIONS.length; i++) {
|
|
3126
2139
|
const isSelected = i === state.menuSelectionIndex;
|
|
3127
2140
|
const opt = MENU_OPTIONS[i];
|
|
3128
|
-
const prefix = isSelected ?
|
|
3129
|
-
const text = isSelected ?
|
|
2141
|
+
const prefix = isSelected ? chalk2.cyan.bold(" \u25B6 ") : " ";
|
|
2142
|
+
const text = isSelected ? chalk2.bgCyan.black(` ${opt} `) : chalk2.dim(` ${opt} `);
|
|
3130
2143
|
contentLines.push(prefix + text);
|
|
3131
2144
|
}
|
|
3132
2145
|
contentLines.push("");
|
|
3133
2146
|
contentLines.push("");
|
|
3134
|
-
contentLines.push(
|
|
2147
|
+
contentLines.push(chalk2.dim("Use \u2191/\u2193 to navigate, Enter to select"));
|
|
3135
2148
|
while (contentLines.length + menuTop < rows) contentLines.push("");
|
|
3136
2149
|
const maxWidth = Math.max(...contentLines.map(visLen));
|
|
3137
2150
|
const padding = Math.max(0, Math.floor((cols - maxWidth) / 2));
|
|
@@ -3145,14 +2158,14 @@ function getReadmeLines() {
|
|
|
3145
2158
|
if (cachedReadmeLines) return cachedReadmeLines;
|
|
3146
2159
|
try {
|
|
3147
2160
|
const __filename = fileURLToPath(import.meta.url);
|
|
3148
|
-
const __dirname =
|
|
3149
|
-
const rootDir =
|
|
3150
|
-
const readmePath =
|
|
3151
|
-
const content =
|
|
2161
|
+
const __dirname = path2.dirname(__filename);
|
|
2162
|
+
const rootDir = path2.resolve(__dirname, "..", "..");
|
|
2163
|
+
const readmePath = path2.join(rootDir, "README.md");
|
|
2164
|
+
const content = fs2.readFileSync(readmePath, "utf8");
|
|
3152
2165
|
cachedReadmeLines = content.split("\n");
|
|
3153
2166
|
return cachedReadmeLines;
|
|
3154
2167
|
} catch (err) {
|
|
3155
|
-
return [
|
|
2168
|
+
return [chalk2.red("Failed to load README.md")];
|
|
3156
2169
|
}
|
|
3157
2170
|
}
|
|
3158
2171
|
function renderReadme(cols, rows) {
|
|
@@ -3163,43 +2176,43 @@ function renderReadme(cols, rows) {
|
|
|
3163
2176
|
const content = lines.slice(startIdx, startIdx + availableLines).map((l) => {
|
|
3164
2177
|
let out2 = l;
|
|
3165
2178
|
if (out2.startsWith("#")) {
|
|
3166
|
-
out2 =
|
|
2179
|
+
out2 = chalk2.cyan.bold(out2);
|
|
3167
2180
|
} else if (out2.startsWith("```")) {
|
|
3168
|
-
out2 =
|
|
2181
|
+
out2 = chalk2.dim(out2);
|
|
3169
2182
|
} else if (out2.startsWith("- ") || out2.startsWith("* ")) {
|
|
3170
|
-
out2 = " " +
|
|
2183
|
+
out2 = " " + chalk2.yellow(out2.substring(0, 2)) + out2.substring(2);
|
|
3171
2184
|
}
|
|
3172
2185
|
return out2;
|
|
3173
2186
|
});
|
|
3174
2187
|
const out = [];
|
|
3175
2188
|
const pct = Math.round(startIdx / Math.max(1, lines.length - availableLines) * 100);
|
|
3176
2189
|
const title = lines.length > availableLines ? `README (${pct}%)` : "README";
|
|
3177
|
-
out.push(...panel(title, content, cols,
|
|
2190
|
+
out.push(...panel(title, content, cols, chalk2.cyan, chalk2.dim, availableLines));
|
|
3178
2191
|
return out;
|
|
3179
2192
|
}
|
|
3180
2193
|
function renderCredits(cols, rows) {
|
|
3181
2194
|
const out = [];
|
|
3182
2195
|
const content = [
|
|
3183
|
-
|
|
2196
|
+
chalk2.bold.hex("#ff6004")("Pyre - The Terminal System Monitor"),
|
|
3184
2197
|
"",
|
|
3185
2198
|
"Created with \u2764\uFE0F using Node.js",
|
|
3186
2199
|
"",
|
|
3187
|
-
|
|
2200
|
+
chalk2.cyan("Libraries used:"),
|
|
3188
2201
|
" - chalk (Terminal styling)",
|
|
3189
2202
|
" - systeminformation (System metrics)",
|
|
3190
2203
|
"",
|
|
3191
|
-
|
|
2204
|
+
chalk2.dim("Press Esc or Enter to return to menu.")
|
|
3192
2205
|
];
|
|
3193
|
-
out.push(...panel("CREDITS", content, cols,
|
|
2206
|
+
out.push(...panel("CREDITS", content, cols, chalk2.magenta, chalk2.dim, rows - 2));
|
|
3194
2207
|
return out;
|
|
3195
2208
|
}
|
|
3196
2209
|
|
|
3197
2210
|
// src/p2p/server.ts
|
|
3198
2211
|
import net from "net";
|
|
3199
2212
|
import tls from "tls";
|
|
3200
|
-
import
|
|
2213
|
+
import fs3 from "fs";
|
|
3201
2214
|
import crypto2 from "crypto";
|
|
3202
|
-
import
|
|
2215
|
+
import chalk3 from "chalk";
|
|
3203
2216
|
|
|
3204
2217
|
// src/p2p/protocol.ts
|
|
3205
2218
|
import crypto from "crypto";
|
|
@@ -3210,9 +2223,9 @@ function buildMessage(kind, payload, hmac) {
|
|
|
3210
2223
|
const envelope = { kind, payload };
|
|
3211
2224
|
if (hmac) envelope.hmac = hmac;
|
|
3212
2225
|
const body = Buffer.from(JSON.stringify(envelope), "utf-8");
|
|
3213
|
-
const
|
|
3214
|
-
|
|
3215
|
-
return Buffer.concat([
|
|
2226
|
+
const header = Buffer.alloc(HEADER_SIZE);
|
|
2227
|
+
header.writeUInt32BE(body.length, 0);
|
|
2228
|
+
return Buffer.concat([header, body]);
|
|
3216
2229
|
}
|
|
3217
2230
|
function generateNonce() {
|
|
3218
2231
|
return crypto.randomBytes(32).toString("hex");
|
|
@@ -3342,22 +2355,22 @@ var P2PServer = class {
|
|
|
3342
2355
|
async start() {
|
|
3343
2356
|
if (this.options.auditLog) {
|
|
3344
2357
|
try {
|
|
3345
|
-
|
|
2358
|
+
fs3.mkdirSync(this.options.auditLog, { recursive: true });
|
|
3346
2359
|
const logFile = `${this.options.auditLog}/p2p-audit.log`;
|
|
3347
|
-
this.auditStream =
|
|
2360
|
+
this.auditStream = fs3.createWriteStream(logFile, { flags: "a" });
|
|
3348
2361
|
} catch {
|
|
3349
|
-
this.log(
|
|
2362
|
+
this.log(chalk3.yellow("Warning: could not open audit log"));
|
|
3350
2363
|
}
|
|
3351
2364
|
}
|
|
3352
2365
|
return new Promise((resolve, reject) => {
|
|
3353
2366
|
const useTLS = !!(this.options.tlsCert && this.options.tlsKey);
|
|
3354
2367
|
if (useTLS) {
|
|
3355
2368
|
const tlsOptions = {
|
|
3356
|
-
key:
|
|
3357
|
-
cert:
|
|
2369
|
+
key: fs3.readFileSync(this.options.tlsKey),
|
|
2370
|
+
cert: fs3.readFileSync(this.options.tlsCert)
|
|
3358
2371
|
};
|
|
3359
2372
|
if (this.options.tlsCA) {
|
|
3360
|
-
tlsOptions.ca =
|
|
2373
|
+
tlsOptions.ca = fs3.readFileSync(this.options.tlsCA);
|
|
3361
2374
|
}
|
|
3362
2375
|
this.server = tls.createServer(tlsOptions, (socket) => this.onConnection(socket));
|
|
3363
2376
|
} else {
|
|
@@ -3370,13 +2383,13 @@ var P2PServer = class {
|
|
|
3370
2383
|
const bound = addr ? typeof addr === "object" ? `${addr.address}:${addr.port}` : String(addr) : "unknown";
|
|
3371
2384
|
this.boundAddress = typeof addr === "object" && addr ? addr.address : String(addr);
|
|
3372
2385
|
const protocol = useTLS ? "TLS" : "TCP";
|
|
3373
|
-
this.log(
|
|
3374
|
-
this.log(
|
|
3375
|
-
this.log(
|
|
2386
|
+
this.log(chalk3.green(`P2P server listening on ${bound} (${protocol})`));
|
|
2387
|
+
this.log(chalk3.dim(` Password: ${this.options.password}`));
|
|
2388
|
+
this.log(chalk3.dim(` Interval: ${this.options.intervalMs}ms`));
|
|
3376
2389
|
const allowedIPs = this.options.allowedIPs ?? [];
|
|
3377
2390
|
const deniedIPs = this.options.deniedIPs ?? [];
|
|
3378
|
-
if (allowedIPs.length > 0) this.log(
|
|
3379
|
-
if (deniedIPs.length > 0) this.log(
|
|
2391
|
+
if (allowedIPs.length > 0) this.log(chalk3.dim(` Allowed IPs: ${allowedIPs.join(", ")}`));
|
|
2392
|
+
if (deniedIPs.length > 0) this.log(chalk3.dim(` Denied IPs: ${deniedIPs.join(", ")}`));
|
|
3380
2393
|
this.emitPeerEvent("connect", `Server listening on ${bound}`);
|
|
3381
2394
|
resolve();
|
|
3382
2395
|
});
|
|
@@ -3446,14 +2459,14 @@ var P2PServer = class {
|
|
|
3446
2459
|
const deniedIPs = this.options.deniedIPs ?? [];
|
|
3447
2460
|
if (!isIPAllowed(ip, allowedIPs, deniedIPs)) {
|
|
3448
2461
|
this.audit(ip, "BLOCKED", "IP not allowed");
|
|
3449
|
-
this.log(
|
|
2462
|
+
this.log(chalk3.yellow(`Blocked IP: ${ip}`));
|
|
3450
2463
|
this.emitPeerEvent("blocked", ip);
|
|
3451
2464
|
socket.destroy();
|
|
3452
2465
|
return;
|
|
3453
2466
|
}
|
|
3454
2467
|
if (!this.checkRateLimit(ip)) {
|
|
3455
2468
|
this.audit(ip, "RATE_LIMITED", "Too many auth attempts");
|
|
3456
|
-
this.log(
|
|
2469
|
+
this.log(chalk3.yellow(`Rate limit exceeded for IP: ${ip}`));
|
|
3457
2470
|
this.emitPeerEvent("rate_limited", ip);
|
|
3458
2471
|
socket.destroy();
|
|
3459
2472
|
return;
|
|
@@ -3471,7 +2484,7 @@ var P2PServer = class {
|
|
|
3471
2484
|
};
|
|
3472
2485
|
this.peers.set(socket, peer);
|
|
3473
2486
|
const remoteAddr = `${ip}:${socket.remotePort ?? "?"}`;
|
|
3474
|
-
this.log(
|
|
2487
|
+
this.log(chalk3.cyan(`Peer connected: ${remoteAddr}`));
|
|
3475
2488
|
this.audit(ip, "CONNECT", remoteAddr);
|
|
3476
2489
|
this.emitPeerEvent("connect", remoteAddr);
|
|
3477
2490
|
socket.on("data", (data) => this.onData(peer, data));
|
|
@@ -3505,7 +2518,7 @@ var P2PServer = class {
|
|
|
3505
2518
|
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Expected auth message first" }, this.hmacKey));
|
|
3506
2519
|
this.disconnectPeer(peer, "Expected auth message");
|
|
3507
2520
|
this.emitPeerEvent("error", `Expected auth message from ${peer.ip}`);
|
|
3508
|
-
this.log(
|
|
2521
|
+
this.log(chalk3.red(`Auth failed from ${peer.ip}: expected auth message first`));
|
|
3509
2522
|
return;
|
|
3510
2523
|
}
|
|
3511
2524
|
const authPayload = msg.payload;
|
|
@@ -3513,7 +2526,7 @@ var P2PServer = class {
|
|
|
3513
2526
|
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid auth payload" }, this.hmacKey));
|
|
3514
2527
|
this.disconnectPeer(peer, "Invalid auth payload");
|
|
3515
2528
|
this.emitPeerEvent("error", `Invalid auth payload from ${peer.ip}`);
|
|
3516
|
-
this.log(
|
|
2529
|
+
this.log(chalk3.red(`Auth failed from ${peer.ip}: invalid auth payload`));
|
|
3517
2530
|
return;
|
|
3518
2531
|
}
|
|
3519
2532
|
if (peer.pendingNonce && authPayload.nonce !== peer.pendingNonce) {
|
|
@@ -3521,7 +2534,7 @@ var P2PServer = class {
|
|
|
3521
2534
|
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid nonce" }, this.hmacKey));
|
|
3522
2535
|
this.disconnectPeer(peer, "Invalid nonce");
|
|
3523
2536
|
this.emitPeerEvent("error", `Invalid nonce from ${peer.ip}`);
|
|
3524
|
-
this.log(
|
|
2537
|
+
this.log(chalk3.red(`Auth failed from ${peer.ip}: invalid nonce`));
|
|
3525
2538
|
return;
|
|
3526
2539
|
}
|
|
3527
2540
|
const expectedHash = hashPassword(this.options.password, authPayload.nonce);
|
|
@@ -3530,14 +2543,14 @@ var P2PServer = class {
|
|
|
3530
2543
|
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid password" }, this.hmacKey));
|
|
3531
2544
|
this.disconnectPeer(peer, "Authentication failed");
|
|
3532
2545
|
this.emitPeerEvent("error", `Invalid password from ${peer.ip}`);
|
|
3533
|
-
this.log(
|
|
2546
|
+
this.log(chalk3.red(`Auth failed from ${peer.ip}: invalid password`));
|
|
3534
2547
|
return;
|
|
3535
2548
|
}
|
|
3536
2549
|
peer.authenticated = true;
|
|
3537
2550
|
this.cleanupRateLimit(peer.ip);
|
|
3538
2551
|
peer.pendingNonce = null;
|
|
3539
2552
|
this.sendSigned(peer, signMessage("auth-ok", { ok: true }, this.hmacKey));
|
|
3540
|
-
this.log(
|
|
2553
|
+
this.log(chalk3.green(`Peer authenticated: ${peer.ip}`));
|
|
3541
2554
|
this.audit(peer.ip, "AUTH_OK", "Challenge-response successful");
|
|
3542
2555
|
this.emitPeerEvent("auth", peer.ip);
|
|
3543
2556
|
this.startDataStream(peer);
|
|
@@ -3545,7 +2558,7 @@ var P2PServer = class {
|
|
|
3545
2558
|
} catch {
|
|
3546
2559
|
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid message format" }, this.hmacKey));
|
|
3547
2560
|
this.disconnectPeer(peer, "Invalid message format");
|
|
3548
|
-
this.log(
|
|
2561
|
+
this.log(chalk3.red(`Auth failed from ${peer.ip}: invalid message format`));
|
|
3549
2562
|
}
|
|
3550
2563
|
}
|
|
3551
2564
|
sendSigned(peer, message) {
|
|
@@ -3577,7 +2590,7 @@ var P2PServer = class {
|
|
|
3577
2590
|
}
|
|
3578
2591
|
onPeerClose(peer) {
|
|
3579
2592
|
this.cleanupPeer(peer);
|
|
3580
|
-
this.log(
|
|
2593
|
+
this.log(chalk3.yellow(`Peer disconnected: ${peer.ip}`));
|
|
3581
2594
|
this.emitPeerEvent("disconnect", peer.ip);
|
|
3582
2595
|
this.audit(peer.ip, "DISCONNECT", "Peer closed connection");
|
|
3583
2596
|
}
|
|
@@ -3585,13 +2598,13 @@ var P2PServer = class {
|
|
|
3585
2598
|
const addr = peer.socket.remoteAddress ?? peer.ip;
|
|
3586
2599
|
const code = err.code;
|
|
3587
2600
|
if (code === "ECONNRESET") {
|
|
3588
|
-
this.log(
|
|
2601
|
+
this.log(chalk3.yellow(`Peer connection reset: ${addr}`));
|
|
3589
2602
|
this.audit(peer.ip, "ERROR", "Connection reset");
|
|
3590
2603
|
this.emitPeerEvent("error", `connection reset: ${addr}`);
|
|
3591
2604
|
} else if (code === "EPIPE") {
|
|
3592
2605
|
this.audit(peer.ip, "ERROR", "Broken pipe");
|
|
3593
2606
|
} else {
|
|
3594
|
-
this.log(
|
|
2607
|
+
this.log(chalk3.red(`Peer error: ${err.message}`));
|
|
3595
2608
|
this.audit(peer.ip, "ERROR", err.message);
|
|
3596
2609
|
this.emitPeerEvent("error", err.message);
|
|
3597
2610
|
}
|
|
@@ -3625,9 +2638,9 @@ async function startP2PServer(options) {
|
|
|
3625
2638
|
// src/p2p/client.ts
|
|
3626
2639
|
import net2 from "net";
|
|
3627
2640
|
import tls2 from "tls";
|
|
3628
|
-
import
|
|
2641
|
+
import fs4 from "fs";
|
|
3629
2642
|
import crypto3 from "crypto";
|
|
3630
|
-
import
|
|
2643
|
+
import chalk4 from "chalk";
|
|
3631
2644
|
var P2PClient = class {
|
|
3632
2645
|
socket = null;
|
|
3633
2646
|
options;
|
|
@@ -3653,11 +2666,11 @@ var P2PClient = class {
|
|
|
3653
2666
|
async connect() {
|
|
3654
2667
|
if (this.options.auditLog) {
|
|
3655
2668
|
try {
|
|
3656
|
-
|
|
2669
|
+
fs4.mkdirSync(this.options.auditLog, { recursive: true });
|
|
3657
2670
|
const logFile = `${this.options.auditLog}/p2p-client-audit.log`;
|
|
3658
|
-
this.auditStream =
|
|
2671
|
+
this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
|
|
3659
2672
|
} catch {
|
|
3660
|
-
console.log(
|
|
2673
|
+
console.log(chalk4.yellow("Warning: could not open audit log"));
|
|
3661
2674
|
}
|
|
3662
2675
|
}
|
|
3663
2676
|
const host = sanitizeHost(this.options.host);
|
|
@@ -3670,7 +2683,7 @@ var P2PClient = class {
|
|
|
3670
2683
|
rejectUnauthorized: !this.options.tlsInsecure
|
|
3671
2684
|
};
|
|
3672
2685
|
if (this.options.tlsCA) {
|
|
3673
|
-
tlsOptions.ca =
|
|
2686
|
+
tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
|
|
3674
2687
|
}
|
|
3675
2688
|
this.socket = tls2.connect(tlsOptions, () => {
|
|
3676
2689
|
this.running = true;
|
|
@@ -4616,7 +3629,7 @@ function stopLive() {
|
|
|
4616
3629
|
}
|
|
4617
3630
|
|
|
4618
3631
|
// src/splash.ts
|
|
4619
|
-
import
|
|
3632
|
+
import chalk5 from "chalk";
|
|
4620
3633
|
var MAXV = 36;
|
|
4621
3634
|
var CHAR_RAMP = [" ", ".", "`", ",", ":", ";", "+", "*", "?", "%", "#", "@"];
|
|
4622
3635
|
var PALETTES = {
|
|
@@ -4920,12 +3933,12 @@ async function showSplash(config3) {
|
|
|
4920
3933
|
for (let x = 0; x < cols; x++) {
|
|
4921
3934
|
const v = fire[y * cols + x];
|
|
4922
3935
|
const color = rgbPalette[Math.min(v, rgbPalette.length - 1)];
|
|
4923
|
-
output +=
|
|
3936
|
+
output += chalk5.rgb(color.r, color.g, color.b)(getChar(v));
|
|
4924
3937
|
}
|
|
4925
3938
|
output += "\n";
|
|
4926
3939
|
}
|
|
4927
|
-
output += " ".repeat(Math.max(0, startCol + (cols - 45) / 2)) +
|
|
4928
|
-
output += " ".repeat(Math.max(0, startCol + (cols - 25) / 2)) +
|
|
3940
|
+
output += " ".repeat(Math.max(0, startCol + (cols - 45) / 2)) + chalk5.bold.white("\u{1F525} pyre \u2014 system monitor (warming up sensors...)\n");
|
|
3941
|
+
output += " ".repeat(Math.max(0, startCol + (cols - 25) / 2)) + chalk5.dim("[Press Enter to continue]");
|
|
4929
3942
|
process.stdout.write(`\x1B[H${output}`);
|
|
4930
3943
|
}, 65);
|
|
4931
3944
|
const onEnter = () => {
|
|
@@ -4942,22 +3955,22 @@ async function showSplash(config3) {
|
|
|
4942
3955
|
}
|
|
4943
3956
|
|
|
4944
3957
|
// src/historyCmd.ts
|
|
4945
|
-
import
|
|
4946
|
-
import
|
|
4947
|
-
import
|
|
3958
|
+
import fs5 from "fs";
|
|
3959
|
+
import path3 from "path";
|
|
3960
|
+
import chalk6 from "chalk";
|
|
4948
3961
|
function runHistoryCommand(opts2 = {}) {
|
|
4949
3962
|
const days = opts2.days || 7;
|
|
4950
3963
|
const dir = opts2.dir || "./pyre-exports";
|
|
4951
|
-
console.log(
|
|
3964
|
+
console.log(chalk6.bold(`
|
|
4952
3965
|
pyre history \u2014 Past ${days} Days Trend
|
|
4953
3966
|
`));
|
|
4954
|
-
if (!
|
|
4955
|
-
console.log(
|
|
3967
|
+
if (!fs5.existsSync(dir)) {
|
|
3968
|
+
console.log(chalk6.yellow(` No logs directory found at ${dir}`));
|
|
4956
3969
|
return;
|
|
4957
3970
|
}
|
|
4958
|
-
const files =
|
|
3971
|
+
const files = fs5.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
|
|
4959
3972
|
if (files.length === 0) {
|
|
4960
|
-
console.log(
|
|
3973
|
+
console.log(chalk6.yellow(` No CSV logs found in ${dir}`));
|
|
4961
3974
|
return;
|
|
4962
3975
|
}
|
|
4963
3976
|
const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
@@ -4965,9 +3978,9 @@ function runHistoryCommand(opts2 = {}) {
|
|
|
4965
3978
|
const memPoints = [];
|
|
4966
3979
|
const tempPoints = [];
|
|
4967
3980
|
for (const file of files.sort()) {
|
|
4968
|
-
const fullPath =
|
|
3981
|
+
const fullPath = path3.join(dir, file);
|
|
4969
3982
|
try {
|
|
4970
|
-
const content =
|
|
3983
|
+
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
4971
3984
|
const lines = content.split("\n").slice(1);
|
|
4972
3985
|
for (const line of lines) {
|
|
4973
3986
|
if (!line.trim()) continue;
|
|
@@ -4989,7 +4002,7 @@ function runHistoryCommand(opts2 = {}) {
|
|
|
4989
4002
|
}
|
|
4990
4003
|
}
|
|
4991
4004
|
if (cpuPoints.length === 0) {
|
|
4992
|
-
console.log(
|
|
4005
|
+
console.log(chalk6.yellow(` No historical entries recorded within the last ${days} days.`));
|
|
4993
4006
|
return;
|
|
4994
4007
|
}
|
|
4995
4008
|
console.log(` Data Points: ${cpuPoints.length} logged snapshots`);
|
|
@@ -5002,22 +4015,22 @@ function runHistoryCommand(opts2 = {}) {
|
|
|
5002
4015
|
}
|
|
5003
4016
|
|
|
5004
4017
|
// src/diffCmd.ts
|
|
5005
|
-
import
|
|
5006
|
-
import
|
|
4018
|
+
import fs6 from "fs";
|
|
4019
|
+
import chalk7 from "chalk";
|
|
5007
4020
|
function runDiffCommand(file1, file2) {
|
|
5008
|
-
console.log(
|
|
4021
|
+
console.log(chalk7.bold(`
|
|
5009
4022
|
pyre diff \u2014 Snapshot Comparison
|
|
5010
4023
|
`));
|
|
5011
|
-
console.log(
|
|
5012
|
-
console.log(
|
|
4024
|
+
console.log(chalk7.dim(` Snapshot A: ${file1}`));
|
|
4025
|
+
console.log(chalk7.dim(` Snapshot B: ${file2}
|
|
5013
4026
|
`));
|
|
5014
4027
|
try {
|
|
5015
|
-
const dataA = JSON.parse(
|
|
5016
|
-
const dataB = JSON.parse(
|
|
4028
|
+
const dataA = JSON.parse(fs6.readFileSync(file1, "utf-8"));
|
|
4029
|
+
const dataB = JSON.parse(fs6.readFileSync(file2, "utf-8"));
|
|
5017
4030
|
const diffNum = (label, valA, valB, unit = "") => {
|
|
5018
4031
|
const delta = valB - valA;
|
|
5019
4032
|
const sign = delta > 0 ? "+" : "";
|
|
5020
|
-
const color = delta > 0 ?
|
|
4033
|
+
const color = delta > 0 ? chalk7.red : delta < 0 ? chalk7.green : chalk7.dim;
|
|
5021
4034
|
console.log(` ${label.padEnd(20)} ${valA.toFixed(1)}${unit} \u2794 ${valB.toFixed(1)}${unit} (${color(sign + delta.toFixed(1) + unit)})`);
|
|
5022
4035
|
};
|
|
5023
4036
|
diffNum("CPU Usage", dataA.cpu.usage, dataB.cpu.usage, "%");
|
|
@@ -5032,15 +4045,15 @@ function runDiffCommand(file1, file2) {
|
|
|
5032
4045
|
diffNum("Network TX Packets", dataA.network.txPackets, dataB.network.txPackets);
|
|
5033
4046
|
console.log();
|
|
5034
4047
|
} catch (err) {
|
|
5035
|
-
console.log(
|
|
4048
|
+
console.log(chalk7.red(`Failed to compare snapshots: ${err.message}`));
|
|
5036
4049
|
}
|
|
5037
4050
|
}
|
|
5038
4051
|
|
|
5039
4052
|
// src/xbar.ts
|
|
5040
|
-
import
|
|
5041
|
-
import
|
|
5042
|
-
import
|
|
5043
|
-
import
|
|
4053
|
+
import fs7 from "fs";
|
|
4054
|
+
import path4 from "path";
|
|
4055
|
+
import os from "os";
|
|
4056
|
+
import chalk8 from "chalk";
|
|
5044
4057
|
function generateXbarPlugin() {
|
|
5045
4058
|
const pyrePath = process.argv[1];
|
|
5046
4059
|
const nodePath = process.execPath;
|
|
@@ -5075,57 +4088,63 @@ echo "\u{1F525} C:\${CPU}% M:\${MEM}% T:\${TEMP}\xB0C | font=Menlo"
|
|
|
5075
4088
|
echo "---"
|
|
5076
4089
|
echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' terminal=true"
|
|
5077
4090
|
`;
|
|
5078
|
-
const pluginDir =
|
|
4091
|
+
const pluginDir = path4.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
|
|
5079
4092
|
const filename = "pyre-monitor.2s.sh";
|
|
5080
|
-
const outPath =
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
console.log(
|
|
4093
|
+
const outPath = path4.join(process.cwd(), filename);
|
|
4094
|
+
fs7.writeFileSync(outPath, script);
|
|
4095
|
+
fs7.chmodSync(outPath, "755");
|
|
4096
|
+
console.log(chalk8.green(`Generated xbar plugin script at ${outPath}`));
|
|
5084
4097
|
console.log(`To install, move it to your xbar plugins directory:`);
|
|
5085
|
-
console.log(
|
|
4098
|
+
console.log(chalk8.cyan(` mv ${filename} "${pluginDir}/"`));
|
|
5086
4099
|
console.log(`Then refresh xbar.`);
|
|
5087
4100
|
}
|
|
5088
4101
|
|
|
5089
4102
|
// src/fleet.ts
|
|
5090
|
-
import {
|
|
5091
|
-
import
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
4103
|
+
import { exec as exec2 } from "child_process";
|
|
4104
|
+
import { promisify } from "util";
|
|
4105
|
+
import chalk9 from "chalk";
|
|
4106
|
+
var execAsync = promisify(exec2);
|
|
4107
|
+
async function runFleetCommand(hosts) {
|
|
4108
|
+
console.log(chalk9.cyan(`Fetching concurrent data from ${hosts.length} fleet host(s)...`));
|
|
4109
|
+
const promises = hosts.map(async (host) => {
|
|
5096
4110
|
try {
|
|
5097
|
-
const
|
|
5098
|
-
const data = JSON.parse(
|
|
5099
|
-
|
|
4111
|
+
const { stdout } = await execAsync(`ssh -o ConnectTimeout=5 ${host} "pyre --once --json"`);
|
|
4112
|
+
const data = JSON.parse(stdout);
|
|
4113
|
+
return { host, data };
|
|
5100
4114
|
} catch (e) {
|
|
5101
|
-
|
|
4115
|
+
return { host, error: e.message || String(e) };
|
|
5102
4116
|
}
|
|
5103
|
-
}
|
|
4117
|
+
});
|
|
4118
|
+
const results = await Promise.all(promises);
|
|
5104
4119
|
for (const result of results) {
|
|
5105
|
-
console.log(
|
|
5106
|
-
=== ${result.host} ===
|
|
4120
|
+
console.log(chalk9.bold.blue(`
|
|
4121
|
+
=== Host: ${result.host} ===
|
|
5107
4122
|
`));
|
|
5108
4123
|
if (result.error) {
|
|
5109
|
-
console.log(
|
|
4124
|
+
console.log(chalk9.red(` \u2716 Error connecting to host: ${result.error}`));
|
|
5110
4125
|
} else if (result.data) {
|
|
5111
|
-
console.log(formatTable(result.data, { width: process.stdout.columns || 80,
|
|
4126
|
+
console.log(formatTable(result.data, { width: process.stdout.columns || 80, minimal: true }));
|
|
5112
4127
|
}
|
|
5113
4128
|
}
|
|
5114
4129
|
}
|
|
5115
4130
|
|
|
5116
4131
|
// src/index.ts
|
|
5117
4132
|
var config2 = readConfig();
|
|
5118
|
-
var pkg = JSON.parse(
|
|
4133
|
+
var pkg = JSON.parse(fs8.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
5119
4134
|
var program = new Command();
|
|
5120
|
-
program.name("pyre").version(pkg.version).description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("--html", "Output as HTML").option("--md", "Output as Markdown").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--theme <name>", "Default theme for live mode (default, dracula, cyberpunk, monochrome, nord, gruvbox)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", config2.exportDir).option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", config2.sortMode).option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)").option("--since <range>", "Time range for anomalies digest (e.g. 1d, 7d, yesterday)").option("--plain", "Output in plain accessible text without ANSI colors or line-drawing characters").option("--a11y", "Alias for --plain").option("
|
|
4135
|
+
program.name("pyre").version(pkg.version).description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("--html", "Output as HTML").option("--md", "Output as Markdown").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--theme <name>", "Default theme for live mode (default, dracula, cyberpunk, monochrome, nord, gruvbox)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", config2.exportDir).option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", config2.sortMode).option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--minimal", "Render minimal macmon-style glanceable view (CPU, GPU, Memory, Temp)").option("--compact", "Launch pyre ui in compact widget mode").option("--ontop", "Keep pyre ui window always on top").option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)").option("--since <range>", "Time range for anomalies digest (e.g. 1d, 7d, yesterday)").option("--plain", "Output in plain accessible text without ANSI colors or line-drawing characters").option("--a11y", "Alias for --plain").option("-d, --duration <seconds>", "Duration in seconds for stress/bench commands", "30").option("--cores <n>", "Number of worker threads for stress command").option("--exit-code", "Exit non-zero if system health checks trigger alerts (for check command)").option("--no-wizard", "Skip first-run interactive setup wizard").option("--install", "Install pyre web as a background launchd agent").option("--uninstall", "Uninstall pyre web launchd agent").option("--port <port>", "Port number for web server mode", "3000");
|
|
5121
4136
|
program.addHelpText("after", `
|
|
5122
4137
|
Commands:
|
|
5123
4138
|
live Interactive live dashboard
|
|
4139
|
+
check One-line plain-English health summary
|
|
4140
|
+
pipe Continuous newline-delimited JSON stream for scripting
|
|
4141
|
+
stress Synthetic CPU/GPU load generator
|
|
5124
4142
|
ui Launch the native macOS UI dashboard
|
|
5125
4143
|
web Serve an auto-refreshing live web portal
|
|
5126
4144
|
ssh <host> Stream live stats from a remote Mac over SSH
|
|
5127
4145
|
fleet <host1> [host2]... Multi-host live dashboard monitoring multiple Macs
|
|
5128
4146
|
bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
|
|
4147
|
+
benchmark Run a 1-minute CPU benchmark calculating digits of PI
|
|
5129
4148
|
anomalies Compute z-score anomalies and print plain-language digest
|
|
5130
4149
|
doctor Run system diagnostics
|
|
5131
4150
|
extensions System Extensions inspector
|
|
@@ -5144,7 +4163,7 @@ Commands:
|
|
|
5144
4163
|
program.parse(process.argv);
|
|
5145
4164
|
var opts = program.opts();
|
|
5146
4165
|
if (opts.plain || opts.a11y) {
|
|
5147
|
-
|
|
4166
|
+
chalk10.level = 0;
|
|
5148
4167
|
}
|
|
5149
4168
|
function isExportMode() {
|
|
5150
4169
|
return opts.json || opts.csv || opts.tsv || opts.html || opts.md;
|
|
@@ -5156,7 +4175,7 @@ function sanitizeHost2(host) {
|
|
|
5156
4175
|
return host.trim().replace(/%$/, "");
|
|
5157
4176
|
}
|
|
5158
4177
|
function detectLocalIP() {
|
|
5159
|
-
const ifaces =
|
|
4178
|
+
const ifaces = os2.networkInterfaces();
|
|
5160
4179
|
const prioritizedNames = ["en0", "en1", "wlan0", "eth0"];
|
|
5161
4180
|
for (const name of prioritizedNames) {
|
|
5162
4181
|
const addrs = ifaces[name];
|
|
@@ -5181,26 +4200,56 @@ function detectLocalIP() {
|
|
|
5181
4200
|
async function runServerCommand() {
|
|
5182
4201
|
const ip = detectLocalIP();
|
|
5183
4202
|
if (!ip) {
|
|
5184
|
-
console.log(
|
|
4203
|
+
console.log(chalk10.red("Could not detect a non-loopback IP address. Connect to a network first."));
|
|
5185
4204
|
process.exit(1);
|
|
5186
4205
|
}
|
|
5187
4206
|
const port = 9876;
|
|
5188
4207
|
const password = "mysecret";
|
|
5189
|
-
console.log(
|
|
4208
|
+
console.log(chalk10.bold(`
|
|
5190
4209
|
pyre server`));
|
|
5191
|
-
console.log(
|
|
5192
|
-
console.log(
|
|
5193
|
-
console.log(
|
|
4210
|
+
console.log(chalk10.dim(` Detected IP: ${ip}`));
|
|
4211
|
+
console.log(chalk10.dim(` Port: ${port}`));
|
|
4212
|
+
console.log(chalk10.dim(` Password: ${password}`));
|
|
5194
4213
|
console.log();
|
|
5195
|
-
console.log(
|
|
5196
|
-
console.log(
|
|
4214
|
+
console.log(chalk10.bold("Run this on THIS machine (server):"));
|
|
4215
|
+
console.log(chalk10.hex("#ff6a39")(`pyre p2p server --p2p-host 0.0.0.0 --p2p-port ${port} --p2p-password ${password}`));
|
|
5197
4216
|
console.log();
|
|
5198
|
-
console.log(
|
|
5199
|
-
console.log(
|
|
4217
|
+
console.log(chalk10.bold("Run this on the OTHER machine (client):"));
|
|
4218
|
+
console.log(chalk10.hex("#ff6a39")(`pyre p2p connect --p2p-host ${ip} --p2p-port ${port} --p2p-password ${password}`));
|
|
5200
4219
|
console.log();
|
|
5201
4220
|
}
|
|
5202
4221
|
async function main() {
|
|
4222
|
+
const { runSetupWizardIfNeeded } = await import("./wizard-GVIAHIVV.js");
|
|
4223
|
+
await runSetupWizardIfNeeded(!opts.wizard);
|
|
5203
4224
|
const cmd = program.args[0];
|
|
4225
|
+
if (cmd === "check") {
|
|
4226
|
+
const { runCheckCommand } = await import("./checkCmd-ULNENOPZ.js");
|
|
4227
|
+
await runCheckCommand({
|
|
4228
|
+
exitCode: !!opts.exitCode,
|
|
4229
|
+
cpuAlertPct: config2.cpuAlertPct,
|
|
4230
|
+
tempAlertC: config2.tempAlertC,
|
|
4231
|
+
detailed: opts.detailed
|
|
4232
|
+
});
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
if (cmd === "pipe") {
|
|
4236
|
+
const { runPipeCommand } = await import("./pipeCmd-BWNMUV2X.js");
|
|
4237
|
+
const intervalSec = parseFloat(opts.interval) || 1;
|
|
4238
|
+
const samples = parseInt(opts.samples, 10) || 0;
|
|
4239
|
+
await runPipeCommand({
|
|
4240
|
+
intervalMs: intervalSec * 1e3,
|
|
4241
|
+
samples,
|
|
4242
|
+
detailed: opts.detailed
|
|
4243
|
+
});
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
if (cmd === "stress") {
|
|
4247
|
+
const { runStressCommand } = await import("./stressCmd-MFXMFXYX.js");
|
|
4248
|
+
const durationSec = parseFloat(opts.duration) || 30;
|
|
4249
|
+
const cores = opts.cores ? parseInt(opts.cores, 10) : void 0;
|
|
4250
|
+
await runStressCommand({ durationSec, cores });
|
|
4251
|
+
return;
|
|
4252
|
+
}
|
|
5204
4253
|
if (cmd === "anomalies") {
|
|
5205
4254
|
const { runAnomaliesDigest } = await import("./anomaliesCmd-25OFKYPK.js");
|
|
5206
4255
|
runAnomaliesDigest({ since: opts.since || "7d", dir: opts.exportDir });
|
|
@@ -5227,19 +4276,19 @@ async function main() {
|
|
|
5227
4276
|
const { saveProfile, loadProfile, listProfiles } = await import("./config-4ZLF4HTS.js");
|
|
5228
4277
|
if (sub === "save" && name) {
|
|
5229
4278
|
saveProfile(name);
|
|
5230
|
-
console.log(
|
|
4279
|
+
console.log(chalk10.green(` \u2714 Profile '${name}' saved successfully.`));
|
|
5231
4280
|
} else if (sub === "load" && name) {
|
|
5232
4281
|
try {
|
|
5233
4282
|
loadProfile(name);
|
|
5234
|
-
console.log(
|
|
4283
|
+
console.log(chalk10.green(` \u2714 Profile '${name}' loaded successfully.`));
|
|
5235
4284
|
} catch (err) {
|
|
5236
|
-
console.log(
|
|
4285
|
+
console.log(chalk10.red(` \u2716 ${err.message}`));
|
|
5237
4286
|
}
|
|
5238
4287
|
} else if (sub === "list") {
|
|
5239
4288
|
const profiles = listProfiles();
|
|
5240
|
-
console.log(
|
|
4289
|
+
console.log(chalk10.bold("\n pyre Profiles:\n"));
|
|
5241
4290
|
if (profiles.length === 0) {
|
|
5242
|
-
console.log(
|
|
4291
|
+
console.log(chalk10.dim(" No saved profiles found. Save one with `pyre profile save <name>`."));
|
|
5243
4292
|
} else {
|
|
5244
4293
|
for (const p of profiles) {
|
|
5245
4294
|
console.log(` \u2022 ${p}`);
|
|
@@ -5261,7 +4310,7 @@ async function main() {
|
|
|
5261
4310
|
const file1 = program.args[1];
|
|
5262
4311
|
const file2 = program.args[2];
|
|
5263
4312
|
if (!file1 || !file2) {
|
|
5264
|
-
console.log(
|
|
4313
|
+
console.log(chalk10.red("Usage: pyre diff <snapshot1.json> <snapshot2.json>"));
|
|
5265
4314
|
process.exit(1);
|
|
5266
4315
|
}
|
|
5267
4316
|
runDiffCommand(file1, file2);
|
|
@@ -5270,13 +4319,24 @@ async function main() {
|
|
|
5270
4319
|
if (cmd === "ssh") {
|
|
5271
4320
|
const host = program.args[1];
|
|
5272
4321
|
if (!host) {
|
|
5273
|
-
console.log(
|
|
4322
|
+
console.log(chalk10.red("Usage: pyre ssh <host>"));
|
|
5274
4323
|
process.exit(1);
|
|
5275
4324
|
}
|
|
5276
4325
|
await runSshCommand(host);
|
|
5277
4326
|
return;
|
|
5278
4327
|
}
|
|
5279
4328
|
if (cmd === "web") {
|
|
4329
|
+
if (opts.install) {
|
|
4330
|
+
const { installLaunchdAgent } = await import("./service-WTS3NBQU.js");
|
|
4331
|
+
const port = parseInt(opts.port || "3000", 10) || 3e3;
|
|
4332
|
+
installLaunchdAgent(port);
|
|
4333
|
+
return;
|
|
4334
|
+
}
|
|
4335
|
+
if (opts.uninstall) {
|
|
4336
|
+
const { uninstallLaunchdAgent } = await import("./service-WTS3NBQU.js");
|
|
4337
|
+
uninstallLaunchdAgent();
|
|
4338
|
+
return;
|
|
4339
|
+
}
|
|
5280
4340
|
await runWebCommand();
|
|
5281
4341
|
return;
|
|
5282
4342
|
}
|
|
@@ -5287,20 +4347,25 @@ async function main() {
|
|
|
5287
4347
|
if (cmd === "bench") {
|
|
5288
4348
|
const benchCmd = program.args.slice(1).join(" ");
|
|
5289
4349
|
if (!benchCmd) {
|
|
5290
|
-
console.log(
|
|
4350
|
+
console.log(chalk10.red("Usage: pyre bench <command>"));
|
|
5291
4351
|
process.exit(1);
|
|
5292
4352
|
}
|
|
5293
4353
|
await runBenchCommand(benchCmd);
|
|
5294
4354
|
return;
|
|
5295
4355
|
}
|
|
4356
|
+
if (cmd === "benchmark") {
|
|
4357
|
+
const { runBenchmarkCommand } = await import("./benchmarkCmd-A3ETK6VY.js");
|
|
4358
|
+
await runBenchmarkCommand();
|
|
4359
|
+
return;
|
|
4360
|
+
}
|
|
5296
4361
|
if (cmd === "config") {
|
|
5297
4362
|
const sub = program.args[1];
|
|
5298
4363
|
if (sub === "show") {
|
|
5299
4364
|
const cfg = readConfig();
|
|
5300
4365
|
console.log(JSON.stringify(cfg, null, 2));
|
|
5301
4366
|
} else if (sub === "reset") {
|
|
5302
|
-
|
|
5303
|
-
console.log(
|
|
4367
|
+
fs8.rmSync(CONFIG_FILE, { force: true });
|
|
4368
|
+
console.log(chalk10.green("Config reset to defaults"));
|
|
5304
4369
|
} else {
|
|
5305
4370
|
console.log("Usage: pyre config <show|reset>");
|
|
5306
4371
|
}
|
|
@@ -5333,7 +4398,7 @@ async function main() {
|
|
|
5333
4398
|
if (cmd === "fleet") {
|
|
5334
4399
|
const hosts = program.args.slice(1);
|
|
5335
4400
|
if (hosts.length === 0) {
|
|
5336
|
-
console.error(
|
|
4401
|
+
console.error(chalk10.red("Usage: pyre fleet <host1> [host2] ..."));
|
|
5337
4402
|
process.exit(1);
|
|
5338
4403
|
}
|
|
5339
4404
|
runFleetCommand(hosts);
|
|
@@ -5380,7 +4445,7 @@ async function main() {
|
|
|
5380
4445
|
await runP2PConnect();
|
|
5381
4446
|
return;
|
|
5382
4447
|
} else {
|
|
5383
|
-
console.log(
|
|
4448
|
+
console.log(chalk10.red("Usage: pyre p2p <server|connect> [options]"));
|
|
5384
4449
|
process.exit(1);
|
|
5385
4450
|
}
|
|
5386
4451
|
}
|
|
@@ -5393,16 +4458,16 @@ async function main() {
|
|
|
5393
4458
|
else if (opts.md) output = formatMarkdown(data);
|
|
5394
4459
|
else if (opts.csv) output = formatCsv(data);
|
|
5395
4460
|
else if (opts.tsv) output = formatTsv(data);
|
|
5396
|
-
else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config2.treeView, visible: { packets: opts.packets ? true : void 0 } });
|
|
4461
|
+
else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config2.treeView, minimal: !!opts.minimal, visible: { packets: opts.packets ? true : void 0 } });
|
|
5397
4462
|
console.log(output);
|
|
5398
4463
|
if (opts.out) {
|
|
5399
|
-
|
|
5400
|
-
console.log(
|
|
4464
|
+
fs8.writeFileSync(opts.out, stripAnsi(output));
|
|
4465
|
+
console.log(chalk10.dim(`
|
|
5401
4466
|
Saved to ${opts.out}`));
|
|
5402
4467
|
}
|
|
5403
4468
|
}
|
|
5404
4469
|
main().catch((err) => {
|
|
5405
|
-
console.error(
|
|
4470
|
+
console.error(chalk10.red(`Error: ${err.message}`));
|
|
5406
4471
|
process.exit(1);
|
|
5407
4472
|
});
|
|
5408
4473
|
async function runP2PServer() {
|
|
@@ -5418,15 +4483,15 @@ async function runP2PServer() {
|
|
|
5418
4483
|
const allowList = opts.p2pAllow ? opts.p2pAllow.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
5419
4484
|
const denyList = opts.p2pDeny ? opts.p2pDeny.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
5420
4485
|
if (!password) {
|
|
5421
|
-
console.log(
|
|
4486
|
+
console.log(chalk10.red("Error: a password is required for P2P server mode. Use --p2p-password <password>"));
|
|
5422
4487
|
process.exit(1);
|
|
5423
4488
|
}
|
|
5424
4489
|
if (tlsCert && !tlsKey) {
|
|
5425
|
-
console.log(
|
|
4490
|
+
console.log(chalk10.red("Error: --p2p-key is required when --p2p-cert is specified"));
|
|
5426
4491
|
process.exit(1);
|
|
5427
4492
|
}
|
|
5428
4493
|
if (tlsKey && !tlsCert) {
|
|
5429
|
-
console.log(
|
|
4494
|
+
console.log(chalk10.red("Error: --p2p-cert is required when --p2p-key is specified"));
|
|
5430
4495
|
process.exit(1);
|
|
5431
4496
|
}
|
|
5432
4497
|
const server = new P2PServer({
|
|
@@ -5446,18 +4511,18 @@ async function runP2PServer() {
|
|
|
5446
4511
|
hmacKey: opts.p2pHmacKey
|
|
5447
4512
|
});
|
|
5448
4513
|
const tlsEnabled = !!(tlsCert && tlsKey);
|
|
5449
|
-
console.log(
|
|
4514
|
+
console.log(chalk10.bold(`
|
|
5450
4515
|
pyre P2P Server`));
|
|
5451
|
-
console.log(
|
|
5452
|
-
console.log(
|
|
5453
|
-
console.log(
|
|
5454
|
-
console.log(
|
|
5455
|
-
console.log(
|
|
5456
|
-
console.log(
|
|
5457
|
-
console.log(
|
|
5458
|
-
if (allowList.length > 0) console.log(
|
|
5459
|
-
if (denyList.length > 0) console.log(
|
|
5460
|
-
console.log(
|
|
4516
|
+
console.log(chalk10.dim(` Host: ${host}`));
|
|
4517
|
+
console.log(chalk10.dim(` Port: ${port}`));
|
|
4518
|
+
console.log(chalk10.dim(` Protocol: ${tlsEnabled ? "TLS" : "TCP"}`));
|
|
4519
|
+
console.log(chalk10.dim(` Password: ${password}`));
|
|
4520
|
+
console.log(chalk10.dim(` Interval: ${interval}s`));
|
|
4521
|
+
console.log(chalk10.dim(` Detailed: ${detailed ? "on" : "off"}`));
|
|
4522
|
+
console.log(chalk10.dim(` Rate limit: ${rateLimit} attempts/min`));
|
|
4523
|
+
if (allowList.length > 0) console.log(chalk10.dim(` Allowed IPs: ${allowList.join(", ")}`));
|
|
4524
|
+
if (denyList.length > 0) console.log(chalk10.dim(` Denied IPs: ${denyList.join(", ")}`));
|
|
4525
|
+
console.log(chalk10.dim(` Waiting for peers...
|
|
5461
4526
|
`));
|
|
5462
4527
|
await server.start();
|
|
5463
4528
|
const rl = readline2.createInterface({ input: process.stdin });
|
|
@@ -5469,7 +4534,7 @@ async function runP2PServer() {
|
|
|
5469
4534
|
process.exit(0);
|
|
5470
4535
|
} else if (trimmed === "status") {
|
|
5471
4536
|
const info = server.info;
|
|
5472
|
-
console.log(
|
|
4537
|
+
console.log(chalk10.dim(` Peers connected: ${info.peersConnected}`));
|
|
5473
4538
|
}
|
|
5474
4539
|
});
|
|
5475
4540
|
process.once("SIGINT", () => {
|
|
@@ -5502,7 +4567,7 @@ async function runP2PConnect() {
|
|
|
5502
4567
|
const tlsCA = opts.p2pCA;
|
|
5503
4568
|
const tlsInsecure = !!opts.p2pInsecure;
|
|
5504
4569
|
if (!password) {
|
|
5505
|
-
console.log(
|
|
4570
|
+
console.log(chalk10.red("Error: a password is required for P2P connect mode. Use --p2p-password <password>"));
|
|
5506
4571
|
process.exit(1);
|
|
5507
4572
|
}
|
|
5508
4573
|
const client = new P2PClient({
|
|
@@ -5516,12 +4581,12 @@ async function runP2PConnect() {
|
|
|
5516
4581
|
hmacKey: opts.p2pHmacKey
|
|
5517
4582
|
});
|
|
5518
4583
|
client.setOnStatus((msg) => {
|
|
5519
|
-
process.stdout.write(`\r${
|
|
4584
|
+
process.stdout.write(`\r${chalk10.dim(msg)}${" ".repeat(40)}\r`);
|
|
5520
4585
|
});
|
|
5521
4586
|
client.setOnData((data) => {
|
|
5522
4587
|
const lines = [];
|
|
5523
|
-
lines.push(
|
|
5524
|
-
lines.push(
|
|
4588
|
+
lines.push(chalk10.bold(` pyre P2P Client \u2014 ${data.header.hostname}`));
|
|
4589
|
+
lines.push(chalk10.dim(` ${data.timestamp}`));
|
|
5525
4590
|
lines.push("");
|
|
5526
4591
|
lines.push(...formatTable(data, {
|
|
5527
4592
|
width: process.stdout.columns || 80,
|
|
@@ -5538,11 +4603,11 @@ async function runP2PConnect() {
|
|
|
5538
4603
|
writeFrame2(lines);
|
|
5539
4604
|
});
|
|
5540
4605
|
const protocolLabel = useTLS ? "TLS" : "TCP";
|
|
5541
|
-
console.log(
|
|
4606
|
+
console.log(chalk10.bold(`
|
|
5542
4607
|
pyre P2P Client`));
|
|
5543
|
-
console.log(
|
|
5544
|
-
console.log(
|
|
5545
|
-
console.log(
|
|
4608
|
+
console.log(chalk10.dim(` Protocol: ${protocolLabel}`));
|
|
4609
|
+
console.log(chalk10.dim(` Connecting to ${host}:${port}...`));
|
|
4610
|
+
console.log(chalk10.dim(` Press Ctrl+C to disconnect
|
|
5546
4611
|
`));
|
|
5547
4612
|
process.stdout.write("\x1B[?25l");
|
|
5548
4613
|
try {
|
|
@@ -5550,23 +4615,23 @@ async function runP2PConnect() {
|
|
|
5550
4615
|
} catch (err) {
|
|
5551
4616
|
const code = err.code;
|
|
5552
4617
|
const msg = err.message || String(err);
|
|
5553
|
-
console.log(
|
|
4618
|
+
console.log(chalk10.red(`Connection failed: ${msg}`));
|
|
5554
4619
|
if (code === "ECONNREFUSED") {
|
|
5555
|
-
console.log(
|
|
5556
|
-
console.log(
|
|
5557
|
-
console.log(
|
|
5558
|
-
console.log(
|
|
4620
|
+
console.log(chalk10.yellow(" Hints:"));
|
|
4621
|
+
console.log(chalk10.dim(" 1. Start the server: pyre p2p server --p2p-host 0.0.0.0 --p2p-port " + port + " --p2p-password <password>"));
|
|
4622
|
+
console.log(chalk10.dim(" 2. Check the server IP with: hostname -I"));
|
|
4623
|
+
console.log(chalk10.dim(" 3. Open the port on the server firewall: sudo ufw allow " + port + "/tcp"));
|
|
5559
4624
|
} else if (code === "ETIMEDOUT") {
|
|
5560
|
-
console.log(
|
|
5561
|
-
console.log(
|
|
5562
|
-
console.log(
|
|
5563
|
-
console.log(
|
|
4625
|
+
console.log(chalk10.yellow(" Hints:"));
|
|
4626
|
+
console.log(chalk10.dim(" 1. Verify the server IP is correct"));
|
|
4627
|
+
console.log(chalk10.dim(" 2. Check that both machines are on the same network/subnet"));
|
|
4628
|
+
console.log(chalk10.dim(" 3. Open the port on the server firewall"));
|
|
5564
4629
|
} else if (code === "EHOSTDOWN" || code === "EHOSTUNREACH") {
|
|
5565
|
-
console.log(
|
|
5566
|
-
console.log(
|
|
5567
|
-
console.log(
|
|
5568
|
-
console.log(
|
|
5569
|
-
console.log(
|
|
4630
|
+
console.log(chalk10.yellow(" Hints:"));
|
|
4631
|
+
console.log(chalk10.dim(" 1. The server at " + host + " is not responding"));
|
|
4632
|
+
console.log(chalk10.dim(" 2. Start the server first: pyre p2p server --p2p-host 0.0.0.0 --p2p-port " + port + " --p2p-password <password>"));
|
|
4633
|
+
console.log(chalk10.dim(" 3. Check connectivity: ping " + host));
|
|
4634
|
+
console.log(chalk10.dim(" 4. Ensure both machines are on the same LAN (ping each other first)"));
|
|
5570
4635
|
}
|
|
5571
4636
|
process.stdout.write("\x1B[?25h");
|
|
5572
4637
|
process.exit(1);
|
|
@@ -5580,11 +4645,11 @@ async function runP2PConnect() {
|
|
|
5580
4645
|
}
|
|
5581
4646
|
async function runInfoCommand() {
|
|
5582
4647
|
const data = await collectAll({ detailed: true });
|
|
5583
|
-
const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-
|
|
4648
|
+
const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-VYUH3UWY.js");
|
|
5584
4649
|
const displays = await getDisplayInfo();
|
|
5585
4650
|
const tmStatus = await getTimeMachineStatus();
|
|
5586
4651
|
const lines = [];
|
|
5587
|
-
lines.push(
|
|
4652
|
+
lines.push(chalk10.bold(" pyre info \u2014 Mac System Information"));
|
|
5588
4653
|
lines.push("");
|
|
5589
4654
|
lines.push(` Hostname: ${data.header.hostname}`);
|
|
5590
4655
|
lines.push(` OS: ${data.header.os}`);
|
|
@@ -5604,17 +4669,17 @@ async function runInfoCommand() {
|
|
|
5604
4669
|
lines.push(` Thermal: ${data.thermal.state}`);
|
|
5605
4670
|
if (displays.length > 0) {
|
|
5606
4671
|
lines.push("");
|
|
5607
|
-
lines.push(
|
|
4672
|
+
lines.push(chalk10.cyan.bold(" \u{1F5A5} Displays:"));
|
|
5608
4673
|
for (const d of displays) {
|
|
5609
4674
|
lines.push(` \u2022 ${d.name}: ${d.resolution}${d.isMain ? " (Main)" : ""}${d.connectionType ? ` [${d.connectionType}]` : ""}`);
|
|
5610
4675
|
}
|
|
5611
4676
|
}
|
|
5612
4677
|
lines.push("");
|
|
5613
|
-
lines.push(
|
|
4678
|
+
lines.push(chalk10.cyan.bold(" \u23F1 Time Machine Backup:"));
|
|
5614
4679
|
if (!tmStatus.configured) {
|
|
5615
4680
|
lines.push(" \u2022 Not configured");
|
|
5616
4681
|
} else {
|
|
5617
|
-
lines.push(` \u2022 Status: ${tmStatus.running ?
|
|
4682
|
+
lines.push(` \u2022 Status: ${tmStatus.running ? chalk10.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk10.green("Idle")}`);
|
|
5618
4683
|
lines.push(` \u2022 Latest Backup: ${tmStatus.latestBackup}`);
|
|
5619
4684
|
}
|
|
5620
4685
|
console.log(lines.join("\n"));
|
|
@@ -5622,18 +4687,18 @@ async function runInfoCommand() {
|
|
|
5622
4687
|
async function runSshCommand(host) {
|
|
5623
4688
|
const password = opts.p2pPassword || "mysecret";
|
|
5624
4689
|
const cmd = `ssh -o LogLevel=QUIET ${host} "pyre --json --once"`;
|
|
5625
|
-
console.log(
|
|
4690
|
+
console.log(chalk10.bold(`
|
|
5626
4691
|
pyre ssh \u2014 ${host}`));
|
|
5627
|
-
console.log(
|
|
4692
|
+
console.log(chalk10.dim(` Running: ${cmd}
|
|
5628
4693
|
`));
|
|
5629
4694
|
const { execFile: execFile2 } = await import("child_process");
|
|
5630
4695
|
const child = execFile2("ssh", ["-o", "LogLevel=QUIET", host, "pyre", "--json", "--once"], (err, stdout, stderr) => {
|
|
5631
4696
|
if (err) {
|
|
5632
|
-
console.log(
|
|
5633
|
-
console.log(
|
|
4697
|
+
console.log(chalk10.red(` SSH failed: ${err.message}`));
|
|
4698
|
+
console.log(chalk10.dim(" Ensure SSH keys are set up or the host is reachable."));
|
|
5634
4699
|
process.exit(1);
|
|
5635
4700
|
}
|
|
5636
|
-
if (stderr) console.log(
|
|
4701
|
+
if (stderr) console.log(chalk10.dim(stderr));
|
|
5637
4702
|
try {
|
|
5638
4703
|
const remote = JSON.parse(stdout);
|
|
5639
4704
|
console.log(formatTable(remote, { width: process.stdout.columns || 80, sortBy: "cpu", treeView: false, visible: { packets: true } }));
|
|
@@ -5658,6 +4723,13 @@ async function runWebCommand() {
|
|
|
5658
4723
|
res.end(JSON.stringify({ status: "ok", uptime: process.uptime(), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
5659
4724
|
return;
|
|
5660
4725
|
}
|
|
4726
|
+
if (url === "/metrics") {
|
|
4727
|
+
const { formatPrometheusMetrics } = await import("./prometheus-LFYHF6VO.js");
|
|
4728
|
+
const data = await collectAll({ detailed: true });
|
|
4729
|
+
res.writeHead(200, { "Content-Type": "text/plain; version=0.0.4; charset=utf-8" });
|
|
4730
|
+
res.end(formatPrometheusMetrics(data));
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
5661
4733
|
if (url === "/api/stream") {
|
|
5662
4734
|
res.writeHead(200, {
|
|
5663
4735
|
"Content-Type": "text/event-stream",
|
|
@@ -5686,9 +4758,9 @@ async function runWebCommand() {
|
|
|
5686
4758
|
res.end(getDashboardHtml());
|
|
5687
4759
|
} else if (url === "/setup") {
|
|
5688
4760
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5689
|
-
const indexPath =
|
|
5690
|
-
if (
|
|
5691
|
-
res.end(
|
|
4761
|
+
const indexPath = path5.join(process.cwd(), "index.html");
|
|
4762
|
+
if (fs8.existsSync(indexPath)) {
|
|
4763
|
+
res.end(fs8.readFileSync(indexPath, "utf-8"));
|
|
5692
4764
|
} else {
|
|
5693
4765
|
res.end(getDashboardHtml());
|
|
5694
4766
|
}
|
|
@@ -5707,14 +4779,14 @@ async function runWebCommand() {
|
|
|
5707
4779
|
});
|
|
5708
4780
|
server.on("error", (err) => {
|
|
5709
4781
|
if (err.code === "EADDRINUSE") {
|
|
5710
|
-
console.log(
|
|
4782
|
+
console.log(chalk10.red(`
|
|
5711
4783
|
Error: Port ${port} is already in use.`));
|
|
5712
|
-
console.log(
|
|
5713
|
-
console.log(
|
|
4784
|
+
console.log(chalk10.yellow(` Try specifying a different port using --port <port>, e.g.:`));
|
|
4785
|
+
console.log(chalk10.dim(` npx tsx src/index.ts web --port ${port + 1}
|
|
5714
4786
|
`));
|
|
5715
4787
|
process.exit(1);
|
|
5716
4788
|
} else {
|
|
5717
|
-
console.log(
|
|
4789
|
+
console.log(chalk10.red(`Server error: ${err.message}`));
|
|
5718
4790
|
process.exit(1);
|
|
5719
4791
|
}
|
|
5720
4792
|
});
|
|
@@ -5723,12 +4795,13 @@ Error: Port ${port} is already in use.`));
|
|
|
5723
4795
|
server.listen(port, "0.0.0.0", () => {
|
|
5724
4796
|
const address = server.address();
|
|
5725
4797
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
5726
|
-
console.log(
|
|
4798
|
+
console.log(chalk10.bold(`
|
|
5727
4799
|
pyre web server running on port ${actualPort}`));
|
|
5728
|
-
console.log(
|
|
5729
|
-
console.log(
|
|
5730
|
-
console.log(
|
|
5731
|
-
console.log(
|
|
4800
|
+
console.log(chalk10.dim(` Local URL: http://localhost:${actualPort}/`));
|
|
4801
|
+
console.log(chalk10.hex("#ff6a39").bold(` Network URL: http://${ip}:${actualPort}/ (Accessible on same Wi-Fi / LAN)`));
|
|
4802
|
+
console.log(chalk10.dim(` API: http://${ip}:${actualPort}/api`));
|
|
4803
|
+
console.log(chalk10.dim(` Metrics: http://${ip}:${actualPort}/metrics`));
|
|
4804
|
+
console.log(chalk10.dim(` SSE Stream: http://${ip}:${actualPort}/api/stream`));
|
|
5732
4805
|
resolve(actualPort);
|
|
5733
4806
|
});
|
|
5734
4807
|
process.once("SIGINT", () => {
|
|
@@ -5757,9 +4830,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
5757
4830
|
|
|
5758
4831
|
func applicationDidFinishLaunching(_ n: Notification) {
|
|
5759
4832
|
// Window
|
|
4833
|
+
let isCompact = ${!!opts.compact}
|
|
4834
|
+
let isAlwaysOnTop = ${!!opts.ontop}
|
|
5760
4835
|
let screen = NSScreen.main?.visibleFrame ?? NSRect(x:0, y:0, width:1280, height:800)
|
|
5761
|
-
let W = min(1180.0, screen.width - 80)
|
|
5762
|
-
let H = min(820.0, screen.height - 80)
|
|
4836
|
+
let W = isCompact ? 420.0 : min(1180.0, screen.width - 80)
|
|
4837
|
+
let H = isCompact ? 280.0 : min(820.0, screen.height - 80)
|
|
5763
4838
|
let rect = NSRect(x: screen.minX + (screen.width - W) / 2,
|
|
5764
4839
|
y: screen.minY + (screen.height - H) / 2,
|
|
5765
4840
|
width: W, height: H)
|
|
@@ -5767,8 +4842,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
5767
4842
|
window = NSWindow(contentRect: rect,
|
|
5768
4843
|
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
|
5769
4844
|
backing: .buffered, defer: false)
|
|
5770
|
-
window.title = "Pyre"
|
|
5771
|
-
window.minSize = NSSize(width: 800, height: 600)
|
|
4845
|
+
window.title = isCompact ? "Pyre Widget" : "Pyre"
|
|
4846
|
+
window.minSize = isCompact ? NSSize(width: 320, height: 200) : NSSize(width: 800, height: 600)
|
|
4847
|
+
if isAlwaysOnTop {
|
|
4848
|
+
window.level = .floating
|
|
4849
|
+
}
|
|
5772
4850
|
window.appearance = NSAppearance(named: .darkAqua)
|
|
5773
4851
|
window.backgroundColor = NSColor(white: 0, alpha: 1)
|
|
5774
4852
|
window.isReleasedWhenClosed = false
|
|
@@ -5837,14 +4915,14 @@ let delegate = AppDelegate()
|
|
|
5837
4915
|
app.delegate = delegate
|
|
5838
4916
|
app.run()
|
|
5839
4917
|
`;
|
|
5840
|
-
const scriptPath =
|
|
5841
|
-
|
|
4918
|
+
const scriptPath = path5.join(os2.tmpdir(), "pyre-ui.swift");
|
|
4919
|
+
fs8.writeFileSync(scriptPath, swiftScript);
|
|
5842
4920
|
const { spawn } = await import("child_process");
|
|
5843
|
-
console.log(
|
|
4921
|
+
console.log(chalk10.cyan(`
|
|
5844
4922
|
Launching native UI window...`));
|
|
5845
4923
|
const ui = spawn("swift", [scriptPath], { stdio: "inherit" });
|
|
5846
4924
|
ui.on("close", () => {
|
|
5847
|
-
console.log(
|
|
4925
|
+
console.log(chalk10.dim("UI window closed, shutting down server..."));
|
|
5848
4926
|
process.exit(0);
|
|
5849
4927
|
});
|
|
5850
4928
|
process.once("SIGINT", () => {
|
|
@@ -5855,14 +4933,14 @@ app.run()
|
|
|
5855
4933
|
async function runBenchCommand(benchCmd) {
|
|
5856
4934
|
const { spawn } = await import("child_process");
|
|
5857
4935
|
const logDir = opts.exportDir || "./pyre-exports";
|
|
5858
|
-
const file =
|
|
5859
|
-
if (!
|
|
5860
|
-
const stream =
|
|
4936
|
+
const file = path5.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
|
|
4937
|
+
if (!fs8.existsSync(logDir)) fs8.mkdirSync(logDir, { recursive: true });
|
|
4938
|
+
const stream = fs8.createWriteStream(file, { flags: "a" });
|
|
5861
4939
|
stream.write("timestamp,cpu_usage,mem_usage_percent,temp_c,net_rx_bytes,net_tx_bytes,net_rx_packets,net_tx_packets,connections,thermal_state,power_watts\n");
|
|
5862
|
-
console.log(
|
|
4940
|
+
console.log(chalk10.bold(`
|
|
5863
4941
|
pyre bench`));
|
|
5864
|
-
console.log(
|
|
5865
|
-
console.log(
|
|
4942
|
+
console.log(chalk10.dim(` Command: ${benchCmd}`));
|
|
4943
|
+
console.log(chalk10.dim(` Log: ${file}
|
|
5866
4944
|
`));
|
|
5867
4945
|
const startTime = Date.now();
|
|
5868
4946
|
let accumulatedWattSeconds = 0;
|
|
@@ -5892,13 +4970,13 @@ async function runBenchCommand(benchCmd) {
|
|
|
5892
4970
|
clearInterval(handle);
|
|
5893
4971
|
stream.end();
|
|
5894
4972
|
const durationSec = Math.max(0.1, (Date.now() - startTime) / 1e3);
|
|
5895
|
-
console.log(
|
|
4973
|
+
console.log(chalk10.green(`
|
|
5896
4974
|
\u2714 Bench complete (${durationSec.toFixed(2)}s). Log saved to ${file}`));
|
|
5897
4975
|
if (powerSamplesCount > 0 && accumulatedWattSeconds > 0) {
|
|
5898
4976
|
const kWh = accumulatedWattSeconds / (3600 * 1e3);
|
|
5899
4977
|
const rate = config2.electricityRateKwH ?? 0.15;
|
|
5900
4978
|
const cost = kWh * rate;
|
|
5901
|
-
console.log(
|
|
4979
|
+
console.log(chalk10.cyan(` \u26A1 Energy Consumption: ${kWh.toFixed(6)} kWh | Est. Cost: $${cost.toFixed(6)} (@ $${rate}/kWh)`));
|
|
5902
4980
|
}
|
|
5903
4981
|
};
|
|
5904
4982
|
child.on("close", () => {
|